当前位置: 首页 > news >正文

1 引言(1.6)

1.6 构建应用程序
一个应用程序由多个文件组成:
二进制文件:该文件安装在 /usr/bin 目录下。
桌面文件:桌面文件向桌面外壳提供应用程序的重要信息,如名称、图标、D-Bus 名称、启动命令等。它安装在 /usr/share/applications 目录下。
图标:图标安装在 /usr/share/icons/hicolor/48x48/apps 目录下,这样无论当前使用什么主题,都能找到该图标。
设置模式:如果应用程序使用 GSettings,它会将其模式安装在 /usr/share/glib-2.0/schemas 目录下,以便 dconf-editor 等工具能够找到它。
其他资源:其他文件(如 GtkBuilder 的 ui 文件)最好从存储在应用程序二进制文件本身的资源中加载。这减少了传统上需要安装在 /usr/share 中特定于应用程序位置的大多数文件的需求。
GTK 包含基于 GApplication 构建的应用程序支持。在本教程中,我们将从零开始构建一个简单的应用程序,逐步添加更多内容。在此过程中,我们将了解 GtkApplication、templates(模板)、resources(资源)、application menus(应用程序菜单)、settings(设置)、GtkHeaderBar、GtkStack、GtkSearchBar、GtkListBox 等。
这些示例的完整可构建源代码可以在 GTK 源代码分发的 examples/ 目录中找到,或者在 GTK 的 git 仓库在线查看。你可以使用 Makefile.example 文件通过 make 单独构建每个示例。有关更多信息,请参见 examples 目录中包含的 README。
一个简单的应用程序
使用 GtkApplication 时,main() 函数可以非常简单。我们只需调用 g_application_run() 并向其传递应用程序类的实例即可。

#include <gtk/gtk.h>#include "exampleapp.h"int
main (int argc, char *argv[])
{return g_application_run (G_APPLICATION (example_app_new()), argc, argv);
}

所有应用程序逻辑都位于应用程序类中,该类是 GtkApplication 的子类。我们的示例目前还没有任何有趣的功能。它所做的只是:当不带参数启动时,打开一个窗口;如果带参数启动,则打开所提供的文件。
为了处理这两种情况,我们重写了 activate() 虚函数(当应用程序不带命令行参数启动时会调用该函数)和 open() 虚函数(当应用程序带命令行参数启动时会调用该函数)。
要了解更多关于 GApplication 入口点的信息,请参考 GIO 文档。

#include <gtk/gtk.h>#include "exampleapp.h"
#include "exampleappwin.h"struct _ExampleApp
{GtkApplication parent;
};G_DEFINE_TYPE(ExampleApp, example_app, GTK_TYPE_APPLICATION);static void
example_app_init (ExampleApp *app)
{
}static void
example_app_activate (GApplication *app)
{ExampleAppWindow *win;win = example_app_window_new (EXAMPLE_APP (app));gtk_window_present (GTK_WINDOW (win));
}static void
example_app_open (GApplication  *app,GFile        **files,int            n_files,const char    *hint)
{GList *windows;ExampleAppWindow *win;int i;windows = gtk_application_get_windows (GTK_APPLICATION (app));if (windows)win = EXAMPLE_APP_WINDOW (windows->data);elsewin = example_app_window_new (EXAMPLE_APP (app));for (i = 0; i < n_files; i++)example_app_window_open (win, files[i]);gtk_window_present (GTK_WINDOW (win));
}static void
example_app_class_init (ExampleAppClass *class)
{G_APPLICATION_CLASS (class)->activate = example_app_activate;G_APPLICATION_CLASS (class)->open = example_app_open;
}ExampleApp *
example_app_new (void)
{return g_object_new (EXAMPLE_APP_TYPE,"application-id", "org.gtk.exampleapp","flags", G_APPLICATION_HANDLES_OPEN,NULL);
}

GTK 应用程序支持中另一个重要的类是 GtkApplicationWindow。它通常也会被子类化。我们的子类目前还没有实现任何功能,所以我们只会得到一个空窗口。

