Mono, .NET, GTK Sharp and Glade

This article explains the basics of using the Glade interface designer, and the simple task of hooking up a Glade interface to Mono based C# code. The overall purpose of these articles is to show how to use Glade to create cross-platform GUI applications that use a Delphi or Visual Studio.NET approach to Visual design.

The two previous articles in this series first introduced GTK#, then explained how to write simple code to handle signals. This article will complete the introduction to this subject by showing how to add two buttons to our main window, and how to respond to clicks on these buttons.

Visual Design in Glade: Horizontal Boxes

In Glade, one traditionally organizes a visual interface by using a set of containers similar to the layout managers found in Java. In this case, we will first put down a widget called a horizontal box. This tool will be used to control the layout of the two buttons that we place on top of this box. In other words, the box will own the two buttons, and help to ensure that they are laid out appropriately.

Go to the Palette and click on the Horizontal Box. Drop it on to the main window. You will be prompted to specify the number of columns that you need. Enter the number two. This will end up creating two GtkHBoxes, one for each of the buttons we intend to drop on top of these containers. When you are done, the box lies on top of the main window. The box is used for layout purposes, and thus has no specific visual representation other than some outlines provided by Glade to help you visualize the dimensions of the horizontal box.

Working with Buttons

The next step is to drop down the two buttons. Click on the button icon in the palette, then drop the button onto the left hand side of the designer.

You can see that the alignment of the buttons is not all we might hope for. In particular, we might want the two buttons to take up the entire work space, and to be distributed equally across it. In other words, we want the left half of the main window to contain button1, and the right half to contain button2. This arrangement is visible at the end of this article, in the last screen shot, showing the final state of our application.

To align the buttons correctly, we will to take two steps. First, we will optionally give each button a border of 5. This is done simply to improve the aesthetic appeal of our simple application. We should then be sure to set the Expand and Fill properties for each button to Yes. First click on the button you want to edit. Now turn to the Properties window. On the Widget page in the Properties window, you can see that there is a field called Border Width. Set its value to 5 for both buttons.

Now turn to the Packing page in the Properties window. Set the Expand and Fill properties to Yes. Be sure to repeat the process twice, first for button1, then for button2.

At this point, you might want to rename the buttons. I called the first button ButtonOne, and the second button ButtonTwo. You might also want to set their captions to Button One and the Button Two.

Setting Up the Button Click Signals

It is almost time to write the C# code for our application. First, however, we should set up the signals. In particular, we need to define the events that will be fired when a user clicks on ButtonOne or ButtonTwo. This is a two step process. First we define the events in Glade, then we write C# code for handling the events.

Select one of the buttons. Turn to the Signals page in the Properties window. Click on the ellipses icon on the Signals line, and select the clicked event. Press the OK button to complete the process.

Now rename the event to OnButtonTwoClicked. Complete the process by selecting the Add button. You will need to repeat the process for the other button. Don’t forget to press the Add button to bring things into the state!

The final step in this process is to save the program. Push the Save button in the main Glade window, and save your code under the name gtkstart06. This will produce two files, called gtkstart06.glade, and gtkstart06.gladep. Any valid file name can be used instead of gtkstart06. Note, however, that you will be loading the gtkstart06.glade file into your program. As a result, you should be sure that you specify the right name when loading the file, as described in the section called Writing the C# Code.

Examining the XML

Glade produces XML to define the actions you have performed inside its editor. If you are Delphi programmer, then you will recognize this XML as the equivalent of the DFM or XFM files produced by the Delphi visual designer.

You can see the XML found for our application in Listing 1. Please don’t panic when looking at this source. As explained in the previous articles in this series, it will be parsed for you automatically. I am showing it to you simply so you will have chance to study it. If it does not interest you, there is no reason why you cannot safely move on to the next section of this article.

Listing 1: The XML for the program we have been creating as it is found in the file called gtkstart06.glade.

