Progmar Marcin Załęczny

Jak utworzyć prostą listę na bazie widgetu GtkTreeView

W tym samouczku stworzymy najbardziej podstawowy program demonstrujący widget GtkTreeView w akcji. W tym celu otwórz program glade i utwórz następujący interfejs użytkownika:

Przy dodawaniu widoku drzewa wyskoczy okienko dialogowe Tworzenie GtkTreeView. Pole Model TreeView pozostawiamy puste i wciskamy przycisk Utwórz (model utworzymy i wypełnimy w kodzie programu):

A oto kod programu:


#include <gtk/gtk.h>
enum {
    COL_NAME = 0,
    COL_AGE,
    NUM_COLS
};
GtkBuilder *builder;



static GtkTreeModel *create_and_fill_model(void) {
    GtkListStore *store;
    GtkTreeIter iter;
    
    store = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_UINT);
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter, COL_NAME, "Jan Kowalski", COL_AGE, 21, -1);

    gtk_list_store_append(store, &iter);
    gtk_list_store_set (store, &iter, COL_NAME, "Piotr Nowak", COL_AGE, 43, -1);

    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter, COL_NAME, "Paweł Szczepanek", COL_AGE, 91, -1);
    
    return GTK_TREE_MODEL(store);
}

static GtkWidget *init_view_and_model(void) {
    GtkCellRenderer *renderer;
    GtkTreeModel *model;
    GtkWidget *treeview;
    
    treeview = GTK_WIDGET(gtk_builder_get_object(builder, "TreeView"));

    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1, "Nazwisko", renderer, "text", COL_NAME, NULL);

    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1, "Wiek", renderer, "text", COL_AGE, NULL);
    
    model = create_and_fill_model();
    gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), model);
    g_object_unref(model);

    return treeview;
}


int main(int argc, char **argv) {
    GtkWidget *window;
    GtkWidget *treeview;
    GError *error;
    
    gtk_init(&argc, &argv);
    
    builder = gtk_builder_new();
	error = NULL;
	gtk_builder_add_from_file(builder, "02_prosta_lista.ui", &error);
	if (error) {
		g_print("Wystąpił błąd: %s\n", error->message);
		g_error_free(error);
		return -1;
	}
    
    window = GTK_WIDGET(gtk_builder_get_object(builder, "MainWindow"));
    g_signal_connect(window, "delete_event", gtk_main_quit, NULL);
    
    init_view_and_model();

    gtk_widget_show_all(window);
    gtk_main();

    return 0;
}

Kompilujemy go poleceniem:
g++ -o  prosta_lista  prosta_lista.cpp `pkg-config --cflags --libs gtk+-3.0`

Jak widzimy, w tworzeniu kontrolki GtkTreeView wykorzystywane są następujące komponenty: model, iterator, renderer, kolumna i widok. Dzieje się tak dlatego gdyż GtkTreeView stosuje wzorzec projektowy MVC (Model / View / Controller) a więc odseparowuje dane od sposobu prezentowania ich na ekranie. Dane rozmaitych typów (łańcuchy znaków, liczby, obrazki, itp.) są przechowywane w modelu. Następnie widok określa, które dane ma wyświetlić, gdzie ma je wyświetlić i jak ma je wyświetlić. Zaletą takiego podejścia jest fakt, że te same dane można wykorzystać do wyświetlenia w wielu widokach. Jeśli dane w modelu zostaną zaktualizowane, wszystkie widoki również zostaną zaktualizowane.

Przystąpmy teraz do analizy kodu przykładowego programu. W metodzie main inicjalizujemy bibliotekę gtk, wczytujemy interfejs użytkownika oraz inicjalizujemy i wypełniamy danymi kontrolkę GtkTreeView. Cała esencja kodu znajduje się w funkcjach init_view_and_model() oraz create_and_fill_model(). Pierwsza z nich odpowiada za utworzenie kolumn oraz zdefiniowanie jakie dane i w jaki sposób będą w nich wyświetlane. W drugiej natomiast tworzymy nowy model i wypełniamy go danymi.

Zacznijmy od sposobu utworzenia magazynu danych w funkcji create_and_fill_model(). Ponieważ chcemy utworzyć dane prezentowane w postaci listy wartości, używamy zmiennej typu GtkListStore* oraz tworzącej ją funkcji gtk_list_store_new:


store = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_UINT);

Funkcja ta w pierwszym parametrze przyjmuje ilość kolumn dla jednego wiersza magazynu danych a następnie listę wartości określających typ przechowywanej wartości w każdej z tych kolumn. Dostępne są następujące typy:

  • G_TYPE_BOOLEAN - wartość logiczna TRUE lub FALSE,
  • G_TYPE_INT , G_TYPE_UINT - wartość liczbowa ze znakiem lub bez znaku,
  • G_TYPE_LONG, G_TYPE_ULONG , G_TYPE_INT64 , G_TYPE_UINT64 - wartość liczbowa długa (64-bitowa) ze znakiem lub bez znaku,
  • G_TYPE_FLOAT , G_TYPE_DOUBLE - zmiennoprzecinkowa wartość liczbowa,
  • G_TYPE_STRING - łańcuch znaków (tworzy kopię oryginalnego łańcucha),
  • G_TYPE_POINTER - wskaźnik na właściwe dane (nie tworzy kopii danych w magazynie danych, przechowuje tylko wartość typu gpointer),
  • GDK_TYPE_PIXBUF - przechowuje wartość typu GdkPixbuf (zwiększa o jeden liczbę referencji do piksmapy).

Dane do magazynu danych dodajemy przy pomocy poniższych dwóch funkcji:


    gtk_list_store_append(store, &iter);
    gtk_list_store_set(store, &iter, COL_NAME, "Jan Kowalski", COL_AGE, 21, -1);

gtk_list_store_append() dodaje nowy wiersz do magazynu danych i w zmiennej iter typu GtkTreeIter przechowuje wskaźnik na ten wiersz. Druga funkcja gtk_list_store_set() wypełnia nowo dodany wiersz danymi. Jako pierwsze dwa argumenty tej funkcji przekazujemy wskaźniki do magazynu danych oraz do nowo dodanego wiersza. W następnej kolejności przekazujemy parami dane, które chcemy ustawić. Pierwszą wartością w takiej parze jest numer kolumny natomiast drugą właściwe dane, które chcemy w tej kolumnie przechowywać. Na końcu umieszczamy wartość -1 oznaczającą koniec przekazywania danych.

Na koniec funkcja create_and_fill_model zwraca utworzony magazyn danych zrzutowany na typ GtkTreeModel* który jest typem interfejsu do danych wykorzystywanym w kontrolce GtkTreeView.

Przyjrzyjmy się teraz sposobowi utworzenia kolumn w widgecie GtkTreeView. Najpierw pobieramy wskaźnik na kontrolkę TreeView. Następnie, aby dodać nową kolumnę wywołujemy następujące funkcje:


    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1, "Nazwisko", renderer, "text", COL_NAME, NULL);

Najpierw tworzymy obiekt typu GtkCellRenderer przy pomocy wywołania gtk_cell_renderer_text_new(). Obiekt ten będzie wyświetlał wartość przypisaną do jego atrybutu "text". Następnie wywołujemy funkcję gtk_tree_view_insert_column_with_attributes aby dodać właściwą kolumnę, przypisać jej obiekt renderera oraz ustawić atrybut "text" tego renderera. Jako pierwszy argument tej funkcji przekazujemy wskaźnik na kontrolkę GtkTreeView. Drugi argument to pozycja kolumny w kontrolce, -1 oznacza, że kolumna ma zostać dodana na końcu. Trzeci argument to tekst nagłówka kolumny. Czwarty argument to renderer, który ma zostać użyty do wyświetlania wartości w tej kolumnie. Kolejne argumenty to lista atrybutów i przypisanych im wartości zakończona wartością NULL. W tym przypadku atrybutowi "text" przypisujemy wartość znajdującą się na pozycji COL_NAME (0) w magazynie danych.
Identycznie wygląda sprawa z dodaniem kolumny "Wiek". Na uwagę zasługuje tu fakt, że renderer komórki podczas wyświetlania wartości automatycznie przekonwertuje sobie wartość typu G_TYPE_UINT na łańcuch znaków.

Na koniec przypisujemy model do widgetu GtkTreeView i zmniejszamy liczbę referencji do tego modelu, żeby zajmowana przez niego pamięć została zwolniona po zniszczeniu GtkTreeView:


    gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), model);
    g_object_unref(model);