Home > Gtk3, PyGObject, python > PyGObject: Gtk.TreeStore

PyGObject: Gtk.TreeStore

13 Aprile 2020

torna all’indice appunti

TreeStore

Il widget Gtk.TreeStore fa parte del pattern model-view di GTK3.
Rispetto al ListStore è un model più complesso, poichè consente di gestire solo più livelli di
righe di dati, ovvero ogni row, può contenere una o più child-rows.
Come il ListStore è potente e flessibile e insieme alla view permette di avere le seguenti funzionalità:

– aggiornamento automatico dei dati quando questi vengono aggiunti, rimossi o modificati
– supporto al Drag and drop
– ordinamento dei dati
– incorporamento di widgets come checkboxes, progressbars, ecc.
– colonne riordinabili e ridimensionabili
– filtraggio dati

Model

Come già detto per il ListStore, ogni Gtk.TreeView ha un Gtk.TreeModel associato
che contiene i dati visualizzati dal widget treeview.
Un Gtk.TreeModel può essere utilizzato da più di un TreeView contemporaneamente.
Quando costruiamo il model, dobbiamo specificare che tipo di dati ogni colonna del model gestirà.
Il costruttore è pertanto Gtk.TreeStore(*column_types)

>>> treestore = Gtk.TreeStore(str, bool)

Questo crea un TreeStore con due colonne, una che conterrà un tipo str e una
con un tipo bool.
Per aggiungere i dati al TreeStore, si utilizza il metodo append(parent, row=None).
parent è il riferimento (iter) della row genitore, se parent = None, la row sarà
di tipo top-level, altrimenti, se parent sarà una row, avremo inserito una child-row.
Row è la lista con i valori da inserire nella row, che devono rispettare il type
definito precedentemente.

Il metodo append() ritorna un oggetto Gtk.TreeIter che punta alla posizione della
row appena inserita e può essere utilizzato in seguito per recuperare i valori dal treestore.

>>> import gi
... gi.require_version('Gtk', '3.0')
... from gi.repository import Gtk, Pango
>>> treestore = Gtk.TreeStore(str, bool)
>>> parent_iter1 = treestore.append(parent=None, row=["Portieri", False])  # top level row
>>> parent_iter2 = treestore.append(parent=None, row=["Difensori", False])  # top level row
>>> child_iter1 = treestore.append(parent=parent_iter1, row=["HANDANOVIC Samir", True])  # child row
>>> child_iter2 = treestore.append(parent=parent_iter2, row=["GODIN Diego", True])  # child row
>>> child_iter3 = treestore.append(parent=parent_iter2, row=["SKRINIAR Milan", True])  # child row

Una volta che i dati sono stati inseriti, è possibile recuperarli con gli stessi treeiters…

>>> treestore[parent_iter1]
<gi.overrides.Gtk.TreeModelRow object at 0x0000000004e070a0>
>>> treestore[parent_iter1][0]
'Portieri'
>>> treestore[child_iter2]
<gi.overrides.Gtk.TreeModelRow object at 0x00000000054cc280>
>>> treestore[child_iter2][0]
'GODIN Diego'

E’ ovviamente possibile iterare sui children di una row con gli appositi metodi, come ad esempio
iterchildren():

>>> for player in treestore[parent_iter2].iterchildren():
...      print(player[0])
...      
GODIN Diego
SKRINIAR Milan

Per conoscere il numero di top-level rows presenti nel treestore si utilizza il metodo len():

>>> len(treestore)
2

Path

Oltre ad accedere ai dati del treestore come si farebbe con le liste (via index), è possibile
farlo anche con Gtk.TreePath.
Questo treepath è un riferimento ad una certa row nel tree model e grazie al metodo get_iter()
è possibile ottenere l’iter desiderato.
In pratica, prima otteniamo l’oggetto generico relativo alla row desiderata di un treestore, ad
esempio vogliamo il riferimento della row 2.
Poi dal nostro treestore otteniamo l’oggetto iter relativo alla seconda row desiderata.
Una volta ottenuto l’iter, ricaviamo i valori:

>>> row2 = Gtk.TreePath(1)
>>> iter = treestore.get_iter(row2)
>>> treestore.get_value(iter, 0)
'Difensori'
>>> child = Gtk.TreePath().new_from_indices([1, 1])  # second row, second child
>>> iter = treestore.get_iter(child)
>>> treestore.get_value(iter, 0)
'SKRINIAR Milan'