<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<requires lib="gnome"/>
<widget class="GtkWindow" id="MyWindow">
  <property name="visible">True</property>
  <property name="title" translatable="yes">My Window</property>
  <property name="type">GTK_WINDOW_TOPLEVEL</property>
  <property name="window_position">GTK_WIN_POS_NONE</property>
  <property name="modal">False</property>
  <property name="resizable">True</property>
  <property name="destroy_with_parent">False</property>
  <property name="decorated">True</property>
  <property name="skip_taskbar_hint">False</property>
  <property name="skip_pager_hint">False</property>
  <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
  <signal name="delete_event" 
    handler="MyWindowDeleteEvent" 
    last_modification_time="Thu, 16 Dec 2004 17:13:38 GMT"/>
  <child>
    <widget class="GtkHBox" id="hbox1">
      <property name="visible">True</property>
      <property name="homogeneous">False</property>
      <property name="spacing">0</property>
      <child>
        <widget class="GtkHBox" id="hbox2">
          <property name="visible">True</property>
          <property name="homogeneous">False</property>
          <property name="spacing">0</property>
          <child>
            <widget class="GtkButton" id="button1">
              <property name="border_width">5</property>
              <property name="visible">True</property>
              <property name="can_focus">True</property>
              <property name="label" translatable="yes">button1</property>
              <property name="use_underline">True</property>
              <property name="relief">GTK_RELIEF_NORMAL</property>
              <property name="focus_on_click">True</property>
              <signal name="clicked" 
                handler="OnButtonOneClicked" 
                last_modification_time="Thu, 16 Dec 2004 18:26:53 GMT"/>
            </widget>
            <packing>
              <property name="padding">0</property>
              <property name="expand">True</property>
              <property name="fill">True</property>
            </packing>
          </child>
          <child>
            <widget class="GtkButton" id="ButtonTwo">
              <property name="border_width">5</property>
              <property name="visible">True</property>
              <property name="can_focus">True</property>
              <property name="label" translatable="yes">Button Two</property>
              <property name="use_underline">True</property>
              <property name="relief">GTK_RELIEF_NORMAL</property>
              <property name="focus_on_click">True</property>
              <signal name="clicked" 
                handler="OnButtonTwoClicked" 
                last_modification_time="Thu, 16 Dec 2004 18:27:39 GMT"/>
            </widget>
            <packing>
              <property name="padding">0</property>
              <property name="expand">True</property>
              <property name="fill">True</property>
            </packing>
          </child>
        </widget>
        <packing>
          <property name="padding">0</property>
          <property name="expand">True</property>
          <property name="fill">True</property>
        </packing>
      </child>
    </widget>
  </child>
</widget>
</glade-interface>

The basic structure of this file consists of an XML glade-interface section in which we nest first a widget of class GtkWindow, then two widgets of class GtkHBox, then two widgets of class GtkButton.

<glade-interface>
<widget class="GtkWindow" id="MyWindow">
    <widget class="GtkHBox" id="hbox1">
        <widget class="GtkHBox" id="hbox2">
            <widget class="GtkButton" id="ButtonTwo">
            <widget class="GtkButton" id="ButtonTwo">

Each of these widgets has various properties filled out in the manner specified by us when working in the Properties window. For instance, as shown in Figure 7, we set the Expand and Fill properties to Yes. The results are shown clearly in the XML:

          <property name="expand">True</property>
          <property name="fill">True</property>

You can also see the event handlers, or signals, that we defined:

            <widget class="GtkButton" id="ButtonTwo">
              // Code omitted here              
              <signal name="clicked" 
                handler="OnButtonTwoClicked" 
                last_modification_time="Thu, 16 Dec 2004 18:27:39 GMT"/>

Here you can see that there is a signal called clicked which will be handled by a method which we shall define called OnButtonTwoClicked.

As I said earlier, there is no need for you to actually parse this XML code; nor need you even look at it if you are not interested in it. However, it is often useful to understand this kind of code. In particular, it is nice to see that the visual tools produce a nice, simple XML file that you can store in CVS or other code repositories. Furthermore, the format here is so simple and straightforward that you can easily make small changes to your code by hand; there is no need to bring up Glade merely to change the name of a component, or to perform other simple tasks.

Writing the C# Code

It is at last time to write the simple C# code that will form the heart of our application. In a normal development cycle, you will be able to finish your work in Glade in just a few minutes. You will then spend most of your time writing C# code. But in this article, I dwelt on the Glade interface in some detail so as to make sure you understood how it works. Covering the code will be an equally important task, but it will take up much less space in this article.

Listing 2: The source code for the simple test application we have been creating.

namespace CodeFezSamples
{
  using System;
  using Gtk;
  using Glade;
  using GtkSharp;
  public class GtkStart06
  {
     ///////////////////////////////
     /// Import the widgets
     ///////////////////////////////
     [Glade.Widget]      
     Button ButtonOne;
     [Glade.Widget]      
     Button ButtonTwo;
     ///////////////////////////////
     /// Constructor
     ///////////////////////////////
     public GtkStart06 (string[] args) 
     {
             Application.Init();
             // Load the main window
             Glade.XML gxml = new Glade.XML ("gtkstart06.glade", 
               "MyWindow", null);
             gxml.Autoconnect (this);
             Application.Run();
     }
     // Called when main window closes
     public void MyWindowDeleteEvent (object o, DeleteEventArgs args) 
     {
             Application.Quit ();
             args.RetVal = true;
     }
     public void OnButtonOneClicked (System.Object obj, EventArgs e) 
     {
             String data = String.Format("ButtonOne {0}", ButtonOne.Label);
             Console.WriteLine (data);
     }
     public void OnButtonTwoClicked (System.Object obj, EventArgs e) 
     {
             String data = String.Format("ButtonTwo {0}", ButtonTwo.Label);
             Console.WriteLine (data);
     }
     ///////////////////////////////
     /// Main
     ///////////////////////////////
     public static void Main (string[] args)
     {
             new GtkStart06(args);
     }
  }
}

