Building user interfaces

When construcing 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, CTK+ 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 CtkBuilder class.

Example 3. Packing buttons with CtkBuilder

Create a new file with the following content named example-3.c.

#include <ctk/ctk.h>

static void
print_hello (CtkWidget *widget G_GNUC_UNUSED,
             gpointer   data G_GNUC_UNUSED)
{
  g_print ("Hello World\n");
}

int
main (int   argc,
      char *argv[])
{
  CtkBuilder *builder;
  GObject *window;
  GObject *button;
  GError *error = NULL;

  ctk_init (&argc, &argv);

  /* Construct a CtkBuilder instance and load our UI description */
  builder = ctk_builder_new ();
  if (ctk_builder_add_from_file (builder, "builder.ui", &error) == 0)
    {
      g_printerr ("Error loading file: %s\n", error->message);
      g_clear_error (&error);
      return 1;
    }

  /* Connect signal handlers to the constructed widgets. */
  window = ctk_builder_get_object (builder, "window");
  g_signal_connect (window, "destroy", G_CALLBACK (ctk_main_quit), NULL);

  button = ctk_builder_get_object (builder, "button1");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  button = ctk_builder_get_object (builder, "button2");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  button = ctk_builder_get_object (builder, "quit");
  g_signal_connect (button, "clicked", G_CALLBACK (ctk_main_quit), NULL);

  ctk_main ();

  return 0;
}

Create a new file with the following content named builder.ui.

<interface>
  <object id="window" class="CtkWindow">
    <property name="visible">True</property>
    <property name="title">Grid</property>
    <property name="border-width">10</property>
    <child>
      <object id="grid" class="CtkGrid">
        <property name="visible">True</property>
        <child>
          <object id="button1" class="CtkButton">
            <property name="visible">True</property>
            <property name="label">Button 1</property>
          </object>
          <packing>
            <property name="left-attach">0</property>
            <property name="top-attach">0</property>
          </packing>
        </child>
        <child>
          <object id="button2" class="CtkButton">
            <property name="visible">True</property>
            <property name="label">Button 2</property>
          </object>
          <packing>
            <property name="left-attach">1</property>
            <property name="top-attach">0</property>
          </packing>
        </child>
        <child>
          <object id="quit" class="CtkButton">
            <property name="visible">True</property>
            <property name="label">Quit</property>
          </object>
          <packing>
            <property name="left-attach">0</property>
            <property name="top-attach">1</property>
            <property name="width">2</property>
          </packing>
        </child>
      </object>
      <packing>
      </packing>
    </child>
  </object>
</interface>

You can compile the program above with GCC using:


        gcc `pkg-config --cflags ctk+-3.0` -o example-3 example-3.c `pkg-config --libs ctk+-3.0`
      

Note that CtkBuilder 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 ctk_builder_get_object() and returns a GObject* instead of a CtkWidget*.

Normally, you would pass a full path to ctk_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 ctk_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.