Metodi

Vediamo quali sono i metodi utilizzabili con Gtk.TreeStore:

new(types)

Metodo costruttore che crea una nuova istanza di Gtk.TreeStore.
Parametri:
types: una lista di tipi rappresentanti il tipo di dato che le
colonne ospiteranno;

append(parent, row=None)

Aggiunge una nuova row di dati al TreeStore e ritorna l’oggetto Gtk.TreeIter che
si riferisce alla row appena aggiunta.
Parametri:
parent: l’oggetto Gtk.TreeIter di riferimento. Se parent=None,
la row aggiunta sarà di tipo top-level, altrimenti la row aggiunta, sarà figlia della row con iter
indicato in parent;
row: è la lista di valori da assegnare alla row appena aggiunta.
Se row è None, verrà aggiunta una row vuota e per inserire i dati in essa, sarà
necessario utilizzare i metodi Gtk.TreeStore.set() o Gtk.TreeStore.set_value().

clear()

Rimuove tutte le rows, top-level e child, dal TreeStore.

insert(parent, position, row=None)

Inserisce una nuova row alla posizione desiderata e ritorna l’oggetto Gtk.TreeIter relativo alla
row aggiunta.
Parametri:
parent: un oggetto Gtk.TreeIter valido, ovvero il
riferimento alla row genitore. Se parent=None, la row inserita sarà top-level, altrimenti sarà un
row figlia della row parent;
position: è il numero (int) relativo alla posizione dove vogliamo
aggiungere la row. Se parent è diverso da None, allora la posizione riguarderà le
children presenti. Se position=-1 o è maggiore del numero di rows presenti in quel livello, la
nuova row sarà inserita alla fine;
row: è la lista di valori da assegnare alla row appena aggiunta.
Se row è None, verrà aggiunta una row vuota e per inserire i dati in essa, sarà
necessario utilizzare i metodi Gtk.TreeStore.set() o Gtk.TreeStore.set_value().

insert_after(parent, sibling, row=None)

Inserisce una nuova row dopo sibiling e ritorna l’oggetto Gtk.TreeIter che si
riferisce alla row aggiunta.
Parametri:
parent: un oggetto Gtk.TreeIter valido, ovvero
il riferimento alla row genitore.
sibling: un oggetto Gtk.TreeIter valido, ovvero
il riferimento alla row, dopo la quale vogliamo inserire la nuova row, o None.
Se sibiling=None, la row verrà anteposta alle children di parent. Se anche parent=None, allora
verrà anteposta alle top-level row. Se sia sibiling che parent sono iter validi, allora parent
deve essere genitore di sibiling. Se sibilig esiste, parent è facoltativa;
row: è la lista di valori da assegnare alla row appena aggiunta.
Se row è None, verrà aggiunta una row vuota e per inserire i dati in essa, sarà
necessario utilizzare i metodi Gtk.TreeStore.set() o Gtk.TreeStore.set_value().

insert_before(parent, sibling, row=None)

Inserisce una nuova row prima di sibiling e ritorna l’oggetto Gtk.TreeIter che si
riferisce alla row aggiunta.
Parametri:
parent: un oggetto Gtk.TreeIter valido, ovvero il
riferimento alla row genitore.
sibling: un oggetto Gtk.TreeIter valido, ovvero
il riferimento alla row, prima della quale vogliamo inserire la nuova row, o None.
Se sibiling=None, la row verrà postposta dopo le children di parent. Se anche parent=None, allora
verrà postposta alle top-level row.
Se sia sibiling che parent sono iter validi, allora parent deve essere genitore di sibiling.
Se sibiling esiste, parent è facoltativa;
row: è la lista di valori da assegnare alla row appena aggiunta.
Se row è None, verrà aggiunta una row vuota e per inserire i dati in essa, sarà
necessario utilizzare i metodi Gtk.TreeStore.set() o Gtk.TreeStore.set_value().

insert_with_values(parent, position, columns, values)

Inserisce i dati, nelle colonne specificate, nella row di posizione desiderata e ritorna l’oggetto
Gtk.TreeIter che si riferisce alla row aggiunta.
Parametri:
parent: un oggetto Gtk.TreeIter valido, ovvero il
riferimento alla row genitore.
position: la posizione (int) dove inseriremo la row.
Se position=-1 o maggiore del numero di rows del TreeStore, verrà inserita in fondo (append);
columns: una lista con gli indici (int) delle colonne dove vogliamo
inserire i valori;
values: un lista di oggetti GObject.Value

