1 GtkLavel,GtkButton and GtkBox
1.1 GtkLabel
在前一节中,我们创建了一个窗口并将其显示在屏幕上。现在我们进入下一个主题,在这个窗口中添加部件。最简单的部件是GtkLabel。它是一个包含文本的部件。
1 #include <gtk/gtk.h>
2
3 static void
4 app_activate (GApplication *app) {
5 GtkWidget *win;
6 GtkWidget *lab;
7
8 win = gtk_application_window_new (GTK_APPLICATION (app));
9 gtk_window_set_title (GTK_WINDOW (win), "lb1");
10 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
11
12 lab = gtk_label_new ("Hello.");
13 gtk_window_set_child (GTK_WINDOW (win), lab);
14
15 gtk_window_present (GTK_WINDOW (win));
16 }
17
18 int
19 main (int argc, char **argv) {
20 GtkApplication *app;
21 int stat;
22
23 app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_DEFAULT_FLAGS);
24 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
25 stat =g_application_run (G_APPLICATION (app), argc, argv);
26 g_object_unref (app);
27 return stat;
28 }
29
将这个程序保存到文件lb1.c中。然后编译并运行它。
$ comp lb1
$ . / a.out
将出现一个消息为“Hello.”的窗口。
pr4.c和lb1.c之间只有很小的变化。程序差异有助于了解两个文件之间的差异。
$ cd misc; diff pr4.c lb1.c
4c4
< app_activate (GApplication *app, gpointer user_data) {
---
> app_activate (GApplication *app) {
5a6
> GtkWidget *lab;
8c9
< gtk_window_set_title (GTK_WINDOW (win), "pr4");
---
> gtk_window_set_title (GTK_WINDOW (win), "lb1");
9a11,14
>
> lab = gtk_label_new ("Hello.");
> gtk_window_set_child (GTK_WINDOW (win), lab);
>
18c23
< app = gtk_application_new ("com.github.ToshioCP.pr4", G_APPLICATION_DEFAULT_FLAGS);
---
> app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_DEFAULT_FLAGS);
这告诉我们:
- 信号处理程序app_activate没有user_data参数。如果g_signal_connect的第四个参数为NULL,可以省略user_data。
- 增加了一个新的变量lab的定义。
- 窗口的标题被更改。
- 创建一个标签,并将其作为子窗口连接到window。
函数gtk_window_set_child (GTK_WINDOW (win), lab)使lab标签成为窗口win的一个子构件。小心些而已。子部件不同于子对象。对象具有父子关系,部件也具有父子关系。但这两种关系是完全不同的。不要困惑。在程序lb1.c中,lab是win的一个子构件。子构件总是位于屏幕上的父构件中。看这个窗口如何出现在屏幕上。应用程序窗口包含标签。
这个窗口win没有任何父类对象。我们将这样的窗口称为顶层窗口。一个应用程序可以有多个顶层窗口。
2 GtkButton
下一个构件是GtkButton。它在屏幕上显示一个带有标签或图标的按钮。在本小节中,我们将创建一个带有标签的按钮。当按钮被点击时,它会发出“clicked”信号。下面的程序展示了如何捕获信号并执行某些操作。
1 #include <gtk/gtk.h>
2
3 static void
4 click_cb (GtkButton *btn) {
5 g_print ("Clicked.\n");
6 }
7
8 static void
9 app_activate (GApplication *app) {
10 GtkWidget *win;
11 GtkWidget *btn;
12
13 win = gtk_application_window_new (GTK_APPLICATION (app));
14 gtk_window_set_title (GTK_WINDOW (win), "lb2");
15 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
16
17 btn = gtk_button_new_with_label ("Click me");
18 gtk_window_set_child (GTK_WINDOW (win), btn);
19 g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), NULL);
20
21 gtk_window_present (GTK_WINDOW (win));
22 }
23
24 int
25 main (int argc, char **argv) {
26 GtkApplication *app;
27 int stat;
28
29 app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_DEFAULT_FLAGS);
30 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
31 stat =g_application_run (G_APPLICATION (app), argc, argv);
32 g_object_unref (app);
33 return stat;
34 }
35
请看第17 ~ 19行。首先,它创建一个带有标签“Click me”的GtkButton实例btn。然后,将该按钮作为子窗口添加到窗口win中。最后,将按钮的“clicked”信号连接到处理程序click_cb。因此,如果单击btn,则调用函数click_cb。后缀“cb”的意思是“call back”。
将程序命名为lb2.c并保存。现在编译并运行它。
出现一个带有按钮的窗口。单击按钮(它是一个很大的按钮,可以在窗口中的任何地方单击),然后在终端上出现一个字符串"Clicked."。它显示处理程序是通过单击按钮被调用的。
我们可以确保捕获了单击的信号,并通过使用g_print调用处理程序。但是,使用g_print与GUI库GTK不协调。因此,我们将更改handler。下面的代码是lb3.c。
1 static void
2 click_cb (GtkButton *btn, GtkWindow *win) {
3 gtk_window_destroy (win);
4 }
5
6 static void
7 app_activate (GApplication *app) {
8 GtkWidget *win;
9 GtkWidget *btn;
10
11 win = gtk_application_window_new (GTK_APPLICATION (app));
12 gtk_window_set_title (GTK_WINDOW (win), "lb3");
13 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
14
15 btn = gtk_button_new_with_label ("Close");
16 gtk_window_set_child (GTK_WINDOW (win), btn);
17 g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), win);
18
19 gtk_window_present (GTK_WINDOW (win));
20 }
lb2.c和lb3.c的不同之处如下:
$ cd misc; diff lb2.c lb3.c
4,5c4,5
< click_cb (GtkButton *btn) {
< g_print ("Clicked.\n");
---
> click_cb (GtkButton *btn, GtkWindow *win) {
> gtk_window_destroy (win);
14c14
< gtk_window_set_title (GTK_WINDOW (win), "lb2");
---
> gtk_window_set_title (GTK_WINDOW (win), "lb3");
17c17
< btn = gtk_button_new_with_label ("Click me");
---
> btn = gtk_button_new_with_label ("Close");
19c19
< g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), NULL);
---
> g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), win);
29c29
< app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_DEFAULT_FLAGS);
---
> app = gtk_application_new ("com.github.ToshioCP.lb3", G_APPLICATION_DEFAULT_FLAGS);
35d34
<
修改如下:
- 删除了lb2.c中的函数g_print,并插入了两行代码。
- click_cb有第二个参数,来自第19行的g_signal_connect的第四个参数。需要注意的是,click_cb的第二个参数和g_signal_connect的第四个参数的类型是不同的。前者是GtkWindow *,后者是GtkWidget *。编译器不会出错,因为g_signal_connect使用了gpointer(指针的一般类型)。在这个程序中,win指向的实例是一个GtkApplicationWindow对象。它是GtkWindow和GtkWidget类的后代,所以GtkWindow *和GtkWidget *都是实例的正确类型。
- gtk_destroy (win)销毁顶层窗口然后应用退出。
- btn的标签由Click me变为Close
- g_signal_connect的第四个参数从NULL改为win。
最重要的变化是g_signal_connect的第四个参数。在g_signal_connect的定义中,该参数被描述为“传递给处理程序的数据”。
3 GtkBox
GtkWindow和GtkApplicationWindow只能有一个子窗口。如果你想在一个窗口中添加两个或更多的部件,你需要一个容器部件。GtkBox是其中一个容器。它将两个或更多的子部件排列到一行或一列中。下面的过程展示了在窗口中添加两个按钮的方法。
- 创建一个GtkApplicationWindow实例。
- 创建一个GtkBox实例,并将其作为子窗口添加到GtkApplicationWindow中。
- 创建一个GtkButton实例并将其附加到GtkBox。
- 创建另一个GtkButton实例并将其附加到GtkBox。
在此之后,构件被连接为下图所示。
程序lb4.c如下:
1 #include <gtk/gtk.h>
2
3 static void
4 click1_cb (GtkButton *btn) {
5 const char *s;
6
7 s = gtk_button_get_label (btn);
8 if (g_strcmp0 (s, "Hello.") == 0)
9 gtk_button_set_label (btn, "Good-bye.");
10 else
11 gtk_button_set_label (btn, "Hello.");
12 }
13
14 static void
15 click2_cb (GtkButton *btn, GtkWindow *win) {
16 gtk_window_destroy (win);
17 }
18
19 static void
20 app_activate (GApplication *app) {
21 GtkWidget *win;
22 GtkWidget *box;
23 GtkWidget *btn1;
24 GtkWidget *btn2;
25
26 win = gtk_application_window_new (GTK_APPLICATION (app));
27 gtk_window_set_title (GTK_WINDOW (win), "lb4");
28 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
29
30 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
31 gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
32 gtk_window_set_child (GTK_WINDOW (win), box);
33
34 btn1 = gtk_button_new_with_label ("Hello.");
35 g_signal_connect (btn1, "clicked", G_CALLBACK (click1_cb), NULL);
36
37 btn2 = gtk_button_new_with_label ("Close");
38 g_signal_connect (btn2, "clicked", G_CALLBACK (click2_cb), win);
39
40 gtk_box_append (GTK_BOX (box), btn1);
41 gtk_box_append (GTK_BOX (box), btn2);
42
43 gtk_window_present (GTK_WINDOW (win));
44 }
45
46 int
47 main (int argc, char **argv) {
48 GtkApplication *app;
49 int stat;
50
51 app = gtk_application_new ("com.github.ToshioCP.lb4", G_APPLICATION_DEFAULT_FLAGS);
52 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
53 stat =g_application_run (G_APPLICATION (app), argc, argv);
54 g_object_unref (app);
55 return stat;
56 }
看看函数app_activate。
在创建GtkApplicationWindow实例之后,会创建一个GtkBox实例。
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
第一个参数是垂直排列box里面的子构件。方向常量的定义如下:
GTK_ORIENTATION_VERTICAL:子构件垂直排列
GTK_ORIENTATION_HORIZONTAL:子构件水平排列
第二个参数是子构件之间的大小。长度的单位是像素。
下一个函数是用子构件填充box时,给它们相同的空间大小(均匀分布)。
之后,创建了两个按钮btn1和btn2,并设置了信号处理程序。然后,将这两个按钮添加到box中。
1 static void
2 click1_cb (GtkButton *btn) {
3 const char *s;
4
5 s = gtk_button_get_label (btn);
6 if (g_strcmp0 (s, "Hello.") == 0)
7 gtk_button_set_label (btn, "Good-bye.");
8 else
9 gtk_button_set_label (btn, "Hello.");
10 }
函数gtk_button_get_lable返回标签中的文本。字符串归按钮所有,你不能修改或释放它。const限定符对于字符串s是必要的。如果你改变了字符串,编译器会给你一个警告。
当您看到GTK 4 API引用时,始终需要小心使用const限定符。
对应于btn1的处理程序切换其标签。btn2对应的处理程序会销毁顶层窗口,然后应用退出。
参考:Gtk4-tutorial Widgets (1)