GTK+ fundamentals, Part 2: How to use GTK+

A hands-on introduction

This article, the second in a three-part series titled "GTK+ fundamentals," introduces you to programming with GTK+. It analyzes a sample GTK+ application written in C, then shows that same application written in Python and C#. Finally, it discusses some useful tools that can help you develop better applications faster with GTK+.

Share:

Maciej Katafiasz (ibmdw@mathrick.org), Student, Computer Science

Maciej KatafiaszMaciej Katafiasz is a graduate student in computer science and has been using open source technologies since high school. A user of the GNOME desktop since its 1.0 days, after version 2.0 was released, he fell in love with it and learned GTK+ to be able to develop for his favorite desktop.



10 January 2006

Also available in Chinese Russian

This article assumes you are familiar with basic object-oriented concepts, such as classes, objects, methods, and inheritance. You also need a basic understanding of C language syntax, although you don't need to be able to write programs in C.

The anatomy of a GTK+ application in C

I find code is best discussed using an example. For this article, I use a short application written in C called Hello World. Although short -- and arguably rather useless as an application -- the code for this application actually shows most of the interesting concepts you're likely to encounter when programming with GTK+ (see Listing 1).

Listing 1. Hello World application in GTK+
#include <gtk/gtk.h>
#include <libintl.h>

#define _(x) gettext (x)
#define N_(x) (x)

#define GETTEXT_PACKAGE "gtk-hello"
#define LOCALEDIR "mo"

static char *greetings[] = { "Hello World",
			     "Witaj świecie",
			     "世界に今日は" };

static char* choose_greeting ()
{
  return greetings[g_random_int_range (0, G_N_ELEMENTS (greetings))];
}

static void cb_button_click(GtkButton *button, gpointer data)
{
  GtkWidget *label = GTK_WIDGET(data);

  g_assert(label != NULL);
  gtk_label_set_text(GTK_LABEL (label), choose_greeting());
}

static gboolean cb_delete(GtkWidget *window, gpointer data)
{
  gtk_main_quit();
  return FALSE;
}

