Controlling All Widgets in GTK Callbacks With GtkBuilder
TL;DR: gtk_builder_connect_signals(builder, builder)
GtkBuilder allows one to conveniently create graphical user interface (GUI) using an interface designer (i.e. Glade), which is convenient because we can draw GUI by dragging an dropping, without writing a loong piece of code in a GUI initialization function.
In a GtkBuilder UI definition file, widget signals can also be assigned callbacks in the designer so that one can conveniently connect them with a single function call (this is what previously I wrote):
gtk_builder_connect_signals(builder, NULL);
Callbacks are declared by the caller, and thus forms of callbacks are fixed for implementors. In GTK (and almost all G* stuff), one gpointer user_data parameter is reserved for extra argument passing. It is natural if we only pass at most one object to the callbacks. If we want to pass more to a callback, we may want to do the following:
- Pack all things into one data structure. In C,
structis certainly used. - Dynamically allocate a piece of memory somewhere in code.
- Put (pointers of) all things to pass in that memory.
- Tell
GObjectto pass the pointer to that piece of memory to the callback.
struct _PrivateFoo {
GtkWidget *btn_left;
GtkWidget *btn_right;
GtkWidget *entry;
};
void btn_xxx_clicked_cb(GtkWidget *self, gpointer data);
/* ... */
struct _PrivateFoo *data = malloc(sizeof(struct _PrivateFoo));
data->btn_left = btn_foo;
data->btn_right = btn_bar;
data->entry = entry_baz;
/* Connect "clicked" signal of a button to the callback declared before */
g_signal_connect(G_OBJECT(btn_xxx), "clicked", G_CALLBACK(btn_xxx_clicked_cb), data);
This must be written in code, and preferably during the initialization process of a program. If we have to do these for all callbacks that controls on multiple widgets1, we need to declare many private structs and write a long piece of code in order to initialize the signals. This eliminates benefits that GtkBuilder brings us.
At some point, I noticed the prototype of gtk_builder_connect_signals2:
void
gtk_builder_connect_signals (GtkBuilder *builder,
gpointer user_data);
In which:
builder: aGtkBuilderuser_data: user data to pass back with all signals
After some search and experiments, I found out that this user_data is what being passed to all callbacks by default. If we pass NULL here, all callbacks without user_data declared in Glade will receive NULL. So, if we do:
/* GtkBuilder used to connect signals is also passed to all callbacks */
gtk_builder_connect_signals(builder, builder);
Then all callbacks written in the UI definition file without a designated widget as user_data will receive a GtkBuilder *. We can, therefore, extract multiple widgets from the GtkBuilder in callbacks:
void some_cb(GtkWidget *self, GtkBuilder *builder) {
/* Widget IDs are declared in Glade */
GtkWidget *btn_left = GTK_WIDGET(gtk_builder_get_object(builder, "btn_foo"));
GtkWidget *btn_right = GTK_WIDGET(gtk_builder_get_object(builder, "btn_bar"));
GtkWidget *entry = GTK_WIDGET(gtk_builder_get_object(builder, "entry_baz"));
/* Operate on these widgets! */
}
This is a graceful way to pass multiple widgets to all callbacks, with the help of GtkBuilder.
Notes
-
Note that if non-widget data is also required by a callback, then we still need to do extra work for the callback to access the data. ↩
-
https://developer.gnome.org/gtk4/stable/GtkBuilder.html#gtk-builder-connect-signals ↩