十七、Gtk4-Menu and action

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)处理程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值