Writing a GTK+ based GUI application is in essence extremely simple, and
we'll use the Hello World example from Ian Main's excellent GTK+ tutorial,
which is a very useful guide to writing gnome applications. But first we'll
talk about the basic philosophy behind GTK+.
A D V E R T I S E M E N T
GTK+ is a container based toolkit, meaning you don't specify where the
widget is, but you specify in what container it is. Some widgets, such as a
window or a frame or a button, are containers that hold only one other
widget. For example a button with a label is actually a button into which we
added a label widget. If you need to put more widgets into that container,
you will need to add another container into them, one that holds more then
one widget such as a horizontal box.
In fact most layout of windows is usually done with containers such as
horizontal boxes, vertical boxes and tables, those are the most important to
learn. A horizontal box is a widget that you can add several widgets into
and they will be added in a horizontal row. The height of the horizontal box
is the height of the highest widget added, and the length is the length of
all widgets combined. Vertical box behaves exactly the same, except that
it's vertical instead of horizontal. A table can take in widgets at
different rows and columns.
Figure 2-1. Example Window Hierarchy
GTK+ Object Model
Gtk's object model is an object oriented framework for C. It includes
singular object inheritance, virtual methods, signals, runtime object
modification, runtime type checking, and other goodies. While writing a GTK+
object is more involved then say writing an object in something like Java,
it does have many advantages. GTK+ is an object model which doesn't require
inheritance for most things you do with objects. For one, since methods are
just functions that take the pointer to the object as the first argument,
it's easy to write more methods in your own code, which are missing in the
original object.
Objects in GTK+ are just C structures and inheritance is done by just
including the parent structure as the first item in the child structure.
This means, we can cast beween types with no problem. In addition to the
object structure which is a specific instance, there is also a class
structure for each class which contains things like pointers to virtual
functionsand default signal handlers.
GTK+ Method Types
GTK+ object model uses 3 types of methods, a method can be a simple C
function which just happens to take the pointer to an object instance as the
first argument. This method type is the fastest in execution time, but
cannot be overriden. The second type of a method is a virtual method. A
virtual method is a function pointer int he class structure, with usually a
wrapper function that takes care of calling the virtual method. This type of
a method can be overriden in derived objects, but that's where it's
advantages stop. The third and most flexible (and slowest to call) is the
signal method. The signal is sort of like a virtual method, but the user can
connect handlers to be run before or after the default method body.
Sometimes objects have a singal method for the sole purpose of having users
connect handlers to it andthus has the body of the method empty.
Data on Objects
There is a way to store arbitrary named data in objects to extend the
object. This is done with the method,
gtk_object_set_data (or gtk_object_set_user_data
for a single unnamed pointer). To retrieve data, one uses
gtk_object_get_data. Example:
GtkObject *obj;
void *some_pointer;
...
/*here we set "some_data" data on obj to point to some_pointer*/
gtk_object_set_data(obj, "some_data", some_pointer);
...
/*retrieve pointer to some_data from obj and store it in
some_pointer*/
some_pointer = gtk_object_get_data(obj, "some_data");
The pointer can be a pointer to anything since it's manipulated as a (void
*).
GTK+/GNOME Naming Conventions
Both GTK+ and GNOME use the same naming convention when naming objects
and functions. GTK+ uses a prefix of gtk_ for
functions, and Gtk for objects, and GNOME uses
gnome_ and Gnome. When a
function is a method for an object, the name (lower case) is appended to the
prefix. For example the button object is named GtkButton
(that is the name of the C struct holding the data for the object), and say
the "new" method for GtkButton
is then called gtk_button_new. Macros associated
with objects use the same naming convention as functions, but are all
capitalized. For example a macro that casts an object to a
GtkButton is called GTK_BUTTON.
There are exceptions, notably the type checking macro, which is called
GTK_IS_BUTTON for GtkButton.
Using GTK+ Methods
Since GTK+ is object oriented, it uses inheritance for it's widgets. For
example GtkHBox and GtkVBox
are derived from GtkBox. And thus you can use any
GtkBox method on a GtkVBox
or GtkHBox. However you need to cast the
GtkVBox object to GtkBox
before you call the function. This could be done with standard C casts such
as:
This would work, however it is unsafe. GTK+ provides a mechanism of checking
the types, so that it can warn you if you are casting an object which does
not derive from the object you are casting to, or if you try to cast a NULL
pointer. The macro is all capital name of the widget. For example the above
code snippet would be
GNOME uses the exact same form so anything you learn about GTK+ can be
used for GNOME widgets, you just replace the GTK prefix with GNOME.
Example Hello World Program
Here is the promised example code for the hello world program. It doesn't
use any advanced containers, just a window and a button onto which a label
is added. It illustrates the basic workings of a GUI program written in GTK+.
Don't be scared by it's size, it's mostly comments.
/* example-start helloworld helloworld.c */
#include <gtk/gtk.h>
/* this is a callback function. the data arguments are ignored in
* this example.. More on callbacks below. */
void
hello (GtkWidget *widget, gpointer data)
{
g_print ("Hello World\n");
}
gboolean
delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
g_print ("delete event occurred\n");
/* if you return FALSE in the "delete_event" signal
* handler, GTK will emit the "destroy" signal.
* Returning TRUE means you don't want the window
* to be destroyed. This is useful for popping up
* 'are you sure you want to quit ?' type dialogs. */
/* Change TRUE to FALSE and the main window will
* be destroyed with a "delete_event". */
return TRUE;
}
/* another callback */
void
destroy (GtkWidget *widget, gpointer data)
{
gtk_main_quit ();
}
int
main (int argc, char *argv[])
{
/* GtkWidget is the storage type for widgets */
GtkWidget *window;
GtkWidget *button;
/* this is called in all GTK applications.
* arguments are parsed from the command line and
* are returned to the application. */
gtk_init (&argc, &argv);
/* create a new window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* when the window is given the "delete_event" signal
* (this is given by the window manager, usually by
* the 'close' option, or on the titlebar), we ask
* it to call the delete_event () function as defined
* above. The data passed to the callback function
* is NULL and is ignored in the callback function. */
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
GTK_SIGNAL_FUNC (delete_event), NULL);
/* here we connect the "destroy" event to a signal
* handler. This event occurs when we call
* gtk_widget_destroy() on the window, or if we
* return 'FALSE' in the "delete_event" callback. */
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
/* sets the border width of the window. */
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* creates a new button with the label "Hello World". */
button = gtk_button_new_with_label ("Hello World");
/* When the button receives the "clicked" signal, it
* will call the function hello() passing it NULL as
* it's argument. The hello() function is defined
* above. */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (hello), NULL);
/* This will cause the window to be destroyed by
* calling gtk_widget_destroy(window) when "clicked".
* Again, the destroy signal could come from here,
* or the window manager. */
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));
/* this packs the button into the window
* (a gtk container). */
gtk_container_add (GTK_CONTAINER (window), button);
/* the final step is to display this newly created
* widget... */
gtk_widget_show (button);
/* and the window */
gtk_widget_show (window);
/* all GTK applications must have a gtk_main().
* Control ends here and waits for an event to occur
* (like a key press or mouse event). */
gtk_main ();
return 0;
}
/* example-end */