Отправляет email-рассылки с помощью сервиса Sendsay

Практические советы по GTK+

  Все выпуски  

Практические советы по GTK+ (Создание списков. часть 2)


 Создание списков  часть 2

В предыдущей часть был создан простой список с сортировкой.
Как и обещал, на этот раз усложним список. В колонке "Имя" теперь слева от имени будет иконка, показывающая пол человека.
Напомню, что в качестве примера пишем небольшое приложение,показывающее список людей. В списке три столбца: порядковый номер, имя и возраст. Теперь в колонке с именем будет ещё и иконка. Для этого придётся создать новый тип поля: картинка+текст, назовём его PixtextItemId. Для этого нестандартного типа придётся писать свои процедуры обработки, копирования, сортировки.

Далее представлен полный исходный код приложения. Комментарии в конце.
Необходимые картинки приложены, если есть желание, можно взять любые другие (2 шт.)
Функция создания виджета со списком: GtkWidget *create_treeview()
В основной функции main() создаётся окно и в него упаковывается созданный список.

#include <gtk/gtk.h>
#include <string.h>
#include <stdio.h>

// Массивы для отображения в списках
// Список людей
static char* user_names[]={"Василий","Ольга","Степан","Татьяна","Костя","Михаил","Тамара","Юля","Катя","Колян","Михалыч","Алексей"};
// Список полов людей (мужской-1, женский-0)
static gchar user_sexes[]={1,0,1,0,1,1,0,0,0,1,1,1};
// Список возрастов людей
static int user_ages[]={34,21,53,67,7,23,46,32,17,73,97,33};


// описание нового типа колонки - StockItemId (картинка + текст)
typedef struct _PixtextItemId PixtextItemId;
// для новой колонки понадобятся картинка, текстовое поле и пол
struct _PixtextItemId
{
    GdkPixbuf    *item_icon;
    gchar        *item_text;
    guchar        item_sex;
};

// новый тип колонки - картинка + текст
#define PIXTEXT_ITEM_ID_TYPE pixtext_item_id_get_type()

static void pixtext_item_id_free (PixtextItemId *id)
{
    if (id->item_icon)    g_object_unref (id->item_icon);
    if (id->item_text)    g_free(id->item_text);
    g_free(id);
}

static PixtextItemId* pixtext_item_id_copy(PixtextItemId *src)
{
    PixtextItemId *id;
    id = g_new (PixtextItemId, 1);
    id->item_sex = src->item_sex;
    id->item_text = g_strdup(src->item_text);
    id->item_icon = src->item_icon;
    if (id->item_icon)    g_object_ref (id->item_icon);
    return id;
}

static GType pixtext_item_id_get_type (void)
{
    static GType our_type = 0;
    // регистрация нового типа (только при первом вызове)
    if (our_type == 0)
        our_type = g_boxed_type_register_static ("PixtextItemId",(GBoxedCopyFunc) pixtext_item_id_copy,(GBoxedFreeFunc) pixtext_item_id_free);
    return our_type;
}

// как отображать первую половину колонки - картинку
static void macro_set_func_icon (GtkTreeViewColumn *tree_column,GtkCellRenderer *cell,GtkTreeModel *model,GtkTreeIter *iter,gpointer data)
{
    PixtextItemId *id;
    short column_num =(short)data;
    gtk_tree_model_get (model, iter,column_num, &id,-1);
    g_object_set (GTK_CELL_RENDERER (cell),"pixbuf", id->item_icon,NULL);
    pixtext_item_id_free (id);
}

// как отображать вторую половину колонки - текст
static void macro_set_func_text (GtkTreeViewColumn *tree_column,GtkCellRenderer *cell,GtkTreeModel *model,GtkTreeIter *iter,gpointer data)
{
    PixtextItemId *id;
    short column_num =(short)data;
    gtk_tree_model_get (model, iter,column_num, &id, -1);
    g_object_set(GTK_CELL_RENDERER (cell),"text", id->item_text,NULL);
    pixtext_item_id_free(id);
}