int main (int argc, char *argv[])
{
  GtkWidget* window, *button, *label, *vbox;
  
  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  textdomain (GETTEXT_PACKAGE);

  gtk_init(&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  button = gtk_button_new_with_label (_("Hello World"));
  label = gtk_label_new (choose_greeting());
  vbox = gtk_vbox_new(FALSE, 0);

  gtk_container_add(GTK_CONTAINER (window), vbox);
  gtk_container_add(GTK_CONTAINER (vbox), label);
  gtk_container_add(GTK_CONTAINER (vbox), button);

  g_signal_connect(G_OBJECT (window), "delete-event", 
		   G_CALLBACK(cb_delete), NULL);

  g_signal_connect (G_OBJECT (button), "clicked", 
		    G_CALLBACK (cb_button_click), label);

  gtk_widget_show_all(window);
  gtk_main();

  return 0;
}

The big picture

Before I get into the details, here is what happens when you run the Hello World program:

  1. GTK+ and internationalization (i18n) support are initialized.
  2. The widgets are created.
  3. The widgets are organized into a hierarchy to let GTK+ know how they should be displayed on the screen.
  4. Two signal handlers are connected -- one to quit the application when the user closes the window and another to change the greeting that is displayed when the user clicks the button.
  5. The window is displayed on the screen, and the application activates the main loop by calling gtk_main().
  6. The main loop runs until gtk_main_quit() is called when the user closes the window.

Initializing

The following lines initialize GTK+ and i18n support:

Listing 2. Initializing GTK+ and i18n support
int main (int argc, char *argv[])
{
  GtkWidget* window, *button, *label, *vbox;
  
  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  textdomain (GETTEXT_PACKAGE);

  gtk_init(&argc, &argv);

The main declaration should be familiar to any C programmer. (If you aren't a C programmer, you only need to know that this is the function from which the execution of your application starts.)

The next line contains several declarations of pointers to the GtkWidget type. GTK+ is an object-oriented tool kit. Therefore, it uses the usual object-oriented concepts, such as inheritance, to realize various types of widgets. As a language, C lacks built-in support for object orientation. GTK+ overcomes this shortcoming by using several clever tricks and helpful properties as mandated by the C standard. In this scheme, objects are represented by pointers, and GtkWidget is the basic type -- called a class -- in the GTK+ hierarchy from which all other classes are derived. Hence, I have declared the variables as GtkWidget*.

The next three lines are calls you should include at the beginning of your program to get support for interface internationalization. Note that in a real application, you wouldn't declare LOCALEDIR and GETTEXT_PACKAGE by hand. Your build system handles those declarations. However, in this simple example, the declarations help clarify what's needed.

The last line is a call to gtk_init(). You must call this function and pass it the arguments your program was invoked with before any other GTK+ call is made. If you fail to do this, you will get several errors from various subsystems that didn't have a chance to properly initialize themselves.

Creating widgets

These four lines are calls to different _new() functions:

Listing 3. Calls to different _new() functions
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  button = gtk_button_new_with_label (_("Hello World"));
  label = gtk_label_new (choose_greeting());
  vbox = gtk_vbox_new(FALSE, 0);

Warning about TOPLEVEL

After seeing the TOPLEVEL parameter for gtk_window_new, you might be wondering if there are other possible types of windows. Indeed, there are. However, the need for a type other than TOPLEVEL is rare. And to use a different type, you must have a firm understanding of how GTK+ interacts with windowing systems. So, the rule is simple: Always use the TOPLEVEL parameter.

As you may have guessed, these functions create new widgets. Therefore, they are constructors for objects representing the widgets. In C++, constructors are marked in a special way and called using special syntax. However, because C doesn't support object orientation, they are no different from ordinary functions. Only the _new() appended to the function names indicates that these are indeed constructors. It's a convention that every constructor in the gtk_* namespace return a pointer to GtkWidget. Thus, by declaring the variables with this type, you can directly assign the result of a constructor call to the corresponding variable.

If you look at the individual constructors, you see they take different parameters that are appropriate for the types of widgets they create. In particular, gtk_window_new (GTK_WINDOW_TOPLEVEL) creates a new TOPLEVEL window; that is, a widget corresponding to what the user sees as a window, with a title bar, close button, or other elements that your windowing system adds.

The calls to the constructors for label and button do exactly what you'd expect them to. However, note the underscore and parentheses ( _()) surrounding the string passed to button. This macro calls the gettext() routine and is essential for translating your interface. (For more information about gettext, see Resources.)

The cryptic-looking gtk_vbox_new(FALSE, 0) creates a vertical box (VBox). Even though that widget doesn't correspond to any pixels visible on the screen, it plays a crucial role in how GTK+ lays out its controls, as you will see shortly.

Determining layout

These three lines determine the layout of the widgets:

    gtk_container_add(GTK_CONTAINER (window), vbox);
    gtk_container_add(GTK_CONTAINER (vbox), label);
    gtk_container_add(GTK_CONTAINER (vbox), button);

These lines are calls to object-oriented methods of type GtkContainer. If you look at the application program interface (API) reference, you'll see that GtkContainer inherits from GtkWidget and that all its methods take GtkContainer* as their first parameter. Therefore, GtkContainer* is the object instance the method should work on. Because the variables are of type GtkWidget*, and the C compiler doesn't support object-oriented inheritance, you need to convince the compiler that it's safe to pass these variables to a function expecting GtkContainer*. The GTK_CONTAINER() macro does this by implementing a type-safe version of casting to GtkContainer. Type-safe means that the macro verifies that the specified operation can be performed on the given type before attempting to cast. If the macro can't perform the operation, it provides a warning.

Because GTK+ employs the box layout model, you don't have to explicitly specify where widgets should be placed on the screen. Instead, you indicate which widget is contained within another widget. In the Hello World application, each gtk_container_add() method call tells the application to take the first parameter, or parent widget, and place the second parameter, or child widget, in it. The VBox widget used in this example is a type of layout widget that stacks its children vertically. So, when you place a label and a button inside, the result is that the button is displayed below the label.

That's all you have to do. If you've ever had to manually adjust or resize widgets using absolute positioning -- the model used in some tool kits, such as Win32 -- you'll be happy to know that all that is done for you in GTK+ automatically.

Connecting the signals and the main loop

After you've created and organized the widgets, it's time to add some logic to them. GTK+, like most GUI tool kits, is an event-driven framework. As such, it's organized around the main loop. The main loop operates on a continuous check-dispatch-sleep cycle. When an event occurs, the object associated with that event emits a signal to inform the main loop that it has occurred. The main loop then looks through its internal mapping table between its signals and handlers, also called callbacks, and calls the handlers registered for the given signal on the given object.

In the Hello World code, the registration of the callbacks looks like this:

Listing 4. Registration of callbacks
  g_signal_connect(G_OBJECT (window),  "delete-event", 
		   G_CALLBACK(cb_delete), NULL);
  g_signal_connect (G_OBJECT(button), "clicked", 
		    G_CALLBACK(cb_button_click), label);

Note that in GTK+, you connect to signals. The first line connects the cb_delete function to the delete-event signal on the window object. Similarly, the second line connects the cb_button_click function to the clicked signal on the button object. Notice the fourth parameter, label, in the second connect call. Later, you will see how it's used in the cb_button_click function.

This is the cb_delete function, which quits the application when the user closes the window:

static gboolean cb_delete(GtkWidget *window, gpointer data)
{
  gtk_main_quit();
  return FALSE;
}

The static modifier

In C, the static keyword links functions internally, meaning that the static function is not visible outside the source file it's declared in. Unless you need to use your callbacks from more than one file, always use the static keyword to declare them. Using this method, you avoid cluttering the limited namespace of available function names. Because static functions are limited to one file, you can safely reuse function names.

This function takes GtkWidget* and an unspecified data pointer (gpointer is a type equivalent to void*) because every callback for "delete-event" must follow this prototype. However, this function doesn't need those arguments, so they are simply ignored. It calls the gtk_main_quit() routine that quits the main loop. Also, the function returns a boolean value because the callback prototype for the delete-event signal defined for GtkWidget states a boolean return. The boolean value determines the action GTK+ takes. If it returns a value of TRUE, the event is considered handled and the default handler, which removes the widget from the windowing system, is not called. This is useful if, for example, you want to display a message asking about unsaved data and stop the window from closing based on your user's response.

This is the cb_button_click function, which changes the greeting displayed when the user clicks the button:

Listing 5. cb_button_click function
static void cb_button_click(GtkButton *button, gpointer data)
{
  GtkWidget *label = GTK_WIDGET(data);

  g_assert(label != NULL);
  gtk_label_set_text(GTK_LABEL (label), choose_greeting());
}

As you can see, this function is similar to the cb_delete function, except that it doesn't return anything and it takes GtkButton*, instead of GtkWidget. The code converts data into a pointer to GtkLabel. Remember the label parameter in the callback registration? The data pointer will now contain the pointer to that label each time the callback is called. You can use the data argument whenever you need to pass additional information to your callback. Similarly, if you need to access the object that emitted the signal, you use the first parameter, which is button in this specific callback.

After obtaining the pointer to the label, the code uses the g_assert macro to determine if the label is equal to NULL. The g_assert macro is a utility macro from Glib (a library of useful C types and routines that GTK+ uses) that will abort the program if the condition passed to it is not met -- in this case, if label is equal to NULL. Because label being equal to NULL signifies that the programmer has made a mistake, this ensures that the mistake will be caught before the code goes into production.

Displaying the window

After the callbacks are connected, the gtk_widget_show_all() function makes the window -- that is, all the widgets -- appears on the screen (see Figure 1).

Figure 1. Hello World application, running in Polish and Japanese
The running application

Activating the main loop

When everything is set up and visible, the gtk_main() function activates the main loop. The main loop enters an indefinite loop, waiting for events and calling callbacks, until someone calls gtk_main_quit() by closing the window.

NOTE: If you still have questions about anything in the code, look at the attached source code. It's identical to what I've shown in the article, but every line is explained through comments.

Compiling and running

To compile this program, you need the C compiler and the development files (headers and libraries) for GTK+. For information about how to obtain these items, see Resources.

After you install the files, extract the source code, go to the directory it was extracted to and run make:

$ tar -xzf gtk_hello.tgz
$ cd gtk_hello
$ make

NOTE: If you are running Microsoft® Windows®, instead of running make, open Microsoft Visual Studio™.NET and run the "hello" project.


GTK+ in other programming languages

You can use GTK+ in multiple programming languages. To do that, you use bindings. Bindings are special packages for a given language that expose the GTK+ API in a manner appropriate for that language.

For example, I have translated the Hello World application into Python and C#. To run GTK+ with these languages, you need PyGTK and Gtk#, respectively, in addition to Python and Mono/.NET (see Resources).

Hello World in PyGTK

Listing 6 displays code for the Hello World application translated into Python.

Listing 6. Hello World application in PyGTK
import pygtk
pygtk.require('2.0')
import gtk
import random

greetings = ["Hello World", "Witaj Świecie", "世界に今日は"]
def choose_greeting (greets):
    return greets[random.randint (0, len(greets) - 1)]

def cb_clicked(button, label):
    label.set_text(choose_greeting(greetings))

window = gtk.Window ()
vbox = gtk.VBox ()
button = gtk.Button("Hello World")
label = gtk.Label (choose_greeting (greetings))

window.add(vbox)
vbox.add(label)
vbox.pack_start(button, False, False)

window.connect("delete-event", lambda a,b: gtk.main_quit())
button.connect("clicked", cb_clicked, label)

window.show_all()
gtk.main()

Thanks to the compact Python code, this version of the application is shorter than its C counterpart. Aside from that, it looks similar. Notice that the code has been translated to use Python idioms, yet the API remains the same.

Hello World in Gtk#

The code for the Hello World application in Gtk# is a bit longer than the C version because of the rather lengthy declarations C# requires. As a result, I have not included the full source code for it here. The source code is included in the attached download. Here's a quick look at how the main concepts translate from C to C#:

  class GtkHello : Gtk.Window
  {

Instead of creating a new window and setting it up, you put the Gtk.Window class into a subclass and move all setup code into a constructor. This approach is not specific to Gtk#. In fact, it's often used in C programs when more than one copy of a window is needed. However, it's so easy to use subclasses in C# that it makes sense to do it even for a single instance -- especially when you consider that C# requires you to declare at least one class.

  this.DeleteEvent += new DeleteEventHandler(DeleteCB);
  button.Clicked += new EventHandler(ButtonClickCB);

As you can see, GTK+ signals are translated into the native C# concept of events. The names are slightly modified to better follow the C# naming conventions.

Listing 7. GTK+ signals translated into native C# concept of events
  private void DeleteCB (object o, DeleteEventArgs args)
  {
    Application.Quit ();
    args.RetVal = true;
  }

Because of the way C# events are constructed, the prototype for the delete-event handler is slightly different. Instead of returning true from your callback, you pass it through args.RetVal. The pair, gtk_main() and gtk_main_quit(), are replaced by Application.Run() and Application.Quit(), respectively.


Useful tools

When developing with GTK+, several tools can make your life easier. Some of the most prominent tools are Glade, Libglade, and Devhelp.

Glade and Libglade

Glade is an interface builder -- a program that lets you build your application graphically, instead of manually from source code. Even more important than that is the second component: Libglade. As the name suggests, Libglade lets you read the Extensible Markup Language (XML) format that Glade uses to store its user interface description. Using Libglade, you can build your application's interface directly from this description without any code at all.

Figure 2 displays a simple Libglade application that contains several widgets.

Figure 2. Simple Libglade application
Application using libglade

Listing 8 shows the full source code for the Libglade application displayed in Figure 2.

Listing 8. Libglade application source code
#include <gtk/gtk.h>
#include <glade/glade.h>

int main (int argc, char *argv[])
{
  GladeXML *ui;
  gtk_init(&argc, &argv);

  /* Read user interface description from glade file */
  ui = glade_xml_new ("glade_hello.glade", "main_window", NULL);
  /* Automatically connect signals */
  glade_xml_signal_autoconnect(ui);
  gtk_main();

  return 0;
}

As you can see, the whole thing is only 17 lines long, including comments and blank lines. Although real applications won't be that short, Libglade gives you tremendous gains in terms of the legibility, modularity, and maintainability of your code.

If you want to examine how this program is built more closely, you can find it, along with the rest of the examples for this article, in the attached download.

Devhelp

Devhelp is a documentation browser designed to read documentation in the format produced by gtk-doc -- the standard tool for building GTK+ documentation, which is also used by related projects, such as Pango and GNOME. Using Devhelp, you can instantly search a function index and browse the installed documentation, allowing you to obtain needed information much more rapidly. Devhelp is a GNOME application. Therefore, to run it, you need a POSIX-compliant operating system that GNOME runs on, such as Linux® or Sun Solaris, and the GNOME runtime libraries. You don't need to run GNOME itself.

If you use another platform, there are many other ways to read GTK+ documentation. Mono project has the Monodoc browser, which usually comes with the Gtk# reference preloaded. GTK+ installers for Windows also usually include documentation in formats suitable for reading by various developer tools, such as Visual Studio®. Finally, there is always the option of reading documentation online using a Web browser, but a dedicated browser is preferred because of its speed and additional features designed to help in searching through program documentation.

Next time

In this article, you have learned the basic concepts used in GTK+ programming. You have also seen how you can use GTK+ in languages other than C, using idioms specific for those languages while preserving the general way you use GTK+. Finally, you have been introduced to tools that will help you develop better applications faster.

In the final installment of this series, you will get a closer look at another aspect of development with GTK+: deployment. The article will analyze your options in detail, including portability concerns and ease of installation. Finally, you will see GTK+ in a broader view -- as a project with a lively community that can help you build applications that will better serve your users.


Download

DescriptionNameSize
Source codeos-gtk2_hello.zip19KB

Resources

Learn

Get products and technologies

  • Get Gtk#, the GTK+ binding for the Microsoft .NET environment.
  • Get the official source code tarballs for GTK+.
  • Visit PyGTK, the official site of GTK+ bindings for Python.
  • Gazpacho is an improved editor for Glade UI description files, written in PyGTK.
  • Devhelp is a programmer-oriented documentation browser for GNOME.
  • Check out GNOME, the usability-focused desktop built using GTK+.
  • Try Xfce, a fast and easy desktop, also developed with GTK+.
  • Visit Gnomefiles and get more than 1,000 applications built using GTK+.
  • Innovate your next open source development project with IBM trial software, available for download or on DVD.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=101359
ArticleTitle=GTK+ fundamentals, Part 2: How to use GTK+
publish-date=01102006