is_ancestor(iter, descendant)

Ritorna True se iter è antenato di descendant;
antenato può ovviamente essere genitore di genitore, se abbiamo a che fare con sottolivelli di
sottolivelli:

>>> treestore.is_ancestor(parent_iter2, child_iter2)
True
>>> treestore.is_ancestor(parent_iter2, child_iter1)
False

Parametri:
iter: un oggetto Gtk.TreeIter valido;
descendant: un oggetto Gtk.TreeIter valido;

iter_depth(iter)

Ritorna un numero che indica il livello di profindità della row indicata da iter.
Tutto ciò che riguarda il livello top-level, dovrebbe essere 0, 1 per i sottolivelli al primo
strato e così via.

>>> treestore.iter_depth(parent_iter2)
0
>>> treestore.iter_depth(child_iter1)
1

Parametri:
iter: un oggetto Gtk.TreeIter valido;

iter_is_valid(iter)

Funzione da utilizzare in fase di debug, causa la lentezza; ritorna True se iter è valido.
Parametri:
iter: un oggetto Gtk.TreeIter;

move_after(iter, position)

Muove l’iter dopo la posizione specificata.
Questo metodo si utilizza solo con treestore non ordinati.
Parametri:
iter: l’oggetto Gtk.TreeIter da spostare;
position: l’oggetto Gtk.TreeIter dopo il quale spostare “iter”.
Se position=None, l’iter sarà spostato all’inizio del TreeStore.

move_before(iter, position)

Muove l’iter prima della posizione specificata.
Questo metodo si utilizza solo con treestore non ordinati.
Parametri:
iter: l’oggetto Gtk.TreeIter da spostare;
position: l’oggetto Gtk.TreeIter prima del quale spostare “iter”.
Se position=None, l’iter sarà spostato alla fine del TreeStore.

prepend(parent, row=None)

Antepone una nuova row al trestore.
Parametri:
parent: un oggetto Gtk.TreeIter valido, ovvero il
riferimento alla row genitore. Se parent=None, la nuova row sarà anteposta alle top-level rows
esistenti. Se parent è un iter esistente, la nuova row sarà una child anteposta alle children
esistenti per parent;
row: è la lista di valori da assegnare alla row appena aggiunta.
Se row è None, verrà aggiunta una row vuota e per inserire i dati in essa, sarà
necessario utilizzare i metodi Gtk.TreeStore.set() o Gtk.TreeStore.set_value().

remove(iter)

Rimuove la row con riferimento “iter” dal TreeStore e ritorna True se l’iter è valido, altrimenti
False ad operazione fallita. Se l’operazione ha buon fine, iter diventerà la row valida
successiva, oppure sarà invalidato se dovesse puntare all’ultima row del TreeStore.

set(iter, columns, values)

Setta i valori nelle colonne indicate, per la row con iter “iter”.
Parametri:
iter: l’oggetto Gtk.TreeIter indicante la row da modificare;
columns: una lista di indici relativi alle colonne interessate
dalla modifiche;
values: un lista di oggetti GObject.Value, ovvero i valori da
inserire nelle rispettive colonne;

set_column_types(types)

Funzione da utilizzare solo quando si costruisce un nuovo Gtk.TreeStore.
Non funziona dopo che una row è stata aggiunta o quando si utilizzano i metodi dell’interfaccia
di Gtk.TreeModel.
Parametri:
types: una lista di tipi rappresentanti il tipo di dato che le
colonne ospiteranno;

set_value(iter, column, value)

Setta i dati nella cella specificata da iter e column. Il valore “value” deve essere convertibile
nel tipo di dato previsto dalla colonna.
Il valore può essere Python value, inquanto sarà convertito in un oggetto GObject.Value.
Parametri:
iter: l’oggetto Gtk.TreeIter indicante la row da modificare;
column: il numero (int) di colonna nel quale inserire il valore;
value: l’oggetto GObject.Value da inserire nella cella definita da iter-column.

swap(a, b)

Inverte di posizione le row “a” e “b”.
Parametri:
a: il Gtk.TreeIter indicante la prima row da cambiare con la seconda;
b: il Gtk.TreeIter indicante la seconda row da cambiare con la prima;

