Navigation

Objekt-Attribute in Gtk.TreeView anzeigen

Wenn man in Python und PyGObject mit Gtk.TreeView arbeitet, ist es oft umständlich, wenn man Daten, die man als Attribute von Objekten gespeichert hat, erst auf die Spalten eines Gtk.ListStore oder Gtk.TreeStore verteilen muss, um sie im TreeView darstellen zu können.

Praktischer ist es oft, wenn der TreeView direkt Objekt-Attribute für die einzelnen Spalten benutzen kann.

Das geht allerdings nur, wenn unser Objekt zu einer Unterklasse von GObject.GObject gehört. Und es ist deutlich langsamer, da der Zugriff auf die Objekt-Attribute über Python-Code statt nativ in Gtk erfolgt. Bei TreeViews mit sehr vielen Einträgen kann der Geschwindigkeitsunterschied für die Anwenderin spürbar sein, besonders beim Sortieren.

Ähnlich kann man natürlich auch auf die Felder eines dict zugreifen, oder berechnete Werte anzeigen.

Objekt-Attribute darstellen

Gtk.TreeViewColumn hat eine Methode set_cell_data_func(). Damit können Sie selbst eine Funktion angeben, die die Spalte benutzen soll, um sich vom zugehörigen Modell die Daten zu beschaffen. Dafür benötigt set_cell_data_func() als Parameter den Gtk.CellRenderer, der für die Darstellung der Spalte zuständig ist, die gewünschte Funktion und bei Bedarf zusätzliche Daten für die Funktion:

treeviewcolumn.set_cell_data_func(cellrenderer, cell_data_func, data)

Die Funktion muss vom Typ Gtk.TreeCellDataFunc sein und wird von Gtk.TreeViewColumn mit fünf Parametern aufgerufen: Der TreeViewColumn, dem für die Darstellung der Spalte zuständigen CellRenderer, dem zugehörigen Modell, einem Iter, der auf die aktuelle Zeile zeigt, und den zusätzlichen Daten, die mit set_cell_data_func übergeben wurden:

def cell_data_func(tree_column, cell, tree_model, iter, data):

Nehmen wir an, unser Objekt steht in der ersten Spalte des Modells, und hat ein Attribut title, das wir in einer Spalte anzeigen wollen, die von einem Gtk.CellRendererText dargestellt wird.

Zuerst besorgen wir uns aus dem Modell das Objekt für die aktuelle Zeile:

obj = tree_model[iter][0]

Dann setzen wir die Eigenschaft text des CellRenderers auf den Wert des title-Attributes unseres Objekts:

cell.set_property('text', obj.title)

Insgesamt sieht unsere Funktion also so aus:

def my_cell_data_func(tree_column, cell, tree_model, iter, data):
    obj = tree_model[iter][0]
    cell.set_property('text', obj.title)

Ein komplettes Programm mit Spalten für zwei Attribute kann dann z.B. so aussehen:

 1#!/usr/bin/python3
 2# -*- coding: utf-8 -*-
 3
 4from gi.repository import Gtk, GObject
 5
 6
 7class Item(GObject.GObject):
 8    """ Eine Klasse für unsere Daten"""
 9    def __init__(self, title, num):
