Создание приложений
Приложение состоит из нескольких файлов:
Бинарный файл | Он устанавливается в |
Файл рабочего стола | Файл рабочего стола предоставляет важную информацию об приложении для оболочки рабочего стола, такую как его имя, значок, имя D-Bus, командную строку для запуска и т. д. Он устанавливается в |
GTK включает поддержку приложений, которая основана на GApplication. В этом руководстве мы создадим простое приложение, начиная с нуля и добавляя все больше и больше элементов по мере необходимости. По пути мы узнаем о GtkApplication, шаблонах, ресурсах, меню приложения, настройках, GtkHeaderBar, GtkStack, GtkSearchBar, GtkListBox и многом другом.
Полные, компилируемые исходники этих примеров можно найти в каталоге examples/ дистрибутива GTK или в онлайн-репозитории GTK. Вы можете скомпилировать каждый пример по отдельности, используя make с файлом Makefile.example. Дополнительную информацию см. в 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. В нашем примере пока нет никакой интересной функциональности. Все, что он делает, это открывает окно при активации без аргументов и открывает файлы, если он запускается с аргументами.
Для обработки этих двух случаев мы переопределяем vfunc activate(), который вызывается при запуске приложения без аргументов командной строки, и vfunc 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);
else
win = 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)
{
}
В рамках начальной настройки нашего приложения мы также создаем значок и файл рабочего стола.
Рисунок 5. Значок
[Desktop Entry] Type=Application Name=Example Icon=exampleapp StartupNotify=true Exec=bindir@/exampleapp
Обратите внимание, что нужно заменить на фактический путь к бинарному файлу, прежде чем этот файл рабочего стола можно будет использовать. bindir@
Вот что мы достигли на данный момент:
Рисунок 6. Приложение
Это пока не очень впечатляет, но наше приложение уже представляется на системном шине, имеет семантику единичного экземпляра и принимает файлы в качестве аргументов командной строки.
Заполнение окна
На этом шаге мы используем шаблон GtkBuilder для связывания файла GtkBuilder с классом окна нашего приложения.
Наш простой файл 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");
}
...
Вы, возможно, заметили, что мы использовали вариант функции, которая устанавливает шаблон. Теперь нам нужно использовать функциональность ресурсов 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
Модуль gnome системы сборки meson предоставляет метод gnome.compile_resources() для этой задачи.
Наше приложение теперь выглядит так:
Рисунок 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, который затем добавляем в качестве страницы в стек:
...
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);
}
...
Наконец, мы добавляем GtkStackSwitcher в область заголовка в файле ui и сообщаем ему отображать информацию о нашем стеке.
Stack switcher получает всю необходимую ему информацию для отображения вкладок из стека, к которому он принадлежит. Здесь мы передаем метку для отображения для каждого файла в качестве последнего аргумента функции gtk_stack_add_titled().
Наше приложение начинает принимать форму:
Рисунок 8. Окно приложения
Меню
Меню отображается справа от headerbar. Оно предназначено для сбора редко используемых действий, которые влияют на все приложение.
Точно так же, как и шаблон окна, мы определяем наше меню в файле 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 и связать полученную модель меню с кнопкой меню, которую мы добавили в headerbar. Поскольку меню работают путем активации GActions, нам также нужно добавить соответствующий набор действий в наше приложение.
Добавление действий лучше всего выполнить в функции 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] = { "<Ctrl>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().
Меню приложения выглядит так:
Рисунок 9. Окно приложения
Диалог настроек
Типичное приложение имеет некоторые настройки, которые следует сохранять между запусками. Даже для нашего простого примера приложения мы можем захотеть изменить шрифт, используемый для содержимого.
Мы будем использовать GSettings для хранения наших настроек. GSettings требует схемы, описывающей наши настройки:
<?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, а модуль gnome системы сборки meson предоставляет метод gnome.compile_schemas() для этой задачи.
Далее, нам нужно связать наши настройки с виджетами, которые они должны контролировать. Один удобный способ сделать это — использовать функциональность привязки GSettings для связывания ключей настроек с свойствами объектов, как мы это делаем здесь для настройки перехода.
...
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>
Реализация поиска требует довольно много изменений кода, которые мы здесь не будем полностью рассматривать. Центральной частью реализации поиска является обработчик сигналов, который прослушивает изменения текста в поле ввода поиска.
...
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. Строка поиска
Добавление боковой панели
Как ещё один функциональный элемент, мы добавляем боковую панель, которая демонстрирует 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 мы используем GAction, соответствующий заданному ключу GSettings.
...
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. Боковая панель
Свойства
Виджеты и другие объекты имеют множество полезных свойств.
Здесь мы покажем несколько способов их использования новыми и гибкими способами, обернув их в действия с помощью GPropertyAction или связав их с помощью GBinding.
Для этого мы добавляем две метки в строку заголовка нашего окна в шаблоне, названные lines_label и 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>
Чтобы этот пункт меню что-то делал, мы создаём действие свойства для свойства visible метки lines и добавляем его к действиям окна. В результате видимость метки будет переключаться каждый раз, когда действие активируется.
Так как мы хотим, чтобы обе метки появлялись и исчезали вместе, мы связываем свойство visible виджета lines_label с тем же свойством виджета 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. Полное приложение
© 2005–2020 The GNOME Project
Licensed under the GNU Lesser General Public License version 2.1 or later.
https://developer-old.gnome.org/gtk4/4.0/ch01s06.html