#include <gtk/gtk.h>#include "exampleapp.h"
#include "exampleappwin.h"struct _ExampleAppWindow
{GtkApplicationWindow parent;
};G_DEFINE_TYPE(ExampleAppWindow, example_app_window, GTK_TYPE_APPLICATION_WINDOW);static void
example_app_window_init (ExampleAppWindow *app)
{
}static void
example_app_window_class_init (ExampleAppWindowClass *class)
{
}ExampleAppWindow *
example_app_window_new (ExampleApp *app)
{return g_object_new (EXAMPLE_APP_WINDOW_TYPE, "application", app, NULL);
}void
example_app_window_open (ExampleAppWindow *win,GFile            *file)
{
}

在应用程序的初始设置中,我们还会创建一个图标和一个桌面文件。

05

图 5 一个图标
[Desktop Entry]
Type=Application
Name=Example
Icon=exampleapp
StartupNotify=true
Exec=bindir@/exampleapp
需要注意的是,在使用这个桌面文件之前,bindir@ 需要替换为二进制文件的实际路径。
到目前为止,我们已经完成了以下工作:

06

图 6 一个应用程序

虽然目前来看它还不是很引人注目,但我们的应用程序已经能在会话总线上呈现自己,具备单实例语义,并且可以接受文件作为命令行参数。

填充窗口
在这一步中,我们使用 GtkBuilder 模板将一个 GtkBuilder ui 文件与我们的应用程序窗口类相关联。
我们这个简单的 ui 文件为窗口设置了标题,并将一个 GtkStack 控件作为主要内容。

<?xml version="1.0" encoding="UTF-8"?>
<interface><template class="ExampleAppWindow" parent="GtkApplicationWindow"><property name="title" translatable="yes">Example Application</property><property name="default-width">600</property><property name="default-height">400</property><child><object class="GtkBox" id="content_box"><property name="orientation">vertical</property><child><object class="GtkStack" id="stack"/></child></object></child></template>
</interface>

为了在应用程序中使用这个文件,我们重新处理 GtkApplicationWindow 的子类,在类初始化函数中调用 gtk_widget_class_set_template_from_resource(),将该 ui 文件设置为这个类的模板。我们还在实例初始化函数中添加对 gtk_widget_init_template() 的调用,为该类的每个实例实例化模板。

...static void
example_app_window_init (ExampleAppWindow *win)
{gtk_widget_init_template (GTK_WIDGET (win));
}static void
example_app_window_class_init (ExampleAppWindowClass *class)
{gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),"/org/gtk/exampleapp/window.ui");
}...

(完整代码)
你可能已经注意到,我们使用了设置模板的函数的 _from_resource() 变体。现在我们需要使用 Glib 的资源功能将 ui 文件包含到二进制文件中。通常的做法是在 .gresource.xml 文件中列出所有资源,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<gresources><gresource prefix="/org/gtk/exampleapp"><file preprocess="xml-stripblanks">window.ui</file></gresource>
</gresources>

这个文件必须转换为 C 源文件,然后与其他源文件一起编译并链接到应用程序中。要完成此操作,我们使用 glib-compile-resources 工具:
glib-compile-resources exampleapp.gresource.xml --target=resources.c --generate-source
meson 构建系统的 gnome 模块提供了 gnome.compile_resources() 方法来完成这项任务。
我们的应用程序现在看起来是这样的:

07

图 7 应用程序

打开文件
在这一步中,我们让应用程序显示命令行中提供的所有文件的内容。
为此,我们在应用程序窗口子类的结构体中添加一个成员,并在其中保留对 GtkStack 的引用。结构体的第一个成员应当是该类所继承的父类型。这里,ExampleAppWindow 继承自 GtkApplicationWindow。gtk_widget_class_bind_template_child() 函数会进行相关设置,以便在实例化模板后,结构体的 stack 成员将指向模板中同名的控件。

