八、Gtk4-GtkBuilder and UI file

1 New, Open and Save button

在上一节中,我们制作了一个非常简单的编辑器。它在程序开始时读取文件,在程序结束时将文件写出来。它可以工作,但不是很好。如果我们有“新建”、“打开”、“保存”和“关闭”按钮就更好了。本节介绍如何在窗口中放置这些按钮。
在这里插入图片描述
上面的截图展示了布局。源代码tfe2.c中的app_open函数如下所示。

 1 static void
 2 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
 3   GtkWidget *win;
 4   GtkWidget *nb;
 5   GtkWidget *lab;
 6   GtkNotebookPage *nbp;
 7   GtkWidget *scr;
 8   GtkWidget *tv;
 9   GtkTextBuffer *tb;
10   char *contents;
11   gsize length;
12   char *filename;
13   int i;
14 
15   GtkWidget *boxv;
16   GtkWidget *boxh;
17   GtkWidget *dmy1;
18   GtkWidget *dmy2;
19   GtkWidget *dmy3;
20   GtkWidget *btnn; /* button for new */
21   GtkWidget *btno; /* button for open */
22   GtkWidget *btns; /* button for save */
23   GtkWidget *btnc; /* button for close */
24 
25   win = gtk_application_window_new (GTK_APPLICATION (app));
26   gtk_window_set_title (GTK_WINDOW (win), "file editor");
27   gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
28 
29   boxv = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
30   gtk_window_set_child (GTK_WINDOW (win), boxv);
31 
32   boxh = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
33   gtk_box_append (GTK_BOX (boxv), boxh);
34 
35   dmy1 = gtk_label_new(NULL); /* dummy label for left space */
36   gtk_label_set_width_chars (GTK_LABEL (dmy1), 10);
37   dmy2 = gtk_label_new(NULL); /* dummy label for center space */
38   gtk_widget_set_hexpand (dmy2, TRUE);
39   dmy3 = gtk_label_new(NULL); /* dummy label for right space */
40   gtk_label_set_width_chars (GTK_LABEL (dmy3), 10);
41   btnn = gtk_button_new_with_label ("New");
42   btno = gtk_button_new_with_label ("Open");
43   btns = gtk_button_new_with_label ("Save");
44   btnc = gtk_button_new_with_label ("Close");
45 
46   gtk_box_append (GTK_BOX (boxh), dmy1);
47   gtk_box_append (GTK_BOX (boxh), btnn);
48   gtk_box_append (GTK_BOX (boxh), btno);
49   gtk_box_append (GTK_BOX (boxh), dmy2);
50   gtk_box_append (GTK_BOX (boxh), btns);
51   gtk_box_append (GTK_BOX (boxh), btnc);
52   gtk_box_append (GTK_BOX (boxh), dmy3);
53 
54   nb = gtk_notebook_new ();
55   gtk_widget_set_hexpand (nb, TRUE);
56   gtk_widget_set_vexpand (nb, TRUE);
57   gtk_box_append (GTK_BOX (boxv), nb);
58 
59   for (i = 0; i < n_files; i++) {
60     if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
61       scr = gtk_scrolled_window_new ();
62       tv = tfe_text_view_new ();
63       tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
64       gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
65       gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
66 
67       tfe_text_view_set_file (TFE_TEXT_VIEW (tv),  g_file_dup (files[i]));
68       gtk_text_buffer_set_text (tb, contents, length);
69       g_free (contents);
70       filename = g_file_get_basename (files[i]);
71       lab = gtk_label_new (filename);
72       gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
73       nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
74       g_object_set (nbp, "tab-expand", TRUE, NULL);
75       g_free (filename);
76     } else if ((filename = g_file_get_path (files[i])) != NULL) {
77         g_print ("No such file: %s.\n", filename);
78         g_free (filename);
79     } else
80         g_print ("No valid file is given\n");
81   }
82   if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
83     gtk_window_present (GTK_WINDOW (win));
84   } else
85     gtk_window_destroy (GTK_WINDOW (win));
86 }

