操作¶
一个 GAction 代表应用程序中单个用户可操作的功能。
要使用 GAction,您应该使用 GtkApplication。
注意
虽然也可以在没有 GtkApplication 的情况下使用 GAction,但这里不会讨论这种情况。
它是什么,它不是什么¶
一个 GAction 本质上是一种告诉工具包程序中某个功能的方式,并为其命名。
操作是纯函数式的。它们不包含任何表现层信息。
一个操作包含四个相关信息
一个 名称 作为标识符(通常全部小写,未翻译的英文字符串)
一个 启用 标志,指示操作是否可以激活(例如“敏感”)
一个可选的 状态 值,用于有状态的操作(例如,切换按钮的布尔值)
一个可选的 参数 类型,用于激活操作时
一个操作支持两种操作
激活,使用可选参数调用(类型正确,如上所述)
状态更改请求,使用新的请求状态值调用(类型正确)。仅支持有状态的操作。
关于一个操作,这里有一些规则
名称 是不可变的(这意味着它永远不会改变),并且永远不会是
NULL启用 标志可以改变
参数 类型是不可变的
参数类型 是可选的:它可以是
NULL如果参数类型是
NULL,则必须在没有参数的情况下激活操作(即:一个NULLGVariant 指针)如果参数类型不是
NULL,则参数必须具有此类型
状态 可以改变,但不能改变类型
如果操作在创建时是有状态的,那么它将始终具有状态,并且它将始终具有完全相同的类型(例如布尔值或字符串)
如果操作在创建时是无状态的,那么它永远不能有状态
您只能请求有状态操作的 状态更改,并且只能请求状态更改为与现有状态相同的类型的值
一个操作**不**具有以下任何一项
标签
图标
创建与其对应的控件的方法
任何其他表现层信息
操作状态和参数¶
您的应用程序中的大多数操作将是无状态操作,没有参数。这些通常显示为没有特殊装饰的菜单项。一个例子是“退出”。
有状态的操作用于表示具有某种密切相关状态的操作。一个很好的例子是“全屏”操作。在这种情况下,当全屏选项处于活动状态时,您希望在菜单项旁边看到一个复选标记。这通常称为切换操作,它具有布尔状态。按照惯例,切换操作没有激活参数类型:激活操作始终切换状态。
另一个常见的情况是具有表示给定类型可能值的枚举的操作(通常是字符串)。这通常称为单选操作,通常在用户界面中用单选按钮或单选菜单项表示,或者有时用组合框表示。一个很好的例子是“文本对齐方式”,其可能值为“左对齐”、“居中”和“右对齐”。按照惯例,这些类型的操作具有等于其状态类型的参数类型,并且使用特定参数值激活它们等同于将状态更改为该值。
注意
这种处理单选按钮的方法与其他许多操作系统不同。使用 GAction,只有一个“文本对齐方式”操作,而“左对齐”、“居中”和“右对齐”是该操作的可能状态。没有三个单独的“左对齐”、“居中”和“右对齐”操作。
最终常见的操作类型是具有参数的无状态操作。这通常用于像“打开书签”这样的操作,其中操作的参数将是要打开的书签的标识符。
操作目标和详细名称¶
由于某些类型的操作在没有参数的情况下无法调用,因此在从可以调用操作的地方(例如从将状态设置为特定值的单选按钮或从打开特定书签的菜单项)引用操作时,指定参数通常很重要。在这些上下文中,用于操作参数的值通常称为操作的目标。
即使切换操作具有状态,它们也没有参数。因此,在引用它们时不需要目标值——它们始终会在激活时切换。
允许使用详细操作名称的大多数 API(例如 GMenuModel 和 GtkActionable)允许使用详细的操作名称。这是一种方便的方法,可以用一个字符串指定操作名称和操作目标。
如果操作目标是一个字符串,没有不寻常的字符(即:仅包含字母数字、加号‘-’和点‘.’),则可以使用类似于 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 是一个接口,用于公开操作名称到操作的映射。它由 GtkApplication 和 GtkApplicationWindow 实现。可以添加、删除或查找操作。
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);
class ActionMap:
def add_action(self, action: Gio.Action):
pass
def remove_action(self, action_name: str):
pass
def lookup_action(self, action_name: str) -> Gio.Action:
pass
public interface ActionMap {
public void add_action (GLib.Action action);
public void remove_action (string action_name);
public GLib.Action lookup_action (string action_name);
}
action_map.add_action(action); // GLib.Action
action_map.remove_action(action_name); // string
action_map.lookup_action(action_name); // string
如果您想同时插入多个操作,通常使用 GActionEntry 更快、更容易。
在引用 GActionMap 上的操作时,仅使用操作本身的名称(即“退出”,而不是“app.quit”)。“app.quit”形式仅在从 GMenu 或 GtkActionable 小部件等地方引用操作时使用,在这些地方不知道操作的范围。由于您正在使用 GtkApplication 或 GtkApplicationWindow 作为 GActionMap,因此清楚您的操作限定于哪个对象,因此不需要前缀。
GSimpleAction 与 GAction¶
GAction 是一个接口,具有多种实现。您最有可能直接使用的实现是 GSimpleAction。
思考 GAction 和 GSimpleAction 之间区别的一个好方法是,GAction 是“消费者接口”,而 GSimpleAction 是“提供者接口”。GAction 接口提供由操作的使用者/调用者/显示者(例如菜单和小部件)使用的函数。只有实现操作本身的代码才能访问 GSimpleAction 接口。
请注意,GActionMap 接受一个 GAction。只有将操作放入 GActionMap 之后,操作才会被“消费”。
比较
GAction具有一个用于检查操作是否启用的函数 (g_action_get_enabled()),但只有GSimpleActionAPI 才能启用或禁用操作 (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 有两个有趣的信号:activate 和 change-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()。
一种方便的方法是批量创建所有需要添加到 GActionMap 的 GSimpleActions,即使用 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);
// ...
}
public class Example.App : GLib.Application {
public override void startup () {
// ...
ActionEntry app_entries[] = {
{ "preferences", this.preferences_activated },
{ "quit", this.quit_activated }
};
this.add_action_entries (app_entries, this);
// ...
}
private void preferences_activated (SimpleAction action, Variant? parameter) {
// ...
}
private void quit_activated (SimpleAction action, Variant? parameter) {
// ...
}
}
其他类型的动作¶
除了 GSimpleAction 之外,GIO 还提供了其他 GAction 的实现。 其中一个是 GSettingsAction,它将 GSettings 键包装成一个动作,该动作代表设置的值,并在激活时允许将键设置为新值。 另一个是 GPropertyAction,它类似地包装一个 GObject 属性。
GSettingsAction 和 GPropertyAction 都实现了布尔状态的激活切换行为 - 请注意,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);
}
from gi.repository import Gio, Gtk
def on_save_activate(action, _):
print "You are welcome"
def on_app_activate(app):
window = Gtk.ApplicationWindow(application=app)
window.present()
action = Gio.SimpleAction(name="save")
action.connect("activate", on_save_activate)
window.add_action(action)
button = Gtk.Button(label="Save")
window.set_child(button)
button.set_action_name("win.save")
app = Gtk.Application()
app.connect("activate", on_app_activate)
app.run([])
public class Example.App : Gtk.Application {
public App () {
Object (application_id: "com.example.App",
flags: ApplicationFlags.FLAGS_NONE);
}
public override void activate () {
var window = new Gtk.ApplicationWindow (this);
window.present ();
var action = new SimpleAction ("save", null);
action.activate.connect (() => {
stdout.printf ("You are welcome\n");
});
window.add_action (action);
var button = new Gtk.Button ("Save");
window.child = button;
button.action_name = "win.save";
}
public static int main (string[] args) {
var app = new Example.App ();
return app.run (args);
}
}
import Gtk from "gi://Gtk?version=4.0";
import Adw from "gi://Adw?version=1";
import Gio from "gi://Gio";
function on_save_activate(action, param) {
log("You are welcome");
}
function on_app_activate(app) {
const window = new Gtk.ApplicationWindow({ application });
window.present();
const action = new Gio.SimpleAction({ name: "save" });
action.connect("activate", on_save_activate);
window.add_action(action);
const button = new Gtk.Button({ label: "Save" });
window.set_child(button);
button.set_action_name("win.save");
}
const application = new Gtk.Application();
application.connect("activate", on_app_activate);
application.run([]);
动作的加速器(键绑定)¶
在应用程序的启动实现中使用 gtk_application_add_accelerator()。 例如
gtk_application_set_accels_for_action (app, "win.new_tab",
(const char *[]) {
"<Control><Shift>T",
NULL,
});
app.set_accels_for_action("win.new_tab", ["<Control><Shift>T"])
this.set_accels_for_action ("win.new_tab", { "<Control><Shift>T", null });
app.set_accels_for_action("win.new_tab", ["<Control><Shift>T"]);
可以使用动作做什么¶
GActions 添加到您的应用程序或窗口可以以多种不同的方式使用。
与 GMenu 一起使用
与 GtkActionable 小部件一起使用
与快捷键一起使用
由远程 GApplication 实例远程激活(仅适用于应用程序动作)
在桌面文件中列出为“其他应用程序动作”(仅适用于应用程序动作)
由其他 D-Bus 调用者远程激活(例如 Ubuntu 的 HUD)
与 GNotification 通知一起使用(仅适用于应用程序动作)