...struct _ExampleAppWindow
{GtkApplicationWindow parent;GtkWidget *stack;
};G_DEFINE_TYPE (ExampleAppWindow, example_app_window, GTK_TYPE_APPLICATION_WINDOW)...static void
example_app_window_class_init (ExampleAppWindowClass *class)
{gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),"/org/gtk/exampleapp/window.ui");gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppWindow, stack);
}...

(完整代码)
现在我们重新看一下 example_app_window_open () 函数,该函数会针对每个命令行参数被调用。我们将创建一个 GtkTextView,并把它作为一个页面添加到栈(stack)中:

...void
example_app_window_open (ExampleAppWindow *win,GFile            *file)
{char *basename;GtkWidget *scrolled, *view;char *contents;gsize length;basename = g_file_get_basename (file);scrolled = gtk_scrolled_window_new();gtk_widget_set_hexpand (scrolled, TRUE);gtk_widget_set_vexpand (scrolled, TRUE);view = gtk_text_view_new();gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (view), FALSE);gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled), view);gtk_stack_add_titled (GTK_STACK (win->stack), scrolled, basename, basename);if (g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)){GtkTextBuffer *buffer;buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));gtk_text_buffer_set_text (buffer, contents, length);g_free (contents);}g_free (basename);
}...

(完整代码)
最后,我们在 ui 文件的标题栏区域添加一个 GtkStackSwitcher,并让它显示关于我们的栈的信息。
栈切换器(stack switcher)所需的所有用于显示标签页的信息都来自它所关联的栈。在这里,我们通过 gtk_stack_add_titled() 函数的最后一个参数,为每个文件指定了要显示的标签。
我们的应用程序开始初具雏形了:

08

图 8应用程序窗口

菜单
菜单显示在标题栏的右侧,用于汇集那些不常使用但会影响整个应用程序的操作。
与窗口模板类似,我们在一个 ui 文件中定义菜单,并将其作为资源添加到二进制文件中。

<?xml version="1.0" encoding="UTF-8"?>
<interface><menu id="menu"><section><item><attribute name="label" translatable="yes">_Preferences</attribute><attribute name="action">app.preferences</attribute></item></section><section><item><attribute name="label" translatable="yes">_Quit</attribute><attribute name="action">app.quit</attribute></item></section></menu>
</interface>

要让菜单显示出来,我们必须加载 ui 文件,并将生成的菜单模型与我们添加到标题栏的菜单按钮按钮相关联。由于菜单通过激活 GAction 来工作,我们还必须向应用程序添加一组合适的动作。
添加这些动作最好在 startup() 虚函数中完成,该函数确保会为每个主应用程序实例调用一次:

...static void
preferences_activated (GSimpleAction *action,GVariant      *parameter,gpointer       app)
{
}static void
quit_activated (GSimpleAction *action,GVariant      *parameter,gpointer       app)
{g_application_quit (G_APPLICATION (app));
}static GActionEntry app_entries[] =
{{ "preferences", preferences_activated, NULL, NULL, NULL },{ "quit", quit_activated, NULL, NULL, NULL }
};static void
example_app_startup (GApplication *app)
{GtkBuilder *builder;GMenuModel *app_menu;const char *quit_accels[2] = { "&lt;Ctrl&gt;Q", NULL };G_APPLICATION_CLASS (example_app_parent_class)->startup (app);g_action_map_add_action_entries (G_ACTION_MAP (app),app_entries, G_N_ELEMENTS (app_entries),app);gtk_application_set_accels_for_action (GTK_APPLICATION (app),"app.quit",quit_accels);
}static void
example_app_class_init (ExampleAppClass *class)
{G_APPLICATION_CLASS (class)->startup = example_app_startup;...
}...

(完整代码)
我们的“偏好设置”菜单项目前还没有任何功能,但“退出”菜单项已经可以正常使用了。需要注意的是,它还可以通过常用的 Ctrl-Q 快捷键来激活。这个快捷键是通过 gtk_application_set_accels_for_action() 函数添加的。
应用程序菜单的样子如下:

