Introduction

Custom controls for Mono and GTK are usually done using basic Widgets. Sometimes, a custom rendering is preferred to achieve more advanced GUIs. This article presents a basic implementation of movable objects within a panel, with a popup menu to modify each object and their appearance.

Background

I recently made the switch to Ubuntu, and I am quite delighted with it. I develop mainly on .NET, and my dependence on some Windows tools was the sticky point that made me wait this long. This is now not really an issue, thanks to the great work of the Mono and MonoDevelop teams, and the related libraries like GTK#, Cairo, and Pango.

Mono brings the .NET platform to Linux, and MonoDevelop offers a good alternative to Visual Studio, making the development of GUI applications on the GTK desktop almost painless.

I wrote a cooperation tool that I use on a daily basis, and my first goal after the switch was to port it over with Mono and GTK#. After a few adjustments caused by the fundamental differences between Windows Forms and GTK#, I have to admit that the port of the application was a lot easier than I initially thought; I replaced the UI controls by widgets, set the various forms to use the GTK# layout, and that is pretty much it. The rest of the non-UI code worked as expected, with an overall good performance.

An area that gave me the most difficulty was the usage of custom controls with a behavior that is different from the base GTK components. This is the focus of this article.

Goals

Visual representation of elements as a graph

Each element has a title and some properties represented on the rendering

Each element can be freely moved in the defined panel

Each element has its own popup menu to change its properties

Solution

After spending some time with the GTK documentation, the Fixed and DrawingArea widgets seem to be the obvious choice. The Fixed panel allows the placement of controls in absolute position. Although it is usually not recommended for most of the forms, it does pretty well in this case.

MVPanel is the inherited Fixed container that contains the movable object. It can be dropped in any form as a regular GTK widget. It contains the following methods:

MVObject is a basic implementation of the custom control that will be movable within the MVPanel. It inherits from DrawingArea.

It contains the following methods/properties:

ShowMenu() to present to the user the options on that particular control.

Edit() to set the mode of the control in edit mode.

Redraw() to force the control to redraw.

Caption to get a basic property of the control.

Rendering a custom control

The rendering of a DrawingArea widget has to be fully specified. That is the drawback of being custom, but that is sometimes what is needed. The libraries Cairo, for graphics, and Pango, for text, seems to be recommended to render a consistent and contemporary look. Pango is used in this sample but not Cairo, since the rendering is quite simple.

The overridden method OnExposeEvent is where all the rendering is done. Here, the area is painted with dark gray and light gray, with some black lines to separate the two. Some text is also added in each colored area.

The QueueDraw method is a call to GTK to indicate that the control has to be redrawn.

Moving a control

A control cannot be moved, by default. But, the Fixed widget allows to put or move a control at a specified position. Also, the control needs to respond to the click of the mouse, the drag, and the release of the button.

TranslateCoordinates is a GTK method, and gives back the relative position of a control in a container, here fixed1.

GetPointer is also a GTK method, and gives back the position of the cursor relative to a control.

The method MoveControl calls the fixed1.Move method and makes sure that the control stays within the panel. It also takes care of redrawing the control after it has been moved.

The method MoveClone calls MoveControl on a clone of the selected widget. This ensures that the user sees the control in both places (origin and destination). The clone is generated when the button is pressed, and follows the mouse movement until the button is released. The event MotionNotifyEvent can be dropped if the intermediate state is not desired.

A DrawingArea object does rendering, but doesn't respond to events. That is why the MVObject is embedded in an EventBox where all the mouse events are controlled. This is done in the GetMovingBox method:

Points of interest

This implementation was easier than I thought it would be. Of course, this is not that useful in its current state, but gave me a good idea on the possibility of using GTK and Mono outside the regular widgets. I could well extend it as a small graphing framework for myself, but I am sure that it already exists as separate projects with a more robust design. However, this will definitely suit more than my needs, with a few additional adjustments.

I wanted to share this code as I didn't find a lot of existing documentation on custom rendering implementations, with complete code, on Mono.

There is, however, a lot of documentation on the base libraries, that I use as references:

Comments and Discussions

I'm curious if you're using MonoDevelop for your Linux development? A few months back I made a commitment to do cross-platform C# development for a client but it turned out that in Ubuntu 7.10, monodevelop was so buggy and crashed even on the simplest forms application I had to tuck my tail between my legs and quietly with drawl from any real enterprise cross platform development.

Is monodevelop stable enough now to where it doesn't crash every 5 minutes?

MonoDevelop is definitely more stable now and I had to wait for Ubuntu 8.04 to benefit directly from the version 1.0.
The build was always a headache whereas with 8.04 the version 1.0 comes as a package.
I am using exclusively MonoDevelop for all my C# development.
The only real problem is the lack of step-by-step debugging.
There are still a few occasional crashes, mainly when working with the designer, but not enough to be too bothersome (maybe 1 or 2 in a day's work).
It happens mainly when the layout is changed, or when some controls are moved or deleted.
If you have a clear idea of the form when you start using the designer then that should not happen that often.
Sometime I also have some crashes when I add a folder or a file.
But usually when I restart MonoDevelop, everything is as expected (I have the habit of forcing a save before adding a file or folder).
If I would have to develop an enterprise cross platform system, I would probably still write most of the non UI libraries on Visual Studio (because of the debugging functionality) and keep only the UI integration with MonoDevelop. But the integrated debugger is almost ready and I am eager for its release.