操作

一个 GAction 代表应用程序中单个用户可操作的功能。

要使用 GAction,您应该使用 GtkApplication

注意

虽然也可以在没有 GtkApplication 的情况下使用 GAction,但这里不会讨论这种情况。

它是什么,它不是什么

一个 GAction 本质上是一种告诉工具包程序中某个功能的方式,并为其命名。

操作是纯函数式的。它们不包含任何表现层信息。

一个操作包含四个相关信息

  • 一个 名称 作为标识符(通常全部小写,未翻译的英文字符串)

  • 一个 启用 标志,指示操作是否可以激活(例如“敏感”)

  • 一个可选的 状态 值,用于有状态的操作(例如,切换按钮的布尔值)

  • 一个可选的 参数 类型,用于激活操作时

一个操作支持两种操作

  • 激活,使用可选参数调用(类型正确,如上所述)

  • 状态更改请求,使用新的请求状态值调用(类型正确)。仅支持有状态的操作。

关于一个操作,这里有一些规则

  • 名称 是不可变的(这意味着它永远不会改变),并且永远不会是 NULL

  • 启用 标志可以改变

  • 参数 类型是不可变的

  • 参数类型 是可选的:它可以是 NULL

    • 如果参数类型是 NULL,则必须在没有参数的情况下激活操作(即:一个 NULL GVariant 指针)

    • 如果参数类型不是 NULL,则参数必须具有此类型

  • 状态 可以改变,但不能改变类型

    • 如果操作在创建时是有状态的,那么它将始终具有状态,并且它将始终具有完全相同的类型(例如布尔值或字符串)

    • 如果操作在创建时是无状态的,那么它永远不能有状态

  • 您只能请求有状态操作的 状态更改,并且只能请求状态更改为与现有状态相同的类型的值

一个操作**不**具有以下任何一项

  • 标签

  • 图标

  • 创建与其对应的控件的方法

  • 任何其他表现层信息

操作状态和参数

您的应用程序中的大多数操作将是无状态操作,没有参数。这些通常显示为没有特殊装饰的菜单项。一个例子是“退出”。

有状态的操作用于表示具有某种密切相关状态的操作。一个很好的例子是“全屏”操作。在这种情况下,当全屏选项处于活动状态时,您希望在菜单项旁边看到一个复选标记。这通常称为切换操作,它具有布尔状态。按照惯例,切换操作没有激活参数类型:激活操作始终切换状态。

另一个常见的情况是具有表示给定类型可能值的枚举的操作(通常是字符串)。这通常称为单选操作,通常在用户界面中用单选按钮或单选菜单项表示,或者有时用组合框表示。一个很好的例子是“文本对齐方式”,其可能值为“左对齐”、“居中”和“右对齐”。按照惯例,这些类型的操作具有等于其状态类型的参数类型,并且使用特定参数值激活它们等同于将状态更改为该值。

注意

这种处理单选按钮的方法与其他许多操作系统不同。使用 GAction,只有一个“文本对齐方式”操作,而“左对齐”、“居中”和“右对齐”是该操作的可能状态。没有三个单独的“左对齐”、“居中”和“右对齐”操作。

最终常见的操作类型是具有参数的无状态操作。这通常用于像“打开书签”这样的操作,其中操作的参数将是要打开的书签的标识符。

操作目标和详细名称

由于某些类型的操作在没有参数的情况下无法调用,因此在从可以调用操作的地方(例如从将状态设置为特定值的单选按钮或从打开特定书签的菜单项)引用操作时,指定参数通常很重要。在这些上下文中,用于操作参数的值通常称为操作的目标。

即使切换操作具有状态,它们也没有参数。因此,在引用它们时不需要目标值——它们始终会在激活时切换。

允许使用详细操作名称的大多数 API(例如 GMenuModelGtkActionable)允许使用详细的操作名称。这是一种方便的方法,可以用一个字符串指定操作名称和操作目标。

如果操作目标是一个字符串,没有不寻常的字符(即:仅包含字母数字、加号‘-’和点‘.’),则可以使用类似于 justify::left 的详细操作名称来指定目标为 left 的 justify 操作。