09

图 9 应用程序窗口

偏好设置对话框
一个典型的应用程序会有一些偏好设置,这些设置需要在多次运行之间保持不变。即便是我们这个简单的示例应用程序,也可能需要更改用于显示内容的字体。
我们将使用 GSettings 来存储这些偏好设置。GSettings 需要一个描述我们设置的模式(schema):

<?xml version="1.0" encoding="UTF-8"?>
<schemalist><schema path="/org/gtk/exampleapp/" id="org.gtk.exampleapp"><key name="font" type="s"><default>'Monospace 12'</default><summary>Font</summary><description>The font to be used for content.</description></key><key name="transition" type="s"><choices><choice value='none'/><choice value='crossfade'/><choice value='slide-left-right'/></choices><default>'none'</default><summary>Transition</summary><description>The transition to use when switching tabs.</description></key></schema>
</schemalist>

在应用程序中使用此模式之前,我们需要将其编译为 GSettings 所需的二进制形式。GIO 为基于 autotools 的项目提供了用于此操作的宏,而 meson 构建系统的 gnome 模块则提供了 gnome.compile_schemas() 方法来完成这项任务。
接下来,我们需要将设置与它们所要控制的控件关联起来。一种便捷的方式是使用 GSettings 的绑定功能,将设置键(settings keys)绑定到对象属性上,就像我们在处理过渡(transition)设置时所做的那样。

...static void
example_app_window_init (ExampleAppWindow *win)
{gtk_widget_init_template (GTK_WIDGET (win));win->settings = g_settings_new ("org.gtk.exampleapp");g_settings_bind (win->settings, "transition",win->stack, "transition-type",G_SETTINGS_BIND_DEFAULT);
}...

(完整代码)
连接字体设置的代码会稍微复杂一些,因为没有与之直接对应的简单对象属性,所以这里我们就不深入探讨了。
到目前为止,如果你修改了某项设置(例如使用 gsettings 命令行工具),应用程序已经能够做出响应。当然,我们希望应用程序能为这些设置提供一个偏好设置对话框。所以现在就来实现它。我们的偏好设置对话框将是 GtkDialog 的子类,并且会使用我们已经学过的那些技术:模板、私有结构体、设置绑定。
让我们从模板开始。

<?xml version="1.0" encoding="UTF-8"?>
<interface><template class="ExampleAppPrefs" parent="GtkDialog"><property name="title" translatable="yes">Preferences</property><property name="resizable">0</property><property name="modal">1</property><child internal-child="content_area"><object class="GtkBox" id="content_area"><child><object class="GtkGrid" id="grid"><property name="margin-start">12</property><property name="margin-end">12</property><property name="margin-top">12</property><property name="margin-bottom">12</property><property name="row-spacing">12</property><property name="column-spacing">12</property><child><object class="GtkLabel" id="fontlabel"><property name="label">_Font:</property><property name="use-underline">1</property><property name="mnemonic-widget">font</property><property name="xalign">1</property><layout><property name="column">0</property><property name="row">0</property></layout></object></child><child><object class="GtkFontButton" id="font"><layout><property name="column">1</property><property name="row">0</property></layout></object></child><child><object class="GtkLabel" id="transitionlabel"><property name="label">_Transition:</property><property name="use-underline">1</property><property name="mnemonic-widget">transition</property><property name="xalign">1</property><layout><property name="column">0</property><property name="row">1</property></layout></object></child><child><object class="GtkComboBoxText" id="transition"><items><item translatable="yes" id="none">None</item><item translatable="yes" id="crossfade">Fade</item><item translatable="yes" id="slide-left-right">Slide</item></items><layout><property name="column">1</property><property name="row">1</property></layout></object></child></object></child></object></child></template>
</interface>

接下来是对话框子类的实现。

