Widget Templates

GTK 提供了一种使用定义文件和 GtkBuilder API 来描述 UI 的方法。通常,您需要创建一个 GtkBuilder 对象实例,加载 UI 定义 XML 文件,提取生成的对象,并将引用存储在您自己的类型和数据结构中。

为了自动化这项工作,GTK 还提供了模板:一种自动加载 GTK 部件类型的 UI 定义、将 XML 中描述的对象绑定到部件实例中的字段以及自动管理其生命周期的方法。

注意

UI 定义可以描述继承自 GObject 的任何对象类型。只有部件可以拥有模板,但模板可以包含任何对象。

使用模板

模板绑定到一种类型,并在创建该类型的任何新实例时加载。

为了使用模板,您需要在类初始化时注册它。通常,您会将 UI 定义文件与您的二进制文件一起打包使用 GResource,以便可靠地从您的项目中访问它。

作为一个例子,我们有一个包含两个子部件的复合部件类型

  • 一个输入框

  • 一个按钮

它的 UI 定义文件将是

<interface>
  <template class="ExampleWidget" parent="GtkWidget">
    <child>
      <object class="GtkEntry" id="entry">
      </object>
    </child>
    <child>
      <object class="GtkButton" id="button">
        <property name="label">Hello</property>
      </object>
    </child>
  </template>
</interface>

它将被保存为 GResource,路径为 /com/example/widget.ui

  1. 将模板注册添加到您的 class_init 函数

G_DECLARE_FINAL_TYPE (ExampleWidget, example_widget, EXAMPLE, WIDGET, GtkWidget)

struct _ExampleWidget
{
  GtkWidget parent_type;

  GtkWidget *entry;
  GtkWidget *button;
};

G_DEFINE_TYPE (ExampleWidget, example_widget, GTK_TYPE_WIDGET)

static void
example_widget_class_init (ExampleWidgetClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  gtk_widget_class_set_template_from_resource (widget_class,
                                               "/com/example/widget.ui");
}
  1. 将模板文件中定义的部件绑定到部件实例数据结构的相应成员

static void
example_widget_class_init (ExampleWidgetClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  gtk_widget_class_set_template_from_resource (widget_class,
                                               "/com/example/widget.ui");

  gtk_widget_class_bind_template_child (widget_class, ExampleWidget, entry);
  gtk_widget_class_bind_template_child (widget_class, ExampleWidget, button);
}
  1. 在初始化部件实例时初始化模板子部件

static void
example_widget_init (ExampleWidget *self)
{
  gtk_widget_init_template (GTK_WIDGET (self));

  // It is now possible to access self->entry and self->button
}
  1. 在释放部件实例时清除模板子部件

static void
example_widget_dispose (GObject *gobject)
{
  gtk_widget_dispose_template (GTK_WIDGET (gobject), EXAMPLE_TYPE_WIDGET);

  G_OBJECT_CLASS (example_widget_parent_class)->dispose (gobject);
}

static void
example_widget_class_init (ExampleWidgetClass *klass)
{
  G_OBJECT_CLASS (klass)->dispose = example_widget_dispose;

  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  gtk_widget_class_set_template_from_resource (widget_class,
                                               "/com/example/widget.ui");

  gtk_widget_class_bind_template_child (widget_class, ExampleWidget, entry);
  gtk_widget_class_bind_template_child (widget_class, ExampleWidget, button);
}

注意

不必绑定 UI 文件中定义的所有模板子部件。您可以使用 gtk_widget_get_template_child() 在运行时访问命名的模板子部件。如果您发现自己多次访问同一个模板子部件,则将引用存储在您的实例数据结构中会更有效。

何时使用模板

模板是一种保持代码可维护性并减少 UI 定义文件大小的有用方法。如果您的 UI 定义变得越来越复杂或包含过多的嵌套部件,那么您应该考虑将功能和相关部件移动到它们自己的复合模板中,然后从父 UI 定义中实例化模板部件。例如,给定这个 UI 定义

<object class="GtkBox">
  <child>
    <object class="GtkBox">
      <child>
        <object class="GtkStack">
          <child>
            <object class="GtkStackPage">
              <property name="child">
                <object class="GtkBox">
                  <property name="orientation">vertical</property>
                  <child>
                    <object class="GtkButton">
                      <!-- ... -->

您可能希望将堆栈页面的子 GtkBox 移动到它自己的复合部件

<template class="ButtonsPage" parent="GtkBox">
  <property name="orientation">vertical</property>
  <child>
    <object class="GtkButton">
      <!-- ... -->

然后从主 UI 定义中引用它

<object class="GtkBox">
  <child>
    <object class="GtkBox">
      <child>
        <object class="GtkStack">
          <child>
            <object class="GtkStackPage">
              <property name="child">
                <object class="ButtonsPage">
                  <!-- ... -->

之后,您可能希望将带有堆栈的框移动到它自己的复合部件

<template class="StackBox" parent="GtkBox">
  <child>
    <object class="GtkStack">
      <child>
        <object class="GtkStackPage">
          <property name="child">
            <object class="ButtonsPage">
              <!-- ... -->

并从主 UI 定义中引用它

<object class="GtkBox">
  <child>
    <object class="StackBox">

这样,我们就用三个较小的文件取代了一个大的 UI 定义文件。

注意

当在复合部件模板的子部件中使用自定义部件时,您必须确保在模板初始化之前已知自定义部件的类型。您可以通过在父实例初始化函数中调用 g_type_ensure() 函数,并传入自定义部件的类型,然后在调用 gtk_widget_init_template() 之前来做到这一点。例如,在上面的例子中,StackBox 引用了一个 ButtonPage 自定义部件;这意味着当 StackBox 实例的模板初始化时,ButtonPage 类型必须已经在 GObject 类型系统中注册。