4. Глава 16: Метки и Базовые Контейнеры Компоновки (GtkBox)

В этой главе мы углубимся в создание более сложных графических интерфейсов, изучив два ключевых элемента: текстовые метки с помощью GtkLabel и контейнеры для компоновки виджетов (GtkBox). Вы научитесь отображать статический текст и эффективно располагать несколько элементов интерфейса по вертикали или горизонтали.

4.1. GtkLabel: Отображение Текста

doc:

GtkLabel <https://docs.gtk.org/gtk3/class.Label.html> — это фундаментальный виджет GTK, предназначенный для отображения одной или нескольких строк текста. Он широко используется для:

  • Подписей к другим элементам управления (например, «Имя пользователя:»).

  • Заголовков разделов или окон.

  • Пояснений и инструкций для пользователя.

  • Отображения динамически изменяющегося текста (например, счётчика, статуса).

Текст в GtkLabel может быть простым или форматированным с использованием Pango Markup, что позволяет применять жирный шрифт, курсив, изменять цвет и размер текста.

### Пример 16.1: Простое окно с меткой

Название исходного файла: label_example.c

Этот пример демонстрирует базовое использование GtkLabel для отображения простого приветствия в центре окна.

// label_example.c
#include <gtk/gtk.h>

int main(int argc, char *argv[]) {
    // Инициализация GTK
    gtk_init(&argc, &argv);

    // Создание главного окна приложения
    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Пример с GtkLabel");
    gtk_window_set_default_size(GTK_WINDOW(window), 300, 100);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

    // Подключение сигнала "destroy" для корректного завершения приложения
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // Создание метки с заданным текстом
    // Обратите внимание на использование русской буквы 'П'. Убедитесь, что ваш файл сохранён в UTF-8!
    GtkWidget *label = gtk_label_new("Привет, GTK!");

    // Добавление метки в окно
    // Поскольку окно может иметь только один прямой дочерний элемент,
    // метка будет размещена по центру окна по умолчанию.
    gtk_container_add(GTK_CONTAINER(window), label);

    // Отображение всех виджетов
    gtk_widget_show_all(window);

    // Запуск главного цикла событий GTK
    gtk_main();

    return 0; // Возвращаем код успешного завершения
}

Комментарии к коду:

  • gtk_label_new("Привет, GTK!");: Эта функция создаёт новый виджет GtkLabel с указанным текстом. Важно, чтобы исходный файл был сохранен в кодировке UTF-8, если вы используете нелатинские символы (например, русские буквы), иначе могут возникнуть предупреждения Pango или некорректное отображение.

  • gtk_container_add(GTK_CONTAINER(window), label);: В данном случае, поскольку GtkWindow является контейнером для одного виджета, добавленная метка будет автоматически центрирована в окне.

4.2. GtkBox: Горизонтальная и Вертикальная Компоновка

doc:

GtkBox <https://docs.gtk.org/gtk3/class.Box.html> — это один из наиболее часто используемых контейнеров компоновки в GTK. Он позволяет размещать несколько виджетов в одном ряду — либо по горизонтали (GTK_ORIENTATION_HORIZONTAL), либо по вертикали (GTK_ORIENTATION_VERTICAL).

GtkBox упрощает создание линейных интерфейсов, будь то строка кнопок или столбец полей ввода.

### Основные функции GtkBox:

  • gtk_box_new(GtkOrientation orientation, int spacing): Создаёт новый контейнер GtkBox.
    • orientation: Определяет направление размещения виджетов. Может быть GTK_ORIENTATION_HORIZONTAL или GTK_ORIENTATION_VERTICAL.

    • spacing: Определяет фиксированный отступ (в пикселях) между соседними виджетами внутри бокса.

  • gtk_box_pack_start(GtkBox *box, GtkWidget *child, gboolean expand, gboolean fill, guint padding): Добавляет дочерний виджет в начало (или верх/лево) бокса.
    • box: Указатель на контейнер GtkBox.

    • child: Виджет, который нужно добавить.

    • expand: Если TRUE, виджет будет занимать максимально доступное пространство в том направлении, в котором он «растёт» (для вертикального бокса — по вертикали, для горизонтального — по горизонтали), если оно доступно. Если FALSE, он займёт только минимально необходимое пространство.

    • fill: Если TRUE, виджет будет заполнять всё доступное пространство в своём сегменте бокса. Если FALSE, он будет использовать только свой естественный размер, а остальное пространство будет пустым.

    • padding: Дополнительный отступ (в пикселях), который будет добавлен вокруг этого конкретного виджета внутри бокса. Этот отступ добавляется к spacing всего бокса.

  • gtk_box_pack_end(GtkBox *box, GtkWidget *child, gboolean expand, gboolean fill, guint padding): Аналогично gtk_box_pack_start, но добавляет виджет в конец (или низ/право) бокса.