#include <gtk/gtk.h>#include "exampleapp.h"
#include "exampleappwin.h"
#include "exampleappprefs.h"struct _ExampleAppPrefs
{GtkDialog parent;GSettings *settings;GtkWidget *font;GtkWidget *transition;
};G_DEFINE_TYPE (ExampleAppPrefs, example_app_prefs, GTK_TYPE_DIALOG)static void
example_app_prefs_init (ExampleAppPrefs *prefs)
{gtk_widget_init_template (GTK_WIDGET (prefs));prefs->settings = g_settings_new ("org.gtk.exampleapp");g_settings_bind (prefs->settings, "font",prefs->font, "font",G_SETTINGS_BIND_DEFAULT);g_settings_bind (prefs->settings, "transition",prefs->transition, "active-id",G_SETTINGS_BIND_DEFAULT);
}static void
example_app_prefs_dispose (GObject *object)
{ExampleAppPrefs *prefs;prefs = EXAMPLE_APP_PREFS (object);g_clear_object (&prefs->settings);G_OBJECT_CLASS (example_app_prefs_parent_class)->dispose (object);
}static void
example_app_prefs_class_init (ExampleAppPrefsClass *class)
{G_OBJECT_CLASS (class)->dispose = example_app_prefs_dispose;gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),"/org/gtk/exampleapp/prefs.ui");gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppPrefs, font);gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppPrefs, transition);
}ExampleAppPrefs *
example_app_prefs_new (ExampleAppWindow *win)
{return g_object_new (EXAMPLE_APP_PREFS_TYPE, "transient-for", win, "use-header-bar", TRUE, NULL);
}

现在我们回过头来处理应用程序类中的 preferences_activated () 函数,让它打开一个新的偏好设置对话框。

...static void
preferences_activated (GSimpleAction *action,GVariant      *parameter,gpointer       app)
{ExampleAppPrefs *prefs;GtkWindow *win;win = gtk_application_get_active_window (GTK_APPLICATION (app));prefs = example_app_prefs_new (EXAMPLE_APP_WINDOW (win));gtk_window_present (GTK_WINDOW (prefs));
}...

(完整代码)
经过所有这些工作,我们的应用程序现在可以显示如下所示的偏好设置对话框:
(网站图片无法加载)
图 10 偏好设置对话框

添加搜索栏
我们继续完善应用程序的功能。现在,我们添加搜索功能。GTK 通过 GtkSearchEntry 和 GtkSearchBar 支持这一功能。搜索栏是一个可以从顶部滑入的控件,用于显示搜索输入框。
我们在标题栏中添加一个切换按钮,通过该按钮可以使搜索栏从标题栏下方滑出。

<?xml version="1.0" encoding="UTF-8"?>
<interface><template class="ExampleAppWindow" parent="GtkApplicationWindow"><property name="title" translatable="yes">Example Application</property><property name="default-width">600</property><property name="default-height">400</property><child type="titlebar"><object class="GtkHeaderBar" id="header"><child type="title"><object class="GtkStackSwitcher" id="tabs"><property name="stack">stack</property></object></child><child type="end"><object class="GtkMenuButton" id="gears"><property name="direction">none</property></object></child><child type="end"><object class="GtkToggleButton" id="search"><property name="sensitive">0</property><property name="icon-name">edit-find-symbolic</property></object></child></object></child><child><object class="GtkBox" id="content_box"><property name="orientation">vertical</property><child><object class="GtkSearchBar" id="searchbar"><child><object class="GtkSearchEntry" id="searchentry"><signal name="search-changed" handler="search_text_changed"/></object></child></object></child><child><object class="GtkStack" id="stack"><signal name="notify::visible-child" handler="visible_child_changed"/></object></child></object></child></template>
</interface>

实现搜索功能需要不少代码改动,这里我们就不逐一详细说明了。搜索实现的核心部分是一个信号处理器,它会监听搜索输入框(search entry)中的文本变化。

