gtkdesktopgnomegtk4

Writing a Custom GTK widget with template UI files


I am writing a custom widget which will be displayed within an AppWindow (AdwApplicationWindow) using AdwOverlaySplitView for the utility pane.

However, when I placed my custom widget which contains a GtkLabel itself under the 'content' property of AdwOverlaySplitView, it did not appear. But, when I used a GtkLabel instead, it was displayed correctly.

Here are my .ui and .c files

?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk" version="4.0"/>
  <requires lib="Adw" version="1.0"/>
  <template class="AppWindow" parent="AdwApplicationWindow">
    <property name="content">
      <object class="AdwToolbarView">
        <child type="top">
          <object class="AdwHeaderBar" id="header_bar">
            <child type="start">
              <object class="GtkToggleButton" id="show_sidebar_button">
                <property name="icon-name">panel-left-symbolic</property>
                <property name="tooltip-text" translatable="yes">Toggle Sidebar</property>
                <property name="active">True</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkMenuButton">
                <property name="primary">True</property>
                <property name="icon-name">open-menu-symbolic</property>
                <property name="tooltip-text" translatable="yes">Menu</property>
                <property name="menu-model">primary_menu</property>
              </object>
            </child>
          </object>
        </child>
        <property name="content">
          <object class="AdwOverlaySplitView" id="split_view">
          <property name="show-sidebar" bind-source="show_sidebar_button" bind-property="active" bind-flags="sync-create|bidirectional"/>
            <property name="sidebar">
                <!-- Sidebar content-->
            </property>
            <property name="content">
                <!-- Main Page content -->
                <object class="MyWidget">                   <!-- This is not getting displayed -->
                </object>
                <object class="GtkLabel">
                  <property name="label">Label</property>   <!-- This is getting displayed -->
              </object>
            </property>
          </object>
        </property>
      </object>
    </property>
  </template>
</interface>
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk" version="4.0"/>
  <requires lib="Adw" version="1.0"/>
  <template class="MyWidget" parent="GtkWidget">
    <object class="GtkLabel">
      <property name="label">Label</property>
    </object>
  </template>
</interface>
struct MyWidget
{
  GtkWidget parent_instance;
};


G_DEFINE_TYPE (MyWidget, my_widget, GTK_TYPE_WIDGET)

static void
my_widget_init (MyWidget *self)
{
  gtk_widget_init_template (GTK_WIDGET (self));
}

static void
my_widget_class_init (MyWidgetClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  gtk_widget_class_set_template_from_resource (widget_class, "<resource_path>");
}

I used the GTK Inspector, and it was able to idenity the widget. However, visually, there is no output.

Even though I've worked with .ui files before, they still confuse me. What am I doing wrong here?


Solution

  • When implementing a custom GtkWidget, your best bet is to use a GtkLayoutManager. Layout managers are delegate classes that can implement the layout details for a GtkWidget subtype, and are really convenient. You set them in the class_init() function:

    static void
    my_widget_class_init (MyWidgetClass *klass)
    {
      GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
    
      gtk_widget_class_set_template_from_resource (widget_class, "<resource_path>");
    
      /* Add this line */
      gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
    }
    
    

    The box layout manager implements GtkOrientable so if you need to change the orientation or the spacing you can do the following in your init() function:

    static void
    my_widget_init (MyWidget *self)
    {
      GtkLayoutManager *box_layout;
    
      box_layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
      gtk_orientable_set_orientation (GTK_ORIENTABLE (box_layout), GTK_ORIENTATION_VERTICAL);
      gtk_box_layout_set_spacing (GTK_BOX_LAYOUT (box_layout), 16);
    
      gtk_widget_init_template (GTK_WIDGET (self));
    }
    

    In this example, I used a box-style layout manager, but there are quite a few other variants, such as bins and tables. See the full list here. There's also more information at this development blog post.

    Alternatively, if your layout is more exotic, you can implement the get_request_mode()/measure()/size_allocate() class methods to manually place subwidgets. This is actually quite complicated to do correctly and not recommended unless you cannot get an existing GtkLayoutManager to work.

    Note, extending GtkBox as suggested in the other answer is the correct way to do it with gtk2 and gtk3, but is discouraged with gtk4 in favor of LayoutManagers. It will still work (for now), but it's overly complicated.