====== Usare il widget GtkTreeView ====== Autore: **//Fabio Di Matteo//** \\ Ultima revisione: **//13/03/2017 - 09:51//** \\ \\ Il widget GtkTreeView è l'omologo di quello che è una tabella nel mondo reale, le celle possono contenere sia dati che altri widget. \\ ====== Il codice ====== **main.c** \\ Ecco un esempio di codice completamente commentato relativo all'immagine: {{programmazione:gtk:gtktreeview.png|GtkTreeView}} #include //costanti enumerative con i nomi delle colonne enum { COL_ID , COL_COGNOME , COL_NOME, COL_CITTA, NUM_COLS } ; /*Con questa funzione prepariamo la struttura dati *che sara' contenuta nel TreeView*/ static GtkTreeModel * create_and_fill_model (void) { GtkListStore *store; //contenitore dati GtkTreeIter iter; // una specie di segnalibro che tiene il conto della posizione corrente //Creo il contenitore dei dati, specificando il tipo per ogni colonna store = gtk_list_store_new (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING,G_TYPE_STRING, G_TYPE_STRING); /* Aggiungiamo una riga */ gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COL_ID, 1, COL_COGNOME, "Di Matteo", COL_NOME, "Fabio", COL_CITTA, "Palermo", -1); /* Aggiungiamo una seconda riga */ gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COL_ID, 2, COL_COGNOME, "Di Matteo", COL_NOME, "Gioacchino", COL_CITTA, "Palermo", -1); //prima di uscire la funzione ritorna il modello di tipo GtkTreeModel return GTK_TREE_MODEL (store); } static GtkWidget * create_view_and_model (void) { GtkCellRenderer *renderer; //la cella GtkTreeModel *model; // il modello contenente la struttura dei dati GtkWidget *view; // puntatore ad un widget generico view = gtk_tree_view_new (); //il widget generico diventa un GtkTreeView /*Disegno le colonne nel GtkTreeView (colonne, non righe! :) )*/ /* --- Colonna 1 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "ID", renderer, "text", COL_ID, NULL); /* --- Colonna 2 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "Cognome", renderer, "text", COL_COGNOME, NULL); /* --- Colonna 3 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "Nome", renderer, "text", COL_NOME, NULL); /* --- Colonna 4 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "Citta'", renderer, "text", COL_CITTA, NULL); /*Associamo al puntatore **locale** il valore di ritorno di create_and_fill_model () , * ovvero la struttura creata nella funzione precedente a questa */ model = create_and_fill_model (); /*Facciamo acuisire al TreeView "view" la struttura dati "model" . * quando distruggeremo il "view" "model" adra' distrutto con esso */ gtk_tree_view_set_model (GTK_TREE_VIEW (view), model); g_object_unref (model); return view; } int main (int argc, char **argv) { GtkWidget *window;//la finestra del programma GtkWidget *view; //il widget che diventera' il GtkTreeView gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (window, "delete_event", gtk_main_quit, NULL); /* dirty */ view = create_view_and_model (); gtk_container_add (GTK_CONTAINER (window), view); gtk_widget_show_all (window); gtk_main (); return 0; } ===== Il makefile ===== CPP = gcc OPTS = `pkg-config --cflags --libs gtk+-2.0` all: $(CPP) main.c -o main $(OPTS) clean: rm main ===== Prelevare il contenuto di una riga ===== Adesso aggiungeremo la callback che permettera' di prelevare il contenuto di alcune celle e stamparlo sul titolo della finestra. Il codice è pressocchè lo stesso, ma con l'aggiunta della callback ''onRowActivated'' . #include GtkWidget *window;//la finestra del programma //costanti enumerative con i nomi delle colonne enum { COL_ID , COL_COGNOME , COL_NOME, COL_CITTA, NUM_COLS } ; // Al doppioclick sul treeview eseguo la seguente callback static void onRowActivated (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col, //non usato gpointer user_data) { GtkTreeModel *model; GtkTreeIter iter; GtkTreeSelection *sel = gtk_tree_view_get_selection (view); g_print ("Doppio click sulla riga.\n"); model = gtk_tree_view_get_model(view); //Se volessi potrei rimuove la riga selezionata con //gtk_list_store_remove(GTK_LIST_STORE(model), &iter); if (gtk_tree_model_get_iter(model, &iter, path)) { //Prelevo alcuni campi dal modello (nome e cognome) gchar *nome, *cognome; gtk_tree_model_get(model, &iter, COL_NOME, &nome, -1); gtk_tree_model_get(model, &iter, COL_COGNOME, &cognome, -1); //Aggiorno il titolo della finestra con nome e cognome selezionati gtk_window_set_title (GTK_WINDOW(window), g_strconcat (nome, " ",cognome, NULL)); g_free(nome); g_free(cognome); }else{ //Se non ho selezionato nessun record esco senza far nulla return; } } /*Con questa funzione prepariamo la struttura dati *che sara' contenuta nel TreeView*/ static GtkTreeModel * create_and_fill_model (void) { GtkListStore *store; //contenitore dati GtkTreeIter iter; // una specie di segnalibro che tiene il conto della posizione corrente //Creo il contenitore dei dati, specificando il tipo per ogni colonna store = gtk_list_store_new (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING,G_TYPE_STRING, G_TYPE_STRING); /* Aggiungiamo una riga */ gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COL_ID, 1, COL_COGNOME, "Di Matteo", COL_NOME, "Fabio", COL_CITTA, "Palermo", -1); /* Aggiungiamo una seconda riga */ gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, COL_ID, 2, COL_COGNOME, "Di Matteo", COL_NOME, "Gioacchino", COL_CITTA, "Palermo", -1); //prima di uscire la funzione ritorna il modello di tipo GtkTreeModel return GTK_TREE_MODEL (store); } static GtkWidget * create_view_and_model (void) //Crea il vidget { GtkCellRenderer *renderer; //la cella GtkTreeModel *model; // il modello contenente la struttura dei dati GtkWidget *view; // puntatore ad un widget generico view = gtk_tree_view_new (); //il widget generico diventa un GtkTreeView /*Disegno le colonne nel GtkTreeView (colonne, non righe! :) )*/ /* --- Colonna 1 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "ID", renderer, "text", COL_ID, NULL); /* --- Colonna 2 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "Cognome", renderer, "text", COL_COGNOME, NULL); /* --- Colonna 3 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "Nome", renderer, "text", COL_NOME, NULL); /* --- Colonna 4 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "Citta'", renderer, "text", COL_CITTA, NULL); /*Associamo al puntatore **locale** il valore di ritorno di create_and_fill_model () , * ovvero la struttura creata nella funzione precedente a questa */ model = create_and_fill_model (); /*Facciamo acquisire al TreeView "view" la struttura dati "model" . * quando distruggeremo il "view" "model" adra' distrutto con esso */ gtk_tree_view_set_model (GTK_TREE_VIEW (view), model); g_object_unref (model); /*Al click su una riga attivo la callback*/ g_signal_connect(view, "row-activated", G_CALLBACK(onRowActivated), NULL); return view; } int main (int argc, char **argv) { GtkWidget *view; //il widget che diventera' il GtkTreeView gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (window, "delete_event", gtk_main_quit, NULL); /* dirty */ view = create_view_and_model (); gtk_container_add (GTK_CONTAINER (window), view); gtk_widget_show_all (window); gtk_main (); return 0; } ===== Prelevare dati da un Gtktreeview al singolo click (GTK+2) ===== Di seguito il codice modificato per prelevare i dati al singolo click sulla griglia in Gtk+2. \\ Al contrario delle Gtk+3 dove basta usare la funzione [[https://developer.gnome.org/gtk3/stable/GtkTreeView.html#gtk-tree-view-set-activate-on-single-click|gtk_tree_view_set_activate_on_single_click()]] in Gtk+2 dobbiamo fare un giochetto un poco piu' lungo. Dove il ruolo da protagonista è affidato al segnale ''button-release-event'' e alla relativa callback. Posto di seguito il codice commentato: \\ \\ **main.c** #include GtkWidget *window; //la finestra del programma GtkTreeView* myView; //il gtk treeview GtkTreeModel* myModel; //il modello dati GtkTreeIter* myIter; //l'iteratore //costanti enumerative con i nomi delle colonne enum { COL_ID , COL_COGNOME , COL_NOME, COL_CITTA, NUM_COLS } ; // Al doppioclick sul treeview eseguo la seguente callback static void onRowActivated (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col, //non usato gpointer user_data) { GtkTreeModel *model; GtkTreeIter iter; GtkTreeSelection *sel = gtk_tree_view_get_selection (view); g_print ("Doppio click sulla riga.\n"); model = gtk_tree_view_get_model(view); //Se volessi potrei rimuove la riga selezionata con //gtk_list_store_remove(GTK_LIST_STORE(model), &iter); if (gtk_tree_model_get_iter(model, &iter, path)) { //Prelevo alcuni campi dal modello (nome e cognome) gchar *nome, *cognome; gtk_tree_model_get(model, &iter, COL_NOME, &nome, -1); gtk_tree_model_get(model, &iter, COL_COGNOME, &cognome, -1); //Aggiorno il titolo della finestra con nome e cognome selezionati gtk_window_set_title (GTK_WINDOW(window), g_strconcat (nome, " ",cognome, NULL)); g_free(nome); g_free(cognome); }else{ //Se non ho selezionato nessun record esco senza far nulla return; } } void onSignleButtonPressed(GtkTreeView *view, GdkEventButton *event, gpointer userdata ) { gchar* label; GtkTreePath *path=gtk_tree_model_get_path (myModel, &myIter); GtkTreeSelection * tsel = gtk_tree_view_get_selection (myView); if ( event->button == 1) { if ( gtk_tree_selection_get_selected ( tsel , &myModel , &myIter ) ) { gtk_tree_model_get(myModel, &myIter, 2, &label, -1);//prelevo il campo 2 g_print("Singolo click su: %s\n",label); } } } /*Con questa funzione prepariamo la struttura dati *che sara' contenuta nel TreeView*/ static GtkTreeModel * create_and_fill_model (void) { GtkListStore *store; //contenitore dati //Creo il contenitore dei dati, specificando il tipo per ogni colonna store = gtk_list_store_new (NUM_COLS, G_TYPE_UINT, G_TYPE_STRING,G_TYPE_STRING, G_TYPE_STRING); /* Aggiungiamo una riga */ gtk_list_store_append (store, &myIter); gtk_list_store_set (store, &myIter, COL_ID, 1, COL_COGNOME, "Di Matteo", COL_NOME, "Fabio", COL_CITTA, "Palermo", -1); /* Aggiungiamo una seconda riga */ gtk_list_store_append (store, &myIter); gtk_list_store_set (store, &myIter, COL_ID, 2, COL_COGNOME, "Di Matteo", COL_NOME, "Gioacchino", COL_CITTA, "Palermo", -1); //prima di uscire la funzione ritorna il modello di tipo GtkTreeModel return GTK_TREE_MODEL (store); } static GtkWidget * create_view_and_model (void) //Crea il vidget { GtkCellRenderer *renderer; //la cella myView = gtk_tree_view_new (); //il widget generico diventa un GtkTreeView /*Disegno le colonne nel GtkTreeView (colonne, non righe! :) )*/ /* --- Colonna 1 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (myView), -1, "ID", renderer, "text", COL_ID, NULL); /* --- Colonna 2 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (myView), -1, "Cognome", renderer, "text", COL_COGNOME, NULL); /* --- Colonna 3 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (myView), -1, "Nome", renderer, "text", COL_NOME, NULL); /* --- Colonna 4 --- */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (myView), -1, "Citta'", renderer, "text", COL_CITTA, NULL); /*Associamo al puntatore **locale** il valore di ritorno di create_and_fill_model () , * ovvero la struttura creata nella funzione precedente a questa */ myModel = create_and_fill_model (); /*Facciamo acquisire al TreeView "view" la struttura dati "model" . * quando distruggeremo il "view" "model" adra' distrutto con esso */ gtk_tree_view_set_model (GTK_TREE_VIEW (myView), myModel); g_object_unref (myModel); return myView; } int main (int argc, char **argv) { gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (window, "delete_event", gtk_main_quit, NULL); /* dirty */ myView = create_view_and_model (); /*Al Doppio click su una riga attivo la callback*/ g_signal_connect(myView, "row-activated", G_CALLBACK(onRowActivated), NULL); /*Al click su una riga attivo la callback*/ g_signal_connect(myView, "button-release-event", (GCallback) onSignleButtonPressed,NULL); gtk_container_add (GTK_CONTAINER (window), myView); gtk_widget_show_all (window); gtk_main (); return 0; } ===== Scandire per intero un Liststore ===== Prima di tutto occorre creare una funzione che agira sulla riga corrente. gboolean scanListStore(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { enum { COL_ID , COL_URL , COL_PROGRESS, NUM_COLS } ; gchar* url; gtk_tree_model_get(GTK_TREE_MODEL(model), iter, COL_URL, &url, -1); g_print("%s\n", url); } In secondo luogo si deve lanciare una funzione che attraversera' tutto il liststore e eseguira' per la riga corrente la sopracitata funzione: gtk_tree_model_foreach (GTK_TREE_MODEL(liststore), &scanListStore, NULL); ==== Un'alternativa tratta da wikibook ==== void traverse_list_store (GtkListStore *liststore) { GtkTreeIter iter; gboolean valid; g_return_if_fail ( liststore != NULL ); /* Get first row in list store */ valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(liststore), &iter); while (valid) { /* ... do something with that row using the iter ... */ /* (Here column 0 of the list store is of type G_TYPE_STRING) */ gtk_list_store_set(liststore, &iter, 0, "Joe", -1); /* Make iter point to the next row in the list store */ valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(liststore), &iter); } } ===== Usare il GtkTreeView con Glade ===== {{:programmazione:gtk:gtktreeview-glade.png?559|}} Trasciniamo il widget "Vista ad albero" (GtkTreeview) dentro un contenitore . Ci verra' chiesto di creare o agganciare un modello per i dati (GtkListstore) che possiamo creare e successivamente editare direttamente dalle sue proprieta'. Per il nostro esempio creeremo un modello con le seguenti colonne: gint ID gchararray Nome gint Gradimento La colonna "Gradimento" mostrera un valore intero sottoforma di progressbar. Adesso andremo a creare le colonne del treeview. Per prima cosa selezioniamolo e clicchiamo su "Edit", sulla toolbar oppure nel menu a comparsa (tasto destro del mouse). Passiamo alla scheda "Gerarchia". Con i bottoni + e x possiamo creare o eliminare le colonne, a lato verranno visualizzate le proprieta'. Possiamo cominciare a creare le colonne ID, Nome e Gradimento. Adesso abbiamo creato le colonne dal punto di vista grafico. Dobbiamo pero' associare ancora la colonna al nostro modello dati (il liststore). Per far questo andiamo sempre in gerarchia, clicchiamo con il tasto destro sul nome di una colonna e aggiungiamo il figlio dal menu a tendina. Una volta creato il figlio abbiniamo la proprieta' "testo" dello stesso al campo del liststore, che comparira' in un comodo menu a discesa , che deve essere dello stesso tipo. Per creare la progressbar nel campo gradimento dobbiamo creare un figlio di tipo "Avanzamento" e asssociare un campo del liststore di tipo gint. A questo punto possiamo gia' aggiungere dei dati di prova direttamente dalle proprieta' del treeview (edit->generale). Di seguito il codice per aggiungere, eliminare e salvare righe. **main.c** #include GObject *myListstore, *myTreeSel, *myTreeView; GObject *entry; GtkTreeIter iter; //Gradimento e id si autoincrementano (per semplicita') gint grad=5; gint id=1; void on_mainWindow_delete_event(GtkWidget *widget, gpointer data) { gtk_main_quit(); } // Aggiunge una riga prelevando il nome dalla entry e gli altri valori // dalle variabili "id" e "grad" void add(GtkWidget *widget, gpointer data) { enum { COL_ID , COL_NOME , COL_GRADIMENTO, NUM_COLS } ; gtk_list_store_append (GTK_LIST_STORE(myListstore), &iter); gtk_list_store_set (GTK_LIST_STORE(myListstore), &iter, COL_ID, id, COL_NOME, gtk_entry_get_text(GTK_ENTRY(entry)), COL_GRADIMENTO, grad, -1); id++; if (grad<=95)grad=grad+5; } void del(GtkWidget *widget, gpointer data) { gtk_list_store_remove(GTK_LIST_STORE(myListstore), &iter); } void save(GtkWidget *widget, gpointer data) { enum { COL_ID , COL_NOME , COL_GRADIMENTO, NUM_COLS } ; gtk_list_store_set (GTK_LIST_STORE(myListstore), &iter, COL_ID, id, COL_NOME, gtk_entry_get_text(GTK_ENTRY(entry)), COL_GRADIMENTO, grad, -1); } // Al doppioclick sul treeview eseguo la seguente callback static void onRowActivated (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col, //non usato gpointer user_data) { enum { COL_ID , COL_NOME , COL_GRADIMENTO, NUM_COLS } ; GtkTreeModel *model; GtkTreeSelection *sel = gtk_tree_view_get_selection (view); g_print ("Selezionata riga.\n"); model = gtk_tree_view_get_model(view); if (gtk_tree_model_get_iter(model, &iter, path)) { //Prelevoil campo nome dal liststore gchar *nome; gtk_tree_model_get(model, &iter, COL_NOME, &nome, -1); gtk_entry_set_text(GTK_ENTRY(entry),nome); g_free(nome); }else{ //Se non ho selezionato nessun record esco senza far nulla return; } } //GUI void mainWindowInit() { GError* error = NULL; gchar* glade_file = g_build_filename("gui.ui", NULL); GtkBuilder *xml; GObject *mainWindow, *btAdd, *btDel, *btSave; xml = gtk_builder_new (); if (!gtk_builder_add_from_file (xml, glade_file, &error)) { g_warning ("Couldn\'t load builder file: %s", error->message); g_error_free (error); } mainWindow=gtk_builder_get_object (xml,"mainWindow" ); btAdd=gtk_builder_get_object (xml,"btAdd" ); btDel=gtk_builder_get_object (xml,"btDel" ); btSave=gtk_builder_get_object (xml,"btSave" ); entry=gtk_builder_get_object (xml,"entry" ); myTreeView=gtk_builder_get_object(xml,"myTreeView"); myListstore=gtk_builder_get_object(xml,"myListstore"); myTreeSel=gtk_builder_get_object(xml,"myTreeSel"); g_object_unref( G_OBJECT( xml ) ); g_signal_connect (mainWindow, "destroy", G_CALLBACK (on_mainWindow_delete_event), NULL); g_signal_connect (btAdd, "clicked", G_CALLBACK (add), NULL); g_signal_connect (btDel, "clicked", G_CALLBACK (del), NULL); g_signal_connect (btSave, "clicked", G_CALLBACK (save), NULL); /*Al click su una riga attivo la callback*/ g_signal_connect(myTreeView, "row-activated", G_CALLBACK(onRowActivated), NULL); } int main (int argc, char **argv) { gtk_init (&argc, &argv); mainWindowInit(); gtk_main (); return 0; } **gui.ui** 1 Fabio Di Matteo 50 2 Tizio Rossi 10 406 True False True True False vertical True True False True 0 500 415 True True in True True myListstore 1 True ID 0 Nome 1 Gradimento 2 False True 1 True False GtkTreeview e Galde Come usare il treeview con glade True True False gtk-add True True True True False True 0 gtk-delete True True True True False True 1 gtk-save True True True True False True 2