...static void
search_text_changed (GtkEntry         *entry,ExampleAppWindow *win)
{const char *text;GtkWidget *tab;GtkWidget *view;GtkTextBuffer *buffer;GtkTextIter start, match_start, match_end;text = gtk_editable_get_text (GTK_EDITABLE (entry));if (text[0] == '\0')return;tab = gtk_stack_get_visible_child (GTK_STACK (win->stack));view = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (tab));buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));/* Very simple-minded search implementation */gtk_text_buffer_get_start_iter (buffer, &start);if (gtk_text_iter_forward_search (&start, text, GTK_TEXT_SEARCH_CASE_INSENSITIVE,&match_start, &match_end, NULL)){gtk_text_buffer_select_range (buffer, &match_start, &match_end);gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (view), &match_start,0.0, FALSE, 0.0, 0.0);}
}static void
example_app_window_init (ExampleAppWindow *win)
{...gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), search_text_changed);...}...

(完整代码)
有了搜索栏后,我们的应用程序现在看起来是这样的:

11

图 11 搜索栏

添加侧边栏
作为另一项功能,我们将添加一个侧边栏,这将展示 GtkMenuButton、GtkRevealer 和 GtkListBox 的用法。

<?xml version="1.0" encoding="UTF-8"?>
<interface><template class="ExampleAppWindow" parent="GtkApplicationWindow"><property name="title" translatable="yes">Example Application</property><property name="default-width">600</property><property name="default-height">400</property><child type="titlebar"><object class="GtkHeaderBar" id="header"><child type="title"><object class="GtkStackSwitcher" id="tabs"><property name="stack">stack</property></object></child><child type="end"><object class="GtkToggleButton" id="search"><property name="sensitive">0</property><property name="icon-name">edit-find-symbolic</property></object></child><child type="end"><object class="GtkMenuButton" id="gears"><property name="direction">none</property></object></child></object></child><child><object class="GtkBox" id="content_box"><property name="orientation">vertical</property><child><object class="GtkSearchBar" id="searchbar"><child><object class="GtkSearchEntry" id="searchentry"><signal name="search-changed" handler="search_text_changed"/></object></child></object></child><child><object class="GtkBox" id="hbox"><child><object class="GtkRevealer" id="sidebar"><property name="transition-type">slide-right</property><child><object class="GtkScrolledWindow" id="sidebar-sw"><property name="hscrollbar-policy">never</property><child><object class="GtkListBox" id="words"><property name="selection-mode">none</property></object></child></object></child></object></child><child><object class="GtkStack" id="stack"><signal name="notify::visible-child" handler="visible_child_changed"/></object></child></object></child></object></child></template>
</interface>

用按钮填充侧边栏(这些按钮对应每个文件中找到的单词)的代码比较复杂,这里就不深入讲解了。但我们会看一下在菜单中为这个新功能添加复选按钮的代码。

<?xml version="1.0" encoding="UTF-8"?>
<interface><menu id="menu"><section><item><attribute name="label" translatable="yes">_Words</attribute><attribute name="action">win.show-words</attribute></item><item><attribute name="label" translatable="yes">_Preferences</attribute><attribute name="action">app.preferences</attribute></item></section><section><item><attribute name="label" translatable="yes">_Quit</attribute><attribute name="action">app.quit</attribute></item></section></menu>
</interface>

为了将菜单项与 show-words 设置关联起来,我们使用一个与指定 GSettings 键对应的 GAction。

...static void
example_app_window_init (ExampleAppWindow *win)
{...builder = gtk_builder_new_from_resource ("/org/gtk/exampleapp/gears-menu.ui");menu = G_MENU_MODEL (gtk_builder_get_object (builder, "menu"));gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (priv->gears), menu);g_object_unref (builder);action = g_settings_create_action (priv->settings, "show-words");g_action_map_add_action (G_ACTION_MAP (win), action);g_object_unref (action);
}...

(完整代码)
我们的应用程序现在是这个样子的:

12

图 12 侧边栏

