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:

Using the MVPanel

The public method AddMovingObject(string name,string caption, int x, int y) is how an object is added to the panel, with the caption being the title, and the name the identification of the object.

//Add a movable control to the panel
publicvoid AddMovingObject(string name,string caption, int x, int y)
{
//Prevent the object to be displayed outside the panel
if (x<0)
{
x = 0;
}
if (y<0)
{
y = 0;
}
//Create the box where the custom object is rendered
EventBox ev = GetMovingBox(name,caption);
//Add the events to control the movement of the box
ev.ButtonPressEvent+=new ButtonPressEventHandler(OnButtonPressed);
ev.ButtonReleaseEvent+=new ButtonReleaseEventHandler(OnButtonReleased);
//Add the control to the panel
this.fixed1.Put(ev,x,y);
this.ShowAll();
}

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:

first off .. i love the mono project... i hope to see it flourish even more in the coming years. opensuse with compiz + vmware (window xp/2003 vms) rocks for dev work.

now the point to my message:
I don't like the idea of having to install gtk library on windows to run a linux/windows compatible software. Especially now that mono claims to support winforms 100%. - as of a couple of days before you posted this.

What are the advantages of developing my next application in gtk# vs using winforms?

the biggest advantage of sticking to winforms is no need for gtk library when on windows. the app runs with native il instead of going through an adapter the gtk provides to support win and linux. when on linux the mono vm should convert the winform object to gtk.... i like this idea better. what are your thoughts?

in your article you mention other architectures for design surfaces:
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part3.aspx

to bad it won't run on mono yet..

so I'm debating on whether to dabble in winform technology or move on to wpf and wait for mono to catch up.... (should i inherit from winform control or wpf ContentControl)

if i was a big company i would stick to winform technlogy to allow my project to run on both operating systems. since i am a small company and the majority of my customers are windows based wpf has taken care of the difficult problems i would need to manually implement in winforms: animations, 3d, vector objects

hopefully the moonlight project is a step towards a desktop based wpf.

I think the main reason the Mono project made a great effort to support Winforms is probably for the reason you state, compatibility.
Winforms looks indeed a better alternative if you want compatibility between Linux/Windows but this is not something that I wanted to achieve primarily.

My main environment is now Linux/Gtk and I am developing under that environment. I chose to use Gtk# over Winforms as the integration in the environment is more mature and the UI object model seems to make more sense. I believe that the UI logic should be designed so that the platform on which it is rendered doesn't matter as much.

For example, I usually define interfaces to only exposes the functionalities that I want on a control. I can this way easily change the underlying rendering library to use Gtk# or Winforms. I will have of course to re-implement the Gtk# library for Windows Winforms, but that also allows me to use a completely different rendering environment (MAC for example), as long as Mono is available for it.

Another advantage to this approach is the possibility to use each environment to its fullest potential. I have plenty of Windows functionalities that are not in the base libraries (COM system calls and third parties libraries) that won't work with Mono. If I can replicate them on Gtk with different libraries, then an interface will do the trick and it will be transparent to the application. However, that requires more effort and the knowledge of two environments instead of only one.

From what you are saying, it seems that your application relies heavily on UI rendering. Since Windows seems to be the main platform for your clients, I would most certainly use WPF to gain some time in development and forget about compatibility at first. Once you have a better understanding of your UI implementation you can always refactor it to support Winforms on Linux. Moonlight may be fully functional by then. At least that is what I would do.

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.