Глава 19: Выпадающие Списки (ComboBox) и Модели Данных (ListStore) ================================================================== В этой главе мы погрузимся в работу с **выпадающими списками (`GtkComboBox`)** и связанными с ними **моделями данных (`GtkListStore`)** в GTK на языке C. Эти элементы являются незаменимыми для предоставления пользователю выбора из ограниченного набора предопределенных значений, например, списка стран, языков или настроек. Мы рассмотрим два основных подхода к созданию `GtkComboBox`: 1. Использование упрощенного `GtkComboBoxText` для простых списков строк. 2. Использование `GtkComboBox` в паре с `GtkListStore` и `GtkCellRendererText` для более гибкого управления данными и их отображением. ### Что вы узнаете в этой главе: * Как создать базовый выпадающий список с помощью `GtkComboBoxText`. * Как добавлять элементы в `GtkComboBoxText` и получать выбранное значение. * Как использовать `GtkListStore` для хранения данных в табличной форме. * Как привязать `GtkListStore` к `GtkComboBox` для динамического отображения данных. * Что такое `GtkCellRendererText` и как он используется для отрисовки текста в ячейках. * Как обрабатывать выбор элемента в выпадающем списке. --- Основные Компоненты для Работы со Списками ------------------------------------------ Прежде чем углубиться в примеры, давайте разберём ключевые компоненты, которые мы будем использовать: * `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`. Этот виджет идеально подходит, когда вам нужен список простых строк без сложной структуры данных. .. code-block:: c #include // Подключаем основную библиотеку 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` используется для отображения текстовых столбцов из этой модели. .. code-block:: c #include // Подключаем основную библиотеку 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; } --- Компиляция и Запуск -------------------- Сохраните каждый пример кода в соответствующий файл: `combo_simple_example.c` и `combo_liststore_example.c`. ### Компиляция: Для сборки программ используйте следующие команды в терминале, находясь в директории с исходными файлами: .. code-block:: bash # Для примера с 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` ### Запуск: После успешной компиляции вы можете запустить каждое приложение: .. code-block:: bash # Для примера с GtkComboBoxText: ./combo_simple_example # Для примера с GtkComboBox и GtkListStore: ./combo_liststore_example --- Ожидаемый Результат ------------------- * **`combo_simple_example.c`**: * Откроется небольшое окно с заголовком "Пример GtkComboBoxText". * В центре окна будет выпадающий список с элементами "C", "Python", "Rust", "Java", "Go". * По умолчанию будет выбрано "C". * При выборе любого элемента из списка (например, "Python"), в терминал будет выведено сообщение: ``Вы выбрали: Python``. * **`combo_liststore_example.c`**: * Откроется небольшое окно с заголовком "Пример ComboBox с ListStore". * В центре окна будет выпадающий список с элементами "Опция А", "Опция Б", "Опция В", "Опция Г". * По умолчанию будет выбрана "Опция Б". * При выборе любого элемента из списка (например, "Опция В"), в терминал будет выведено сообщение, включающее как текст, так и связанный ID: ``Вы выбрали: Опция В (ID: 103)``. --- Дополнительные Ресурсы ----------------------- * `GtkComboBox`_: Официальная документация GtkComboBox. * `GtkListStore`_: Официальная документация GtkListStore. * `GtkCellRendererText`_: Официальная документация GtkCellRendererText. * `GtkTreeModel`_: Интерфейс GtkTreeModel, который реализует GtkListStore. * `C_GUI_Handbook GitHub`_ - Репозиторий с примерами кода из этого руководства. --- .. _GtkComboBox: https://docs.gtk.org/gtk3/class.ComboBox.html .. _GtkListStore: https://docs.gtk.org/gtk3/class.ListStore.html .. _GtkCellRendererText: https://docs.gtk.org/gtk3/class.CellRendererText.html .. _GtkTreeModel: https://docs.gtk.org/gtk3/iface.TreeModel.html .. _C_GUI_Handbook GitHub: https://github.com/AIDevelopersMonster/C_GUI_Handbook .. _GtkComboBox_docs: https://docs.gtk.org/gtk3/class.ComboBox.html .. _GtkComboBoxText: https://docs.gtk.org/gtk3/class.ComboBoxText.html .. _GtkListStore_docs: https://docs.gtk.org/gtk3/class.ListStore.html .. _GtkCellRendererText_docs: https://docs.gtk.org/gtk3/class.CellRendererText.html