GTK 4添加了新的列表对象GtkListView、GtkGridView和GtkColumnView。这个新特性在Gtk API参考—列表小构件概述中有描述。
GTK 4还有其他实现列表的方法。它们是GtkListBox和GtkTreeView,它们是从GTK 3接管的。在Gtk开发博客中有一篇关于Matthias Clasen所写的列表小构件的文章。他描述了为什么开发GtkListView来取代GtkListBox和GtkTreeView。
Outline
列表是一种顺序数据结构。例如,有序字符串序列"one", “two”, “three”, "four"就是一个列表。每个元素都称为item。列表类似于数组,但在很多情况下,它是由指向列表下一项的指针实现的。它有一个起始点。因此,每个元素都可以通过索引来引用(第一元素,第二元素,…,第n个元素,…)。有两种情况。一个是从1开始的索引(以1为基数),另一个是从0开始的索引(以0为基数)。
Gio提供GListModel接口。它是一个从零开始的列表,它的元素都是相同类型的GObject后代对象,或者实现相同接口的对象。实现了GListModel的对象不是小构件。因此,列表不会直接显示在屏幕上。还有一个对象GtkListView,它是一个显示列表的小构件。列表中的项目需要连接到GtkListView中的项目。GtkListItemFactory实例将列表中的项目映射到GListView。
GListModel
如果你想用GListModel建立一个包含字符串的列表,例如"one", “two”, “three”, “four”,请注意,字符串不能是列表中的项。因为GListModel是GObject对象的列表,而字符串不是GObject对象。“GObject”这个词在这里的意思是“GObject类或其子类”。所以,你需要一个包装器,它是一个GObject,包含一个字符串。GtkStringObject是包装器对象,而实现了GListModel的GStringList是GtkStringObject的一个列表。还有一个是GtkDirectoryList(下面会讲到)。
GStringList
GtkDirectoryList
char *array[] = {"one", "two", "three", "four", NULL};
GtkStringList *stringlist = gtk_string_list_new ((const char * const *) array);
函数gtk_string_list_new创建一个GtkStringList对象。它的项是gtkstringgobject对象,包含字符串"one", “two”, “three"和"four”。有一些函数可以向列表中添加项或从列表中删除项。
- gtk_string_list_append将一个元素添加到列表
- gtk_string_list_remove从列表表删除一个元素
- gtk_string_list_get_string取得列表中的一个字符串
有关更多信息,请参阅GTK 4 API参考——GtkStringList。
其他列表对象将在后面解释。
用xml格式创建GStringList模型。
<object class="GtkDropDown">
<property name="model">
<object class="GtkStringList">
<items>
<item translatable="yes">test1</item>
<item translatable="yes">test2</item>
<item translatable="yes">test3</item>
</items>
</object>
</property>
<binding name="selected">
<lookup name="out1" type="VpfCameraList">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
GtkSelectionModel
GtkSelectionModel是一个支持选择的接口。有了这个模型,用户可以通过点击来选择项目。它由GtkMultiSelection、GtkNoSelection和GtkSingleSelection对象实现。这三个对象通常足以构建应用程序。它们是用GListModel
创建的。你也可以单独创建它们,然后添加一个GListModel。
GtkMultiSelection
支持多选。GtkNoSelection
不支持选择。当需要GtkSelectionModel时,它是GListModel的包装器。GtkSingleSelection
支持单选。
GtkNoSelection *gtk_no_selection_new (GListModel *model);
GtkSingleSelection *gtk_single_selection_new (GListModel *model);
GtkMultiSelection *gtk_multi_selection_new (GListModel *model);
GtkListView
GtkListView是一个显示GListModel item的构件。GtkListView使用GtkListItem表示列表模型中的项。但是,GtkListItem本身不是一个构件,因此用户需要将一个构件(例如GtkLabel)设置为GtkListItem的子构件,以显示list模型的一个项目。GtkListItem的"item"属性指向一个属于list模型的对象。
如果物品的数量非常大,例如超过1000个,GtkListItem会被回收并连接到另一个新显示的物品。这种循环使得gtklisttem对象的数量相当少,小于200。这对于抑制内存消耗的增长非常有效,因此GListModel可以包含很多元素,例如超过100万个元素。
每一项item都是一个独立的GtkListItem,下面例子中就是一个GtkListView中有四个Item(GtkListItem)
GtkListItemFactory
GtkListItemFactory创建或回收GtkListItem,并将其连接到list model的一个item。这个工厂有两个子类,GtkSignalListItemFactory和GtkBuilderListItemFactory。
GtkSignalListItemFactory
GtkSignalListItemFactory为用户提供配置gtklisttitem对象的信号。有四个信号。
- 发出
“setup”
是为了设置GtkListItem对象。用户在处理程序中设置它的子部件。例如,创建一个GtkLabel部件并将gtklisttitem的child属性设置为它。即使gtklisttitem实例被回收(用于绑定到GListModel的另一项),这个设置也会保持不变。 "bind"
是为了将列表模型中的项绑定到小部件上。例如,用户从GtkListItem实例的"item"属性中获取物品。然后获取该项的字符串,并使用该字符串设置GtkLabel实例的label属性。当GtkListItem新创建、回收或列表项发生某些更改时,就会发出此信号。"unbind"
是为了解除一个元素的绑定。用户在信号处理程序中撤销第2步中所做的一切。如果在第2步中创建了一些对象,那么它们必须被销毁。“teardown”
用来撤销第一步中完成的所有操作。因此,必须销毁步骤1中创建的widget。在这个信号之后,list元素将被销毁。
下面的程序list1.c给出了字符串"one"、“two”、"three"和"four"的列表。使用GtkNoSelection,因此用户不能选择任何项目。
1 #include <gtk/gtk.h>
2
3 static void
4 setup_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) {
5 GtkWidget *lb = gtk_label_new (NULL);
6 gtk_list_item_set_child (listitem, lb);
7 /* Because gtk_list_item_set_child sunk the floating reference of lb, releasing (unref) isn't necessary for lb. */
8 }
9
10 static void
11 bind_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) {
12 GtkWidget *lb = gtk_list_item_get_child (listitem);
13 /* Strobj is owned by the instance. Caller mustn't change or destroy it. */
14 GtkStringObject *strobj = gtk_list_item_get_item (listitem);
15 /* The string returned by gtk_string_object_get_string is owned by the instance. */
16 gtk_label_set_text (GTK_LABEL (lb), gtk_string_object_get_string (strobj));
17 }
18
19 static void
20 unbind_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) {
21 /* There's nothing to do here. */
22 }
23
24 static void
25 teardown_cb (GtkSignalListItemFactory *self, GtkListItem *listitem, gpointer user_data) {
26 /* There's nothing to do here. */
27 /* GtkListItem instance will be destroyed soon. You don't need to set the child to NULL. */
28 }
29
30 static void
31 app_activate (GApplication *application) {
32 GtkApplication *app = GTK_APPLICATION (application);
33 GtkWidget *win = gtk_application_window_new (app);
34 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
35 GtkWidget *scr = gtk_scrolled_window_new ();
36 gtk_window_set_child (GTK_WINDOW (win), scr);
37
38 char *array[] = {
39 "one", "two", "three", "four", NULL
40 };
41 /* sl is owned by ns */
42 /* ns and factory are owned by lv. */
43 /* Therefore, you don't need to care about their destruction. */
44 GtkStringList *sl = gtk_string_list_new ((const char * const *) array);
45 GtkNoSelection *ns = gtk_no_selection_new (G_LIST_MODEL (sl));
46
47 GtkListItemFactory *factory = gtk_signal_list_item_factory_new ();
48 g_signal_connect (factory, "setup", G_CALLBACK (setup_cb), NULL);
49 g_signal_connect (factory, "bind", G_CALLBACK (bind_cb), NULL);
50 /* The following two lines can be left out. The handlers do nothing. */
51 g_signal_connect (factory, "unbind", G_CALLBACK (unbind_cb), NULL);
52 g_signal_connect (factory, "teardown", G_CALLBACK (teardown_cb), NULL);
53
54 GtkWidget *lv = gtk_list_view_new (GTK_SELECTION_MODEL (ns), factory);
55 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), lv);
56 gtk_window_present (GTK_WINDOW (win));
57 }
58
59 /* ----- main ----- */
60 #define APPLICATION_ID "com.github.ToshioCP.list1"
61
62 int
63 main (int argc, char **argv) {
64 GtkApplication *app;
65 int stat;
66
67 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
68
69 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
70
71 stat =g_application_run (G_APPLICATION (app), argc, argv);
72 g_object_unref (app);
73 return stat;
74 }
文件list1.c位于目录src/misc下。请在下面创建一个shell脚本并将其保存到bin目录,例如$HOME/bin。
gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`
将当前目录更改为包含list1.c和type的目录,如下所示。
$ chmod 755 $HOME/bin/comp # or chmod 755 (your bin directory)/comp
$ comp list1
$ ./a.out
这个程序并不难。如果您感到有些困难,请再次阅读本节,特别是GtkSignalListItemFactory小节。
GtkBuilderListItemFactory
GtkBuilderListItemFactory是另一个gtklisttitemfactory。其行为是用ui文件定义的。
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkLabel">
<binding name="label">
<lookup name="string" type="GtkStringObject">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
模板标签用于定义GtkListItem。它的子属性是GtkLabel对象。工厂看到这个模板并创建GtkLabel并设置GtkListItem的子属性。这与GtkSignalListItemFactory的设置处理程序所做的相同。
然后,将GtkLabel的label属性绑定到gtkstringgobject的string属性。string对象引用GtkListItem的item属性。所以,lookup标签是这样的:
label <- string <- GtkStringObject <- item <- GtkListItem
最后一个查找标签有一个内容GtkListItem。通常,像GtkListItem这样的C类型不会出现在标签的内容中。这是一个特例。Matthias Clasen在GTK开发博客中有解释。
请记住,ui模板中的classname (GtkListItem)用作“this”指针,指向正在实例化的对象。
因此,在求值时,使用GtkListItem实例作为lookup标记的this对象。这个对象将在第后面章节解释。
C语言源代码如下。其名称是list2.c,位于src/misc目录下。
1 #include <gtk/gtk.h>
2
3 char *
4 get_file_name (GtkListItem *item, GFileInfo *info) {
5 return G_IS_FILE_INFO (info) ? g_strdup (g_file_info_get_name (info)) : NULL;
6 }
7
8 static void
9 app_activate (GApplication *application) {
10 GtkApplication *app = GTK_APPLICATION (application);
11 gtk_window_present (gtk_application_get_active_window(app));
12 }
13
14 static void
15 app_startup (GApplication *application) {
16 GtkApplication *app = GTK_APPLICATION (application);
17 GtkWidget *win = gtk_application_window_new (app);
18 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
19 GtkWidget *scr = gtk_scrolled_window_new ();
20 gtk_window_set_child (GTK_WINDOW (win), scr);
21
22 GFile *file = g_file_new_for_path (".");
23 GtkDirectoryList *dl = gtk_directory_list_new ("standard::name", file);
24 g_object_unref (file);
25 GtkNoSelection *ns = gtk_no_selection_new (G_LIST_MODEL (dl));
26
27 const char *ui_string =
28 "<interface>"
29 "<template class=\"GtkListItem\">"
30 "<property name=\"child\">"
31 "<object class=\"GtkLabel\">"
32 "<binding name=\"label\">"
33 "<closure type=\"gchararray\" function=\"get_file_name\">"
34 "<lookup name=\"item\">GtkListItem</lookup>"
35 "</closure>"
36 "</binding>"
37 "</object>"
38 "</property>"
39 "</template>"
40 "</interface>"
41 ;
42 GBytes *gbytes = g_bytes_new_static (ui_string, strlen (ui_string));
43 GtkListItemFactory *factory = gtk_builder_list_item_factory_new_from_bytes (NULL, gbytes);
44
45 GtkWidget *lv = gtk_list_view_new (GTK_SELECTION_MODEL (ns), factory);
46 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), lv);
47 }
48
49 /* ----- main ----- */
50 #define APPLICATION_ID "com.github.ToshioCP.list3"
51
52 int
53 main (int argc, char **argv) {
54 GtkApplication *app;
55 int stat;
56
57 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
58
59 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
60 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
61
62 stat =g_application_run (G_APPLICATION (app), argc, argv);
63 g_object_unref (app);
64 return stat;
65 }
GtkBulderListItemFactory不需要信号处理程序。使用GtkSingleSelection,因此用户可以一次选择一个项目。
因为这是一个小程序,所以ui数据是一个字符串。
GtkDirectoryList
GtkDirectoryList是一个包含GFileInfo对象的列表模型,GFileInfo对象是某一目录下文件的信息。它使用g_file_enumerate_children_async()来获取GFileInfo对象。链表模型由gtk_directory_list_new函数创建。
ui数据(上面的xml数据)用于在运行时构建GListItem模板。GtkBuilder根据符号表查找函数get_file_name。
GtkDirectoryList *gtk_directory_list_new (const char *attributes, GFile *file);
attributes
是用逗号分隔的文件属性列表。文件属性是键值对。键由命名空间和名称组成。例如,“standard::name"键是一个文件的名称。“标准”指一般档案资料。“名称”指的是文件名。下表给出了一些例子。
当前目录为”.”。下面的程序使GtkDirectoryList dl及其内容成为当前目录下的GFileInfo对象。
GFile *file = g_file_new_for_path (".");
GtkDirectoryList *dl = gtk_directory_list_new ("standard::name", file);
g_object_unref (file);
通过修改前一小节中的list2.c来编写文件列表程序并不困难。一个问题是GInfoFile没有属性。Lookup标签查找属性,因此它对于从GFileInfo对象中查找文件名是无用的。在这种情况下,闭包标签是合适的。闭包标记指定一个函数以及函数返回值的类型。
const char *
get_file_name (GtkListItem *item, GFileInfo *info) {
return G_IS_FILE_INFO (info) ? g_strdup (g_file_info_get_name (info)) : NULL;
}
... ...
... ...
"<interface>"
"<template class=\"GtkListItem\">"
"<property name=\"child\">"
"<object class=\"GtkLabel\">"
"<binding name=\"label\">"
"<closure type=\"gchararray\" function=\"get_file_name\">"
"<lookup name=\"item\">GtkListItem</lookup>"
"</closure>"
"</binding>"
"</object>"
"</property>"
"</template>"
"</interface>"
- 字符串“gchararray”是一个类型名称。类型“gchar”是一个类型名称,它与C类型“char”相同。因此,“gchararray”是“char类型的数组”,与string类型相同。它用于获取GValue对象的类型。GValue是一个泛型值,它可以包含各种类型的值。例如,类型名可以是gboolean、gchar (char)、gint (int)、gfloat (float)、gdouble (double)、gchararray (char *)等。这些类型名是注册到类型系统的基本类型的名称。请参阅GObject教程。
- 闭包标签有type属性和function属性。Function attribute指定函数名,type属性指定函数返回值的类型。闭包标签的内容(它位于
<closure…>
和</closure>)
是函数的参数。<lookup name="item">GtkListItem</lookup>
返回GtkListItem的item属性的值这将是函数的第二个参数。第一个参数总是this
对象,即GListItem实例。 - gtk_file_name函数是闭包标记的回调函数。它首先检查info参数。因为当GListItem元素无界时,它可以为NULL。如果是GFileInfo,则返回复制的文件名。因为g_file_info_get_name的返回值(文件名)是由GFileInfo对象拥有的。因此,需要复制字符串以将所有权授予调用者。绑定标签将GtkLabel的“label”属性绑定到闭包标签。
整个程序(list3.c)如下所示。该程序位于src/misc目录中。
1 #include <gtk/gtk.h>
2
3 char *
4 get_file_name (GtkListItem *item, GFileInfo *info) {
5 return G_IS_FILE_INFO (info) ? g_strdup (g_file_info_get_name (info)) : NULL;
6 }
7
8 static void
9 app_activate (GApplication *application) {
10 GtkApplication *app = GTK_APPLICATION (application);
11 gtk_window_present (gtk_application_get_active_window(app));
12 }
13
14 static void
15 app_startup (GApplication *application) {
16 GtkApplication *app = GTK_APPLICATION (application);
17 GtkWidget *win = gtk_application_window_new (app);
18 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
19 GtkWidget *scr = gtk_scrolled_window_new ();
20 gtk_window_set_child (GTK_WINDOW (win), scr);
21
22 GFile *file = g_file_new_for_path (".");
23 GtkDirectoryList *dl = gtk_directory_list_new ("standard::name", file);
24 g_object_unref (file);
25 GtkNoSelection *ns = gtk_no_selection_new (G_LIST_MODEL (dl));
26
27 const char *ui_string =
28 "<interface>"
29 "<template class=\"GtkListItem\">"
30 "<property name=\"child\">"
31 "<object class=\"GtkLabel\">"
32 "<binding name=\"label\">"
33 "<closure type=\"gchararray\" function=\"get_file_name\">"
34 "<lookup name=\"item\">GtkListItem</lookup>"
35 "</closure>"
36 "</binding>"
37 "</object>"
38 "</property>"
39 "</template>"
40 "</interface>"
41 ;
42 GBytes *gbytes = g_bytes_new_static (ui_string, strlen (ui_string));
43 GtkListItemFactory *factory = gtk_builder_list_item_factory_new_from_bytes (NULL, gbytes);
44
45 GtkWidget *lv = gtk_list_view_new (GTK_SELECTION_MODEL (ns), factory);
46 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), lv);
47 }
48
49 /* ----- main ----- */
50 #define APPLICATION_ID "com.github.ToshioCP.list3"
51
52 int
53 main (int argc, char **argv) {
54 GtkApplication *app;
55 int stat;
56
57 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
58
59 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
60 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
61
62 stat =g_application_run (G_APPLICATION (app), argc, argv);
63 g_object_unref (app);
64 return stat;
65 }
通常,链接器使用符号表将对象链接到可执行文件。它包括函数名及其位置。链接器通常不会在创建的可执行文件中放入符号表。但是如果给出了--export-dynamic
选项,链接器会将符号表添加到可执行文件中。
为此,C编译器提供了一个选项-Wl
,--export-dynamic
-Wl
是一个C编译器选项,它将以下选项传递给链接器。--export-dynamic
是一个链接器选项。以下内容摘自链接器文档。“当创建动态链接的可执行文件时,将所有符号添加到动态符号表中。动态符号表是运行时动态对象可见的符号集合。”
编译然后执行
$ gcc -Wl,--export-dynamic `pkg-config --cflags gtk4` list3.c `pkg-config --libs gtk4`
你也可以制作shell脚本编译list3.c
gcc -Wl,--export-dynamic `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`
将这一行文件保存到文件comp中,然后将其复制到$HOME/bin并赋予其可执行权限。
$ cp comp $HOME/bin/comp
$ chmod +x $HOME/bin/comp
$ comp list3
$ ./a.out