函数app_open在主应用程序窗口中构建构件。

  • 25-27:创建一个GtkApplicationWindow实例,并设置标题和默认大小。
  • 29-30:创建GtkBox实例boxv。它是一个垂直框,是GtkApplicationWindow的一个子构件。boxv有两个子构件。第一个子构件是一个水平的boxh。第二个构件是一个GtkNotebook。
  • 32-33:创建一个GtkBox实例boxh,并将其作为第一个子构件附加到boxv中。
  • 35-40:创建3个假标签。标签dmy1和dmy3的字符宽度为10。另一个标签dmy2具有hexpand属性,该属性被设置为TRUE。这使得标签尽可能地水平扩展。
  • 41-44:创建四个按钮。
  • 46-52:添加这些GtkLabel和GtkButton到boxh。
  • 54-57:创建一个gtnotebook实例,并设置hexpand和vexpand属性为TRUE。这使得它在水平和垂直方向上尽可能扩展。它作为第二个子构件添加到boxv中。

构件件构建行的数量是33(=57-25+1)。我们还需要许多变量(boxv、boxh、dmy1等),其中大多数只用于构建窗口组件。有什么好的解决方案来减少这些工作吗?

Gtk提供GtkBuilder。它读取用户界面(UI)数据并构建窗口。它减少了这种繁琐的工作。

2 The UI File

Look at the UI file tfe3.ui that defines widget structure.

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <interface>
 3   <object class="GtkApplicationWindow" id="win">
 4     <property name="title">file editor</property>
 5     <property name="default-width">600</property>
 6     <property name="default-height">400</property>
 7     <child>
 8       <object class="GtkBox" id="boxv">
 9         <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
10         <child>
11           <object class="GtkBox" id="boxh">
12             <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
13             <child>
14               <object class="GtkLabel" id="dmy1">
15                 <property name="width-chars">10</property>
16               </object>
17             </child>
18             <child>
19               <object class="GtkButton" id="btnn">
20                 <property name="label">New</property>
21               </object>
22             </child>
23             <child>
24               <object class="GtkButton" id="btno">
25                 <property name="label">Open</property>
26               </object>
27             </child>
28             <child>
29               <object class="GtkLabel" id="dmy2">
30                 <property name="hexpand">TRUE</property>
31               </object>
32             </child>
33             <child>
34               <object class="GtkButton" id="btns">
35                 <property name="label">Save</property>
36               </object>
37             </child>
38             <child>
39               <object class="GtkButton" id="btnc">
40                 <property name="label">Close</property>
41               </object>
42             </child>
43             <child>
44               <object class="GtkLabel" id="dmy3">
45                 <property name="width-chars">10</property>
46               </object>
47             </child>
48           </object>
49         </child>
50         <child>
51           <object class="GtkNotebook" id="nb">
52             <property name="hexpand">TRUE</property>
53             <property name="vexpand">TRUE</property>
54           </object>
55         </child>
56       </object>
57     </child>
58   </object>
59 </interface>

是一个XML文件。标签以<开始,以>结束。标签有两种类型:开始标签和结束标签。例如,<interface>是开始标签,</interface>是结束标签。UI文件以interface标签开始和结束。有些标签,例如对象标签,可以在其开始标签中具有class和id属性。

  • 1: XML声明。它指定XML版本为1.0,编码为UTF-8。
  • 3-6:一个带有GtkApplicationWindow类和id=win的对象标签。这是顶层窗口。并定义了窗口的三个属性。title属性是"file editor", default-width属性是600,default-height属性是400。
  • 7:child标签意味着子构件。例如,第7行告诉我们,id属性为"boxv"的GtkBox对象是win的一个子构件。

比较这个ui文件和tfe2.c源代码中的第25 ~ 57行。两者都使用其后代构件构建相同的窗口。

你可以使用gtk4-builder-tool检查ui文件。

  • gtk4-builder-tool validate <ui file name>对ui文件进行验证。如果ui文件包含一些语法错误,gtk4-builder-tool会打印出错误。
  • gtk4-builder-tool simplify <ui file name>简化ui文件并打印结果。如果指定了--replace选项,它会将ui文件替换为简化的文件。如果ui文件指定了一个属性值,但它是默认值,那么它将被删除。有些值被简化了。例如,“TRUE"和"FALSE"分别变成了"1"和"0”。然而,“TRUE或“FALSE”更利于维护。

在编译之前检查你的ui文件是个好主意。

3 GtkBuilder

GtkBuilder基于ui文件建造构件。

GtkBuilder *build;

build = gtk_builder_new_from_file ("tfe3.ui");
win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));