如果操作目标不是字符串,或者包含不寻常的字符,则可以使用更通用的格式 action-name(5),其中“5”是任何有效的文本格式 GVariant(即:可以由 g_variant_parse() 解析的字符串)。另一个例子是 open-bookmark('https://gnome.org.cn/')

您可以使用 g_action_parse_detailed_action_name()g_action_print_detailed_action_name() 在详细操作名称和拆分的操作名称和目标值之间进行转换,但通常您不需要这样做。大多数 API 将提供两种指定带有目标的操作的方法。

操作范围

操作始终限定于在其上操作的特定对象。

GTK 允许您为操作创建任意数量的范围,但始终会提供两个预定义的范围

  • app,用于应用程序全局的操作

  • win,用于与应用程序窗口关联的操作

限定于窗口的操作应该是专门影响该窗口的操作。这些操作包括“全屏”和“关闭”,或者在窗口包含文档的情况下,“保存”和“打印”。

影响整个应用程序而不是单个窗口的操作限定于应用程序。这些操作包括“关于”和“首选项”。

如果某个操作限定于窗口,则它限定于特定的窗口。另一种说法是:如果您的应用程序有一个适用于窗口的“全屏”操作,并且它有三个窗口,那么它将有三个全屏操作:每个窗口一个。

每个窗口都有一个单独的操作,允许每个窗口为每个操作实例具有单独的状态,并且能够以每窗口为基础控制操作的启用状态。

使用 GActionMap 接口将操作添加到其相关范围(应用程序或窗口)。

GActionMap

GActionMap 是一个接口,用于公开操作名称到操作的映射。它由 GtkApplicationGtkApplicationWindow 实现。可以添加、删除或查找操作。

void      g_action_map_add_action    (GActionMap  *action_map,
                                      GAction     *action);
void      g_action_map_remove_action (GActionMap  *action_map,
                                      const gchar *action_name);
GAction * g_action_map_lookup_action (GActionMap  *action_map,
                                      const gchar *action_name);

如果您想同时插入多个操作,通常使用 GActionEntry 更快、更容易。

在引用 GActionMap 上的操作时,仅使用操作本身的名称(即“退出”,而不是“app.quit”)。“app.quit”形式仅在从 GMenuGtkActionable 小部件等地方引用操作时使用,在这些地方不知道操作的范围。由于您正在使用 GtkApplicationGtkApplicationWindow 作为 GActionMap,因此清楚您的操作限定于哪个对象,因此不需要前缀。

GSimpleActionGAction

GAction 是一个接口,具有多种实现。您最有可能直接使用的实现是 GSimpleAction

思考 GActionGSimpleAction 之间区别的一个好方法是,GAction 是“消费者接口”,而 GSimpleAction 是“提供者接口”。GAction 接口提供由操作的使用者/调用者/显示者(例如菜单和小部件)使用的函数。只有实现操作本身的代码才能访问 GSimpleAction 接口。

请注意,GActionMap 接受一个 GAction。只有将操作放入 GActionMap 之后,操作才会被“消费”。

比较

  • GAction 具有一个用于检查操作是否启用的函数 (g_action_get_enabled()),但只有 GSimpleAction API 才能启用或禁用操作 (g_simple_action_set_enabled())。

  • GAction 具有一个用于查询操作状态的函数 (g_action_get_state()) 和请求更改它的函数 (g_action_change_state()),但只有 GSimpleAction 具有直接设置状态值的 API (g_simple_action_set_state())。

如果您想提供自定义 GAction 实现,您可以拥有自己的机制来控制对状态设置和启用的访问。例如,GSettings GAction 实现直接从 GSettings 中的值获取其状态,并根据密钥上的锁定状态进行启用。无法以任何方式直接修改这些值(尽管如果您有权这样做,可以通过更改设置的值来间接影响状态)。

使用 GSimpleAction

如果您正在实现操作,您可能将使用 GSimpleAction 来实现。

GSimpleAction 有两个有趣的信号:activatechange-state。这些直接对应于 g_action_activate()g_action_change_state()。您几乎肯定需要连接一个处理程序到 activate 信号,以便处理激活操作。信号处理程序接受一个 GVariant 参数,该参数是传递给 g_action_activate() 的参数。