View

La view è unica sia per un model TreeStore che per un model ListStore.
Settare un Gtk.TreeView è semplicissimo, dobbiamo solo fornirgli l’oggetto
Gtk.TreeModel dal quale attingere i dati, o passandolo il fase di costruzione,
come argomento, o utilizzando successivamente il metodo Gtk.TreeView.set_model().

>>> treeview = Gtk.TreeView(model=treestore)

Una volta che il treeview possiede un model, deve sapere come visualizzare i dati.
Qui entrano in gioco i column-renderers ed i cell-renderers.
I CellRenderers
sono usati per disegnare i dati del model nella view. GTK+ mette a disposizione cell-renderers
specifici, come Gtk.CellRendererText, Gtk.CellRendererPixbuf e Gtk.CellRendererToggle, ma è
possibile codificare un renderer personalizzato.

Il Gtk.TreeViewColumn è l’oggetto che Gtk.TreeView usa per organizzare le colonne.
Ha bisogno di conoscere le label per la colonna, che tipo di cell-renderer utilizzare e quale dato
ottenere dal model per una data row.

>>> treeview = Gtk.TreeView(model=treestore)
>>> renderer = Gtk.CellRendererText()
>>> column = Gtk.TreeViewColumn("Giocatore", renderer, text=0)
>>> treeview.append_column(column)
1

Selection

In questo tipo di widget molto probabilmente verranno effettuate delle selezioni.
Per poter accedere al contenuto della selezione, dobbiamo connettere il segnale “changed”
all’oggetto Gtk.TreeSelection.
Quindi dobbiamo recuperare un reference della selezione con il metodo get_selection():

>>> select = treeview.get_selection()
>>> def on_tree_selection_changed(selection):
...     model, treeiter = selection.get_selected()
...     if treeiter is not None:
...         print("You selected", model[treeiter][0])
...         
>>> select.connect("changed", on_tree_selection_changed)

E’ possibile controllare quali selezioni sono permesse chiamando il metodo Gtk.TreeSelection.set_mode().
Gtk.TreeSelection.get_selected() non funziona se il selection-mode è settato a
Gtk.SelectionMode.MULTIPLE, use invece Gtk.TreeSelection.get_selected_rows().

Sorting

il Sorting (ordinamento) è una delle caratteristiche più importanti del treeview
ed è supportata dai treemodels standard (Gtk.TreeStore and Gtk.TreeStore), che implementano
l’interfaccia Gtk.TreeSortable.

Sorting da colonna

Una colonna di un Gtk.TreeView può essere resa sortable semplicemente chiamando
il metodo Gtk.TreeViewColumn.set_sort_column_id().
In questo modo la colonna potrà essere ordinata semplicemente cliccando sulla sua intestazione.

>>> column.set_sort_column_id(0)

Se invece volessimo utilizzare una funzione di sorting personalizzata, dovremmo settarla con il
metodo set_sort_func().
Per prima cosa va definita la funzione di sorting che deve prendere 2 rows e confrontarle, se la
prima deve stare prima della seconda, la funzione ritorna -1, 0 se sono uguali in posizione
e 1 se la seconda row deve stare prima:

def compare(model, row1, row2, user_data):
    sort_column, _ = model.get_sort_column_id()
    value1 = model.get_value(row1, sort_column)
    value2 = model.get_value(row2, sort_column)
    if value1 < value2:
        return -1
    elif value1 == value2:
        return 0
    else:
        return 1

Poi la settiamo con il metodo set_sort_func().

>>> treestore.set_sort_func(0, compare, None)

Filtering

Diversamente dal sorting, il filtering non è gestito dai models, ma dalla classe
Gtk.TreeModelFilter.
Questa classe, come i Gtk.TreeStore e Gtk.TreeStore, è una Gtk.TreeModel.
Agisce come una “velina” davanti al model reale (Gtk.TreeStore o Gtk.TreeStore), nascondendo
alcuni elementi alla view.
In pratica fornisce il Gtk.TreeView con un set of the model sottostanti.
Si possono creare istanze di Gtk.TreeModelFilter, una sull’altra, in modo da utilizzare filtri
multipli sullo stesso model è possibile creare una nuova istanza di Gtk.TreeModelFilter e darle
un model da filtrare, ma il modo più semplice è usare direttamente il metodo Gtk.TreeModel.filter_new().