函数gtk_builder_new_from_file读取作为参数给出的文件。然后,它构建小构件并创建GtkBuilder对象。函数gtk_builder_get_object (build, “win”)返回指向属性name=win的指针,也就是ui文件中的id。所有部件都是基于ui文件中描述的父子关系连接的。对于下面的程序,我们只需要win和nb。这减少了C源文件中的行数。

$ cd tfe; diff tfe2.c tfe3.c
58a59
>   GtkBuilder *build;
60,103c61,65
<   GtkWidget *boxv;
<   GtkWidget *boxh;
<   GtkWidget *dmy1;
<   GtkWidget *dmy2;
<   GtkWidget *dmy3;
<   GtkWidget *btnn; /* button for new */
<   GtkWidget *btno; /* button for open */
<   GtkWidget *btns; /* button for save */
<   GtkWidget *btnc; /* button for close */
< 
<   win = gtk_application_window_new (GTK_APPLICATION (app));
<   gtk_window_set_title (GTK_WINDOW (win), "file editor");
<   gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
< 
<   boxv = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
<   gtk_window_set_child (GTK_WINDOW (win), boxv);
< 
<   boxh = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
<   gtk_box_append (GTK_BOX (boxv), boxh);
< 
<   dmy1 = gtk_label_new(NULL); /* dummy label for left space */
<   gtk_label_set_width_chars (GTK_LABEL (dmy1), 10);
<   dmy2 = gtk_label_new(NULL); /* dummy label for center space */
<   gtk_widget_set_hexpand (dmy2, TRUE);
<   dmy3 = gtk_label_new(NULL); /* dummy label for right space */
<   gtk_label_set_width_chars (GTK_LABEL (dmy3), 10);
<   btnn = gtk_button_new_with_label ("New");
<   btno = gtk_button_new_with_label ("Open");
<   btns = gtk_button_new_with_label ("Save");
<   btnc = gtk_button_new_with_label ("Close");
< 
<   gtk_box_append (GTK_BOX (boxh), dmy1);
<   gtk_box_append (GTK_BOX (boxh), btnn);
<   gtk_box_append (GTK_BOX (boxh), btno);
<   gtk_box_append (GTK_BOX (boxh), dmy2);
<   gtk_box_append (GTK_BOX (boxh), btns);
<   gtk_box_append (GTK_BOX (boxh), btnc);
<   gtk_box_append (GTK_BOX (boxh), dmy3);
< 
<   nb = gtk_notebook_new ();
<   gtk_widget_set_hexpand (nb, TRUE);
<   gtk_widget_set_vexpand (nb, TRUE);
<   gtk_box_append (GTK_BOX (boxv), nb);
< 
---
>   build = gtk_builder_new_from_file ("tfe3.ui");
>   win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
>   gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
>   nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
>   g_object_unref(build);
138c100
<   app = gtk_application_new ("com.github.ToshioCP.tfe2", G_APPLICATION_HANDLES_OPEN);
---
>   app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
144a107
> 

60,103c61,65表示44(=103-60+1)行更改为5(=65-61+1)行。因此,减少了39行代码。使用ui文件不仅缩短了C源文件的长度,而且使部件的结构更加清晰。

现在我将展示C文件tfe3.c中的app_open函数。

 1 static void
 2 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
 3   GtkWidget *win;
 4   GtkWidget *nb;
 5   GtkWidget *lab;
 6   GtkNotebookPage *nbp;
 7   GtkWidget *scr;
 8   GtkWidget *tv;
 9   GtkTextBuffer *tb;
10   char *contents;
11   gsize length;
12   char *filename;
13   int i;
14   GtkBuilder *build;
15 
16   build = gtk_builder_new_from_file ("tfe3.ui");
17   win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
18   gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
19   nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
20   g_object_unref(build);
21   for (i = 0; i < n_files; i++) {
22     if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
23       scr = gtk_scrolled_window_new ();
24       tv = tfe_text_view_new ();
25       tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
26       gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
27       gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
28 
29       tfe_text_view_set_file (TFE_TEXT_VIEW (tv),  g_file_dup (files[i]));
30       gtk_text_buffer_set_text (tb, contents, length);
31       g_free (contents);
32       filename = g_file_get_basename (files[i]);
33       lab = gtk_label_new (filename);
34       gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
35       nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
36       g_object_set (nbp, "tab-expand", TRUE, NULL);
37       g_free (filename);
38     } else if ((filename = g_file_get_path (files[i])) != NULL) {
39         g_print ("No such file: %s.\n", filename);
40         g_free (filename);
41     } else
42         g_print ("No valid file is given\n");
43   }
44   if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
45     gtk_window_present (GTK_WINDOW (win));
46   } else
47     gtk_window_destroy (GTK_WINDOW (win));
48 }

