Building Robust UIs in Mono with Gtk#

ust as Microsoft provides a native desktop UI toolkit called Windows Forms that ships with Microsoft .NET, the Mono project provides a compatible implementation of this same API, making it possible to build Windows Forms-based applications that run under a variety of operating systems.

Author’s Note: When this article was written, Mono’s implementation of Windows Forms had not yet been finalized (see this blog to view the current status of the project).

Even though Mono’s Windows Forms implementation is not yet feature-complete you can use Mono to build robust desktop applications today by choosing an alternative API (see Table 1).

Table 1. A Survey of .NET desktop APIs: The table shows some of the APIs available for building desktop applications with Mono.

.NET Desktop
GUI API
Description
Gtk# Gtk# is considered the “native” GUI toolkit that ships with the Mono platform.
Glade# Glade# is an extension to Gtk#. Beyond adding several useful widgets, GUIs built using Glade# can be described via XML and bound to a runtime object model.
Cocoa# Cocoa# is a managed wrapper around the native Mac OS X cocoa API.
wx.NET Wx.NET is a wrapper around the platform-independent wx toolkit. A (major) benefit of Wx.NET is that it maintains the correct look-and-feel of the host OS (including Mac OS X).

This article introduces you to a very popular .NET GUI alternative named Gtk#. As you would expect from a modern-day desktop API, Gtk# provides a very rich fabric for building desktop applications. I’ll discuss the following core topics:

  • Building main windows
  • Working with widgets
  • Creating menu systems
  • Understanding the role of HBox and VBox types
  • Creating and using dialog boxes
Author’s Note: This article assumes that you’re comfortable with C# and working with the .NET type system (classes, interfaces, enumerations, structures and delegates).

Getting to Know Gtk#
Gtk# is a managed wrapper around a Linux-centric API termed Gtk+. Gtk+ is a toolkit originally created to build the popular image processing application GIMP (the GNU Image Manipulation Program), which explains the origin of the acronym ‘Gtk’: GIMP Tool Kit.

Although Gtk+ was originally developed for use on Unix/Linux, this API has been ported to numerous operating systems (Win32, Mac OX X, etc). Given the connection between Gtk# and Gtk+, it should come as no surprise that you can use Gtk# to build platform-independent user interfaces on any operating system that supports Gtk+.

Structurally, Gtk# consists of a set of .NET assemblies deployed to the Mono Global Assembly Cache. Table 2 defines the role of the core Gtk# assemblies.

Table 2. The Core Gtk# Assemblies: The table lists the most important Gtk# assemblies and a description of each.

Gtk# Assembly Description
gtk-sharp.dll Defines the core UI .NET bindings to Gtk+.
glib-sharp.dll Defines additional (non-UI) .NET bindings to Gtk+.
pango-sharp.dll Provides binding to the Pango API, used to format and internationalize textual data.
atk-sharp.dll Binding for the atk accessibility API.
gdk-sharp.dll Defines numerous low-level rendering APIs.
glade-sharp.dll Provides support for Glade development with Gtk#.
art-sharp.dll A vector rending library.
rsvg-sharp.dll A SVG rendering library.

 
Figure 1. The MonoDoc UI: MonoDoc provides solid documentation for Gtk#.

The Mono installation automatically deploys the Gtk# assemblies to the Mono GAC when you install the Mono platform on a Windows or Linux-based OS. Do be aware that installing Gtk# on Mac OS X is a bit more involved. Interested individuals can find OS X installation instructions here.

While this article will in no way attempt to dive into the details of every type found within each Gtk# assembly, you’ll find the API is well documented in the MonoDoc utility (see Figure 1).

A Survey of Gtk# Development Options
In this article you’ll see how to build several Gtk# applications at the command line using the Mono C# compiler (gmcs) and your text editor of choice. I realize that this is not the visual GUI-building process you may be used to in Visual Studio, but bear with me?this approach is ideal from a learning point of view. Rest assured that if you are serious about building Gtk# user interfaces, several Mono-aware IDEs provide design time assistance. Table 3 documents several possibilities.

Author’s Note: See Mono IDEs: Going Beyond the Command Line for more information on these Mono-aware IDEs.

Table 3. Mono-Aware IDEs: The table lists several IDEs that offer visual design support for Gtk# development.

.NET Desktop UI Technology Description
#develop SharpDevelop 2.0 provides Gtk# and Glade# project templates, however it currently lacks visual designer support.
MonoDevelop Provides a variety of visual tools for building Gtk# / Glade# projects.
X-Develop Provides a variety of visual tools for building Gtk# / Glade# projects.