如果您的操作是有状态的,您可能还想连接一个 change-state 处理程序来处理状态更改请求。如果您的操作是有状态的,并且您没有连接 change-state 信号的处理程序,那么默认情况下所有状态更改请求都将始终将状态更改为请求的值。即使您始终希望将状态设置为请求的值,您也可能希望连接一个处理程序,以便您可以采取一些操作来响应状态更改。

连接 change-state 处理程序时,默认情况下禁用响应 g_action_change_state() 设置状态的行为。因此,如果您实际上希望状态更改,则需要确保从您的处理程序调用 g_simple_action_set_state()

一种方便的方法是批量创建所有需要添加到 GActionMapGSimpleActions,即使用 GActionEntry 数组和 g_action_map_add_action_entries()

static GActionEntry app_entries[] = {
  { "preferences", preferences_activated, NULL, NULL, NULL },
  { "quit", quit_activated, NULL, NULL, NULL }
};

static void
example_app_startup (GApplication *app)
{
  // ...
  g_action_map_add_action_entries (G_ACTION_MAP (app),
                                app_entries, G_N_ELEMENTS (app_entries),
                                app);
  // ...
}

其他类型的动作

除了 GSimpleAction 之外,GIO 还提供了其他 GAction 的实现。 其中一个是 GSettingsAction,它将 GSettings 键包装成一个动作,该动作代表设置的值,并在激活时允许将键设置为新值。 另一个是 GPropertyAction,它类似地包装一个 GObject 属性。

GSettingsActionGPropertyAction 都实现了布尔状态的激活切换行为 - 请注意,GSimpleAction 没有实现,您必须自己实现激活处理程序来实现该功能。

将动作添加到您的 GtkApplication

您可以将 GAction 添加到实现 GActionMap 接口的任何对象,包括 GtkApplication。 这可以使用 g_action_map_add_action()g_action_map_add_action_entries() 来完成。

通常,您希望在应用程序的启动阶段执行此操作。

可以在任何时候添加或删除动作,但在启动之前执行此操作是浪费的,因为如果应用程序是一个远程实例(并且将立即退出),则会发生这种情况。 也可以在应用程序运行时,随时动态添加和删除动作。

将动作添加到您的 GtkApplicationWindow

GtkApplicationWindow 也实现了 GActionMap。 您通常希望在窗口构造时将大多数动作添加到窗口。 可以在窗口存在期间的任何时候添加和删除动作。

例如

static void
on_save_activate (GAction *action,
                  GVariant *param)
{
  g_print ("You are welcome");
}

static void
on_app_activate (GApplication *app)
{
  GtkWidget *window = gtk_application_window_new (GTK_APPLICATION (app));
  gtk_window_present (GTK_WINDOW (window));

  GAction *action = g_simple_action_new ("save", NULL);
  g_signal_connect (action, "activate", G_CALLBACK (on_save_activate), NULL);
  g_action_map_add_action (G_ACTION_MAP (window), action);

  GtkWidget *button = gtk_button_new_with_label ("Save");
  gtk_window_set_child (GTK_WINDOW (window), button);
  gtk_actionable_set_action_name (GTK_ACTIONABLE (button), "win.save");
}

// ...

int
main (int argc,
      char *argv[])
{
  GtkApplication *app =
    gtk_application_new ("com.example.App", G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (on_app_activate), NULL);
  return g_application_run (G_APPLICATION (app), argc, argv);
}

动作的加速器(键绑定)

在应用程序的启动实现中使用 gtk_application_add_accelerator()。 例如

gtk_application_set_accels_for_action (app, "win.new_tab",
                                       (const char *[]) {
                                         "<Control><Shift>T",
                                         NULL,
                                       });

可以使用动作做什么

GActions 添加到您的应用程序或窗口可以以多种不同的方式使用。

  • 与 GMenu 一起使用

  • 与 GtkActionable 小部件一起使用

  • 与快捷键一起使用

  • 由远程 GApplication 实例远程激活(仅适用于应用程序动作)

  • 在桌面文件中列出为“其他应用程序动作”(仅适用于应用程序动作)

  • 由其他 D-Bus 调用者远程激活(例如 Ubuntu 的 HUD)

  • 与 GNotification 通知一起使用(仅适用于应用程序动作)