tfe3.c的整个源代码保存在目录src/tfe中。

4 使用ui字符串

GtkBuilder可以使用字符串建立构件。使用gtk_builder_new_from_string而不是gtk_builder_new_from_file。

char *uistring;

uistring =
"<interface>"
  "<object class="GtkApplicationWindow" id="win">"
    "<property name=\"title\">file editor</property>"
    "<property name=\"default-width\">600</property>"
    "<property name=\"default-height\">400</property>"
    "<child>"
      "<object class=\"GtkBox\" id=\"boxv\">"
        "<property name="orientation">GTK_ORIENTATION_VERTICAL</property>"
... ... ...
... ... ...
"</interface>";

build = gtk_builder_new_from_string (uistring, -1);

这种方法有利有弊。这样做的好处是ui字符串是在源代码中编写的。因此,在运行时不需要ui文件。缺点是编写C字符串有点麻烦,因为有双引号。如果你想使用这个方法,你应该写一个脚本来把ui文件转换成C-string。

  • 在每个双引号之前添加反斜杠。
  • 在每行字符串的左右分别添加双引号。

5 Gresource

使用Gresource类似于使用string。但是Gresource是压缩的二进制数据,而不是文本数据。还有一个编译器可以将ui文件编译为Gresource。它不仅可以编译文本文件,还可以编译图像、声音等二进制文件。在编译之后,它将它们打包成一个Gresource对象。

resource编译器glib-compile-resources需要一个xml文件。它描述了资源文件。

1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3   <gresource prefix="/com/github/ToshioCP/tfe3">
4     <file>tfe3.ui</file>
5   </gresource>
6 </gresources>
  • 2: gresources标签可以包含多个gresources (gresource标签)。然而,这个xml只有一个gresource。
  • 3: gresource的前缀为/com/github/ToshioCP/tfe3。
  • 4: gresource的名字是tfe3.ui。资源指向/com/github/ToshioCP/tfe3/tfe3。ui由GtkBuilder。模式是“prefix”+“name”。如果想添加更多文件,请将它们插入第4行和第5行之间。

将这个xml文本保存到tfe3.gresource.xml。gresource编译器glib-compile-resources通过参数 --help显示其用法。

$ LANG=C glib-compile-resources --help
Usage:
  glib-compile-resources [OPTION?] FILE

Compile a resource specification into a resource file.
Resource specification files have the extension .gresource.xml,
and the resource file have the extension called .gresource.

Help Options:
  -h, --help                   Show help options

Application Options:
  --version                    Show program version and exit
  --target=FILE                Name of the output file
  --sourcedir=DIRECTORY        The directories to load files referenced in FILE from (default: current directory)
  --generate                   Generate output in the format selected for by the target filename extension
  --generate-header            Generate source header
  --generate-source            Generate source code used to link in the resource file into your code
  --generate-dependencies      Generate dependency list
  --dependency-file=FILE       Name of the dependency file to generate
  --generate-phony-targets     Include phony targets in the generated dependency file
  --manual-register            Don?t automatically create and register resource
  --internal                   Don?t export functions; declare them G_GNUC_INTERNAL
  --external-data              Don?t embed resource data in the C file; assume it's linked externally instead
  --c-name                     C identifier name used for the generated source code
  -C, --compiler               The target C compiler (default: the CC environment variable)

现在运行编译器。

$ glib-compile-resources tfe3.gresource.xml --target=resources.c --generate-source

然后生成一个C源文件resources.c。修改tfe3.c,并保存为tfe3_r.c。

#include "resources.c"
... ... ...
... ... ...
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe3.ui");
... ... ...
... ... ...

函数gtk_builder_new_from_resource从resource构建小部件。

然后,编译并运行它。出现一个窗口,它与本页开始时的截图相同。

一般来说,resource是C语言最好的表达方式。如果您使用其他语言,如Ruby, string比resource更好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值