### Пример 16.2: Окно с вертикальным GtkBox

Название исходного файла: layout_vbox_example.c

Этот пример демонстрирует создание вертикального GtkBox, который содержит метку и кнопку, расположенные друг под другом.

// layout_vbox_example.c
#include <gtk/gtk.h>

// Функция-обработчик для кнопки (из предыдущей главы)
static void on_button_clicked(GtkWidget *widget, gpointer data) {
    g_print("Кнопка в GtkBox нажата!\n");
}

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);

    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "GtkBox Вертикальный Пример");
    gtk_window_set_default_size(GTK_WINDOW(window), 300, 150);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // 1. Создание вертикального контейнера GtkBox с отступом 10 пикселей между элементами.
    // GTK_ORIENTATION_VERTICAL указывает, что виджеты будут располагаться друг под другом.
    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);

    // 2. Создание дочерних виджетов: метки и кнопки.
    GtkWidget *label = gtk_label_new("Это метка внутри VBox");
    GtkWidget *button = gtk_button_new_with_label("Нажми меня!");

    // 3. Подключение обработчика для кнопки.
    g_signal_connect(button, "clicked", G_CALLBACK(on_button_clicked), NULL);

    // 4. Добавление виджетов в GtkBox с использованием gtk_box_pack_start.
    //   - expand: TRUE - виджет может расширяться, занимая доступное пространство.
    //   - fill: TRUE - виджет будет заполнять своё выделенное пространство.
    //   - padding: 5 - дополнительный отступ в 5 пикселей вокруг этого виджета.
    gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 5);
    gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, TRUE, 5);

    // 5. Добавление GtkBox в окно.
    // Теперь окно содержит не метку или кнопку напрямую, а контейнер GtkBox,
    // который уже содержит метку и кнопку.
    gtk_container_add(GTK_CONTAINER(window), vbox);

    // 6. Отображение всех виджетов.
    gtk_widget_show_all(window);

    // 7. Запуск главного цикла GTK.
    gtk_main();

    return 0;
}

### Пример 16.3: Окно с горизонтальным GtkBox

Название исходного файла: layout_hbox_example.c

Этот пример демонстрирует создание горизонтального GtkBox, который содержит три кнопки, расположенные рядом.

// layout_hbox_example.c
#include <gtk/gtk.h>

// Единый обработчик для всех кнопок
static void on_generic_button_clicked(GtkWidget *widget, gpointer data) {
    // Получаем текст кнопки, чтобы вывести, какая именно кнопка была нажата
    const char *button_label = gtk_button_get_label(GTK_BUTTON(widget));
    g_print("Нажата кнопка: %s\n", button_label);
}

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);

    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "GtkBox Горизонтальный Пример");
    gtk_window_set_default_size(GTK_WINDOW(window), 450, 100);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // 1. Создание горизонтального контейнера GtkBox с отступом 5 пикселей.
    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);

    // 2. Создание трёх кнопок.
    GtkWidget *button1 = gtk_button_new_with_label("Кнопка 1");
    GtkWidget *button2 = gtk_button_new_with_label("Кнопка 2");
    GtkWidget *button3 = gtk_button_new_with_label("Кнопка 3");

    // 3. Подключение обработчика к каждой кнопке.
    // Все кнопки будут использовать одну и ту же функцию обработчика.
    g_signal_connect(button1, "clicked", G_CALLBACK(on_generic_button_clicked), NULL);
    g_signal_connect(button2, "clicked", G_CALLBACK(on_generic_button_clicked), NULL);
    g_signal_connect(button3, "clicked", G_CALLBACK(on_generic_button_clicked), NULL);

    // 4. Добавление кнопок в GtkBox.
    // Обратите внимание на параметры expand и fill для разных кнопок.
    // Кнопка 1: не расширяется, не заполняет, небольшой отступ.
    gtk_box_pack_start(GTK_BOX(hbox), button1, FALSE, FALSE, 0);

    // Кнопка 2: расширяется и заполняет доступное пространство.
    gtk_box_pack_start(GTK_BOX(hbox), button2, TRUE, TRUE, 0);

    // Кнопка 3: не расширяется, но заполняет своё пространство, если оно есть.
    gtk_box_pack_start(GTK_BOX(hbox), button3, FALSE, TRUE, 0);

    // 5. Добавление GtkBox в окно.
    gtk_container_add(GTK_CONTAINER(window), hbox);

    // 6. Отображение всех виджетов.
    gtk_widget_show_all(window);

    // 7. Запуск главного цикла GTK.
    gtk_main();

    return 0;
}