The code for this application begins with a Main statement, shown at the bottom of Listing 1. This block does nothing more than create an instance of our main class, called GtkStart06. Again, you can use any valid C# identifier to specify the name of your class.

After writing simple using statements to import the libraries we need, the next step is to import the two buttons that we defined inside of Glade:

  [Glade.Widget]      
  Button ButtonOne;
  [Glade.Widget]
  Button ButtonTwo;

We now have two instances of these buttons. If we want to work with them, we can write simple code such as the following:

ButtonOne.BorderWidth=10;

There is no actual instance of code of this type in our program, but I show this sample to you so that you can understand that ButtonOne is an instance of your button, and that you can execute code on this instance. To see this in action, you could insert this code into the end of the constructor just before the Application.Run statement, then compile and launch your program to view the result. More of this kind of code will be shown in later articles in this series.

As explained in earlier articles, we use two simple lines of code to parse the XML file and to connect to the events and controls defined by us while working inside of Glade:

Glade.XML gxml = new Glade.XML ("gtkstart06.glade",
  "MyWindow", null);
gxml.Autoconnect (this);

This is all you need to do to connect to the code defined in your XML file, and to set up the event handlers. All the details are taken care of for you automatically.

The final step is to create the three simple signal handlers that will define the events that occur when the user closes the main window, or clicks on either of the two buttons we have created:

   // Called when main window closes
   public void MyWindowDeleteEvent (object o, DeleteEventArgs args) 
   {
      Application.Quit ();
      args.RetVal = true;
   }
    
   public void OnButtonOneClicked (System.Object obj, EventArgs e) 
   {
      String data = String.Format("ButtonOne {0}", ButtonOne.Label);
      Console.WriteLine(data);
   }
   public void OnButtonTwoClicked (System.Object obj, EventArgs e) 
   {
      String data = String.Format("ButtonTwo {0}", ButtonTwo.Label);
      Console.WriteLine(data);
   }

As explained in the previous article, our first handler calls Application.Quit to ensure that the application itself is destroyed when the main window is closed. The next two handlers simply write text out to the console when either of the buttons are clicked. In particular, note the use of the ButtonOne.Label and ButtonTwo.Label properties. If we did not access these properties, there would be no need to import ButtonOne and ButtonTwo. More complex button handling code will be defined in future articles.

The final step is to once again compile and run the program. This is best done with a simple bash shell script:

export MONO_PATH=/usr/lib/mono/gtk-sharp/
mcs /unsafe -r gtk-sharp.dll -r glade-sharp.dll -r gnome-sharp.dll gtkstart06.cs
export MONO_PATH=

If you are working on Windows, there is no need for the code that sets the MONO_PATH, and you should probably write -pkg: instead of -r:

mcs -pkg:gtk-sharp -pkg:glade-sharp gtkstart06.cs 

After running this script, you should have a program called gtkstart06.exe on your hard drive in the current directory. Run the program by typing the following at the shell prompt:

mono gtkstart06.exe

After pressing either of the buttons, look back at the shell prompt from which you launched the application in order to see the output.

Summary

In this article you have seen the simple steps necessary to create a visual interface in Glade, and you have seen how to create C# code that can hook into the widgets and signals that you created.

The simple steps outlined in this chapter can be boiled down to the following four bullet points:

  • Drop down a Horizontal Box with two sections.

  • Place a Button in each section

  • Define clicked signals for each button.

  • Write C# code to handle the signals at run time.

I’ve gone over these steps in some detail to ensure that you understand how to perform them, and why each step exists. However, if you practice writing this kind of application a few times, you will find that it is a very easy process. After a few minutes practice, and assuming you cut and paste some code, you should be able to write an application of this type in five to ten minutes, or perhaps less.

It is easy to write code of this kind, and it runs smoothly in Windows and Linux. Glade interfaces can also be called from C, C++, Python, Perl, and other languages. This a powerful and easy to use technology that frees you from the tyranny of proprietary, single platform, APIs.