属性
控件和其他对象具有许多有用的属性。
这里我们展示一些以新颖且灵活的方式使用这些属性的方法:通过 GPropertyAction 将它们包装为动作,或者通过 GBinding 进行绑定。
为了实现这一点,我们在窗口模板的标题栏中添加两个标签,分别命名为 lines_label 和 lines,并将它们绑定到私有结构体的成员中,这是我们已经见过好几次的操作了。
我们在齿轮菜单中添加一个新的“lines”菜单项,它会触发 show-lines 动作:

<?xml version="1.0" encoding="UTF-8"?>
<interface><menu id="menu"><section><item><attribute name="label" translatable="yes">_Words</attribute><attribute name="action">win.show-words</attribute></item><item><attribute name="label" translatable="yes">_Lines</attribute><attribute name="action">win.show-lines</attribute></item><item><attribute name="label" translatable="yes">_Preferences</attribute><attribute name="action">app.preferences</attribute></item></section><section><item><attribute name="label" translatable="yes">_Quit</attribute><attribute name="action">app.quit</attribute></item></section></menu>
</interface>

为了让这个菜单项发挥作用,我们为lines标签的 visible 属性创建一个属性动作(property action),并将其添加到窗口的动作中。这样一来,每次激活该动作时,标签的可见性就会切换。
由于我们希望两个标签同时显示或同时隐藏,因此我们将 lines_label 控件的 visible 属性与 lines 控件的同一属性进行绑定。

...static void
example_app_window_init (ExampleAppWindow *win)
{...action = (GAction*) g_property_action_new ("show-lines", win->lines, "visible");g_action_map_add_action (G_ACTION_MAP (win), action);g_object_unref (action);g_object_bind_property (win->lines, "visible",win->lines_label, "visible",G_BINDING_DEFAULT);
}...

(完整代码)
我们还需要一个函数来计算当前活动标签页的行数,并更新lines标签。如果您想了解详情,可以查看完整源代码。
这使我们的示例应用程序呈现出如下外观:

13

图 13 完整的应用程序

通过网盘分享的知识:GTK 4 Reference Manual
链接: https://pan.baidu.com/s/57mKoejMZPrxs_wh3a7Kj9g
--来自百度网盘超级会员v7的分享

http://www.sczhlp.com/news/791.html

相关文章:

  • 可并堆(左偏树)
  • 7-28
  • DAY24
  • 2025 ZR暑假集训 CD联考 Day2 E 环球旅行
  • zk后集训
  • 乘法逆元(部分施工)、exgcd
  • 夏令营Ⅲ期
  • centos8.2 挂载本地镜像作为yum源
  • 非常值得学习渲染入门的一个教程
  • HDU 多校 2025 R3
  • 7.28SAM后缀自动机,回文自动机
  • Linux开机自动登录的一种方法
  • day5
  • JAVA语言学习总结(第27天)
  • CVE-2021-45232 Apache APISIX Dashboard身份验证绕过漏洞 (复现)
  • IIS中配置HTTPS证书的详细步骤
  • Python入门学习(七)高级部分:正则表达式
  • 在运维工作中,如果运行的一个容器突然挂了,如何排查?
  • SciTech-EECS-Library: img2pdf 与 pdf2image : Python 的 pdf 与 image 双向转换库
  • 在运维工作中,docker封闭了哪些资源?
  • 深度学习(pytorch量化)
  • 在运维工作中,传统虚拟化与docker有什么区别?
  • 在运维工作中,Docker怎么清理容器磁盘空间?
  • 在运维工作中,Dockerfile中常见指令有哪些?
  • 英语_阅读_Rivers are important in culture_单词_待读
  • 题解:P12151 【MX-X11-T5】「蓬莱人形 Round 1」俄罗斯方块
  • 题解:P1291 [SHOI2002] 百事世界杯之旅
  • 题解:P4170 [CQOI2007] 涂色
  • 课堂分组赛、组队赛小结
  • 【AI News | 20250725】每日AI进展