Комментарии к `gtk_box_pack_start` (и `gtk_box_pack_end`):

Эти функции являются ключевыми для управления тем, как виджеты будут себя вести внутри GtkBox:

  • expand (gboolean):
    • TRUE: Виджет будет пытаться занять любое дополнительное пространство, которое остаётся в контейнере GtkBox после того, как все виджеты с expand=FALSE займут своё минимальное место. Если несколько виджетов имеют expand=TRUE, они разделят это дополнительное пространство между собой.

    • FALSE: Виджет займёт только минимально необходимое пространство. Он не будет расширяться, даже если есть свободное место.

  • fill (gboolean):
    • TRUE: Если виджету выделено больше пространства, чем его естественный размер (например, потому что expand был TRUE или контейнер просто большой), он заполнит всё выделенное ему пространство.

    • FALSE: Виджет будет использовать только свой естественный размер, даже если ему выделено больше места. Оставшееся пространство внутри его «ячейки» в боксе будет пустым.

  • padding (guint):
    • Дополнительный отступ (в пикселях), который будет добавлен к естественному пространству, занимаемому виджетом. Этот отступ применяется по обе стороны от виджета в направлении ориентации бокса.

Понимание expand и fill критически важно для создания гибких и адаптивных макетов, которые хорошо выглядят при разных размерах окон.

### Компиляция и Запуск

Сохраните каждый пример кода в соответствующий файл (например, label_example.c, layout_vbox_example.c, layout_hbox_example.c).

Для сборки используйте следующую команду, адаптируя её для каждого файла:

# Для примера с GtkLabel:
gcc label_example.c -o label_example `pkg-config --cflags --libs gtk+-3.0`
./label_example

# Для примера с вертикальным GtkBox:
gcc layout_vbox_example.c -o layout_vbox_example `pkg-config --cflags --libs gtk+-3.0`
./layout_vbox_example

# Для примера с горизонтальным GtkBox:
gcc layout_hbox_example.c -o layout_hbox_example `pkg-config --cflags --libs gtk+-3.0`
./layout_hbox_example

### Ожидаемый результат

  • `label_example.c`: Откроется небольшое окно с заголовком «Пример с GtkLabel» и по центру будет виден текст «Привет, GTK!».

  • `layout_vbox_example.c`: Появится окно с заголовком «GtkBox Вертикальный Пример». Внутри будут расположены метка («Это метка внутри VBox») над кнопкой («Нажми меня!»). Между ними будет небольшой отступ. При нажатии кнопки, в терминал будет выведено сообщение.

  • `layout_hbox_example.c`: Откроется окно «GtkBox Горизонтальный Пример». Внутри будут расположены три кнопки («Кнопка 1», «Кнопка 2», «Кнопка 3») рядом друг с другом. Вы заметите, что «Кнопка 2» занимает больше места, растягиваясь, так как для неё expand и fill установлены в TRUE. При нажатии любой из кнопок, в терминал будет выведено сообщение, указывающее, какая кнопка была нажата.

### Дополнительные ресурсы

  • GtkLabel: Официальная документация GtkLabel.

  • GtkBox: Официальная документация GtkBox.

  • GTK3 Containers: Раздел «Containers» в официальной документации GTK3, описывающий различные контейнеры компоновки.

  • C_GUI_Handbook GitHub - Репозиторий с примерами кода из этого руководства.