====== 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**