Работа с GtkTreeView и GtkListStore с помощью редактора Glade для начинающих

    В этом посте я хочу рассказать про работу с очень интересными виджетами (объектами) библиотеки графических интерфейсов GTK+: GtkTreeView и GtkListStore. GtkTreeView — виджет для отображения деревьев и списков. GtkListStore — виджет представляющий модель списка.
    Создавать их я буду с помощью редактора Glade, в интернете очень мало материала именно по работе с виджетами GTK+ (и особенно с этими) с помощью этого редактора интерфейсов. Я уже писал немного про работу с ним. Те, кто никогда с ним не работал — советую прочитать этот пост.

    Возможностей работы с этими виджетами очень много и я планирую написать еще несколько статей по этой теме (если это конечно заинтересует читателей). Этот пост расчитан на начинающих еще только знакомиться с библиотекой GTK+ и её возможностями. Поэтому сегодня я рассмотрю достаточно простой пример.

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



    Но добавлять в GtkListStore будем не только информацию о персонаже. Это хранилище позволяет хранить различные данные. Например стили шрифта, имя шрифта, цвета, картинки, различные настройки виджетов. В данном примере я покажу как можно для каждого отдельного персонажа задать отдельно цвет его написания в GtkTreeView (его еще называют виджет просмотра дерева, хотя в данном примеры мы будем просмотривать структуру списочного формата, а не древовидного).

    Начнём с того, что рассмотрим хранилище данных — виджет GtkListStore. Он представляет структуру данных похожую на таблицу (точно так же как таблица в базе данных) каждая строка это запись, запись состоит из данных (атомарных) различных типов. Каждая колонка задаёт свой тип данных. Колонки идентифицируются порядковым номером: первая колонка — 0, вторая колонка — 1, третья колонка — 2 и т.д.

    Обычно создают перечисление для более понятного обращения к колонкам хранилища:
    enum
    {
        NAME = 0,
        HEALTH,
        POWER,
        SELECTED,
        FONT_COLOR
    };
    

    Для начала открываем Glade (я для примера буду использовать версию 3.6.7 — из репозитория Ubuntu, хотя есть более новые версии, поддерживающие GTK+ 3.0). Создаём форму, слева в панеле объектов ищем Window и кладём на рабочую область. Слева на панелей свойств во вкладке General ищем Window Title и задаём его как: «Пример работы с GtkTreeView и GtkListStore», во вкладке Common задаём Width request и Height requst: 650 и 350 соответственно. Далее нужно будет положить на неё Horizontal Panes, в два отсека которых кладутся Scrolled Window. У Вас должно получится на данном шаге, как показано картинке ниже (кликабельно):

    image

    теперь идём в панель объектов нажимаем на Tree Model и ищем там List Store — наше будущее хранилище (виджет GtkLitStore).

    image

    По нажатию по этому виджету он автоматически появится в дереве виджетов справо во вкладке Objects. Нажмаем на него и смотрим на свойства. Отобразятся вот такие настройки, правда можно еще ниже прокрутить и там будет Add and remove rows, но нам это не нужно, т.к. данные будем вводить из контролов (которые сделаем чуть позже).

    image

    Во-первых, меняем имя на liststorePlayers. Во-вторых, в Add and Remove Columns нам нужно добавить следующие колонки: Имя (name) — строка, Здоровье (неalth) — целочисленный, Сила (power) — целочисленный, Флаг выделения (seleted) — логический и цвет шрифта (colortext) — тип цвета.

    Итак добавляем первую колонку имя — выбираем тип gchararray в Column type, а в Column name пишем name. Далее вторая колонка здоровье — в Column type пишем gint, в Column name health. Аналогично для power типа gint. Далее флаг выделение — выбираем тип gboolean, а в Column name selected. А далее… А далее нас ждёт небольшая неприятность. Нам нужен для цвета шрифта типа GdkColor (структура, используемая в GTK+ для представления цвета). Вы посмотрите на то большое количество возможных вариантов выбора типа, но GdkColor там нет. Видимо это недоработка разработчиков Glade и прийдёт добавить нам самим. Если попробуем вписать его туда — всё равно ничего не получится. Нужно вспомнить, что Glade сохраняет описание интефейса в XML файле, а следовательно его можно будет исправить. Значит, создаём еще одно поле типа gchararray, например, в Column type пишем colortext.

    image

    Сохраняем наше описание интерфейса с именем mainForm и открываем полученный файл. Ищем там GtkListStore.

    <object class="GtkListStore" id="liststorePlayers">
        <columns>
          <!-- column-name name -->
          <column type="gchararray"/>
          <!-- column-name health -->
          <column type="gint"/>
          <!-- column-name power -->
          <column type="gint"/>
          <!-- column-name selected -->
          <column type="gboolean"/>
          <!-- column-name colortext -->
          <column type="gchararray"/>
        </columns>
    </object>
    


    Меняем наш gchararray на GdkColor

    <object class="GtkListStore" id="liststorePlayers">
        <columns>
          <!-- column-name name -->
          <column type="gchararray"/>
          <!-- column-name health -->
          <column type="gint"/>
          <!-- column-name power -->
          <column type="gint"/>
          <!-- column-name selected -->
          <column type="gboolean"/>
          <!-- column-name colortext -->
          <column type="GdkColor"/>
        </columns>
    </object>
    


    Сохраняемся и открываем снова в Glade. Смотрим тип колонки и видим, что всё так, как нам и нужно:



    Настройка хранилища GtkListStore на этом завершена. Теперь нам необходимо сделать так, чтобы данные можно было отображать в более менее красивой форме. Для этого идём снова в панель объектов и ищем там Tree View — это виджет GtkTreeView.

    image

    Кладём его на левый scrolledwindow (у меня он называется scrolledwindow1, Вы можете вообще все объекты переименовывать, но я переименовываю только те, которые будут потом в коде использоваться, вообще желательного все переименовывать). После того как Вы взяли Tree View на панели объектов и щелкнув по левому scrolledwindow Вам вылетит сообщение о том, что следует указать TreeView Model.

    image

    В нашем случае TreeView Model это и есть наше хранилище — GtkListStore, а именно виджет liststorePlayers. Нажимаем ... и выбираем там liststorePlayers.



    Нажимаем OK для этого диалога и для предыдущего. Теперь нам необходимо настроить или правильнее будет сказать связать данные из хранилища с конкретного формой отображения их на экране. Нажимаем на treeview1 (а именно так он у Вас называется) на правой панеле объектов и идём в верхнее меню, где ищем Edit.

    image

    Откроется диалог редактирования нашего Tree View.

    image

    Во вкладке General находятся общие свойства для всего Tree View в целом. Во-первых, меняем Name на treeviewPlayers. А так же свойство Enable Grid Lines ставим в Horizontal and Vertical — отображение визуального разделения строк и колонок. Переходим во вкладку Hierarcy. В ней как раз мы и будем связать данные из хранилища с конкретной реализацией их отображения.

    image

    Немножко теории. Запомните, что колонки в List Store и колонки в Tree View — это абсолютно разные вещи и никакой связи между ними нет. Конкретные данные привязывают не к колонкам Tree View. Колонки Tree View больше служат каким-то логическо-визуальным разделением для выводимых данных. Для представления конкретных данных из хранилища используются объекты класса GtkCellRenderer.

    В нашем примере будет использоваться 4 различных типов объектов представления:
    GtkCellRendererText — обычный текст, для отображения name из хранилища;
    GtkCellRendererProgress — шкала выполнения, а если более просто — это обычный ProgressBar, с помощью него мы будем отображать health и power из хранилища;
    GtkCellRendererSpin — кнопка «карусель», с помощью неё мы сможем сможем изменять значение power прямо в Tree View;
    GtkCellRendererToggle — кнопку переключения в ячейке, c помощью неё будем отображать флаг выделения записи.

    Возвращаемся в Glade. Нажимаем кнопку Add и у нас появится одна колонка. Вообще можно колонок будет не добавлять в принципе. Одной хватит, но будет не очень красиво и не очень гибко в плане вывода. Поэтому сделаем следующим образом. У нас будет 4 колонки: Name, Health, Power, Seleted. Но в колонке Power мы выведем значение Power из хранилища и в GtkCellRendererSpin и в GtkCellRendererProgress.

    Нажмём правой клавишей по добавленной строке: column column и выберем там Add child Text item. В итоге у Вас должно получится следующее:

    image

    Тем самым мы добавили GtkCellRendererText — для отображения текстового содержимого. Начинаем изменять свойства, которые располагаются справа. В Name ставим название cellrenderertextName. Далее свойство Text. Там где у написано unset нажимаем на стрелочки и в выпадающем меню выбираем name — gchararray. Ноль означает, что привязана из хранилища (GtkListStore) колонка с номером 0, с хранящимися там данными (строка имени персонажа, которую мы задали в GtkListStore: name типа gchararray).

    image

    Прокручиваем список свойств вниз и ищем Background colour. Я предлагаю его задать жестко, т.е. для всех строк этого GtkCellRendererText будет одинаковый цвет фона. Для этого снимаем галочку и появится кнопка выбора цвета. Нажимаете и на неё и выбираете цвет, который Вам больше по душе. А вот ниже идёт свойство Foreground colour. Его я уже предлагаю брать из хранилища. Тем самым для каждого конкретной строки можно будет разное свойство цвета шрифта. Для этого нажимаем справо на unset и выбираем там colortext — GdkColor. И у Вас должна появится цифра 4. т.е. номер колонки из хранилища GtkListStore, где мы храним цвет. Так же необходимо сделать так, чтобы можно было редактировать имя прямо в Tree View. Для этого прокручиваем свойства еще ниже и ищем там Editable. Снимаем галочку и нажимаем кнопку, так чтобы появилось Yes. У Вас должно получится так:

    image

    Я думаю стало уже более менее понятно в чём суть привязвки. Когда у свойства стоит галочка значит данные берутся из хранилища. По умолчанию у всех свойств стоит -1, т.е. не из какой колонки хранилища GtkListStore ничего не берётся. Наша задача указать номер колонки хранилища откуда будут браться значения для свойства. Если мы хотим сами жестко что-то задать — снимаем галочку и настраиваем как хотим. Но тогда уже эти настройки никак не будут зависеть от данных в хранилище GtkListStore и будут применены абсолютно ко всем строкам данного объекта представления.

    Далее создаём еще одну колонку. Нажимаем на нашу первую колонку, она выделется и нажимаем Add. Добавится еще одна колонка. Если бы у нас был выделен наш GtkCellRendererText, то добавился бы еще один объект представления в первую колонку. Glade порой немного неудобен. Но ничего страшного.

    Теперь добавим объект представления GtkCellRendererProgress, где будем отображать Health — здоровье нашего персонажа с помощью ProgressBar. Нажимаем правой клавишей по новой колонке и выбираем Add child Progress item. Устанавливаем свойство Name в cellrendererprogressHealth, в Value выбираем health — gint, появится цифра 1 — значит данные будут браться из первой колонки хранилища GtkListStore.

    image

    Устанавливаем свойство Background colour. Вы можете выбрать такой же цвет как и для GtkCellRendererText на предыдущем шаге (я именно так и сделал) а можете выбрать свой цвет. Может получится небольшой баг, когда вы выберете цвет на кнопке, он отобразится, а в строке код цвета будет #000000000000. Не расстраивайтесь. Цвет задан нормально, после того как вы закроете диалог редактирования этого Tree View и снова откроете, всё будет нормально. Небольшой глюк Glade.

    image

    Теперь добавляем еще один column и в него вставляем GtkCellRendererSpin и GtkCellRendererProgress (точно так же как мы выше вставляли GtkCellRendererText). Для первого Add child Spin item, для второго Add child Progress item. Назовём их соответственно cellrendererspinPower и cellrendererprogressPower (свойство Name).



    Теперь временно необходимо закрыть окно Tree View Editor и создать для cellrendererspinPower виджет GtkAdjustment под названием adjCellRendererSpinPower. На панели объектов выбираем Adjustment, как показано на рисунке ниже и он отобразиться в дереве виджетов в Objects.



    В свойствах adjCellRendererSpinPower установим Maximum value: 100.00, Page Size: 0.00. Возвращаемся в режим редактирования нашего виджета GtkTreeView. Теперь необходимо определить adjCellRendererSpinPower для cellrendererspinPower. Снимаем галочку с Adjustment, нажимаем ... и выбираем adjCellRendererSpinPower.



    В Glade есть еще один нехороший баг. При новом открытии GladeXML файла с описанием интерфейса, который мы создаём сбрасывается вот этот самый флажок Adjustment. Не забывайте про это. Теперь устанавливаем другие свойства, начнём с Text. Выбираем из выпадаюшего списка power — gint, т.е. привязываем из хранилища колонка с номером 2 с данными, с данными о силе.



    а так же Editable делаем Yes:



    Еще необходимо установить, как и для прошлых объектов width: 60; Cell background color: #c0c0e5e5e7e7 (напоминаю, что у Вас может быть другой цвет) и Horizontal Padding: 5. Теперь для cellrendererprogressPower устанавливаем Text (отображаемый текст на шкале) и Value (значение шкалы) тоже в power — gint.



    И конечно же установливаем, как и для прошлых объектов width: 100; Cell background color: #c0c0e5e5e7e7 и Horizontal Padding: 5.

    Осталось добавить последний объект в GtkTreeView — cellrenderertoggleSelected — виджет типа Toggle (Add Toggle Item). Соотносим его свойство Toggle State с selected gboolean, с колонкой с номером 3 из хранилща с данными:



    Так же устанавливаем Cell background color: #c0c0e5e5e7e7.

    На последок необходимо подписать колонки, т.е. установать для всех Column свойство Title. В соответствующие значения: Name, Health, Power, Selected:



    На этом настройка GtkTreeView почти завершена. Теперь дерево виджетов будет выглядеть следующим образом:



    Далее необходимо прописать обработчики сигналов (событий) для некоторых виджетов GtkTreeView и для него самого. Нажимаем на виджет cellrenderertextName и переходим во вкладку Signals (сигналы), ищем там событие (я буду всё-таки сигналы называть как события) edited — редактирование текстового поля. С помощью выпадающего списка или в ручную пишем название обработчика этого события on_cellrenderertextName_edited, как показано на рисунке ниже:



    Точно так же делаем для виджета cellrendererspinPower для события edited обработчик on_cellrendererspinPower_edited — изменение значения с помощью «карусели». Для виджета cellrenderertoggleSelected ищем событие toggled — изменение состояния переключателя и выбираем название обработчика on_cellrenderertoggleSelected_toggled. И теперь нажимаем на сам наш виджет treeviewPlayers и ищем там во вкладке Signals row_activated. Пишем сами или выбираем название обработчика on_treeviewPlayers_row_activated.
    Хотя Вы в принципе можете как Вам больше нравится называть обработчики событий.Главное их так же как и в Glade указать далее в коде.

    Теперь необходимо добавить небольшую диалоговую панель, с помощью которой можно будет быстро добавлять новых персожей и вызывать другие манипуляции. Я не буду подробно описывать процесс создания этого меню, т.к. цель поста рассказать про GtkTreeView и GtkListStore, просто вкратце скажу основые шаги и покажу, что в итоге должно получиться.

    Сначала для scrolledwindow1 установим оба свойства Resize и Shrink в Yes (вкладка Packing) и Width request в 370 (вкладка Common). Для scrolledwindow2 свойство Resize в No, а Shrink в Yes, а Width request в 250. Далее добавим виджет GtkViewport в scrolledwindow2, а в него уже виджет GtkVBox(вертикальный контейнер), состоящий из 8 ячеек. Первые четыре будут представлять собой виджеты GtkHBox (горизонтальный контейнер), состоящие из GtkLabel (подписи) и контролов: entryName (GtkEntry), spinbuttonHealth (GtkSpinButton), spinbuttonPower (GtkSpinButton) и colorbutFontColor (GtkColorButton). Поясняю: необходимо добавить 4 GtkHBox, состоящих из 2 двух ячеек: одна для подписи (левая), другая для контрола (правая), перечисленных ранее.
    Четыре нижних это кнопки: butAdd, butDelete, butPrint, butQuit. Для всех виджетов внутри GtkVBox рекомендую установить во вкладке Packing свойства Expand и Fill в No (об этих свойствах рассказывается в моём предыдущем посте). Так же виджеты spinbuttonHealth и spinbuttonPower необходимо связать с соответствующими виджетами GtkAdjustment. Я их назвал adjHealth и adjPower. И установился соответствующие настройки:



    Связывание с spinbuttonHealth и spinbuttonPower происходит посредством выбора в их свойствах Adjustment созданных adjHealth и adjPower.

    Всё, что осталось сделать в Glade — это определить обработчики для кнопок: butAdd, butDelete, butPrint, butQuit. Нажимаем на каждый виджет и во вкладке Event для свойства clicked выбираем on_butAdd_clicked, on_butDelete_clicked, on_butPrint_clicked, on_butQuit_clicked.

    В итоге должно получиться следующее:



    А в целом всё окно нашего тренировочного приложения выглядит так:



    Теперь переходим к написанию кода. Весь код будет содержаться в файле Example1.cpp, хотя Вы можете назвать его как Вам больше нравится.

    #include <stdlib.h>
    #include <gtk/gtk.h>
    
    #define UI_FILE "mainForm.glade"
    


    Структура для работы с хранилищем, о которой было сказано в начале:
    enum
    {
        NAME = 0,
        HEALTH,
        POWER,
        SELECTED,
        FONT_COLOR
    };
    


    Протопипы обработчиков событий, названия должно быть такими, как мы указали во вкладе Signals для виджетов в редакторе Glade. Т.к. используется C++ компилятор, то указываем, что они должны связываться согласно порядку связывания в языке С.
    extern "C" void on_butQuit_clicked(GtkWidget *TopWindow, gpointer data);
    extern "C" void on_butAdd_clicked(GtkWidget *window, gpointer data);
    extern "C" void on_butDelete_clicked (GtkButton *remove, gpointer data);
    extern "C" void on_butPrint_clicked(GtkWidget *button, gpointer data);
    extern "C" void on_cellrendererspinPower_edited(GtkCellRendererText *render, gchar *path, gchar *new_text, gpointer data);
    extern "C" void on_cellrenderertoggleSelected_toggled(GtkCellRendererToggle *render, gchar *path, gpointer data);
    extern "C" void on_treeviewPlayers_row_activated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data);
    extern "C" void on_cellrenderertextName_edited(GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer data);
    


    Структура, содержащая указатели на виджеты, которые был созданы в Glade и будут получены из GladeXML в функции main.
    struct MainWindowObjects
    {
        GtkWindow      *topWindow;         
        GtkTreeView    *treeviewPlayers;
        GtkListStore   *liststorePlayers;
        GtkEntry       *entryName;
        GtkAdjustment  *adjHealth;
        GtkAdjustment  *adjPower;
        GtkColorButton *colorbutFontColor;
    } mainWindowObjects;
    


    В main получаем виджеты из GladeXML-формата. С помощью gtk_tree_selection_set_mode устанавливаем возможность выделения в GtkTreeView нескольких строк. Связываем наши обработчики событий используя функцию gtk_builder_connect_signals и заодно передаём указатели на виджеты с помощью ссылки mainWindowObjects. Тем самым, как вы увидите дальше с помощью указателя gpointer data можно будет обращаться ко всем виджетам из функции обработчика.
    int main(int argc, char** argv)
    {
        GtkBuilder *builder;
        GError *error = NULL;
        gtk_init( &argc, &argv );
    
        builder = gtk_builder_new();
    
        if( ! gtk_builder_add_from_file( builder, UI_FILE, &error ) )
        {
    	g_warning( "%s\n", error->message );
    	g_free( error );
    	return( 1 );
        }
    
        mainWindowObjects.topWindow = GTK_WINDOW(gtk_builder_get_object(builder, "topWindow"));
        mainWindowObjects.treeviewPlayers = GTK_TREE_VIEW( gtk_builder_get_object( builder, "treeviewPlayers" ) );
        mainWindowObjects.liststorePlayers = GTK_LIST_STORE( gtk_builder_get_object(builder, "liststorePlayers") );
        mainWindowObjects.entryName = GTK_ENTRY( gtk_builder_get_object(builder, "entryName") );
        mainWindowObjects.adjHealth = GTK_ADJUSTMENT( gtk_builder_get_object(builder, "adjHealth") );
        mainWindowObjects.adjPower = GTK_ADJUSTMENT( gtk_builder_get_object(builder, "adjPower") );
        mainWindowObjects.colorbutFontColor = GTK_COLOR_BUTTON( gtk_builder_get_object(builder, "colorbutFontColor") );
    
        GtkTreeSelection *selection;
        selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(mainWindowObjects.treeviewPlayers) );
        gtk_tree_selection_set_mode( selection, GTK_SELECTION_MULTIPLE );
    
        gtk_builder_connect_signals (builder, &mainWindowObjects);
        
        g_object_unref( G_OBJECT( builder ) );
        gtk_widget_show_all ( GTK_WIDGET (mainWindowObjects.topWindow) );
        gtk_main ();
    
    }
    


    Реализация обработчика добавления нового персонажа. gtk_list_store_append — добавляет новую строку в модель (хранилище) и возвращает iter — ссылку на неё (или еще это называют итератором, указывающим на строку). А функция gtk_list_store_set устанавливает значения ячеек в строке, на которую указывает iter. Значения указываются с помощью перечисления: NAME, HEALTH и т.д. Признак окончания это -1.
    void on_butAdd_clicked(GtkWidget *button, gpointer data)
    {
        MainWindowObjects* mwo = static_cast<MainWindowObjects*>( data );
        GtkTreeIter iter;
        GdkColor color;
        gtk_color_button_get_color( mwo->colorbutFontColor, &color );
        gtk_list_store_append(GTK_LIST_STORE( mwo->liststorePlayers ), &iter);
        gtk_list_store_set(GTK_LIST_STORE( mwo->liststorePlayers ), &iter, 
    		       NAME, gtk_entry_get_text(mwo->entryName),
                           HEALTH, static_cast<int>( gtk_adjustment_get_value(mwo->adjHealth) ),
                           POWER, static_cast<int>( gtk_adjustment_get_value(mwo->adjPower) ),
                           SELECTED, false,
                           FONT_COLOR, &color,
                           -1 );
    }
    


    Реализация обработчика события редактирования ячейки с именем. С помощью функции gtk_tree_view_get_model получаем модель (хранилище), связанную с нашим GtkTreeView. Используя функцию gtk_tree_model_get_iter_from_string получаем ссылку на строку из пути path (который находится в сигнатуре функции, для данного представления хранилища этот путь — просто номер строки, которую изменили), а дальше с помощью уже знакомой нам gtk_list_store_set устанавливаем новое значение, которое находится в new_text. Вообще сигнатура данного обработчика (как и остальных обработчиков) взята из официальной документации по Gtk+.
    Т.е. для каждого виджета можно найти там сигнатуру события (или как там пишут signal — сигнатуру сигнала).
    void on_cellrenderertextName_edited(GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer data)
    {
        MainWindowObjects* mwo = static_cast<MainWindowObjects*>( data );
        if ( g_ascii_strcasecmp(new_text, "") != 0 )
        {
            GtkTreeIter iter;
            GtkTreeModel *model;
            model = gtk_tree_view_get_model (mwo->treeviewPlayers);
            if (gtk_tree_model_get_iter_from_string(model, &iter, path) )
                gtk_list_store_set(GTK_LIST_STORE (model), &iter, NAME, new_text, -1 );
        }
    }
    


    Реализация обработчика события редактирвоания с помощью кнопки «карусель». Оно почти аналогично описанному выше. За исключением того, что нужно новое значение преобразовать в int. Для простоты я использовал старую Си-шную функцию atoi, хотя можно было реализовать это, используя stringstream.
    void on_cellrendererspinPower_edited(GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer data)
    {
        MainWindowObjects* mwo = static_cast<MainWindowObjects*>( data );
        if ( g_ascii_strcasecmp(new_text, "") != 0 )
        {
            GtkTreeIter iter;
            GtkTreeModel *model;
            model = gtk_tree_view_get_model (mwo->treeviewPlayers);
            if (gtk_tree_model_get_iter_from_string(model, &iter, path) )
                gtk_list_store_set(GTK_LIST_STORE (model), &iter, POWER, atoi(new_text), -1 );
        }
    }
    


    Реализация обработчика нажатия по ячейке с переключателем (флагом) — 4я колонка. Получаем с помощью gtk_tree_model_get текущение состояние переключателя и устанавливаем противоложное: !selected
    void on_cellrenderertoggleSelected_toggled(GtkCellRendererToggle *render, gchar *path, gpointer data)
    {
        MainWindowObjects* mwo = static_cast<MainWindowObjects*>( data );
        GtkTreeIter iter;
        GtkTreeModel *model;
        gboolean selected;
    
        model = gtk_tree_view_get_model ( mwo->treeviewPlayers );
        if (gtk_tree_model_get_iter_from_string(model, &iter, path) )
        {
            gtk_tree_model_get( model, &iter, 3, &selected, -1 );
            gtk_list_store_set( GTK_LIST_STORE (model), &iter, SELECTED, !selected, -1 );
        }
    }
    


    Реализация метода печати выделенных строк. С помощью функции gtk_tree_model_get_iter_first получаем ссылку на первую строку и заодно проверяем: не пусто ли хранилище (флаг reader). С помощью метода gtk_tree_model_get получаем интересующие нас значения ячеек для текущей строки, на которую «смотрит» iter. К следующей строке переходим с помощью gtk_tree_model_iter_next.
    void on_butPrint_clicked(GtkWidget *button, gpointer data)
    {
        MainWindowObjects* mwo = static_cast<MainWindowObjects*>( data );
        GtkTreeIter   iter;
    
        gboolean reader = gtk_tree_model_get_iter_first(GTK_TREE_MODEL( mwo->liststorePlayers ), &iter);
        g_print( "Selected rows:\n" );
        while ( reader )
        {
            gboolean selected;
            gtk_tree_model_get (GTK_TREE_MODEL( mwo->liststorePlayers ), &iter, 
    			SELECTED, &selected, -1);
            if ( selected )
            {
                gchar* name;
                gint health;
                gint power;
                gtk_tree_model_get (GTK_TREE_MODEL( mwo->liststorePlayers ), &iter, 
    				NAME, &name,
    				HEALTH, &health,
                                    POWER, &power,
                                    -1);
                g_print( "%s %d %d\n", name, health, power );
            }
            reader = gtk_tree_model_iter_next(GTK_TREE_MODEL( mwo->liststorePlayers ), &iter);
        }
    }
    


    Реализация метода, обрабатывающего двойной щелчок по строке в GtkTreeView. С помощью функции gtk_tree_model_get_iter получаем iter из path, а потом получаем интересующие нас данные с помощью gtk_tree_model_get и выводим их в консоль.
    void on_treeviewPlayers_row_activated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data)
    {
        GtkTreeModel *model;
        GtkTreeIter   iter;
        model = gtk_tree_view_get_model( treeview );
        if ( gtk_tree_model_get_iter(model, &iter, path) )
        {
            gchar* name;
            gint health;
            gint power;
            gtk_tree_model_get(model, &iter, 
    			   NAME, &name,
                               HEALTH, &health,
                               POWER, &power,
                               -1);
            g_print( "Current row: %s %d %d\n", name, health, power);
        }
    }
    


    Данная функция является вспомогательной, для удаления выделенных строк в GtkTreeView. gtk_list_store_remove — удаляет данную строку из списка, на которую ссылается iter. Перед этим извлекаем path с помощью gtk_tree_row_reference_get_path, а потом преобразуем path в iter с помощью gtk_tree_model_get_iter.
    static void remove_row (GtkTreeRowReference *ref, GtkTreeModel *model)
    {
        GtkTreeIter iter;
        GtkTreePath *path;
        path = gtk_tree_row_reference_get_path (ref);
        gtk_tree_model_get_iter (model, &iter, path);
        gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
    }
    


    С помощью функции gtk_tree_selection_get_selected_rows получаем список выделенных строк, потом с помощью цикла while проходим по каждой выделенной строке и создаем ссылку на основе пути с помощью функции gtk_tree_row_reference_new и добавляем эту ссылку в список references с помощью g_list_prepend.
    Вызываем функцию remove_row для каждой ссылки, с помощью g_list_foreach. И в конце, аналогично с помощью g_list_foreach освобождаем наши списки.
    void on_butDelete_clicked (GtkButton *remove, gpointer data)
    {	
        MainWindowObjects* mwo = static_cast<MainWindowObjects*>( data );
        GtkTreeSelection *selection;
        GtkTreeRowReference *ref;
        GtkTreeModel *model;
        GList *rows, *ptr, *references = NULL;
        selection = gtk_tree_view_get_selection ( mwo->treeviewPlayers );
        model = gtk_tree_view_get_model ( mwo->treeviewPlayers );
        rows = gtk_tree_selection_get_selected_rows (selection, &model);
    
        ptr = rows;
        while (ptr != NULL)
        {
            ref = gtk_tree_row_reference_new (model, (GtkTreePath*) ptr->data);
            references = g_list_prepend (references, gtk_tree_row_reference_copy (ref));
            gtk_tree_row_reference_free (ref);
            ptr = ptr->next;
        }
        
        g_list_foreach ( references, (GFunc) remove_row, model );
        g_list_foreach ( references, (GFunc) gtk_tree_row_reference_free, NULL );
        g_list_foreach ( rows, (GFunc) gtk_tree_path_free, NULL );
        g_list_free ( references );
        g_list_free ( rows );
    }
    


    Реализация обработчика закрытия GTK+ приложения
    void on_butQuit_clicked(GtkWidget *window, gpointer data)
    {
        gtk_main_quit();
    }
    


    Для компиляции необходимо создать makefile:
    CC=g++
    LDLIBS=`pkg-config --libs gtk+-2.0 gmodule-2.0`
    CFLAGS=-Wall -g `pkg-config --cflags gtk+-2.0 gmodule-2.0`
    
    example1: example1.o
    	$(CC) $(LDLIBS) example1.o -o example1
    
    example1.o: example1.cpp
    	$(CC) $(CFLAGS) -c example1.cpp
    
    clean:
    	rm -f example1
    	rm -f *.o
    


    Теперь можно скомпилировать данный пример и попробовать «поиграться» с тем, что мы сделали. Советую запускать из консоли дистрибутива вашего дистрибутива Linux. Попробуйте добавить несколько персонажей, выбирая разные цвета их имени. Попробуйте выделить в 4ой колонке несколько строк и нажать Print, тогда в консоле напечатаются выделенные. Попробуйте изменить значение Power с помощью «карусели» (двойной щелчок по ней, левее Progress bar'a). Попробуйте с помощью зажатой клавишы Ctrl или Shift выделить несколько добавленных строк и удалить их, нажав Delete.

    Данный пример можно скачать отсюда.

    Компиляция происходит командой:
    $ make
    


    Запуск:
    $ ./example1
    


    Я постарался максимально подробно описать многие вещи (некоторые может быть даже слишком), но всё это сделано для более быстрого понимания данной темы.
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 19
    • +3
      У новичка в Gtk (в лице меня) возникло желание повторить все написанное.
      • +2
        Смотрел по диагонали (ибо GTK+ не знаю и не планировал изучать) и возникло три вопроса — 1) почему не выровнять name, health, power, text color? В какой-нибудь layout. Смотрится неаккуратно. 2) что за жуткая мода прикручивать нестандартные цвета наполовину. Нужно либо следовать исключительно системной палитре, либо полностью свою применять. Вот Вы поставили голубой, а у пользователя тёмная тема и голубой — стандартный цвет шрифта — и он же не видит никакого power. 3) в GTK+ цвет кодируется по 16 бит на канал?
        • 0
          Ну голубой фон — просто для того, что показать как это делается. Ето же пример для обучения, поэтому тут используется как можно больше всего.
          • +1
            Ну, я сторонник, что даже в примерах стоит показывать «как надо делать», а не «как не надо делать», поэтому и придираюсь :) Просто не раз сталкивался, когда авторы программ начинают делать кастомизацию приложений и не учитывают работу в нестандартных условиях.
          • +3
            Спасибо большое за вопросы
            1) Я пока не стал усложнять пример (потому что считаю, что и так много всего накручено), целью было как раз показать как работать с GtkTreeView и GtkListStore. Можно попробовать использовать тот же горизонтальный контейнер GtkHBox (кому интересно могут попробовать и посмотреть что получится). Вообще про слои в GTK+ можно написать отдельный огромный пост, там много интересного и не так всё просто как кажется. Будет время займусь обязательно этим.
            2) Вы абсолютно правы, но я к сожалению пока не нашел способа как это задать в Glade. возможно прийдётся опять какой-то хак делать в нём. Если кто-то мне подскажет, буду рад.
            3) Да, цвет кодируется 16 бит на канал и представляется вот в такой структуре:
            typedef struct {
            guint32 pixel;
            guint16 red;
            guint16 green;
            guint16 blue;
            } GdkColor;

            • +1
              А чего такое guint32 pixel; в этой структуре?
              • +2
                Это поле обычно рекомендуют игнорировать, этот параметр служит для отрисовки конкретного писеля на экране. Если попробовать проследить за ним, например отладчике или вывести в лог, то каждый раз он будет разный.
              • +1
                Спасибо за ответы :) ИМХО, первый пункт действительно стоило бы раскрыть — если не здесь, то в отдельном топике.
            • +3
              Дождались :) Спасибо за труд
              • 0
                Я ни коим образом не специалист по GTK+, но я прочитал по диагонали этот и предыдущий ваши посты по GTK+.

                Там тоже задавали этот вопрос, но внятный ответ так и не был получен.

                Поэтому спрошу ещё раз здесь: почему вы используете C++, а не C?
                • +3
                  Я показываю как можно в С++ проект добавить использование библиотеки пользовательских интерфейсов GTK+, как раз одним из её плюсов и является, что можно писать на большим количестве языков. Также мне как-то ближе С++, чем C. Да, есть специальная обёртка gtkmm для того чтобы писать на C++ и она как раз заточена для этого, но чтобы лучше понять GTK+ я всё же считаю учиться на «чистом» GTK+, а потом уже перейти, например, на gtkmm или PyGTK (обёртка для питона).
                  • +1
                    Также мне как-то ближе С++, чем C.
                    Я насчитал только три вещи из C++, которые здесь используются:
                    extern "C"
                    static_cast<>
                    false (тип bool)

                    Сомнительная мотивация только из-за них использовать C++.

                    В остальном больше вопросов нет. И я бы с радостью почитал статью про gtkmm.
                    • 0
                      bool есть в Си (stdbool.h)
                      • 0
                        О. Спасибо, буду знать. Википедия говорит, что он появился начиная с C99.
                        • 0
                          Так и есть. Но по-моему все уже используют C99 хотя бы из-за for(type var = ...;; )
                • 0
                  Отличная статья! Обязательно попробую повторить все вышеизложенное! :]
                  • +1
                    Спасибо за подробный туториал.

                    У меня вопрос несколько не по теме, но просто к знатоку GTK. В Exaile ведь используются именно эти виджеты?

                    Вопрос такой. Если текст в колонке не умещается в ее ширину, то он отображается с многоточием в конце. Это понятно. А чтобы при наведении на него мышкой появлялось всплывающая подсказка с полным текстом, какие нужно сделать телодвижения? По умолчанию этого не происходит?

                    Для сравнения, в Windows в системных TreeView и ListView это по умолчанию есть, и следовательно работает во всех программах одинаково (если только разработчик сам это не запрещает). А в Линуксе, сколько встречаю программ на GTK, где-то работает, но в большинстве нет. Что порой очень расстраивает.
                    • 0
                      Да в Exaile используются эти виджеты — виджеты библиотеки GTK+.
                      По поводу Вашего вопроса. Я поискал решение и могу Вам предложить вот такой вариант. Если интефейс спроектирован в редакторе Glade, то нажимайте на GtkTreeView идёте в свойства, вкладка General. Там в самом низу будет свойство Tooltip column



                      и там нужно поставить номер колонки из хранилища GtkListStore (который как раз я в посте описывал) данные из которой для данной строки Вы хотите чтобы появлялись во всплывающей подсказке при наведении на строку.



                      Я подумаю еще над этим вопросом. Мне тоже интересно. Если интерфейс спроектирован не в Glade я тоже подумаю как сделать, там с помощью методов я уверен.
                      • 0
                        По-моему это не то, о чем писал я. Я о том, чтобы для любой колонки в tooltip'е отображался текст из этой же колонки.

                        Раз вы меня сразу не поняли, скорее всего такого в GTK+ нет. :) Во всяком случае по умолчанию. Печально.

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