>>> filter = treestore.filter_new()

Nello stesso modo in cui lavora la funzione di sorting, il Gtk.TreeModelFilter necessita di una
funzione “visibility”, la quale, data una row dal modello sottostante, ritornerà un boolean
indicante se questa row deve essere filtrata o meno.
Questa funzione viene settata con il metodo Gtk.TreeModelFilter.set_visible_func():

>>> filter.set_visible_func(filter_func, data=None)

Ecco un codice di esempio:

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango


players = [["Portieri",
            ["HANDANOVIC Samir", True], ["PADELLI Daniele", False],
            ["BERNI Tommaso", False]],
           ["Difesa",
            ["GODIN Diego", True], ["DE VRIJ Stefan", True],
            ["RANOCCHIA Andrea", False], ["ASAMOAH Keadwo", False],
            ["DIMARCO Federico", False], ["D'AMBROSIO Danilo", False],
            ["BIRAGHI Cristiano", True], ["SKRINIAR Milan", True],
            ["BASTONI Alessandro", False]],
           ["Centrocampo",
            ["GAGLIARDINI Roberto", False], ["VECINO Matias", False],
            ["SENSI Stefano", True], ["LAZARO Valentino", False],
            ["BORJA Valero", False], ["BARELLA Nicolò", True],
            ["BROZOVIC Marcelo", True], ["CANDREVA Antonio", True]],
           ["Attacco",
            ["SANCHEZ Alexis", False], ["LUKAKU Romelu", True],
            ["MARTINEZ Lautaro", True], ["ESPOSITO Sebastiano", False]]
           ]


class GWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="Library")
        self.set_default_size(250, 500)
        self.set_border_width(10)

        self.current_filter_role = None

        self.treestore = Gtk.TreeStore(str, bool)

        for i in range(len(players)):
            group = players[i]
            # Gtk.TreeStore.append(parent, [ruolo, titolare])
            parent_iter = self.treestore.append(None, [group[0], False])
            for player in group[1:]:  # il primo elemento è il ruolo e lo salto
                self.treestore.append(parent_iter, player)

        view = Gtk.TreeView()
        view.set_model(self.treestore)
        renderer_players = Gtk.CellRendererText()
        column_players = Gtk.TreeViewColumn("Giocatori", renderer_players,
                                            text=0)
        view.append_column(column_players)

        renderer_in_out = Gtk.CellRendererToggle()
        column_in_out = Gtk.TreeViewColumn("Titolari", renderer_in_out,
                                           active=1)
        view.append_column(column_in_out)

        renderer_in_out.connect("toggled", self.on_toggled)
        view.connect("row-activated", self.on_select_row)
        self.add(view)

    def on_toggled(self, widget, path):
        # il valore boolean della row che seleziono
        current_value = self.treestore[path][1]
        # eseguo il toggle del valore boolean della row che ho selezionato
        self.treestore[path][1] = not current_value
        # path ha un formato 'n' per top-level e 'n:n' per child
        if len(path) == 1:  # sto selezionando un ruolo (top level)
            parent_iter = self.treestore.get_iter(path)
            child_iter = self.treestore.iter_children(parent_iter)
            # il toggle sul ruolo si riflette su tutti i giocatori
            while child_iter is not None:  # child_iter=None sono in fondo
                self.treestore[child_iter][1] = not current_value
                child_iter = self.treestore.iter_next(child_iter)
        else:  # sto selezionando un giocatore
            selected_child_iter = self.treestore.get_iter(path)
            parent_iter = self.treestore.iter_parent(selected_child_iter)
            child_iter = self.treestore.iter_children(parent_iter)
            # controllo se tutti i children sono selezionati
            all_selected = True
            while child_iter is not None:
                if not self.treestore[child_iter][1]:
                    all_selected = False
                    break
                child_iter = self.treestore.iter_next(child_iter)
            # Se sono tutti selezionati, il ruolo viene selezionato
            self.treestore[parent_iter][1] = all_selected

    @ staticmethod
    def on_select_row(tree_view, path, column):
        print("INFO: '%s' selected!" % tree_view.get_model()[path][0])


if __name__ == "__main__":
    win = GWindow()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()

link di riferimento:

torna all’indice degli appunti
Gnome gtk3 TreeStore
Gtk3 TreeStore

Categorie:Gtk3, PyGObject, python Tag:
I commenti sono chiusi.