// Функция сортировки в столбце картинка + текст
static gint iter_compare_func (GtkTreeModel *model, GtkTreeIter  *a,GtkTreeIter  *b,gpointer   user_data)
{
    // номер колонки, передаётся в дополнительном параметре
    short column_num =(short)user_data;
    PixtextItemId *a_id;
    PixtextItemId *b_id;
    const gchar *a_name, *b_name;
    gint retval = 0;
    // получаем содержимое колонки column_num в a_id и b_id
    gtk_tree_model_get (model, a,column_num, &a_id, -1);
    gtk_tree_model_get (model, b,column_num, &b_id, -1);
    // Сортировка по признаку пола
    if(a_id->item_sex>b_id->item_sex)
        retval = 1;
    else
    if(a_id->item_sex<b_id->item_sex)
        retval =-1;
    else
    // если пол одинаков - приступаем к сортировке строк (имён)
    //if(a_id->item_sex==b_id->item_sex)
    {
        a_name = a_id->item_text;
        b_name = b_id->item_text;
        // сортируем только строки, иконки не трогаем
        retval = strcmp (a_name, b_name);
    }
    // чистим память
    pixtext_item_id_free (a_id);
    pixtext_item_id_free (b_id);
    return retval;
}

// Создание списка
GtkWidget *create_treeview()
{
    PixtextItemId id;
    GdkPixbuf *pixbuf_man   = gdk_pixbuf_new_from_file("man.png", NULL);
    GdkPixbuf *pixbuf_woman = gdk_pixbuf_new_from_file("woman.png", NULL);
    int i, i_max = sizeof(user_names)/sizeof(char*);// сколько всего записей в таблице
    GtkTreeView        *treeview_users;
    GtkListStore    *store;
    GtkTreeIter        iter;
    /* Создаём модель списка.
    У нас будет три столбца - первый типа INT (порядковый номер), второй - новый тип PIXTEXT_ITEM_ID_TYPE и третий - число(возраст) */
    store = gtk_list_store_new(3,G_TYPE_INT,PIXTEXT_ITEM_ID_TYPE,G_TYPE_INT);
    // Заполняем список
    for (i = 0; i < i_max; i++)
    {
        // пол
        id.item_sex = user_sexes[i];
        // картинка (зависит от пола)
        if(user_sexes[i]==1)    id.item_icon = pixbuf_man;
        else                    id.item_icon = pixbuf_woman;
        // текст (Имя)
        id.item_text = user_names[i];

        // добавляем строчку в список
        gtk_list_store_append (store, &iter);
        // заполнем добавленню строчку в списоке
        gtk_list_store_set (store, &iter,
            0, i+1,// порядковый номер
            1,&id,// картинка+имя
            2, user_ages[i],// возраст
            -1);// последний элемент д.б. -1
    }
    g_object_unref(pixbuf_man);
    g_object_unref(pixbuf_woman);

    // создать список(treeview) согласно модели store
    treeview_users = (GtkTreeView*)gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
    gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (treeview_users), TRUE);// разделить строки по цвету;
    // Создать колонки в списке
    {
        short column_number = 0;// номер текущей колонки
        GtkCellRenderer *renderer;
        GtkTreeViewColumn *column;
        // колонка - Порядковый номер;
        renderer = gtk_cell_renderer_text_new();
        column = gtk_tree_view_column_new_with_attributes ("№",renderer,"text",column_number,NULL);
        gtk_tree_view_append_column (treeview_users, column);// вставляем колонку в список;
        gtk_tree_view_column_set_sort_column_id (column, column_number);// колонка сортируется;
        // выравнивание колонки по центру
        GTK_CELL_RENDERER (renderer)->xalign = 0.5;
        column_number++;
       
        // колонка - Имя (pixbuf+текст);
        column = gtk_tree_view_column_new();// создать колонку
        gtk_tree_view_column_set_title(column,"Имя");// заголовок колонки
        // первая часть колонки - картинка
        renderer = gtk_cell_renderer_pixbuf_new();
        gtk_tree_view_column_pack_start (column,renderer,FALSE);// добавляем ячейку в колонку
        gtk_tree_view_column_set_cell_data_func (column, renderer,macro_set_func_icon,(gpointer)column_number, NULL);// как отображать ячейку
        // вторая часть колонки - текст
        renderer = gtk_cell_renderer_text_new();
        gtk_tree_view_column_pack_start (column,renderer,FALSE);// добавляем ячейку в колонку
        gtk_tree_view_column_set_cell_data_func (column, renderer,macro_set_func_text,(gpointer)column_number, NULL);// как отображать ячейку
        // вставляем колонку в список;
        gtk_tree_view_append_column (treeview_users, column);
        gtk_tree_view_column_set_sort_column_id (column, column_number);// колонка сортируется;
        // подключаем функцию сортировки
        gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(store),column_number,iter_compare_func,(gpointer)column_number,NULL);
        column_number++;
       
        // колонка - Возраст;
        renderer = gtk_cell_renderer_text_new ();
        column = gtk_tree_view_column_new_with_attributes ("Возраст",renderer,"text",column_number,NULL);
        gtk_tree_view_append_column (treeview_users, column);// вставляем колонку в список;
        gtk_tree_view_column_set_sort_column_id (column, column_number);// колонка сортируется;
        // выравнивание колонки по правому краю
        GTK_CELL_RENDERER (renderer)->xalign = 1;
        column_number++;
    }
    return GTK_WIDGET(treeview_users);
}

