7. Глава 19: Выпадающие Списки (ComboBox) и Модели Данных (ListStore)
В этой главе мы погрузимся в работу с выпадающими списками (`GtkComboBox`) и связанными с ними моделями данных (`GtkListStore`) в GTK на языке C. Эти элементы являются незаменимыми для предоставления пользователю выбора из ограниченного набора предопределенных значений, например, списка стран, языков или настроек.
Мы рассмотрим два основных подхода к созданию GtkComboBox: 1. Использование упрощенного GtkComboBoxText для простых списков строк. 2. Использование GtkComboBox в паре с GtkListStore и GtkCellRendererText для более гибкого управления данными и их отображением.
### Что вы узнаете в этой главе:
Как создать базовый выпадающий список с помощью GtkComboBoxText.
Как добавлять элементы в GtkComboBoxText и получать выбранное значение.
Как использовать GtkListStore для хранения данных в табличной форме.
Как привязать GtkListStore к GtkComboBox для динамического отображения данных.
Что такое GtkCellRendererText и как он используется для отрисовки текста в ячейках.
Как обрабатывать выбор элемента в выпадающем списке.
—
7.1. Основные Компоненты для Работы со Списками
Прежде чем углубиться в примеры, давайте разберём ключевые компоненты, которые мы будем использовать:
GtkComboBox: Это основной виджет выпадающего списка. Он может быть привязан к различным моделям данных (например, GtkListStore или GtkTreeStore) для получения своих элементов.
GtkComboBoxText: Это упрощенная версия GtkComboBox, предназначенная специально для работы с простыми списками строк. Она автоматически создаёт и управляет внутренней моделью данных, что значительно упрощает код для базовых случаев.
GtkListStore: Это гибкая модель данных в GTK, которая представляет собой список строк, где каждая строка может содержать несколько столбцов данных разных типов. GtkListStore — это реализация интерфейса GtkTreeModel и используется для хранения данных, которые затем отображаются такими виджетами, как GtkComboBox или GtkTreeView.
GtkCellRendererText: Это «рендерер ячейки». В GTK архитектура «модель-представление» разделяет данные (модель) от их визуального представления (рендерер). GtkCellRendererText отвечает за отображение текста в ячейке виджета. Он не является виджетом сам по себе, а используется виджетами-представлениями (такими как GtkComboBox или GtkTreeView) для рисования содержимого ячеек.
—
### Пример 19.1: Простой GtkComboBox с массивом значений (GtkComboBoxText)
Название исходного файла: combo_simple_example.c
Этот пример демонстрирует наиболее простой способ создания выпадающего списка, используя GtkComboBoxText. Этот виджет идеально подходит, когда вам нужен список простых строк без сложной структуры данных.
#include <gtk/gtk.h> // Подключаем основную библиотеку GTK.
/**
* @brief Обработчик события "changed" для GtkComboBoxText.
*
* Эта функция вызывается, когда пользователь выбирает новый элемент
* из выпадающего списка.
*
* @param widget Указатель на GtkComboBox, который испустил сигнал.
* Здесь мы приводим его к GtkComboBoxText, чтобы использовать
* специфичную для него функцию получения текста.
* @param data Пользовательские данные (в данном примере NULL).
*/
void on_combo_changed(GtkComboBox *widget, gpointer data) {
// Получаем активный (выбранный) текст из GtkComboBoxText.
// gtk_combo_box_text_get_active_text() возвращает новую строку,
// которую необходимо освободить с помощью g_free(), когда она больше не нужна.
gchar *text = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(widget));
// Проверяем, что текст не NULL (т.е. что-то выбрано).
if (text != NULL) {
g_print("Вы выбрали: %s\n", text); // Выводим выбранный текст в консоль.
g_free(text); // Освобождаем выделенную память.
}
}
/**
* @brief Главная функция программы.
*
* Инициализирует GTK, создает окно, добавляет в него GtkComboBoxText
* с предопределенными значениями и запускает главный цикл событий GTK.
*
* @param argc Количество аргументов командной строки.
* @param argv Массив строк аргументов командной строки.
* @return Код завершения программы.
*/
int main(int argc, char *argv[]) {
// Инициализация GTK.
gtk_init(&argc, &argv);
// 1. Создание главного окна.
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "Пример GtkComboBoxText");
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);
// 2. Создание GtkComboBoxText.
// Это упрощенный выпадающий список для работы с текстовыми строками.
GtkWidget *combo = gtk_combo_box_text_new();
// 3. Добавление элементов в GtkComboBoxText.
// gtk_combo_box_text_append_text() добавляет новую строку в конец списка.
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "C");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "Python");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "Rust");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "Java");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "Go");
// 4. Установка активного элемента по умолчанию.
// gtk_combo_box_set_active() устанавливает элемент с заданным индексом
// как выбранный (0-индексированный). Здесь выбираем первый элемент ("C").
gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
// 5. Подключение обработчика события "changed".
// Сигнал "changed" испускается, когда выбранный элемент в ComboBox изменяется.
g_signal_connect(combo, "changed", G_CALLBACK(on_combo_changed), NULL);
// 6. Размещение GtkComboBox в контейнере.
// Используем GtkBox для размещения ComboBox в окне.
// VERTICAL - для вертикальной компоновки, 10 - отступ.
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
// Устанавливаем внешний отступ вокруг всего GtkBox.
gtk_container_set_border_width(GTK_CONTAINER(box), 20);
// Добавляем GtkComboBox в GtkBox.
// TRUE, TRUE, 10: виджет будет расширяться, заполнять пространство
// и иметь дополнительный отступ в 10 пикселей.
gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 10);
// Добавляем GtkBox в главное окно.
gtk_container_add(GTK_CONTAINER(window), box);
// 7. Отображение всех виджетов.
gtk_widget_show_all(window);
// 8. Запуск главного цикла событий GTK.
gtk_main();
return 0;
}
—
### Пример 19.2: GtkComboBox с GtkListStore для более сложных данных
Название исходного файла: combo_liststore_example.c
В более сложных сценариях, когда элементы выпадающего списка имеют не только текстовое представление, но и связанные данные (например, ID, числовые значения и т.д.), или когда список генерируется динамически, рекомендуется использовать GtkComboBox с GtkListStore. GtkListStore выступает как модель данных, а GtkCellRendererText используется для отображения текстовых столбцов из этой модели.
#include <gtk/gtk.h> // Подключаем основную библиотеку GTK.
// Перечисление для удобства определения столбцов в GtkListStore.
// COLUMN_NAME будет хранить строку (имя), COLUMN_ID будет хранить целое число (ID).
// NUM_COLS - общее количество столбцов.
enum {
COLUMN_NAME,
COLUMN_ID,
NUM_COLS
};
/**
* @brief Обработчик события "changed" для GtkComboBox с GtkListStore.
*
* Эта функция вызывается, когда пользователь выбирает новый элемент
* из выпадающего списка. Она демонстрирует, как получить данные
* из модели GtkListStore, используя итератор GtkTreeIter.
*
* @param widget Указатель на GtkComboBox, который испустил сигнал.
* @param data Пользовательские данные (в данном примере NULL).
*/
void on_combo_liststore_changed(GtkComboBox *widget, gpointer data) {
// Получаем модель данных, связанную с GtkComboBox.
// Модель является GtkTreeModel, но мы знаем, что это GtkListStore.
GtkTreeModel *model = gtk_combo_box_get_model(widget);
// Получаем активный (выбранный) итератор в модели.
GtkTreeIter iter;
// gtk_combo_box_get_active_iter() возвращает TRUE, если элемент выбран.
if (gtk_combo_box_get_active_iter(widget, &iter)) {
gchar *name; // Переменная для хранения имени.
gint id; // Переменная для хранения ID.
// Получаем данные из модели по итератору и индексу столбца.
// gtk_tree_model_get() позволяет извлечь значения из строки модели.
// model: наша модель данных.
// &iter: итератор, указывающий на текущую строку.
// COLUMN_NAME: индекс столбца для имени (0).
// &name: адрес переменной, куда будет записано имя.
// COLUMN_ID: индекс столбца для ID (1).
// &id: адрес переменной, куда будет записан ID.
// -1: признак конца списка аргументов.
gtk_tree_model_get(model, &iter,
COLUMN_NAME, &name,
COLUMN_ID, &id,
-1); // -1 означает конец списка свойств
g_print("Вы выбрали: %s (ID: %d)\n", name, id); // Выводим выбранные данные.
g_free(name); // Освобождаем строку, полученную от gtk_tree_model_get().
}
}
/**
* @brief Главная функция программы.
*
* Инициализирует GTK, создает окно, GtkListStore, наполняет его данными,
* создает GtkComboBox, связывает его с GtkListStore и GtkCellRendererText,
* и запускает главный цикл событий GTK.
*
* @param argc Количество аргументов командной строки.
* @param argv Массив строк аргументов командной строки.
* @return Код завершения программы.
*/
int main(int argc, char *argv[]) {
gtk_init(&argc, &argv);
// 1. Создание главного окна.
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "Пример ComboBox с ListStore");
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);
// 2. Создание GtkListStore (модели данных).
// gtk_list_store_new() создает новую модель данных.
// Первый аргумент: количество столбцов (NUM_COLS).
// Остальные аргументы: типы данных для каждого столбца.
// COLUMN_NAME будет G_TYPE_STRING, COLUMN_ID будет G_TYPE_INT.
GtkListStore *store = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_INT);
GtkTreeIter iter; // Итератор, который будет указывать на текущую строку.
// 3. Наполнение GtkListStore данными.
// Для каждой записи:
// - gtk_list_store_append(store, &iter): добавляет новую пустую строку в store
// и устанавливает iter на эту новую строку.
// - gtk_list_store_set(store, &iter, column_idx, value, -1): устанавливает значения
// для столбцов в текущей строке (указанной iter).
gtk_list_store_append(store, &iter); // Добавляем первую строку.
gtk_list_store_set(store, &iter,
COLUMN_NAME, "Опция А",
COLUMN_ID, 101,
-1); // -1 означает конец списка аргументов.
gtk_list_store_append(store, &iter); // Добавляем вторую строку.
gtk_list_store_set(store, &iter,
COLUMN_NAME, "Опция Б",
COLUMN_ID, 102,
-1);
gtk_list_store_append(store, &iter); // Добавляем третью строку.
gtk_list_store_set(store, &iter,
COLUMN_NAME, "Опция В",
COLUMN_ID, 103,
-1);
gtk_list_store_append(store, &iter); // Добавляем четвертую строку.
gtk_list_store_set(store, &iter,
COLUMN_NAME, "Опция Г",
COLUMN_ID, 104,
-1);
// 4. Создание GtkComboBox с использованием GtkListStore в качестве модели.
// GTK_TREE_MODEL(store) - приведение GtkListStore* к GtkTreeModel*,
// так как GtkComboBox работает с обобщенным интерфейсом GtkTreeModel.
GtkWidget *combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
// Поскольку мы передали GtkListStore, GtkComboBox теперь знает, откуда брать данные.
// Но ему нужно знать, как эти данные отображать. Для этого используются GtkCellRenderer.
// 5. Создание GtkCellRendererText для отображения текстового столбца.
// GtkCellRendererText - это объект, который знает, как отрисовать текст в ячейке.
GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
// 6. Добавление GtkCellRenderer в GtkComboBox и связывание его со столбцом модели.
// gtk_cell_layout_pack_start(): добавляет рендерер в компоновку ячеек ComboBox.
// GTK_CELL_LAYOUT(combo): GtkComboBox реализует интерфейс GtkCellLayout.
// renderer: наш GtkCellRendererText.
// TRUE: рендерер будет расширяться, если есть доступное пространство.
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
// gtk_cell_layout_set_attributes(): связывает свойство рендерера ("text")
// с конкретным столбцом в модели данных (COLUMN_NAME).
// Это говорит ComboBox: "Используй данные из COLUMN_NAME модели для свойства 'text' рендерера".
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer, "text", COLUMN_NAME, NULL);
// 7. Установка активного элемента по умолчанию (например, второй элемент - "Опция Б").
gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 1); // 0-индексированный
// 8. Подключение обработчика события "changed".
g_signal_connect(combo, "changed", G_CALLBACK(on_combo_liststore_changed), NULL);
// 9. Размещение GtkComboBox в контейнере.
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
gtk_container_set_border_width(GTK_CONTAINER(box), 20);
gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0); // Не расширять ComboBox
gtk_container_add(GTK_CONTAINER(window), box);
// 10. Отображение всех виджетов.
gtk_widget_show_all(window);
// 11. Запуск главного цикла событий GTK.
gtk_main();
// Важно: освободить GtkListStore.
// g_object_unref() уменьшает счетчик ссылок.
// GtkComboBox увеличивает счетчик ссылок на модель при ее установке.
// Когда ComboBox уничтожается, он уменьшает счетчик.
// Здесь мы уменьшаем ссылку, полученную при создании ListStore.
g_object_unref(store);
return 0;
}
—
7.2. Компиляция и Запуск
Сохраните каждый пример кода в соответствующий файл: combo_simple_example.c и combo_liststore_example.c.
### Компиляция:
Для сборки программ используйте следующие команды в терминале, находясь в директории с исходными файлами:
# Для примера с GtkComboBoxText:
gcc combo_simple_example.c -o combo_simple_example `pkg-config --cflags --libs gtk+-3.0`
# Для примера с GtkComboBox и GtkListStore:
gcc combo_liststore_example.c -o combo_liststore_example `pkg-config --cflags --libs gtk+-3.0`
### Запуск:
После успешной компиляции вы можете запустить каждое приложение:
# Для примера с GtkComboBoxText:
./combo_simple_example
# Для примера с GtkComboBox и GtkListStore:
./combo_liststore_example
—
7.3. Ожидаемый Результат
- `combo_simple_example.c`:
Откроется небольшое окно с заголовком «Пример GtkComboBoxText».
В центре окна будет выпадающий список с элементами «C», «Python», «Rust», «Java», «Go».
По умолчанию будет выбрано «C».
При выборе любого элемента из списка (например, «Python»), в терминал будет выведено сообщение:
Вы выбрали: Python
.
- `combo_liststore_example.c`:
Откроется небольшое окно с заголовком «Пример ComboBox с ListStore».
В центре окна будет выпадающий список с элементами «Опция А», «Опция Б», «Опция В», «Опция Г».
По умолчанию будет выбрана «Опция Б».
При выборе любого элемента из списка (например, «Опция В»), в терминал будет выведено сообщение, включающее как текст, так и связанный ID:
Вы выбрали: Опция В (ID: 103)
.
—
7.4. Дополнительные Ресурсы
GtkComboBox: Официальная документация GtkComboBox.
GtkListStore: Официальная документация GtkListStore.
GtkCellRendererText: Официальная документация GtkCellRendererText.
GtkTreeModel: Интерфейс GtkTreeModel, который реализует GtkListStore.
C_GUI_Handbook GitHub - Репозиторий с примерами кода из этого руководства.
—