10        GObject.GObject.__init__(self)
11        self.title = title
12        self.num = num
13
14
15def my_title_cell_data_func(tree_column, cell, tree_model, iter, data):
16    """ Eine eigene cell_data_func für Item.title"""
17    obj = tree_model[iter][0]    # Objekt für aktuelle Zeile
18    cell.set_property('text', obj.title)
19
20def my_num_cell_data_func(tree_column, cell, tree_model, iter, data):
21    """ Eine eigene cell_data_func für Item.num"""
22    obj = tree_model[iter][0]    # Objekt für aktuelle Zeile
23    cell.set_property('text', str(obj.num))
24
25
26# Fenster erzeugen
27window = Gtk.Window()
28window.set_default_size(200, 250)
29window.connect("destroy", lambda w: Gtk.main_quit())
30
31# TreeView mit ListStore ins Fenster einfügen
32lstore = Gtk.ListStore(Item)
33treeview = Gtk.TreeView(model=lstore)
34window.add(treeview)
35
36
37## Titel-Spalte erzeugen:
38
39cellrenderer = Gtk.CellRendererText()
40treeviewcolumn = Gtk.TreeViewColumn('Titel')
41
42# benutze my_title_cell_data_func
43treeviewcolumn.set_cell_data_func(cellrenderer, my_title_cell_data_func)
44
45# CellRenderer zur Spalte, und Spalte zum TreeView hinzufügen
46treeviewcolumn.pack_start(cellrenderer, True)
47treeview.append_column(treeviewcolumn)
48
49
50## Num-Spalte erzeugen:
51
52cellrenderer = Gtk.CellRendererText()
53treeviewcolumn = Gtk.TreeViewColumn('Num')
54
55# benutze my_num_cell_data_func
56treeviewcolumn.set_cell_data_func(cellrenderer, my_num_cell_data_func)
57
58# CellRenderer zur Spalte, und Spalte zum TreeView hinzufügen
59treeviewcolumn.pack_start(cellrenderer, True)
60treeview.append_column(treeviewcolumn)
61
62
63
64# Ein paar Demo-Objekte ins Modell packen
65for i in range(19):
66    lstore.append((Item('This is {}'.format(i), i*i), ))
67
68
69# Alles anzeigen
70window.show_all()
71Gtk.main()

Sortieren

Damit die Anwenderin die Spalten durch einen Klick auf die Spaltenköpfe sortieren kann, müssen wir mit Gtk.TreeViewColumn.set_sort_column_id() eine Sortier-ID für die Spalte setzen. Damit das Sortieren auch mit unseren Objekt-Attributen funktioniert, müssen wir zusätzlich dem Modell unseres TreeViews eine Vergleichsfunktion für diese ID mitgeben.

Die Sortierfunktion muss vom Typ Gtk.TreeIterCompareFunc sein:

def sort_func(model, iter_a, iter_b, data):

Als model bekommt sie das Modell des TreeView übergeben, iter_a und iter_b sind Iter zu den beiden Objekten, die verglichen werden sollen, und in data stehen eventuell zusätzliche Daten. Ihre Rückgabewerte sind:

  • -1, falls a < b

  • 0, falls a = b

  • 1, falls a > b

Um unsere Objekte nach dem Titel zu sortieren, können wir beispielsweise folgende Funktion verwenden:

def my_title_sort_func(model, iter_a, iter_b, data):
    a = model[iter_a][0]
    b = model[Iter_b][0]
    return (a.title > b.title) - (a.title < b.title)

Jetzt müssen wir diese Funktion noch dem Modell unseres TreeView mitteilen. Dafür gibt es die Methode Gtk.TreeSortable.set_sort_func():

model.set_sort_func(sort_column_id, sort_func, user_data=None)

Dabei ist sort_column_id die ID, sort_func eine Vergleichsfunktion, und mit user_data können wir bei Bedarf zusätzliche Daten für die Vergleichsfunktion angeben.

Wenn treeviewcolumn eine Spalte ist und model das Modell unseres TreeView, könne wir mit:

treeviewcolumn.set_sort_column_id(0)
model.set_sort_func(0,  my_title_sort_func)

die Funktion my_title_sort_func als Sortier-Funktion für die Sortier-ID 0 setzen.

Das komplette Programm, wieder mit Spalten für zwei Attribute, kann dann z.B. so aussehen:

 1#!/usr/bin/python3
 2# -*- coding: utf-8 -*-
 3
 4from gi.repository import Gtk, GObject
 5
 6def cmp(a, b):
 7    return (a > b) - (a < b)
 8
 9class Item(GObject.GObject):
10    """ Eine Klasse für unsere Daten"""
11    def __init__(self, title, num):
12        GObject.GObject.__init__(self)
13        self.title = title
14        self.num = num
15
16
17def my_title_cell_data_func(tree_column, cell, tree_model, iter, data):
18    """ Eine eigene cell_data_func für Item.title"""
19    obj = tree_model[iter][0]    # Objekt für aktuelle Zeile
20    cell.set_property('text', obj.title)
21
22def my_title_sort_func(model, a, b, data):
23    obj_a = model[a][0]
24    obj_b = model[b][0]
25    return cmp(obj_a.title, obj_b.title)
26
27
28def my_num_cell_data_func(tree_column, cell, tree_model, iter, data):
29    """ Eine eigene cell_data_func für Item.num"""
30    obj = tree_model[iter][0]    # Objekt für aktuelle Zeile
31    cell.set_property('text', str(obj.num))
32
33def my_num_sort_func(model, a, b, data):
34    obj_a = model[a][0]
35    obj_b = model[b][0]
36    return cmp(obj_a.num, obj_b.num)
37
38
39
40# Fenster erzeugen
41window = Gtk.Window()
42window.set_default_size(200, 250)
43window.connect("destroy", lambda w: Gtk.main_quit())
44
45# TreeView mit ListStore ins Fenster einfügen
46lstore = Gtk.ListStore(Item)
47treeview = Gtk.TreeView(model=lstore)
48window.add(treeview)
49
50
51## Titel-Spalte erzeugen:
52
53cellrenderer = Gtk.CellRendererText()
54treeviewcolumn = Gtk.TreeViewColumn('Titel')
55
56# benutze my_title_cell_data_func
57treeviewcolumn.set_cell_data_func(cellrenderer, my_title_cell_data_func)
58
59# CellRenderer zur Spalte, und Spalte zum TreeView hinzufügen
60treeviewcolumn.pack_start(cellrenderer, True)
61treeview.append_column(treeviewcolumn)
62
63# Sortierfunktion setzen
64treeviewcolumn.set_sort_column_id(0)
65lstore.set_sort_func(0,  my_title_sort_func)
66
67
68## Num-Spalte erzeugen:
69
70cellrenderer = Gtk.CellRendererText()
71treeviewcolumn = Gtk.TreeViewColumn('Num')
72
73# benutze my_num_cell_data_func
74treeviewcolumn.set_cell_data_func(cellrenderer, my_num_cell_data_func)
75
76# CellRenderer zur Spalte, und Spalte zum TreeView hinzufügen
77treeviewcolumn.pack_start(cellrenderer, True)
78treeview.append_column(treeviewcolumn)
79
80# Sortierfunktion setzen
81treeviewcolumn.set_sort_column_id(1)
82lstore.set_sort_func(1,  my_num_sort_func)
83
84
85# Ein paar Demo-Objekte ins Modell packen
86for i in range(19):
87    lstore.append((Item('This is {}'.format(i), i*i), ))
88
89
90# Alles anzeigen
91window.show_all()
92Gtk.main()

Suchen

Damit auch die Suche im TreeView funktioniert, müssen wir mit Gtk.TreeView.set_search_equal_func() eine Funktion angeben, die für die Vergleiche bei der Suche benutzt wird. Das ist auch oft bei “normalen” TreeViews sinnvoll, um zum Beispiel eine Suche nach Teilstrings zu ermöglichen.

Eine Suchfunktion für Teilstrings kann zum Beispiel so aussehen:

def my_title_search_func(model, column, value, iter):
    obj = model[iter][0]
    return value not in obj.title

Dabei ist model wie üblich das Model des TreeViews, column gibt die Spalte an, in der gesucht wird, value ist der Wert, nach dem die Benutzerin sucht, und iter der Iter zu der du vergleichenden Zeile im Model. Die Funktion gibt True zurück, wenn der Eintrag nicht passt (der TreeView also weitersuchen muss), und False, falls der Eintrag passt.

Damit der TreeView die Suchfunktion aktiviert, müssen wir noch eine Suchspalte setzen:

treeview.set_search_column(0)

Der Parameter ist dabei eigentlich egal, er wird nur als column an my_title_search_func() weitergegeben. Das könnte man zum Beispiel benutzen, um das für die Suche verwendete Objekt-Attribut zu ändern.

Das komplette Programm, wieder mit Spalten für zwei Attribute, kann dann z.B. so aussehen:

  1#!/usr/bin/python3
  2# -*- coding: utf-8 -*-
  3
  4from gi.repository import Gtk, GObject
  5
  6def cmp(a, b):  # Praktisch für die Sortier-Funktionen
  7    return (a > b) - (a < b)
  8
  9class Item(GObject.GObject):
 10    """ Eine Klasse für unsere Daten"""
 11
 12    def __init__(self, title, num):
 13        GObject.GObject.__init__(self)
 14        self.title = title
 15        self.num = num
 16
 17
 18def my_title_cell_data_func(tree_column, cell, tree_model, iter, data):
 19    """ cell_data_func für Item.title"""
 20    obj = tree_model[iter][0]    # Objekt für aktuelle Zeile
 21    cell.set_property('text', obj.title)
 22
 23def my_title_sort_func(model, a, b, data):
 24    """ Sortier-Funktion für Item.title"""
 25    obj_a = model[a][0]
 26    obj_b = model[b][0]
 27    return cmp(obj_a.title, obj_b.title)
 28
 29def my_title_search_func(model, column, key, iter):
 30    """ Such-Funktion für Item.title""" 
 31    obj = model[iter][0]
 32    return key not in obj.title
 33
 34
 35def my_num_cell_data_func(tree_column, cell, tree_model, iter, data):
 36    """ cell_data_func für Item.num"""
 37    obj = tree_model[iter][0]    # Objekt für aktuelle Zeile
 38    cell.set_property('text', str(obj.num))
 39
 40def my_num_sort_func(model, a, b, data):
 41    """ Sortier-Funktion für Item.num"""
 42    obj_a = model[a][0]
 43    obj_b = model[b][0]
 44    return cmp(obj_a.num, obj_b.num)
 45
 46
 47
 48## Fenster erzeugen
 49window = Gtk.Window()
 50window.set_default_size(200, 250)
 51window.connect("destroy", lambda w: Gtk.main_quit())
 52
 53## TreeView mit ListStore ins Fenster einfügen
 54lstore = Gtk.ListStore(Item)
 55treeview = Gtk.TreeView(model=lstore)
 56window.add(treeview)
 57
 58
 59## Titel-Spalte erzeugen:
 60
 61cellrenderer = Gtk.CellRendererText()
 62treeviewcolumn = Gtk.TreeViewColumn('Titel')
 63
 64# benutze my_title_cell_data_func
 65treeviewcolumn.set_cell_data_func(cellrenderer, my_title_cell_data_func)
 66
 67# CellRenderer zur Spalte, und Spalte zum TreeView hinzufügen
 68treeviewcolumn.pack_start(cellrenderer, True)
 69treeview.append_column(treeviewcolumn)
 70
 71# Sortierfunktion setzen
 72treeviewcolumn.set_sort_column_id(0)
 73lstore.set_sort_func(0,  my_title_sort_func)
 74
 75# Suchfunktion setzen
 76treeview.set_search_equal_func(my_title_search_func)
 77treeview.set_search_column(0)
 78
 79
 80## Num-Spalte erzeugen:
 81
 82cellrenderer = Gtk.CellRendererText()
 83treeviewcolumn = Gtk.TreeViewColumn('Num')
 84
 85# benutze my_num_cell_data_func
 86treeviewcolumn.set_cell_data_func(cellrenderer, my_num_cell_data_func)
 87
 88# CellRenderer zur Spalte, und Spalte zum TreeView hinzufügen
 89treeviewcolumn.pack_start(cellrenderer, True)
 90treeview.append_column(treeviewcolumn)
 91
 92# Sortierfunktion setzen
 93treeviewcolumn.set_sort_column_id(1)
 94lstore.set_sort_func(1,  my_num_sort_func)
 95
 96
 97## Ein paar Demo-Objekte ins Modell packen
 98for i in range(19):
 99    lstore.append((Item('This is {}'.format(i), i*i), ))
100
101
102# Alles anzeigen
103window.show_all()
104Gtk.main()