gint main (gint argc, gchar **argv)
{
    GtkWidget *window;
    GtkWidget *treeview;

    gtk_init (&argc, &argv);
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window),"Список");
    gtk_signal_connect( GTK_OBJECT(window),"delete_event",GTK_SIGNAL_FUNC(gtk_false),NULL);
    gtk_signal_connect( GTK_OBJECT(window),"destroy",GTK_SIGNAL_FUNC(gtk_main_quit), NULL);

    treeview = create_treeview();
    gtk_container_add (GTK_CONTAINER (window),treeview);
    gtk_widget_show_all (window);
    gtk_main ();
    return 0;
}

 

Комментарии к исходнику:
Создание колонок в списке и заполнение списка можно безболезненно поменять местами.

При подключении функции сортировки для колонки "Имя" через gtk_tree_sortable_set_sort_func() в качестве дополнительного параметра передаём номер текущего столбца column_number (у нас всегда будет равен 1, счёт идёт от 0). Сортировка выполняется в подключенной функции iter_compare_func(). Сначала идёт сортировка по полу, а если пол одинаков, то далее сравниваются имена. При желании пол можно проигнорировать и сравнивать только имена.

Ещё было добавлено выравнивание первой колонки по центру, а последней по правому краю. Поскольку функции для этой операции нет, пришлось лезть напрямую в свойства класса GtkCellRenderer, ответственного за отображение колонки на экране.
GTK_CELL_RENDERER(renderer)->xalign = 0 ... 1;// 0-по левому краю, 1-по правому

Содержимое любого поля всегда можно узнать через: gtk_tree_model_get(model,&iter,column_num,&value,-1);
Только значение value нужно будет удалить после использования через g_value_unset(&value).
value - полученное значение поля, может быть числом, строкой или указателем на структуру, как у нас в средней колонке.
column_num - номер колонки (у нас 0-2)
GtkTreeIter iter; - указатель на строку.
К примеру, iter первой строки в списке можно узнать через gtk_tree_model_get_iter_first(), а последующие через gtk_tree_model_iter_next().
Если нужно узнать выбранную пользователем строку, то можно поступить так:
gboolean get_selected_iter(GtkTreeView *tree_view,GtkTreeIter *iter)
{
 GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view);
 if (gtk_tree_selection_get_selected (selection,NULL,iter))
    return TRUE;
 return FALSE;
}


Обработку выбора элемента в списке пользователем намеренно не включил для упрощения кода, но сделать это можно следующим образом:
Подключение обработчика:
GtkTreeSelection  *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
g_signal_connect (sel, "changed",G_CALLBACK (list_changed),NULL);

Примерная функция обработчика:
static void list_changed(GtkTreeSelection *sel,  gpointer user_data)
{
  GtkTreeIter iter;
  GtkTreeModel *model;
  if (gtk_tree_selection_get_selected(sel,&model, &iter))
  {
    gint value_int;
    gint column = 0;// требуемый номер колонки
    GValue val = { 0, };
    gtk_tree_model_get_value(model,&iter,column,&val);
    // узнаём значение числа в первом(т.е. нулевом) столбце выбранной строки
    value_int = g_value_get_int (&val);
    g_value_unset(&val)
  }
}


Требуемые картинки для проекта:

man man.png  

woman woman.png

 

Скриншот приложения из ОС Windows, где список отсортирован по имени(т.е. сначала по полу, а потом по имени):

list_sample2


В заключение хочу добавить, что в качестве полей можно использовать:
GtkCellRendererText - текст
GtkCellRendererPixbuf - картинкa
GtkCellRendererToggle - чек-бокс(галочка)
GtkCellRendererCombo - комбо-бокс (выпадающий список)
GtkCellRendererProgress - прогресс-бар (индикатор выполнения)
GtkCellRendererSpin - spin button (цифровое поле со стрелками вверх и вниз)
А также их комбинации в любом виде.
Можно, конечно, добавить любой другой виджет, но тогда придётся работать напрямую с GtkCellRenderer.

Также ячейки могут быть редактируемыми, поможет в этом интерфейс GtkCellEditable


В избранное