In addition to the IDEs in Table 3, it is possible to build Gtk# applications using Visual Studio 2005 (as well as earlier editions of the product). Check out the following blog for more information.

Building a Main Window
At absolute minimum, building a Gtk# user interface requires working with the Window and Application class types. Not too surprisingly, the Window class represents the main window of an application. The Window type has the ability to host menu systems, status bars, toolbars, drawing surfaces, and other internal widgets.

The Application type represents a running instance of a Gtk# application. This type encapsulates various low-level details such as dispatching messages to their intended targets, initializing and tearing down the Gtk# libraries, and so forth. To get the ball rolling, consider the following C# code file, which you can find in the downloadable code in the file BasicWindow.cs:

   using System;   using Gtk;      namespace BasicWindow   {         static class Program      {         static void Main()         {            // Initialize the application.            Application.Init();                        // Create the main Window, handle DeleteEvent event             // and show it!             Window wnd = new Window("My Simple Window");            wnd.SetDefaultSize(300, 200);            wnd.DeleteEvent += new DeleteEventHandler(               MainWindow_Delete);            wnd.ShowAll();                                 // Run the program.            Application.Run();         }                  // When user clicks the close button, shut down          // the application.         static void MainWindow_Delete(object sender,             DeleteEventArgs args)         {            Application.Quit();            args.RetVal = true;         }      }   }

The first point of interest in the preceding code is that it uses the Gtk namespace, which not only defines the Window and Application type, but also the DeleteEventHandler delegate (used to handle the window’s DeleteEvent event).

The Main() method begins by initializing the Gtk+ infrastructure via a call to the static Application.Init() method. After instantiating a Window object and establishing a default size, the code handles the DeleteEvent event using standard C# event syntax (again note that the DeleteEvent event works in conjunction with the DeleteEventHandler delegate).

This DeleteEvent event fires when the user clicks on the close button of the displayed window, at which point you destroy the application via a call to the Application.Quit() method, and inform the Gtk# system that this event is fully handled by assigning the RetVal property of the incoming DeleteEventArgs type to true (this step is technically optional, but is considered good form).

Finally, the code tells the Window object to display itself (and any contained widgets) and run the application.

At this point you can compile the application with the Mono C# compiler by entering the following command set at the command line (I’ll provide more information on the -pkg option in the next section):

   gmcs *.cs -pkg:gtk-sharp
 
Figure 2. Your First Gtk# Application: Although not exciting, this application is perhaps the simplest possible example of a Gtk#-based Mono application running under Microsoft Windows.

Assuming you don’t have any compilation errors, you can now run your Gtk# application under the Mono runtime using the following command:

   mono BasicWindow.exe   

Figure 2 shows the Gtk# application hosted by the Microsoft Windows operating system.

Author’s Note: You can compile and execute all the remaining examples in this article using the same command line instructions shown in this section; therefore, I won’t repeat them again. Just be sure to change the name of the .exe file appropriately when running the mono utility.

The Role of the?pkg Option
Unlike the Microsoft C# compiler, the Mono C# compilers (mcs/gmcs) support the notion of importing “packages.” Packages are similar to C# response files, in that they allow you to define a set of assemblies to include with the current compilation, but packages go one step further by also allowing you to define detailed compilation flags and version requirements for external dependencies.

 
Figure 3. Package Contents: If you investigate the gtk-sharp package by opening it with a text editor, you’ll see that it references the Gtk# assemblies.

The details of the gtk-sharp package shown in the command line earlier are defined within a file named gtk-sharp-2.0.pc, located under the libpkconfig subdirectory of your Mono installation (C:Program FilesMono-libpkgconfig on a Win32 machine). If you were to open this file using a text editor, you would find that it specifies the Gtk# assemblies as input (see Figure 3).

Building a Better Window
With a basic example application running, it’s time to improve the code. Currently, the logic used to define the Window type is embedded directly within the type defining Main(), which does not lend itself to code reuse. Consider the BasicWindowRefactored.cs code file below, which now subclasses the Window type to enforce encapsulation services.

   using System;   using Gtk;      namespace BasicWindowRefactored   {         // The Program class initializes the Gtk# app.      static class Program      {         static void Main()         {            Application.Init();                        // Create the Window derived type.            MainWindow wnd = new MainWindow("My Simple Window");            wnd.ShowAll();                                 Application.Run();         }      }            // The MainWindow class represents our custom Window.      class MainWindow : Window      {         public MainWindow(string title) : base(title)         {            this.SetDefaultSize(300, 200);            this.DeleteEvent += new DeleteEventHandler(               MainWindow_Delete);         }                void MainWindow_Delete(object sender, DeleteEventArgs args)         {            Application.Quit();            args.RetVal = true;         }      }   }

Despite the changed code, if you compile and run this version of the code, the output is identical. The benefit is that this version isolates the application logic and the main window logic into two independent classes.

Author’s Note: I’ll use the types defined in the BasicWindowRefactored.cs file as the basis for the remaining examples in this article. I won’t modify the Program class further, and therefore won’t show it beyond this point. However, the MainWindow type will undergo a number of modifications as the article examines new concepts.

The World of Widgets
In terms of Gtk#, a “widget” is a user interface element that can be contained within a host such as a main window or dialog. The Gtk namespace defines numerous widgets whose overall usefulness should be quite familiar to those of you who have worked with Windows Forms.

As you would expect, there are Button widgets, AboutDialog, Calendar, Clipboard, FileChooser, Label, and Menu widgets as well as many others. The Gtk.Widget class is the parent to each widget type. As any good base class does, Widget provides a polymorphic interface to derived types. I’ll use various properties, methods, and events of the Widget type in the remainder of the article; you can explore the details using MonoDoc.

Building a Menu System
Any main Window worth its salt will support a menu system to let users perform common operations (save data, exit the application, display dialogs, etc). To build a menu system with Gtk#, you need three core widgets (see Table 4).

Table 4. Menu Construction Widgets: The table lists the Gtk# programming primitives used in constructing menus.

Menu System Widget Description
MenuBar Represents the entire menu structure (top most items, sub-items, embedded icons, etc).
Menu Represents a top most item within the MenuBar.
MenuItem Represents the top most item and the related sub menus.

Building a menu system boils down to a set of fairly predictable steps:

  1. Allocate a MenuBar object to represent the entire menu system.
  2. Create a Menu object to represent the entirety of each topmost menu.
  3. Add MenuItems to the appropriate Menu object.
  4. Add the Menu object to the MenuBar

After establishing the overall menu system, you need to add it to the Window’s widget collection (typically within an Hbox or VBox widget, described in just a moment). To illustrate the basic process, consider the C# class definition in Listing 1 (see the MenuWindow.cs file in the downloadable sample code).

The real meat of the class in Listing 1 is the CreateMenu() method called from the window’s constructor. Notice that when creating a MenuItem object, you may specify an underscore (_) character before a particular letter of the menu title. Doing that causes Windows to associate that letter with a shortcut key, making the menu item accessible from the keyboard by pressing it in combination with the Alt key.

 
Figure 4. A Basic Menu: The figure shows a new version of the simple window that includes a basic Gtk# menu system.
Author’s Note: For simplicity, the menu-centric widgets have been declared as local variables, rather than as member variables of the MainWindow class type. If you wish to interact with these widgets from other aspects of the Window derived type (for example to check or disable menu items from code), this would be an obvious improvement.

The only part of Listing 1 that might give you pause is the use of the VBox type. Again, I’ll discuss this type in more detail shortly; for now, it’s sufficient to know that you use the VBox type to position a widget within a host container. Figure 4 shows the menu system in action.

Because the code in Listing 1 handles the Activated event for the Quit menu item, the application terminates when you select File | Quit:

   menuItem.Activated += new EventHandler(FileQuit_Activated);

In addition to the core menu-centric widgets shown in Table 3, Gtk# provides many additional types to create extremely elaborate menu systems. For example the RadioMenuItem type represents a set of radio buttons embedded within a menu, while CheckMenuItem allows you to embed check boxes within a menu system. Furthermore, you can create Gtk# menu systems that support accelerator keys, menu separators, and custom images (as well as numerous ‘stock’ menu images).

The following code improves on the basic CreateMenu() method by adding ImageMenuItem and AccelGroup objects to provide a standard File | Quit icon and shortcut key.

   void CreateMenu()   {      // MenuBar represents the entire whole of the menu system.      MenuBar mainMenu = new MenuBar();         // Now define an accelerator group.      AccelGroup aGroup = new AccelGroup();      this.AddAccelGroup(aGroup);         // Build File menu item.      Menu fileMenu = new Menu();      MenuItem menuItem = new MenuItem("_File");      menuItem.Submenu = fileMenu;      mainMenu.Append(menuItem);   
 
Figure 5. Updated File Menu: Here's the Gtk# menu after adding a stock image and an accelerator key to the Quit menu item.
// Build File | Exit menu Item. menuItem = new ImageMenuItem(Stock.Quit, aGroup); menuItem.Activated += new EventHandler(FileQuit_Activated); fileMenu.Append(menuItem); // Add the menu into a VBox and then add the VBox into the // Window's widget collection. VBox v = new VBox(); v.PackStart(mainMenu, false, false, 0); this.Add(v); }

In the preceding code, the ImageMenuItem constructor accepts a parameter value from the Stock type. The Stock class (as the name suggests) defines several built in UI elements including common icons, text messages, shortcut keys, etc. Figure 5 shows the newly updated menu system.

Understanding Gtk# Layout Managers
Gtk# provides a number of widgets which, while invisible at runtime, are used to organize (or in the nomenclature of Gtk#, pack) a collection of related widgets into a container. Using types such as VBox (vertical box) and HBox (horizontal box) you can establish a consistent GUI layout that maintains the relative position of the widgets when end users resize your windows.

Both of these types have methods named PackStart() that add a widget to the HBox or VBox type. Calling PackStart() on an HBox type packs the widget into the box from left to right, while calling the same method on a VBox type adds the items in a top-down manner. In either case, PackStart() takes four parameters that control how to position the incoming Widget-derived type within the box:

 
Figure 6. HBox Packing: Packing the MenuBar within an HBox object results in a radically different menu layout.
   void PackStart( Widget widgetToAdd, bool expand,       bool fill, uint padding )

The CreateMenu() method shown earlier uses the VBox type to pack in the MenuBar object; therefore the menu system is attached to the upper left corner of the Window type. However, by way of illustration, if you were to update CreateMenu() to make use of an HBox type, you would see a radically different widget arrangement (see Figure 6). Here’s the altered code:

   void CreateMenu()   {      ...      // Now with an HBox!      HBox h = new HBox();      h.PackStart(mainMenu, false, false, 0);      this.Add(h);   }

Using HBox and VBox types in this manner is very common when building Gtk# user interfaces. You may be thinking that building a complicated UI in this manner would be quite tedious and labor intensive (and I would agree). Thankfully, many Mono IDEs provide visual designers for working with the visual layout of widgets. However, to further illustrate the role of the HBox and VBox types, it’s worth exploring the process of building a dialog box using Gtk#.

 
Figure 7. The Updated Menu System: Here’s the menu showing an added Tools menu, with a sub item called “Enter Name.”

Building Gtk# Dialog Boxes
Assume that the standard (VBox-based) version of the CreateMenu() method has been updated to define a new topmost menu item named Tools, which defines a sub item called “Enter Name” (see Figure 7).

I won’t bother to list the menu construction code here (as it is more or less identical to the code used to build the File | Exit menu), however you can find the full CreateMenu() implementation in the WindowWithDialog.cs file in the sample code.

When the end user clicks on the Enter Name menu option, the application should display a custom dialog in which users can enter a first and last name. This activity will occur in the event hander for the Enter Name MenuItem’s Activated event. You’ll see the full implementation shortly; for now, some simple stub code will suffice:

   void ToolsEnterName_Activated(object sender, EventArgs args)   {               // ToDo!  Show our custom dialog box and gather user input.    }
 
Figure 8. A Custom Dialog Box: The figure shows the dialog that appears when users click the Enter Name menu item.

The Gtk.Dialog class is the parent for dialogs in the Gtk# API. Like the Window type, you can either create a Dialog object directly or subclass Dialog to build a strongly-typed derived class. To facilitate code reuse, let’s opt for the later alternative. When complete, the dialog (named UserNameDialog) will look like Figure 8.

The dialog implementation breaks down into the following main tasks:

  • UserNameDialog will extend the Gtk.Dialog base class.
  • The dialog will define two member variables of type Entry (text fields) that allow users to enter their first and last names.
  • The dialog will support stock OK and Cancel buttons.
  • The dialog will define two read-only properties (FirstName and LastName) to allow the owning window to extract the value in each Entry object.

With that said, Listing 2 contains the complete definition of the UserNameDialog type, with analysis to follow.

The first point of interest takes place in the BuildDialogUI() method. Notice that the Gtk.Dialog parent class defines a VBox property, to which the method adds an HBox object:

   // Add an HBox to the dialog's VBox.   HBox hbox = new HBox (false, 8);   hbox.BorderWidth = 8;   this.VBox.PackStart (hbox, false, false, 0);

If adding HBoxes to VBoxes sounds odd, recall that VBox widgets add items in a top-down manner. It just so happens that the item in the dialog’s VBox is an HBox (which adds widgets in a left-to-right manner).

Next, the method adds an Image widget to the HBox, set to make use of the stock information image of the Gtk# libraries.

   // Add an Image widget to the HBox using a stock 'info' icon.   Image stock = new Image (Stock.DialogInfo, IconSize.Dialog);   hbox.PackStart (stock, false, false, 0);

The second item added to the HBox is a Gtk.Table widget. Much like an HTML-based Table, the Table widget allows you to pack in items using a cell-like placement scheme.

   Table table = new Table (2, 2, false);   table.RowSpacing = 4;   table.ColumnSpacing = 4;   hbox.PackStart (table, true, true, 0);      Label label = new Label ("_First Name");   table.Attach (label, 0, 1, 0, 1);   table.Attach (fNameEntry, 1, 2, 0, 1);   label.MnemonicWidget = fNameEntry;      label = new Label ("_Last Name");   table.Attach (label, 0, 1, 1, 2);         table.Attach (lNameEntry , 1, 2, 1, 2);   label.MnemonicWidget = lNameEntry ;   hbox.ShowAll ();

Notice that the code aligns the Label and Entry items using the Attach() method. The final four arguments of that method represent the coordinates of the top left and bottom right positions to place the widget:

   void Attach (Widget widget, uint left_attach,       uint right_attach, uint top_attach,       uint bottom_attach)

After informing the HBox to display each item, we then add two stock button types via the AddButton() method inherited from the parent Gtk.Dialog class.

   this.AddButton(Stock.Ok, ResponseType.Ok);   this.AddButton(Stock.Cancel, ResponseType.Cancel);

Take note that the second argument to this method is a value from the ResponseType enumeration. As you’ll see shortly, assigning ResponseType values to a Button widget makes it very simple to determine which button the end user clicked.

Displaying Dialog Boxes
Now that the dialog class is complete, the last step is to flesh out the code that runs in the ToolsEnterName_Activated() method when users click the “Enter Name” menu item. The code needs to display the UserNameDialog and fetch the data within the Entry widgets. Consider this first implementation, which simply prints the values to the Console window if (and only if) the user clicks on the OK button:

   void ToolsEnterName_Activated(object sender, EventArgs args)   {               // Run the dialog and grab the ResponseType.      UserNameDialog dlg = new UserNameDialog();      ResponseType rsp = (ResponseType)dlg.Run();         // Figure out which button was clicked.      if(rsp == ResponseType.Ok)      {         Console.WriteLine("Hello {0} {1}!," dlg.FirstName,             dlg.LastName));      }         // Destroy the dialog (just to keep things tidy).       dlg.Destroy();    }
Authors Note: Technically speaking, you do not need to explicitly destroy dialog objects, as they will be garbage collected like any other .NET heap allocated type.

After allocating the UserNameDialog object, the preceding code displays the dialog by calling Run(). The Run() method displays a modal dialog; therefore users must dismiss the dialog by clicking on one of Button widgets (or the ‘X’ button on the dialog window). The return value from the Run() method will be one of the ResponseType values assigned to the buttons earlier. At this point you can check for the ResponseType.Ok value and format a friendly salutation.

To spruce up the current Activated event handler, the improved version below retrofits the code to make use of the Gtk.MessageDialog type (which represents a simple message box):

   void ToolsEnterName_Activated(object sender, EventArgs args)   {               UserNameDialog dlg = new UserNameDialog();      ResponseType rsp = (ResponseType)dlg.Run();         if(rsp == ResponseType.Ok)      {         // Hide the UserNameDialog object from view.         dlg.Hide();   
 
Figure 9. The MessageDialog in Action: After extracting the first and last name values users enter into the UserNameDialog, this MessageDialog displays them.
// Create a MessageDialog to show the salutation. MessageDialog md = new MessageDialog (this, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, string.Format("Hello {0} {1}!," dlg.FirstName, dlg.LastName)); // This time we don't care which button they click on. // Any button will simply destroy the MessageDialog. md.Run (); md.Destroy(); } // Now destroy the UserNameDialog. dlg.Destroy(); }

The call to Hide() is the more interesting statement of the method, as this keeps the dialog in memory but not visible on the screen. This is important, because you need to extract the first and last name values to format the salutation. Figure 9 shows one possible MessageDialog configuration.

That wraps up this initial look at the Gtk# programming API. Obviously, this introductory article could cover only the basics of working with Gtk#, but it should give you a good start for further exploration.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Related Posts