Objekt-Attribute in Gtk.TreeView anzeigen

 

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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#!/usr/bin/python3
# -*- coding: utf-8 -*-

from gi.repository import Gtk, GObject


class Item(GObject.GObject):
    """ Eine Klasse für unsere Daten"""
    def __init__(self, title, num):
        GObject.GObject.__init__(self)
        self.title = title
        self.num = num


def my_title_cell_data_func(tree_column, cell, tree_model, iter, data):
    """ Eine eigene cell_data_func für Item.title"""
    obj = tree_model[iter][0]    # Objekt für aktuelle Zeile
    cell.set_property('text', obj.title)

def my_num_cell_data_func(tree_column, cell, tree_model, iter, data):
    """ Eine eigene cell_data_func für Item.num"""
    obj = tree_model[iter][0]    # Objekt für aktuelle Zeile
    cell.set_property('text', str(obj.num))


# Fenster erzeugen
window = Gtk.Window()
window.set_default_size(200, 250)
window.connect("destroy", lambda w: Gtk.main_quit())

# TreeView mit ListStore ins Fenster einfügen
lstore = Gtk.ListStore(Item)
treeview = Gtk.TreeView(model=lstore)
window.add(treeview)


## Titel-Spalte erzeugen:

cellrenderer = Gtk.CellRendererText()
treeviewcolumn = Gtk.TreeViewColumn('Titel')

# benutze my_title_cell_data_func
treeviewcolumn.set_cell_data_func(cellrenderer, my_title_cell_data_func)

# CellRenderer zur Spalte, und Spalte zum TreeView hinzufügen
treeviewcolumn.pack_start(cellrenderer, True)
treeview.append_column(treeviewcolumn)


## Num-Spalte erzeugen:

cellrenderer = Gtk.CellRendererText()
treeviewcolumn = Gtk.TreeViewColumn('Num')

# benutze my_num_cell_data_func
treeviewcolumn.set_cell_data_func(cellrenderer, my_num_cell_data_func)

# CellRenderer zur Spalte, und Spalte zum TreeView hinzufügen
treeviewcolumn.pack_start(cellrenderer, True)
treeview.append_column(treeviewcolumn)



# Ein paar Demo-Objekte ins Modell packen
for i in range(19):
    lstore.append((Item('This is {}'.format(i), i*i), ))


# Alles anzeigen
window.show_all()
Gtk.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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#!/usr/bin/python3
# -*- coding: utf-8 -*-

from gi.repository import Gtk, GObject

def cmp(a, b):
    return (a > b) - (a < b)

class Item(GObject.GObject):
    """ Eine Klasse für unsere Daten"""
    def __init__(self, title, num):
        GObject.GObject.__init__(self)
        self.title = title
        self.num = num


def my_title_cell_data_func(tree_column, cell, tree_model, iter, data):
    """ Eine eigene cell_data_func für Item.title"""
    obj = tree_model[iter][0]    # Objekt für aktuelle Zeile
    cell.set_property('text', obj.title)

def my_title_sort_func(model, a, b, data):
    obj_a = model[a][0]
    obj_b = model[b][0]
    return cmp(obj_a.title, obj_b.title)


def my_num_cell_data_func(tree_column, cell, tree_model, iter, data):
    """ Eine eigene cell_data_func für Item.num"""
    obj = tree_model[iter][0]    # Objekt für aktuelle Zeile
    cell.set_property('text', str(obj.num))

def my_num_sort_func(model, a, b, data):
    obj_a = model[a][0]
    obj_b = model[b][0]
    return cmp(obj_a.num, obj_b.num)



# Fenster erzeugen
window = Gtk.Window()
window.set_default_size(200, 250)
window.connect("destroy", lambda w: Gtk.main_quit())

# TreeView mit ListStore ins Fenster einfügen
lstore = Gtk.ListStore(Item)
treeview = Gtk.TreeView(model=lstore)
window.add(treeview)


## Titel-Spalte erzeugen:

cellrenderer = Gtk.CellRendererText()
treeviewcolumn = Gtk.TreeViewColumn('Titel')

# benutze my_title_cell_data_func
treeviewcolumn.set_cell_data_func(cellrenderer, my_title_cell_data_func)

# CellRenderer zur Spalte, und Spalte zum TreeView hinzufügen
treeviewcolumn.pack_start(cellrenderer, True)
treeview.append_column(treeviewcolumn)

