Click here to Skip to main content
Click here to Skip to main content

Custom controls with MonoDevelop and GTK#

By , 18 May 2008
 

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:

  • AddMovingObject(string name,string caption, int x, int y) to add a new movable object.
  • RefreshChildren() to force the controls to redraw.

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.

using System;
using Gtk;

namespace GtkControl.Control
{
    
    public class MVObject : Gtk.DrawingArea 
    {
        
        public MVObject(string pName, string cap)
        {
                  ...
        }
                
        public void Redraw()
        {
            this.QueueDraw();
        }

               private Pango.Layout GetLayout(string text)
        {
            Pango.Layout layout = new Pango.Layout(this.PangoContext);
            layout.FontDescription = Pango.FontDescription.FromString("monospace 8");
            layout.SetMarkup(&quot;<span color=\&quot;black\&quot;>&quot; + text + &quot;</span>&quot;);
            return layout;
        }
        
        protected override bool OnExposeEvent (Gdk.EventExpose args)
        {    
            Gdk.Window win = args.Window;
            Gdk.Rectangle area = args.Area;
                
            win.DrawRectangle(Style.DarkGC(StateType.Normal), true, area);
            win.DrawRectangle(Style.MidGC(StateType.Normal),true,0,15,1000,1000);
            win.DrawRectangle(Style.BlackGC,false,area);
            win.DrawLine(Style.BlackGC,0,15,1000,15);            
            win.DrawLayout(Style.BlackGC,2,2,titleLayout);

            if (!string.IsNullOrEmpty(body))
            {
                win.DrawLayout(Style.BlackGC,2,17,GetLayout(body));
            }
            return true;
        } 
    }
}

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.

using System;
using Gtk;

namespace GtkControl.Control
{
    public partial class MVPanel : Gtk.Bin
    {
               private Widget currCtrl = null;
        private Widget currClone = null;
        private int origX = 0;
        private int origY = 0;
        private int pointX = 0;
        private int pointY = 0;

        ...
        
        //Mouse click on the controls of the panel  
        protected void OnButtonPressed(object sender, ButtonPressEventArgs a)
        {        
            //Save the origin of the move in origX and origY
            currCtrl = sender as Widget;
            currCtrl.TranslateCoordinates(this.fixed1,0,0,out origX, out origY);
                      //Save the pointer position relative the origin of the move
            fixed1.GetPointer(out pointX,out pointY);
        }

        protected void OnButtonReleased(object sender, ButtonReleaseEventArgs a)
        {
            //Final destination of the control
            MoveControl(currCtrl, a.Event.X,a.Event.Y,false);
            currCtrl = null;
            if (currClone!=null)
            {
                this.fixed1.Remove(currClone);
                currClone.Destroy();
                currClone = null;
            }
        }

        //Called whenever a control is moved
        protected virtual void OnFixed1MotionNotifyEvent (object o, 
                               Gtk.MotionNotifyEventArgs args)
        {
            //Rendering of a clone at the desired location
            if (currCtrl!=null)
            {
                MoveClone(ref currClone, args.Event.X,args.Event.Y);
            }
        }
    }    
}

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:

//Create the event box for the custom control
private EventBox GetMovingBox(string name, string caption)
{ 
    MVObject ctrl = new MVObject(name,caption);
    EventBox rev = new EventBox();
    rev.Name = name;
    rev.Add(ctrl);
    Console.WriteLine(&quot;Creating new moving object&quot;+rev.Name);
    return rev;
}

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
public void 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();
}

For the above screenshot, that translates to:

this.mvpanel1.AddMovingObject(&quot;mv1&quot;,&quot;Moving Object 1&quot;,10,10);
this.mvpanel1.AddMovingObject(&quot;mv2&quot;,&quot;Moving Object 2&quot;,10,55);
this.mvpanel1.AddMovingObject(&quot;mv3&quot;,&quot;Mono&quot;,10,100);
this.mvpanel1.AddMovingObject(&quot;mv4&quot;,&quot;Gtk#&quot;,10,145);
this.mvpanel1.AddMovingObject(&quot;mv5&quot;,&quot;MonoDevelop&quot;,10,190);
this.mvpanel1.AddMovingObject(&quot;mv6&quot;,&quot;Pango&quot;,10,235);
this.mvpanel1.AddMovingObject(&quot;mv7&quot;,&quot;Test&quot;,10,280);

Showing a context menu on the control

The final piece of the sample, the context menu for the control.

The MVObject implements a public method ShowMenu that simply calls the Popup method of a predefined Gtk.Menu on the control.

Gtk.Menu popup = new Gtk.Menu();
Gtk.MenuItem text1 = new MenuItem(&quot;Test1&quot;);
text1.Activated+=new EventHandler(Menu1Clicked);
Gtk.MenuItem text2 = new MenuItem(&quot;Test2&quot;);
text2.Activated+=new EventHandler(Menu2Clicked);

Menu1Clicked changes the body property on the control and forces a redrawing. This alters its appearance through the OnExposedEvent method.

protected void Menu1Clicked(object sender, EventArgs args)
{
    body = &quot;Test1&quot;;
    this.QueueDraw();
}

The call to the menu is defined by the mouse right click in MVPanel.OnButtonPress:

//Mouse click on the controls of the panel  
protected void OnButtonPressed(object sender, ButtonPressEventArgs a)
{        
    //Right click
    if (a.Event.Button==3)
    {
        if (sender is EventBox)
        {
            ((sender as EventBox).Child as MVObject).ShowMenu();
        }    
    }
    //Left click
    else if (a.Event.Button==1)
    {
        //Double-click
        if (a.Event.Type==Gdk.EventType.TwoButtonPress)
        {
            if (sender is EventBox)
            {
                //Calling the edit method of the control
                ((sender as EventBox).Child as MVObject).Edit();
            }    
        }
        else
        {
            //Setup the origin of the move
            currCtrl = sender as Widget;
            currCtrl.TranslateCoordinates(this.fixed1,0,0,out origX, out origY);
            fixed1.GetPointer(out pointX,out pointY);
        }
    }
}

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:

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

About the Author

Olivier Lecointre
Software Developer
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralPrint and Print Previewmembereg_Anubhava20 Nov '10 - 1:30 
Hello Sir,
 
I want to generate print and Print Privew with Mono and GTK.
So please help me.
 

Thanks
If you can think then I Can.

Generaladvantages of gtk# over winformsmemberLeblanc Meneses13 Jun '08 - 21:49 
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.
 
- lm
GeneralRe: advantages of gtk# over winformsmemberOlivier Lecointre28 Jun '08 - 4:19 
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.
 
Olivier Lecointre
lecointre@gmail.com

GeneralLunar eclipsememberBaimey4 Jun '08 - 19:43 
I'm now currently working on moonlight.
Have u worked on it?
If yes, will you please provide me with a sample on moonlight, and describe the process of compilation through gnome terminal in Open Suse.
 
thanks
paru Roll eyes | :rolleyes:
GeneralMonoDevelopmemberthund3rstruck19 May '08 - 3:40 
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?
GeneralRe: MonoDevelopmemberMember 167491819 May '08 - 4:53 
I use Monodevelop; recent versions have been much more stable than in the past (on openSUSE at least)
GeneralRe: MonoDevelopmemberOlivier Lecointre19 May '08 - 11:31 
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.
 
Olivier Lecointre
lecointre@gmail.com

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 18 May 2008
Article Copyright 2008 by Olivier Lecointre
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid