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

PyGObject: Gtk.ListStore

13 Aprile 2020

torna all’indice degli appunti

ListStore

Il widget Gtk.ListStore fa parte del pattern model-view di GTK3.
E’ di fatto un model molto semplice, poichè consente di gestire solo un livello di righe di dati,
senza cioè le child-rows.
Per gestire dati annidati, si passa quindi al model “cugino” Gtk.TreeStore.
Entrambi i model sono potenti e flessibili e insieme alla view permettono 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 accennato, 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.
Teoricamente è possibile implementare il proprio Model, ma vedremo nel dettaglio il Gtk.ListStore.

Quando costruiamo il model, dobbiamo specificare che tipo di dati ogni colonna del model gestirà.
Il costruttore è pertanto Gtk.ListStore(*column_types)

>>> store = Gtk.ListStore(str, str, float)

Questo crea un liststore con 3 colonne, 2 che conterranno un tipo str e 1 float.
Per aggiungere i dati al liststore, si utilizza il metodo append(iterable).
Iterable è 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.

>>> treeiter = store.append(["HANDANOVIC", "portiere", 1])

Una volta che i dati sono stati inseriti, è possibile recuperarli con lo stesso treeiter…

>>> store[treeiter][0]
'HANDANOVIC'

o modificarli direttamente:

>>> store[treeiter][0] = "HANDANOVIC Samir"
>>> store[treeiter][0]
'HANDANOVIC Samir'

Per conoscere il numero di rows si utilizza il metodo len():

>>> len(store)
1

ed è possibile iterare con for direttamente sull’oggetto ListStore:

>>> for row in store:
...     print(row[:])
...     
['HANDANOVIC Samir', 'portiere', 1]

Path

Oltre ad accedere ai dati dello store come si fa con le liste, è 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’iterator desiderato.

>>> row1 = Gtk.TreePath(0)
>>> treeiter = store.get_iter(row1)
>>> store.get_value(treeiter, 1)
'portiere'

Metodi

Vediamo quali sono i metodi utilizzabili con Gtk.ListStore:

new(types)

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

append(row=None)

Aggiunge una row di dati al liststore e ritorna l’oggetto Gtk.TreeIter che si
riferisce alla row aggiunta.
Parametri:
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.ListStore.set() o Gtk.ListStore.set_value().

clear()

Rimuove tutte le rows dal liststore.

insert(position, row=None)

Inserisce una row alla posizione desiderata e ritorna l’oggetto Gtk.TreeIter che si riferisce alla
row aggiunta.
Parametri:
position: la posizione (int) dove inseriremo la row.
Se position=-1 o maggiore del numero di rows del liststore, verrà inserita in fondo (append);
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.ListStore.set() o Gtk.ListStore.set_value().

insert_after(sibling, row=None)

Inserisce la row dopo il riferimento specificato e ritorna l’oggetto Gtk.TreeIter che si riferisce
alla row aggiunta.
Parametri:
sibling: un oggetto Gtk.TreeIter valido, ovvero
il riferimento alla row, dopo la quale vogliamo inserire la nuova row, o None.
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.ListStore.set() o Gtk.ListStore.set_value().

insert_before(sibling, row=None)

Inserisce la row prima del riferimento specificato e ritorna l’oggetto Gtk.TreeIter che si
riferisce alla row aggiunta.
Parametri:
sibling: un oggetto Gtk.TreeIter valido, ovvero
il riferimento alla row, prima della quale vogliamo inserire la nuova row, o None.
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.ListStore.set() o Gtk.ListStore.set_value().

insert_with_valuesv(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:
position: la posizione (int) dove inseriremo la row.
Se position=-1 o maggiore del numero di rows del liststore, 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

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. Questa funzione 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 liststore.

move_before(iter, position)

Muove l’iter prima della posizione specificata. Questa funzione 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 liststore.

prepend(row=None)

Inserisce i valori in una row anteposta e ritorna l’oggetto Gtk.TreeIter che si riferisce alla row anteposta.
Parametri:
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.ListStore.set() o Gtk.ListStore.set_value().

remove(iter)

Rimuove la row con riferimento “iter” dal liststore 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 liststore.
Parametri:
iter: l’oggetto Gtk.TreeIter da rimuovere;

reorder(new_order)

Riordina il liststore secondo l’iterable new_order.
Questa funzione si utilizza solo con treestore non ordinati.
Parametri:
new_order: una lista di interi che mappa la nuova posizione delle rows.
Il numero di elementi deve essere lo stesso delle rows presenti nel liststore.

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.ListStore.
Non funziona dopo che una row è stata aggiunta o quando si utilizzano i metodi dell’interfaccia
di Gtk.TreeModel.

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 scambiare con la seconda;
b: il Gtk.TreeIter indicante la seconda row da scambiare con la prima;

View

La view è unica sia per un model liststore che per un model treestore.
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=store)

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=store)
>>> renderer = Gtk.CellRendererText()
>>> column = Gtk.TreeViewColumn("Giocatore", renderer, text=0)
>>> treeview.append_column(column)
1

Per fare il render di più di un model in una colonna della view, dobbiamo creare l’istanza
Gtk.TreeViewColumn e usare il metodo Gtk.TreeViewColumn.pack_start().

>>> column = Gtk.TreeViewColumn("Giocatore e ruolo")
>>> r_nome = Gtk.CellRendererText()
>>> r_ruolo = Gtk.CellRendererText()
>>> column.pack_start(r_nome, True)
>>> column.pack_start(r_ruolo, True)
>>> column.add_attribute(r_nome, "text", 0)
>>> column.add_attribute(r_ruolo, "text", 1)
>>> treeview.append_column(column)

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.ListStore), 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.

>>> store.append(["GODIN Diego", "defender", 2])
>>> store.append(["DE VRIJ Stefan", "defender", 6])
>>> store.append(["LUKAKU Romelu", "forward", 9])

abbiamo semplicemente aggiunto delle rows allo store precedentemente creato.
il TreeView è già esistente ed automaticamente aggiornato:

>>> len(treeview.get_model())
4
>>> 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().

>>> store.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.ListStore, è una Gtk.TreeModel.
Agisce come una “velina” davanti al model reale (Gtk.TreeStore o Gtk.ListStore), 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 E’ 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 = store.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


player_list = [("HANDANOVIC Samir", "goalkeeper",  1),
               ("PADELLI Daniele", "Portiere", 27),
               ("BERNI Tommaso", "Portiere", 46),
               ("GODIN Diego", "Difensore Centrale", 2),
               ("DE VRIJ Stefan", "Difensore Centrale", 6),
               ("RANOCCHIA Andrea", "Difensore Centrale", 13),
               ("ASAMOAH Keadwo", "Difensore", 18),
               ("DIMARCO Federico", "Difensore", 21),
               ("D'AMBROSIO Danilo", "Difensore", 33),
               ("BIRAGHI Cristiano", "Difensore", 34),
               ("SKRINIAR Milan", "Difensore Centrale", 37),
               ("BASTONI Alessandro", "Difensore Centrale", 95),
               ("GAGLIARDINI Roberto", "Centrocampista Centrale", 5),
               ("VECINO Matias", "Centrocampista Centrale", 8),
               ("SENSI Stefano", "Centrocampista Centrale", 12),
               ("LAZARO Valentino", "Ala", 19),
               ("BORJA Valero", "Centrocampista Centrale", 20),
               ("BARELLA Nicolò", "Centrocampista Centrale", 23),
               ("BROZOVIC Marcelo", "Centrocampista Centrale", 77),
               ("CANDREVA Antonio", "Ala", 87),
               ("SANCHEZ Alexis", "Seconda Punta", 7),
               ("LUKAKU Romelu", "Punta", 9),
               ("MARTINEZ Lautaro", "Punta", 10),
               ("ESPOSITO Sebastiano", "Punta", 30),]


class GWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="Treeview with model ListStore example")
        self.set_border_width(10)

        self.current_filter_role = None

        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        self.add(hbox)

        # Creo il ListStore model
        self.player_liststore = Gtk.ListStore(str, str, int)
        for player in player_list:
            self.player_liststore.append(list(player))

        # Creo il model filter associandolo al liststore model
        self.role_filter = self.player_liststore.filter_new()
        # setto la funzione di filter
        self.role_filter.set_visible_func(self.role_filter_func)

        # creo il treeview ed uso il filter come model
        self.treeview = Gtk.TreeView.new_with_model(
            Gtk.TreeModelSort(model=self.role_filter))
        # creo le colonne
        for i, column_title in enumerate(["Nome", "Ruolo", "Numero"]):
            renderer = Gtk.CellRendererText()
            if i == 0:
                renderer.props.weight_set = True
                renderer.props.weight = Pango.Weight.BOLD
            column = Gtk.TreeViewColumn(column_title, renderer, text=i)
            column.set_sort_column_id(i)
            self.treeview.append_column(column)

        # creo i Buttons per attivare il filter
        for role in ["Portiere", "Difensore", "Difensore Centrale",
                     "Centrocampista Centrale", "Ala",
                     "Punta", "Seconda Punta", "Tutti"]:
            button = Gtk.Button(label=role)
            vbox.pack_start(button, True, True, 0)
            button.connect("clicked", self.on_selection_button_clicked)

        self.treeview.connect("row-activated", self.on_select_row)
        hbox.pack_start(self.treeview, True, True, 0)
        hbox.pack_start(vbox, True, True, 0)

    def role_filter_func(self, model, iter, data):
        """Tests if the language in the row is the one in the filter"""
        if self.current_filter_role is None or \
                self.current_filter_role == "Tutti":
            return True
        else:
            # il ruolo è il secondo dato della row
            return model[iter][1] == self.current_filter_role

    def on_selection_button_clicked(self, widget):
        self.current_filter_role = widget.get_label()
        print("INFO: '%s' role selected!" % self.current_filter_role)
        self.role_filter.refilter()

    @ 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
Gtk3 ListStore

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