Menu
用户经常使用菜单向计算机发出命令。它是这样的:
现在让我们分析一下上面的菜单。对象有两种类型。
- “File”, “Edit”, “View”, “Cut”, “Copy”, “Paste” and “Select All”. 它们被称为“菜单项(menu item)”或简单地称为“item”。当用户单击其中一项时,就会发生一些事情。
- Menubar,submenu它们被称为“菜单”。Menu是一个有序的item列表。它们类似于数组。
- 菜单栏(Menubar)是一个包含 “File”, “Edit” 和 "View"三项的菜单。
- 标签为“Edit”的菜单项有一个链接到包含两个项目的子菜单。这两项没有标签。每一项都代表一个section。
- 第一部分是一个菜单,包含"Cut", “Copy” and "Paste"三个项目。
- 第二部分是一个菜单,其中有一个项目——“Select All”。
由于菜单项之间的链接,菜单的结构很复杂。
GMenuModel, GMenu and GMenuItem
GMenuModel是一个表示菜单的抽象对象。GMenu是GMenuModel的一个简单实现,也是GMenuModel的一个子对象。
GObject – GMenuModel – GMenu
因为GMenuModel是一个抽象对象,它是不可实例化的。因此,它没有任何函数来创建它的实例。如果要创建一个菜单,使用g_menu_new创建一个GMenu实例。GMenu继承了GMenuModel的所有函数。
GMenuItem是一个直接派生自GObject的对象。GMenuItem和Gmenu(或者GMenuModel)没有父子关系。
GObject – GMenuModel – GMenu
GObject – GMenuItem
GMenuItem有属性。其中一个属性是label。例如,在第一个图中有一个菜单项(menu item)具有“Edit”标签。“Cut”、“Copy”、“Paste”和“Select All”也是菜单项的标签。其他属性将在后面解释。
一些菜单项有一个链接到另一个GMenu。有两种类型的链接,submenu 和 section。
GMenuItem可以被插入、添加或添加到GMenu中。当它被插入时,所有的属性和链接值都会被复制并存储在菜单中。GMenuItem本身并没有真正插入。因此,在插入之后,GMenuItem是无用的,它应该被释放
。appending或prepending也是如此。
下面的代码展示了如何将GMenuItem追加到GMenu。
GMenu *menu = g_menu_new ();
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_menu_append_item (menu, menu_item_quit);
g_object_unref (menu_item_quit);
Menu and action
菜单项的一个属性是一个action。这个属性指向一个action对象。
有两个action对象,GSimpleAction和GPropertyAction。通常使用GSimpleAction。它与菜单项一起使用。本节只描述GSimpleAction。
当单击菜单项时,对应于菜单项的操作(action)将被激活。然后这个动作会发出一个激活信号。
- 单击菜单项。
- 相应的操作被激活。
- 这个action发出一个信号。
- 调用连接的处理程序。
下面的代码是一个示例。
static void
quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app) { ... ... ...}
GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
- 变量menu_item_quit指向一个菜单项。它实际上是一个指针,但我们经常说menu_item_quit是一个菜单项。它有一个标签“Quit”,并连接到一个操作“app.quit”。"app"是前缀,"quit"是操作的名称。前缀“app”表示操作属于GtkApplication实例。
- act_quit是一个动作。它有个名字叫“quit”。函数g_simple_action_new创建一个无状态的动作(stateless action)。因此,act_quit是无状态的。无状态的含义将在后面解释。实参NULL表示操作没有参数。大多数操作都是无状态的,也没有参数。
- 动作act_quit通过g_action_map_add_action添加到GtkApplication实例。因此,动作的作用域是应用程序。app.quit的前缀表示作用域。
- 该操作的"activate"信号连接到处理程序quit_activated。
如果点击菜单,则激活相应的动作“quit”,并发出“activate”信号。然后,调用处理程序quit_activated。
Menu bar
菜单栏和菜单都是传统风格的。菜单按钮最近经常被用来代替菜单栏,但旧的风格仍然被广泛使用。
应用程序只有一个菜单栏。如果一个应用程序有两个或多个具有菜单栏的窗口,则菜单栏是完全相同的。因为应用程序中的每个窗口都指向同一个菜单栏实例。
应用程序的菜单栏一旦设置好,通常不会改变。因此,在startup处理程序中设置它是合适的。因为它只在主应用程序实例中被调用一次。
我认为这对读者阐明应用程序的行为是有好处的。
- 当应用程序第一次运行时,该实例称为主实例。
- 主实例将自己注册到系统。如果成功,它会发出“startup”信号。
- 当实例被激活时,会发出“activate”或“open”信号。
- 如果应用程序第二次或以后运行,并且存在一个主实例,则该实例称为远程实例(remote instance)。
- 远程实例不会发出“startup”信号。
- 如果它试图发出“activate”或“open”信号,信号不会在远程 - 实例上发出,而是在主实例上发出。
- 远程实例退出。
因此,“activate”或“open”处理程序可以被调用两次或多次。另一方面,“startup”处理程序只会被调用一次。因此,设置菜单栏应该在“startup”处理程序中完成。
static void
app_startup (GApplication *app) {
... ... ...
gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
... ... ...
}
Simple example
下面是一个简单的菜单menu和操作action的例子。源文件menu1.c位于src/menu目录下。
1 #include <gtk/gtk.h>
2
3 static void
4 quit_activated(GSimpleAction *action, GVariant *parameter, GApplication *application) {
5 g_application_quit (application);
6 }
7
8 static void
9 app_activate (GApplication *application) {
10 GtkApplication *app = GTK_APPLICATION (application);
11 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
12 gtk_window_set_title (GTK_WINDOW (win), "menu1");
13 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
14
15 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
16 gtk_window_present (GTK_WINDOW (win));
17 }
18
19 static void
20 app_startup (GApplication *application) {
21 GtkApplication *app = GTK_APPLICATION (application);
22
23 GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
24 g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
25 g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), application);
26
27 GMenu *menubar = g_menu_new ();
28 GMenuItem *menu_item_menu = g_menu_item_new ("Menu", NULL);
29 GMenu *menu = g_menu_new ();
30 GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
31 g_menu_append_item (menu, menu_item_quit);
32 g_object_unref (menu_item_quit);
33 g_menu_item_set_submenu (menu_item_menu, G_MENU_MODEL (menu));
34 g_menu_append_item (menubar, menu_item_menu);
35 g_object_unref (menu_item_menu);
36
37 gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
38 }
39
40 #define APPLICATION_ID "com.github.ToshioCP.menu1"
41
42 int
43 main (int argc, char **argv) {
44 GtkApplication *app;
45 int stat;
46
47 app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
48 g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
49 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
50
51 stat =g_application_run (G_APPLICATION (app), argc, argv);
52 g_object_unref (app);
53 return stat;
54 }
55
- 3-6: quit_activated是动作act_quit的“activate”信号处理程序。“activate”信号的处理程序有三个参数。
- 发出信号的action实例。
- 参数。在这个例子中,它是NULL,因为g_simple_action_new(第23行)的第二个参数是NULL。你不需要关心它。
- 用户数据。这是g_signal_connect(第25行)中连接操作和处理程序的第四个参数。
- 5:函数g_application_quit立即退出应用。
- 8-17: app_activate是一个“激活”信号处理程序。
- 11-13:创建一个GtkApplicationWindow窗口。并设置标题和默认大小。
- 15: 设置GtkApplicationWindow显示菜单栏。
- 16:显示窗口。
- 19-38: app_startup是一个“启动”信号处理器
- 23:创建GSimpleAction act_quit。它是无状态的。g_simple_action_new的第一个参数是动作的名称,第二个参数是一个形参。如果你不需要这个参数,就传递NULL。因此,act_quit有一个名字"quit",没有参数。
- 24:在GtkApplication app中增加action。GtkApplication实现接口GActionMap和GActionGroup。GtkApplication (GActionMap)可以有一组动作,这些动作是用函数g_action_map_add_action添加的。该函数在Gio API参考——g_action_map_add_action中描述。因为这个动作属于GtkApplication,所以它的作用域是"app",如果需要前缀(作用域),它会通过
"app.quit"
进行使用。 - 25:连接操作的“activate”信号和处理程序quit_activated
- 27-30:创建GMenu和GMenuItem实例。menubar和menu是GMenu。menu_item_menu和menu_item_quit是GMenuItem。menu_item_menu只有一个“Menu”标签,没有任何操作。menu_item_quit有一个标签"Quit"和一个操作"app.quit"。
- 31-32:将menu_item_quit添加到menu。正如我之前提到的,所有的属性和链接都被复制并用于在menu中形成一个新项目。因此,添加menu_item_quit之后,就不再需要menu_item_quit了。它由g_object_unref释放。
- 33:将menu_item_menu中的子菜单链接设置为指向菜单。
- 34-35:将menu_item_menu添加到menubar。然后释放menu_item_menu。将GMenu和GMenuItem连接起来,最终组成一个菜单。菜单的结构如下图所示。
- 37:菜单栏插入到应用程序中。
Compiling
将当前目录更改为src/menu。使用comp编译menu1.c。
$ comp menu1
$ ./a.out
然后,出现一个窗口。点击菜单栏上的“Menu”,就会出现一个菜单。点击“退出”菜单,应用程序退出。
Primary and remote application instances
让我们试着运行应用程序两次。在shell命令行中使用&,应用程序将并发运行。
然后,出现了两个窗口。
- 第一个./out调用应用程序并创建一个主实例。它调用“startup”和“activate”处理程序并显示一个窗口。
- 第二个./out再次调用应用程序,并且创建的实例是一个远程实例。它不会发出“启动”信号。它激活了应用程序,但“activate”信号是在主实例上发出的。远程实例退出。
- 主实例称为activate处理程序。处理程序创建一个新窗口。它使用gtk_application_window_set_show_menubar函数向窗口添加一个菜单栏。
两个窗口都有菜单栏。它们完全一样。这两个窗口属于主实例。
如果单击“Quit”菜单,应用程序(主实例)将退出。
第二次运行创建了一个新窗口。然而,这取决于activate处理程序。如果在startup处理程序中创建窗口,activate处理程序只是显示窗口,那么在第二次运行时不会创建新窗口。例如,tfe(文本文件编辑器)不会创建第二个窗口。它只是创建了一个新的notebook页面。因为它的activate处理程序不创建任何窗口,而只是创建一个新的notebook页面。
在桌面应用程序上经常发生第二次或更多的执行。如果双击图标两次或两次以上,应用程序将运行多次。因此,你需要仔细考虑startup和activate (open)处理程序。