1 GtkApplication
1.1 GtkApplication and g_application_run
人们编写编程代码来开发应用程序。什么是应用程序?应用程序是使用库运行的软件,其中包括操作系统、框架等。在GTK 4编程中,GTK应用程序是使用GTK库运行的程序(或可执行程序)。
编写GtkApplication的基本方法如下。
- 创建一个GtkApplication实例。
- 运行这个实例。
这是所有。非常简单。下面是代表上述方法的C代码。
1 #include <gtk/gtk.h>
2
3 int
4 main (int argc, char **argv) {
5 GtkApplication *app;
6 int stat;
7
8 app = gtk_application_new ("com.github.ToshioCP.pr1", G_APPLICATION_DEFAULT_FLAGS);
9 stat =g_application_run (G_APPLICATION (app), argc, argv);
10 g_object_unref (app);
11 return stat;
12 }
13
第一行说明这个程序包含Gtk库的头文件。函数main是C语言中的一个启动函数。变量app被定义为一个指向GtkApplication实例的指针。函数gtk_application_new创建一个GtkApplication实例,并返回一个指向该实例的指针。GtkApplication实例是一个C结构体数据,其中存储了有关应用程序的信息。后面会解释这些参数的含义。函数g_application_run运行实例定义的应用程序。(我们经常说这个函数运行app。实际上,app不是一个应用程序,而是一个指向该应用程序实例的指针。但是,它简单而简短,可能不会造成混淆。)
这里我使用了单词instance。实例、类和对象是面向对象编程中的术语。我用同样的方式使用这些词。但是,在本教程中,我将经常使用“对象”而不是“实例”。这意味着“对象”和“实例”是相同的。对象是一个有点模棱两可的词。从广义上讲,对象比实例具有更广泛的含义。因此,读者应注意语境,以找到“对象”的含义。在很多情况下,object和instance是相同的。
要编译它,需要运行以下命令。字符串pr1.c是上述C源代码的文件名。
gcc `pkg-config --cflags gtk4` pr1.c `pkg-config --libs gtk4`
./a.out
(a.out:13533): GLib-GIO-WARNING **: 15:30:17.449: Your application does not implement
g_application_activate() and has no handlers connected to the “activate” signal.
It should do one of these.
哦,它只是产生一个错误信息。这个错误消息意味着GtkApplication对象运行了,毫无疑问。现在,让我们想想这条消息意味着什么。
1.2 信号
这个消息告诉我们:
- 应用程序GtkApplication没有实现g_application_activate(),
- 它没有连接到“activate”信号的处理程序,而且
- 你需要解决其中至少一个问题。
这两个error的原因与信号有关。我先给你们解释一下。
当有事情发生时,就会发出信号。例如,创建窗口,销毁窗口,等等。当应用被激活时,会发出"activate"信号。(Activated和started有点不同,但你可以认为到目前为止两者几乎是一样的。)如果信号连接到一个函数,该函数被称为信号处理程序或简单地称为处理程序,那么在信号发出时将调用该函数。
流程是这样的:
- 一些事情发生。
- 如果它与某个信号相关,那么这个信号就会被发射出来。
- 如果信号预先连接到某个处理程序,则调用该处理程序。
信号在对象中定义。例如,“activate”信号属于GApplication
对象,它是GtkApplication对象的父对象(注意GApplication和GtkApplication区别)。
GApplication对象是GObject对象的子对象。GObject是所有对象层次结构中的顶层对象。
GObject -- GApplication -- GtkApplication
<---parent --->child
子对象从父对象继承信号、函数、属性等。因此,GtkApplication也有“activate”信号。
现在我们可以解决pr1.c中的问题。我们需要将“activate”信号连接到一个处理程序。我们使用函数g_signal_connect将信号连接到处理程序。
1 #include <gtk/gtk.h>
2
3 static void
4 app_activate (GApplication *app, gpointer *user_data) {
5 g_print ("GtkApplication is activated.\n");
6 }
7
8 int
9 main (int argc, char **argv) {
10 GtkApplication *app;
11 int stat;
12
13 app = gtk_application_new ("com.github.ToshioCP.pr2", G_APPLICATION_DEFAULT_FLAGS);
14 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
15 stat =g_application_run (G_APPLICATION (app), argc, argv);
16 g_object_unref (app);
17 return stat;
18 }
19
首先,我们定义处理程序app_activate,它只是显示一条消息。函数g_print是在GLib中定义的,它类似于C标准库中的printf。在函数main中,我们在g_application_run之前添加g_signal_connect。函数g_signal_connect有4个参数。
- 信号所属的实例。
- 信号的名称。
- 一个处理程序函数(也称为callback),需要用G_CALLBACK强制转换。
- 传递给处理程序的数据。如果没有必要的数据,则应该给出NULL。
在GObject API参考中有描述。正确地说,g_signal_connect是一个宏(不是C函数)。
#define g_signal_connect(instance, detailed_signal, c_handler, data) \
g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, (GConnectFlags) 0)
你可以在API参考手册中找到每个信号的描述。例如,“activate”信号位于GIO API参考中的GApplication部分。
void
activate (
GApplication* self,
gpointer user_data
)
这是一个“activate”信号处理程序的声明。你可以使用任何名称来代替上面声明中的“activate”。参数有:
- self是信号所属的实例。
- user_data是g_signal_connect函数的第四个参数中定义的数据。如果它是NULL,那么你可以忽略并省略第二个参数。
API参考手册非常重要。你应该看到并理解它。
让我们编译上面的源文件(pr2.c)并运行它。
$ gcc `pkg-config --cflags gtk4` pr2.c `pkg-config --libs gtk4`
$ ./a.out
GtkApplication is activated.
$
好的,做得好。然而,你可能已经注意到,输入这么长的代码行来编译是很痛苦的。使用shell脚本来解决这个问题是个好主意。创建一个文本文件,其中包含下面一行。
gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`
然后,将其保存在$HOME/bin目录下,通常是/ HOME/(用户名)/bin。(如果用户名为James,则目录为/home/james/bin)。然后打开文件的执行位。如果文件名是comp,这样做:
$ chmod 755 $HOME/bin/comp
$ ls -log $HOME/bin
... ... ...
-rwxr-xr-x 1 62 May 23 08:21 comp
... ... ...
如果这是第一次创建$HOME/bin目录并保存文件,则需要注销并重新登录。
$ comp pr2
$ ./a.out
GtkApplication is activated.
$
2 GtkWindow and GtkApplicationWindow
2.1 GtkWindow
在前面的小节中打印了一条消息“GtkApplication is activated.”。就gtk应用程序的测试而言,它是很好的。然而,由于Gtk是图形用户界面(GUI)的框架,因此它是不够的。现在我们继续在这个程序中添加一个窗口。我们需要做的是:
- 创建一个GtkWindow。
- 将它连接到GtkApplication。
- 显示窗口。
现在重写函数app_activate。
创建一个GtkWindow
1 static void
2 app_activate (GApplication *app, gpointer user_data) {
3 GtkWidget *win;
4
5 win = gtk_window_new ();
6 gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
7 gtk_window_present (GTK_WINDOW (win));
8 }
Widget是一个抽象的概念,它包括所有GUI界面,如窗口、对话框、按钮、多行文本、容器等。GtkWidget是所有GUI对象的派生对象的基类。
parent <-----> child
GtkWidget -- GtkWindow
GtkWindow顶部成员就是GtkWidget
函数gtk_window_new的定义如下。
GtkWidget *
gtk_window_new (void);
根据这个定义,它返回一个指向GtkWidget的指针,而不是GtkWindow。它实际上创建了一个新的GtkWindow实例(不是GtkWidget),但返回了一个指向GtkWidget的指针。但是,该指针指向GtkWidget,同时它也指向包含GtkWidget的GtkWindow。
如果您想将win用作指向GtkWindow类型实例的指针,则需要对其进行强制转换。
(GtkWindow *) win
它可以工作,但通常不被使用。相反,使用GTK_WINDOW宏。
GTK_WINDOW (win)
推荐使用宏,因为它不仅转换,还检查类型。
连接win到GtkApplication
函数gtk_window_set_application用于将GtkWindow连接到GtkApplication。
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
您需要用GTK_WINDOW宏把win转换到GtkWindow型指针,用GTK_WINDOW宏把app转换到GtkApplication型指针。
GtkApplication会继续运行,直到相关的窗口被销毁。如果你没有连接GtkWindow和GtkApplication, GtkApplication会立即销毁自己。因为没有窗口连接到GtkApplication,所以GtkApplication不需要等待任何东西。因为它会销毁自己,所以GtkWindow也会被销毁。
显示窗口
函数gtk_window_present将窗口呈现给用户(显示给用户)。
GTK4 将默认的小部件可见性更改为on,因此每个小部件都不需要将其更改为on。但是,有一个例外。顶层窗口(稍后解释)在创建时是不可见的。所以你需要使用上面的函数来显示窗口。
你可以使用gtk_widget_set_visible (win, true)代替gtk_window_present。但是这两者的行为是不同的。假设屏幕上有两个windows win1和win2, win1在win2后面。两个窗口都是可见的。函数gtk_widget_set_visible (win1, true)什么也不做,因为win1已经是可见的。所以win1仍然落后于win2。另一个函数gtk_window_present (win1)将win1移动到windows栈的顶部。因此,如果想显示窗口,应该使用gtk_window_present。
两个函数gtk_widget_show和gtk_widget_hide自GTK 4.10以来已被弃用。您应该使用gtk_widget_set_visible.
将程序保存为pr3.c,然后编译并运行它。
保存程序为pr3.c,这时编译运行它:
$ comp pr3
$ ./a.out
一个小窗口将会出现。
单击关闭按钮,然后窗口消失,程序结束。
2.2 GtkApplicationWindow
GtkApplicationWindow是GtkWindow的一个子对象。它有一些额外的功能,可以更好地与GtkApplication集成。建议将它用作应用程序的顶层窗口,而不是GtkWindow。
现在重写程序并使用GtkApplicationWindow。
1 static void
2 app_activate (GApplication *app, gpointer user_data) {
3 GtkWidget *win;
4
5 win = gtk_application_window_new (GTK_APPLICATION (app));
6 gtk_window_set_title (GTK_WINDOW (win), "pr4");
7 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
8 gtk_window_present (GTK_WINDOW (win));
9 }
当您创建GtkApplicationWindow时,您需要提供GtkApplication实例作为参数。然后它自动连接这两个实例。所以你不需要再调用gtk_window_set_application了。
该程序设置窗口的标题和默认大小。编译并运行a.out,你会看到一个标题为"pr4"的大窗口。
参考:Gtk4-tutorial sec3: GtkApplication and GtkApplicationWindow