5. Глава 17: Гибкая Компоновка с GtkGrid
В предыдущей главе мы освоили линейную компоновку с GtkBox, которая идеально подходит для размещения виджетов по горизонтали или вертикали. Однако для более сложных, табличных или многомерных макетов нам потребуется более мощный инструмент.
GtkGrid позволяет располагать виджеты в виде таблицы с заданным количеством строк и столбцов. Это даёт разработчику полную свободу и точный контроль над позиционированием элементов, а также возможность объединять ячейки, создавая гибкие и адаптивные интерфейсы.
### Что вы узнаете в этой главе:
Как создать и инициализировать контейнер GtkGrid.
Как добавлять виджеты в конкретные ячейки сетки, используя координаты (строка, столбец).
Как объединять ячейки по горизонтали (column-span) и вертикали (row-span).
Как управлять отступами между элементами сетки и вокруг неё.
Как использовать параметры расширения (expand) для адаптивного дизайна.
—
5.1. Основные Возможности GtkGrid
GtkGrid предоставляет гораздо больший контроль над расположением элементов по сравнению с GtkBox:
Размещение элементов в ячейках (row, column): Каждый виджет помещается в определённую ячейку сетки, заданную координатами строки и столбца, начиная с (0,0) для левого верхнего угла.
Объединение ячеек (row-span, column-span): Виджет может занимать не одну ячейку, а растягиваться на несколько строк или столбцов, что идеально подходит для создания сложных макетов (например, заголовок, занимающий всю ширину, или боковая панель, занимающая несколько строк).
Установка отступов: Можно задавать отступы как между строками и столбцами, так и вокруг всей сетки.
Растягивание/масштабирование элементов: С помощью свойств расширения (expand) и заполнения (fill), а также гомогенности, можно контролировать, как виджеты и сама сетка реагируют на изменение размера окна.
—
### Пример 17.1: Использование GtkGrid для размещения кнопок в виде таблицы
Название исходного файла: grid_example.c
Этот пример демонстрирует базовое использование GtkGrid для создания простого калькулятороподобного макета с кнопками, включая объединение ячеек.
#include <gtk/gtk.h> // Подключаем основную библиотеку GTK.
/**
* @brief Callback-функция, которая будет вызвана при нажатии любой кнопки.
*
* Эта функция извлекает метку с нажатой кнопки и выводит её в консоль.
* Это полезно для демонстрации, какая именно кнопка была активирована.
*
* @param widget Указатель на GtkWidget, который испустил сигнал (GtkButton).
* @param data Универсальный указатель на пользовательские данные (в данном случае NULL).
*/
static void on_button_clicked(GtkWidget *widget, gpointer data) {
const char *button_label = gtk_button_get_label(GTK_BUTTON(widget));
g_print("Нажата кнопка: %s\n", button_label);
}
/**
* @brief Функция активации приложения.
*
* Эта функция вызывается, когда приложение запускается и готово к отображению
* своих окон. Здесь создаются главное окно, GtkGrid и все виджеты.
*
* @param app Указатель на объект GtkApplication.
* @param user_data Универсальный указатель на пользовательские данные,
* переданные при подключении сигнала "activate". В данном случае NULL.
*/
static void activate(GtkApplication *app, gpointer user_data) {
GtkWidget *window;
GtkWidget *grid;
GtkWidget *button1, *button2, *button3, *button4; // Объявляем все кнопки
// 1. Создание главного окна приложения.
// gtk_application_window_new(app) создает окно, связанное с GtkApplication,
// что позволяет GTK лучше управлять жизненным циклом приложения.
window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(window), "GtkGrid Пример");
gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
// 2. Создание контейнера GtkGrid.
// GtkGrid - это контейнер, который позволяет располагать виджеты
// в виде таблицы (по строкам и столбцам).
grid = gtk_grid_new();
// 3. Настройка GtkGrid.
// Установка внешнего отступа (границы) вокруг всей сетки в 10 пикселей.
gtk_container_set_border_width(GTK_CONTAINER(grid), 10);
// Установка отступа в 5 пикселей между строками.
gtk_grid_set_row_spacing(GTK_GRID(grid), 5);
// Установка отступа в 5 пикселей между столбцами.
gtk_grid_set_column_spacing(GTK_GRID(grid), 5);
// Можно также сделать строки/столбцы гомогенными (равными по размеру)
// gtk_grid_set_row_homogeneous(GTK_GRID(grid), TRUE);
// gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE);
// 4. Создание кнопок, которые будут размещены в сетке.
button1 = gtk_button_new_with_label("Кнопка 1");
button2 = gtk_button_new_with_label("Кнопка 2");
button3 = gtk_button_new_with_label("Кнопка 3 (объединена)");
button4 = gtk_button_new_with_label("Кнопка 4");
// 5. Подключение обработчика кликов для каждой кнопки.
g_signal_connect(button1, "clicked", G_CALLBACK(on_button_clicked), NULL);
g_signal_connect(button2, "clicked", G_CALLBACK(on_button_clicked), NULL);
g_signal_connect(button3, "clicked", G_CALLBACK(on_button_clicked), NULL);
g_signal_connect(button4, "clicked", G_CALLBACK(on_button_clicked), NULL);
// 6. Добавление кнопок в сетку с использованием gtk_grid_attach().
// Эта функция - ключевая для GtkGrid. Её параметры:
// - GTK_GRID(grid): Указатель на GtkGrid.
// - buttonN: Виджет, который добавляется.
// - left: Индекс столбца, с которого начинается виджет (0-индексированный).
// - top: Индекс строки, с которой начинается виджет (0-индексированный).
// - width: Количество столбцов, которое занимает виджет (column-span).
// - height: Количество строк, которое занимает виджет (row-span).
// Кнопка 1: Столбец 0, Строка 0, занимает 1 столбец, 1 строку.
gtk_grid_attach(GTK_GRID(grid), button1, 0, 0, 1, 1);
// Кнопка 2: Столбец 1, Строка 0, занимает 1 столбец, 1 строку.
gtk_grid_attach(GTK_GRID(grid), button2, 1, 0, 1, 1);
// Кнопка 3: Столбец 0, Строка 1, занимает 2 столбца (растягивается), 1 строку.
gtk_grid_attach(GTK_GRID(grid), button3, 0, 1, 2, 1);
// Кнопка 4: Столбец 0, Строка 2, занимает 1 столбец, 1 строку.
gtk_grid_attach(GTK_GRID(grid), button4, 0, 2, 1, 1);
// 7. Добавление GtkGrid в окно.
// GtkWindow может содержать только один дочерний виджет, поэтому мы добавляем GtkGrid.
gtk_container_add(GTK_CONTAINER(window), grid);
// 8. Отображение всех виджетов (окна, сетки и всех кнопок внутри).
gtk_widget_show_all(window);
}
/**
* @brief Главная функция программы, точка входа.
*
* Эта функция отвечает за создание объекта GtkApplication,
* подключение функции активации и запуск основного цикла приложения.
*
* @param argc Количество аргументов командной строки.
* @param argv Массив строк аргументов командной строки.
* @return Код завершения программы.
*/
int main(int argc, char **argv) {
GtkApplication *app; // Указатель на объект приложения GTK
int status; // Переменная для хранения статуса завершения приложения
// Создание нового объекта GtkApplication.
// "org.example.GridApp" - уникальный идентификатор приложения (обычно в стиле DNS).
// G_APPLICATION_FLAGS_NONE - стандартные флаги приложения.
app = gtk_application_new("org.example.GridApp", G_APPLICATION_FLAGS_NONE);
// Подключение сигнала "activate" к нашей функции 'activate'.
// Сигнал "activate" испускается, когда приложение запускается и готово
// создать свое первое окно или показать себя пользователю.
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
// Запуск приложения.
// g_application_run() запускает главный цикл событий GLib (и GTK).
// Управление передается GTK, и приложение будет ждать событий.
// Функция возвращает статус завершения, когда приложение закрывается.
status = g_application_run(G_APPLICATION(app), argc, argv);
// Освобождение ресурсов, связанных с объектом GtkApplication.
g_object_unref(app);
return status; // Возвращаем статус завершения.
}