# Sortierfunktion setzen
treeviewcolumn.set_sort_column_id(0)
lstore.set_sort_func(0,  my_title_sort_func)


## Num-Spalte erzeugen:

cellrenderer = Gtk.CellRendererText()
treeviewcolumn = Gtk.TreeViewColumn('Num')

# benutze my_num_cell_data_func
treeviewcolumn.set_cell_data_func(cellrenderer, my_num_cell_data_func)

# CellRenderer zur Spalte, und Spalte zum TreeView hinzufügen
treeviewcolumn.pack_start(cellrenderer, True)
treeview.append_column(treeviewcolumn)

# Sortierfunktion setzen
treeviewcolumn.set_sort_column_id(1)
lstore.set_sort_func(1,  my_num_sort_func)


# Ein paar Demo-Objekte ins Modell packen
for i in range(19):
    lstore.append((Item('This is {}'.format(i), i*i), ))


# Alles anzeigen
window.show_all()
Gtk.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
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
#!/usr/bin/python3
# -*- coding: utf-8 -*-

from gi.repository import Gtk, GObject

def cmp(a, b):  # Praktisch für die Sortier-Funktionen
    return (a > b) - (a < b)

class Item(GObject.GObject):
    """ Eine Klasse für unsere Daten"""

    def __init__(self, title, num):
        GObject.GObject.__init__(self)
        self.title = title
        self.num = num


def my_title_cell_data_func(tree_column, cell, tree_model, iter, data):
    """ cell_data_func für Item.title"""
    obj = tree_model[iter][0]    # Objekt für aktuelle Zeile
    cell.set_property('text', obj.title)

def my_title_sort_func(model, a, b, data):
    """ Sortier-Funktion für Item.title"""
    obj_a = model[a][0]
    obj_b = model[b][0]
    return cmp(obj_a.title, obj_b.title)

def my_title_search_func(model, column, key, iter):
    """ Such-Funktion für Item.title""" 
    obj = model[iter][0]
    return key not in obj.title


def my_num_cell_data_func(tree_column, cell, tree_model, iter, data):
    """ cell_data_func für Item.num"""
    obj = tree_model[iter][0]    # Objekt für aktuelle Zeile
    cell.set_property('text', str(obj.num))

def my_num_sort_func(model, a, b, data):
    """ Sortier-Funktion für Item.num"""
    obj_a = model[a][0]
    obj_b = model[b][0]
    return cmp(obj_a.num, obj_b.num)



## Fenster erzeugen
window = Gtk.Window()
window.set_default_size(200, 250)
window.connect("destroy", lambda w: Gtk.main_quit())

## TreeView mit ListStore ins Fenster einfügen
lstore = Gtk.ListStore(Item)
treeview = Gtk.TreeView(model=lstore)
window.add(treeview)


## Titel-Spalte erzeugen:

cellrenderer = Gtk.CellRendererText()
treeviewcolumn = Gtk.TreeViewColumn('Titel')

# benutze my_title_cell_data_func
treeviewcolumn.set_cell_data_func(cellrenderer, my_title_cell_data_func)

# CellRenderer zur Spalte, und Spalte zum TreeView hinzufügen
treeviewcolumn.pack_start(cellrenderer, True)
treeview.append_column(treeviewcolumn)

# Sortierfunktion setzen
treeviewcolumn.set_sort_column_id(0)
lstore.set_sort_func(0,  my_title_sort_func)

# Suchfunktion setzen
treeview.set_search_equal_func(my_title_search_func)
treeview.set_search_column(0)


## Num-Spalte erzeugen:

cellrenderer = Gtk.CellRendererText()
treeviewcolumn = Gtk.TreeViewColumn('Num')

# benutze my_num_cell_data_func
treeviewcolumn.set_cell_data_func(cellrenderer, my_num_cell_data_func)

# CellRenderer zur Spalte, und Spalte zum TreeView hinzufügen
treeviewcolumn.pack_start(cellrenderer, True)
treeview.append_column(treeviewcolumn)

# Sortierfunktion setzen
treeviewcolumn.set_sort_column_id(1)
lstore.set_sort_func(1,  my_num_sort_func)


## Ein paar Demo-Objekte ins Modell packen
for i in range(19):
    lstore.append((Item('This is {}'.format(i), i*i), ))


# Alles anzeigen
window.show_all()
Gtk.main()