Rapid GNOME Development with Mono

Robert Love

Issue #143, March 2006

How to get started with GNOME monkeyshines using the open-source .NET system Mono.

Mono is an open-source implementation of the .NET development platform, a powerful and now open development platform. Mono contains a number of components: a Common Language Infrastructure (CLI) virtual machine, a C# compiler and a set of class libraries. Mono implements the C# language and runtime environment according to ECMA standards 334 and 335, respectively.

Mono—which is the word for monkey in Spanish—provides various class libraries, including an open-source implementation of the .NET Framework SDK. In this article, however, we discuss one of Mono's brightest assets: its GNOME support, in the form of Gtk#.

Gtk# is a .NET language binding for the Gtk+ toolkit and various other GNOME libraries. More than a simple wrapper, Gtk# provides a powerful platform for developing GUI software in the GNOME environment. Gtk#'s language bindings utilize good object-oriented principles and C#-style design to make GNOME development easy and natural, yet flexible and powerful.

In this article, we investigate the construction of a simple C# application, starting with a trivial “Hello, World” application and building it into a basic Wikipedia search tool. Our only dependencies will be Mono and Gtk#. Packages are available for most distributions—see the on-line Resources.

Hello, World!

Let's start by constructing the most basic Mono application possible, which prints “Hello, World!” to the console:

using System;

class first {
    public static void Main (string[] args)
    {
        Console.WriteLine ("Hello, World!");
    }
}

Fire up your favorite editor, enter this code and save it as first.cs. We can then compile the program into an executable image with the following:

$ mcs first.cs

Finally, we can run it via the following command:

$ mono first.exe
Hello, World!

This application implements the first class. Every application needs an entry point, an initial function inside of the class for the Mono runtime to jump to and begin the program's execution. As with C and C++, this is the Main function. The prototype of this function is:

public static void Main (string[] args)

In our program's Main, we invoke a single function, WriteLine, which is found inside the Console class. This function, similar to printf(), writes a line of text to the console. It can be used to output the values of variables too:

int x = 5;
String s = "wolf";

Console.WriteLine ("x={0} s={1}", x, s);

This gives us:

x=5 s=wolf

Hello, World! in Color

We do not, however, have to confine our “Hello, World!” to the console; with Gtk#, we can build a trivial GUI dialog to hoist our message onto the world:

using System;
using Gtk;

class Two {
    static void WindowDelete (object o, DeleteEventArgs args)
    {
        Application.Quit ();
    }

    static void InitGui ()
    {
        Window w = new Window ("My Window");

        HBox h = new HBox ();
        h.BorderWidth = 6;
        h.Spacing = 6;
        w.Add (h);

        VBox v = new VBox ();
        v.Spacing = 6;
        h.PackStart (v, false, false, 0);

        Label l = new Label ("Hello, World!");
        l.Xalign = 0;
        v.PackStart (l, true, true, 0);

        w.DeleteEvent += WindowDelete;
        w.ShowAll ();
    }

    public static void Main (string[] args)
    {
        Application.Init ();
        InitGui ();
        Application.Run ();
    }
}

As before, enter the code via your favorite editor. This time, save it as two.cs. To compile this program, we need to tell the Mono compiler that we want to use the Gtk# assembly:

$ mcs two.cs -pkg:gtk-sharp

Running it, however, is the same:

$ mono two.exe

The application creates a small window, titled My Window, and writes “Hello, World!” into the window (Figure 1). The window is a GtkWindow, the label a GtkLabel. Gtk lays out windows via boxes. Boxes are invisible widgets—existing solely for the purpose of layout—into which other widgets are packed. The arrangement of widgets within a box determines the physical layout of the widgets within the window. Although it is possible in Gtk to arrange widgets using tables, most programmers prefer boxes for their flexibility and power—plus, once you get the hang of them, they are not difficult to use.

Figure 1. My Window

Gtk provides two types of boxes: vertical and horizontal. A vertical box, called a vbox, defines the vertical arrangement of widgets—vboxes arrange widgets into columns. A horizontal box, known as an hbox, defines the horizontal arrangement of widgets, by arranging them into rows. Widgets are packed into boxes, boxes are packed into other boxes, and the boxes are added to windows.

A new hbox is created via:

HBox h = new HBox ();

And a new vbox is created via:

VBox v = new VBox ();

The new objects representing the boxes have various properties that one can set to manipulate the look and feel of the box. In our previous example, we set two properties on the hbox:

h.BorderWidth = 6;
h.Spacing = 6;

Here, we set the border of and the spacing around the hbox to six pixels each. This provides a net spacing of 12 pixels around the hbox. Coincidentally, for purposes of aesthetics and consistency, the GNOME HIG (Human Interface Guideline) dictates a minimum spacing of 12 pixels between widgets—thus the six and six pixels in this example are perfect.

To add a box to a window, the Add() member function is called on the window in question and provided to the box:

w.Add (h);

To pack a widget into a box, the PackStart() member function is called on the box in question:

public void PackStart (Widget child,
            bool expand,
            bool fill,
            uint padding)

In our example, we did:

v.PackStart (l, true, true, 0);

This call packs our label into our vbox. If the expand parameter is true, the widget child expands to fill all available space within the box. If the fill parameter is true, the widget consumes all of its space for rendering; otherwise, if false, the widget uses superfluous space for padding. The padding parameter allows for the addition of extra spacing around the widget, within the box, beyond any padding already provided.

Starting our application is simple. We perform three simple steps:

Application.Init ();
InitGui ();
Application.Run ();

Application.Init() initializes Gtk# and the application's GUI. It should be one of the first functions that a Gtk# application invokes. After executing this function, applications set up their GUI, create and arrange their widgets, and draw the initial windows and other UI elements. In our program, we do this in the InitGui() function. Once everything is complete and ready to roll, the program calls Application.Run() and the race is off. Our main window pops up because we exposed it, back in InitGui(), via:

w.ShowAll ();

This exposes the window and all widgets packed therein. Thus, once the application invoked Application.Run(), our UI elements appear.

While executing, the program's responses are handled by the behavior of widgets. Some widgets behave in predetermined ways, not requiring the programmer to write any code manually. Most of the time, however, the programmer wants to handle events personally. This is done by writing an event handler, using the incredibly useful C# events feature, that Gtk# invokes in response to widget interaction.

In our last example, we have one such event handler. We want our application to exit when the user clicks the close box on the main window, so we need to handle the associated event, which is called DeleteEvent. This is done by writing the event handler:

static void WindowDelete (object o, DeleteEventArgs args)
{
    Application.Quit ();
}

And adding it as an event handler:

w.DeleteEvent += WindowDelete;

The function Application.Quit() causes Gtk# to destroy the UI, shut down and terminate the application. Consequently, when the user clicks the close box on the main window, our application gracefully exits.

Building a Better Example

Let's turn now to building a more complete—dare I say useful—application: a tool for searching Wikipedia. To be sure, we will not build anything fancy, but we can implement something fun. Let's look at building a simple window, with a text-entry widget. We will allow the user to type a search query into the window and click a button (Figure 2). The application then launches the user's Web browser and looks up the search term on Wikipedia. We build the whole thing, including the GUI and code to launch the Web browser, in only a handful of lines.

Figure 2. The Fancy GUI

We can construct this new application by building on our last example, adding new widgets and the requisite features. Surprisingly, thanks to Gtk#, the additional code is not very much! Here we go:


using System;
using Gtk;

class Example {
    public static Entry search_entry;

    public static void ButtonClicked (object o, EventArgs args)
    {
        String s = "http://en.wikipedia.org/wiki/Special:Search?search=";
        s += search_entry.Text;
        s += "&go=Go";
        Gnome.Url.Show (s);
    }

    static void WindowDelete (object o, DeleteEventArgs args)
    {
        Application.Quit ();
    }

    static void InitGui ()
    {
        Window w = new Window ("Wikipedia Search");

        HBox h = new HBox ();
        h.BorderWidth = 6;
        h.Spacing = 6;
        w.Add (h);

        VBox v = new VBox ();
        v.Spacing = 6;
        h.PackStart (v, false, false, 0);

        Label l = new Label ("_Search:");
        l.Xalign = 0;
        v.PackStart (l, true, false, 0);

        v = new VBox ();
        v.Spacing = 6;
        h.PackStart (v, true, true, 0);

        search_entry = new Entry ();
        search_entry.ActivatesDefault = true;
        l.MnemonicWidget = search_entry;
        v.PackStart (search_entry, true, true, 0);

        v = new VBox ();
        v.Spacing = 6;
        h.PackStart (v, true, true, 0);

        Button b = new Button ("Search");
        b.CanDefault = true;
        w.Default = b;
        v.PackStart (b, true, true, 0);

        b.Clicked += ButtonClicked;
        w.DeleteEvent += WindowDelete;
        w.ShowAll ();
    }

    public static void Main (string[] args)
    {
        Application.Init ();
        InitGui ();
        Application.Run ();
    }
}

We need to add a new assembly, gnome-sharp, to our compiler command line. Assuming you name this program three.cs, you would perform the following:

$ mcs three.cs -pkg:gtk-sharp -pkg:gnome-sharp

And now run it via:

$ mono three.exe

This third and final program contains a few new widgets—buttons, labels and text entries—but follows the same basic structure as our second program. If we work backward, from the last box outward to the first, we can get a good feel for the layout of the UI elements without even seeing a screenshot.

The other big difference is a new event, Clicked. We define a function and add it as an event handler:

b.Clicked += ButtonClicked;

We made the text-entry widget, search_entry, public, and thus our new event handler can grab the contents of the search entry when the user presses the button via search_entry.Text.

In ButtonClicked, we grab this text, construct the Wikipedia URL required for searching for the text, and use the Gnome.Url.Show() function to open the user's preferred Web browser (a global GNOME setting) and send it off to our URL.

Conclusion

Although it is impressive to implement so much in so little, the real boon is that this is a C-like language, not a scripting language. C# retains much of the power and performance of C and adds powerful object-oriented programming constructs.

Let me be frank. I am a C programmer. I spend most of my days hacking the kernel. Higher-level languages are not my cup of tea. Indeed, I am no fan of C++ or Java. Yet, having spent time with C#--working on Beagle, among other C# endeavors—I am thoroughly impressed. C# is an elegant language that readily makes clear an obvious facet: it is quite well designed.

In Mono, we have a free and open C# compiler, runtime and family of libraries, supported by a vibrant Open Source community. Although excellently paired with GNOME development, Mono is a smart tool for many jobs.

Resources for this article: /article/8750.

Robert Love is a senior kernel hacker in Novell's Ximian Desktop group and the author of Linux Kernel Development (SAMS 2005), now in its second edition. He holds degrees in CS and Mathematics from the University of Florida. Robert lives in Cambridge, Massachusetts.