When constructing a more complicated user interface, with dozens or hundreds of widgets, doing all the setup work in C code is cumbersome, and making changes becomes next to impossible.
Thankfully, GTK supports the separation of user interface layout from your business logic, by using UI descriptions in an XML format that can be parsed by the GtkBuilder class.</para>
Create a new file with the following content named
example-3.c
.
#include <gtk/gtk.h>
#include <glib/gstdio.h>
static void
print_hello (GtkWidget *widget,
gpointer data)
{
g_print ("Hello World\n");
}
static void
quit_cb (GtkWindow *window)
{
gtk_window_close (window);
}
static void
activate (GtkApplication *app,
gpointer user_data)
{
/* Construct a GtkBuilder instance and load our UI description */
GtkBuilder *builder = gtk_builder_new()
;
gtk_builder_add_from_file (builder, "builder.ui", NULL);
/* Connect signal handlers to the constructed widgets. */
GObject *window = gtk_builder_get_object (builder, "window");
gtk_window_set_application (GTK_WINDOW (window), app);
GObject *button = gtk_builder_get_object (builder, "button1");
g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);
button = gtk_builder_get_object (builder, "button2");
g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);
button = gtk_builder_get_object (builder, "quit");
g_signal_connect_swapped (button, "clicked", G_CALLBACK (quit_cb), window);
gtk_widget_show (GTK_WIDGET (window));
/* We do not need the builder any more */
g_object_unref (builder);
}
int
main (int argc,
char *argv[])
{
#ifdef GTK_SRCDIR
g_chdir (GTK_SRCDIR);
#endif
GtkApplication *app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
int status = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return status;
}
Create a new file with the following content named
builder.ui
.
<?xml version="1.0" encoding="UTF-8"?> <interface> <object id="window" class="GtkWindow"> <property name="title">Grid</property> <child> <object id="grid" class="GtkGrid"> <child> <object id="button1" class="GtkButton"> <property name="label">Button 1</property> <layout> <property name="column">0</property> <property name="row">0</property> </layout> </object> </child> <child> <object id="button2" class="GtkButton"> <property name="label">Button 2</property> <layout> <property name="column">1</property> <property name="row">0</property> </layout> </object> </child> <child> <object id="quit" class="GtkButton"> <property name="label">Quit</property> <layout> <property name="column">0</property> <property name="row">1</property> <property name="column-span">2</property> </layout> </object> </child> </object> </child> </object> </interface>
You can compile the program above with GCC using:
gcc `pkg-config --cflags gtk4` -o example-3 example-3.c `pkg-config --libs gtk4`
Note that GtkBuilder can also be used to construct objects that
are not widgets, such as tree models, adjustments, etc. That is
the reason the method we use here is called
gtk_builder_get_object()
and returns a GObject* instead of a
GtkWidget*.
Normally, you would pass a full path to
gtk_builder_add_from_file()
to make the execution of your
program independent of the current directory. A common location
to install UI descriptions and similar data is
/usr/share/appname
.
It is also possible to embed the UI description in the source
code as a string and use gtk_builder_add_from_string()
to load
it. But keeping the UI description in a separate file has
several advantages: It is then possible to make minor
adjustments to the UI without recompiling your program, and,
more importantly, graphical UI editors such as
glade can load the
file and allow you to create and modify your UI by
point-and-click.