The TreeModel interface is implemented by
all the TreeModel subclasses and provides methods
to:
TreeIter (a transient
reference) that points at a row in the modelTreeModel
data changesThe base data store classes: ListStore and
TreeStore provide the means to define and manage the
rows and columns of data in the tree model. The constructors of both these
objects require the column types to be specified as any of:
Button,
VBox, gdk.Rectangle,
gdk.PixbufGObject types (GTK+ GTypes)
specified either as GObject Type constants or as strings. Most GTypes are
mapped to a Python type:
For example to create a ListStore or
TreeStore with rows containing a
gdk.Pixbuf, an integer, a string and boolean you
could do something like:
liststore = ListStore(gtk.gdk.Pixbuf, int, str, 'gboolean') treestore = TreeStore(gtk.gdk.Pixbuf, int, str, 'gboolean')
Once a ListStore or
TreeStore is created and its columns defined, they
cannot be changed or modified. It's also important to realize that there is
no preset relation between the columns in a TreeView
and the columns of its TreeModel. That is, the fifth
column of data in a TreeModel may be displayed in the
first column of one TreeView and in the third column
in another. So you don't have to worry about how the data will be displayed
when creating the data store.
If these two data stores do not fit your application it is possible to define your own custom data store in Python as long as it implements the TreeModel interface. I'll talk more about this later in Section 14.11, “The Generic TreeModel”.
Before we can talk about managing the data rows in a
TreeStore or ListStore we need
a way of specifying which row we want to deal with. PyGTK has three ways of
referring to TreeModel rows: a tree path, a
TreeIter and a
TreeRowReference.
A tree path is a int, string or tuple representation of the
location of a row in the store. An int value specifies the top level row in
the model starting from 0. For example, a tree path value of 4 would specify
the fifth row in the store. By comparison, a string representation of the
same row would be "4" and the tuple representation would be (4,). This is
sufficient for specifying any row in a ListStore but
for a TreeStore we have to be able to represent the child rows. For these
cases we have to use either the string or tuple representations.
Since a TreeStore can have an arbitrarily
deep hierarchy the string representation specifies the path from the top
level to the designated row using ints separated by the ":"
character. Similarly, the tuple representation specifies the tree path
starting from the top level to the row as a sequence of ints. For example,
valid tree path string representations are: "0:2" (specifies the row that is
the third child of the first row) and "4:0:1" (specifies the row that is the
second child of the first child of the fifth row). By comparison the same
tree paths are represented by the tuples (0, 2) and (4, 0, 1)
respectively.
A tree path provides the only way to map from a
TreeView row to a TreeModel
row because the tree path of a TreeView row is the
same as the tree path of the corresponding TreeModel
row. There are also some problems with tree paths:
ListStore or
TreeStore.PyGTK uses the tuple representation when returning tree paths but will accept any of the three forms for a tree path representation. You should use the tuple representation for a tree path for consistency.
A tree path can be retrieved from a
TreeIter using the
get_path() method:
path = store.get_path(iter)
where iter is a
TreeIter pointing at a row in store and
path is the row's tree path as a tuple.
A TreeIter is an object that provides a
transient reference to a ListStore or
TreeStore row. If the contents of the store change
(usually because a row is added or deleted) the
TreeIters can become invalid. A
TreeModel that supports persistent TreeIters should
set the gtk.TREE_MODEL_ITERS_PERSIST flag. An application
can check for this flag using the get_flags()
method.
A TreeIter is created by one of the
TreeModel methods that are applicable to both
TreeStore and ListStore
objects:
treeiter = store.get_iter(path)
where treeiter points at the row at the
tree path path. The ValueError exception is raised if
the tree path is invalid.
treeiter = store.get_iter_first()
where treeiter is a TreeIter pointing
at the row at tree path (0,). treeiter will be
None if the store is empty.
treeiter = store.iter_next(iter)
where treeiter is a
TreeIter that points at the next row at the same
level as the TreeIter specified by
iter. treeiter will be
None if there is no next row (iter
is also invalidated).
The following methods are useful only for retrieving a
TreeIter from a
TreeStore:
treeiter = treestore.iter_children(parent)
where treeiter is a
TreeIter pointing at the first child row of the row
specified by the TreeIter
parent. treeiter will be
None if there is no child.
treeiter = treestore.iter_nth_child(parent,n)
where treeiter is a
TreeIter pointing at the child row (with the index
n) of the row specified by the
TreeIter
parent. parent may be
None to retrieve a top level
row. treeiter will be None if
there is no child.
treeiter = treestore.iter_parent(child)
where treeiter is a
TreeIter pointing at the parent row of the row
specified by the TreeIter
child. treeiter will be None if
there is no child.
A tree path can be retrieved from a
TreeIter using the
get_path() method:
path = store.get_path(iter)
where iter is a
Treeiter pointing at a row in store and
path is the row's tree path as a tuple.
A TreeRowReference is a persistent
reference to a row of data in a store. While the tree path (i.e. the
location) of the row might change as rows are added to or deleted from the
store, the TreeRowReference will point at the same
data row as long as it exists.
TreeRowReferences are only available in
PyGTK 2.4 and above.
You can create a TreeRowReference using
its constructor:
treerowref = TreeRowReference(model,path)
where model is the
TreeModel containing the row and
path is the tree path of the row to track. If
path isn't a valid tree path for
model, None is returned.
Once you have a ListStore you'll need to
add data rows using one of the following methods:
iter = append(row=None) iter = prepend(row=None) iter = insert(position,row=None) iter = insert_before(sibling,row=None) iter = insert_after(sibling,row=None)
Each of these methods inserts a row at an implied or specified
position in the ListStore. The
append() and prepend()
methods use implied positions: after the last row and before the first row,
respectively. The insert() method takes an integer
(the parameter position) that specifies the location
where the row will be inserted. The other two methods take a
TreeIter (sibling) that
references a row in the ListStore to insert the row
before or after.
The row parameter specifies the data that
should be inserted in the row after it is created. If
row is None or not specified, an
empty row will be created. If row is specified it
must be a tuple or list containing as many items as the number of columns in
the ListStore. The items must also match the data
type of their respective ListStore columns.
All methods return a TreeIter that points
at the newly inserted row. The following code fragment illustrates the
creation of a ListStore and the addition of data rows
to it:
...
liststore = gtk.ListStore(int, str, gtk.gdk.Color)
liststore.append([0,'red',colormap.alloc_color('red')])
liststore.append([1,'green',colormap.alloc_color('green')])
iter = liststore.insert(1, (2,'blue',colormap.alloc_color('blue')) )
iter = liststore.insert_after(iter, [3,'yellow',colormap.alloc_color('blue')])
...
Adding a row to a TreeStore is similar to
adding a row to a ListStore except that you also have
to specify a parent row (using a TreeIter) to add the
new row to. The TreeStore methods are:
iter = append(parent,row=None) iter = prepend(parent,row=None) iter = insert(parent,position,row=None) iter = insert_before(parent,sibling,row=None) iter = insert_after(parent,sibling,row=None)
If parent is None, the
row will be added to the top level rows.
Each of these methods inserts a row at an implied or specified
position in the TreeStore. The
append() and prepend()
methods use implied positions: after the last child row and before the first
child row, respectively. The insert() method takes
an integer (the parameter position) that specifies
the location where the child row will be inserted. The other two methods
take a TreeIter (sibling) that
references a child row in the TreeStore to insert the
row before or after.
The row parameter specifies the data that
should be inserted in the row after it is created. If
row is None or not specified, an
empty row will be created. If row is specified it
must be a tuple or list containing as many items as the number of columns in
the TreeStore. The items must also match the data
type of their respective TreeStore columns.
All methods return a TreeIter that points
at the newly inserted row. The following code fragment illustrates the
creation of a TreeStore and the addition of data rows
to it:
...
folderpb = gtk.gdk.pixbuf_from_file('folder.xpm')
filepb = gtk.gdk.pixbuf_from_file('file.xpm')
treestore = gtk.TreeStore(int, str, gtk.gdk.Pixbuf)
iter0 = treestore.append(None, [1,'(0,)',folderpb] )
treestore.insert(iter0, 0, [11,'(0,0)',filepb])
treestore.append(iter0, [12,'(0,1)',filepb])
iter1 = treestore.insert_after(None, iter0, [2,'(1,)',folderpb])
treestore.insert(iter1, 0, [22,'(1,1)',filepb])
treestore.prepend(iter1, [21,'(1,0)',filepb])
...
When a ListStore or
TreeStore contains a large number of data rows,
adding new rows can become very slow. There are a few things that you can do
to mitigate this problem:
TreeModel from its TreeView
(using the set_model() method with the
model parameter set to None) to
avoid TreeView updates for each row
entered.set_default_sort_func() method with the
sort_func set to None) while
adding a large number of rows.TreeRowReferences in use since they update their path
with each addition or removal.TreeView
"fixed-height-mode" property to TRUE making all rows have
the same height and avoiding the individual calculation of the height of
each row. Only available in PyGTK 2.4 and above.You can remove a data row from a
ListStore by using the
remove() method:
treeiter = liststore.remove(iter)
where iter is a
TreeIter pointing at the row to remove. The returned
TreeIter (treeiter) points at
the next row or is invalid if iter was pointing at
the last row.
The clear() method removes all rows
from the ListStore:
liststore.clear()
The methods for removing data rows from a
TreeStore are similar to the
ListStore methods:
result = treestore.remove(iter)
treestore.clear()
where result is TRUE
if the row was removed and iter points at the next
valid row. Otherwise, result is
FALSE and iter is
invalidated.
The methods for accessing the data values in a
ListStore and TreeStore have
the same format. All store data manipulations use a
TreeIter to specify the row that you are working
with. Once you have a TreeIter it can be used to
retrieve the values of a row column using the
get_value() method:
value = store.get_value(iter,column)
where iter is a
TreeIter pointing at a row,
column is a column number in
store, and, value is the value
stored at the row-column location.
If you want to retrieve the values from multiple columns in
one call use the get() method:
values = store.get(iter, column, ...)
where iter is a
TreeIter pointing at a row,
column is a column number in
store, and, ... represents
zero or more additional column numbers and values is
a tuple containing the retrieved data values. For example to retrieve the
values in columns 0 and 2:
val0, val2 = store.get(iter, 0, 2)
The get() method is only available
in PyGTK 2.4 and above.
Setting a single column value is effected using the
set_value() method:
store.set_value(iter,column,value)
where iter (a
TreeIter) and column (an int)
specify the row-column location in store and
column is the column number where
value is to be set. value must
be the same data type as the store column.
If you wish to set the value of more than one column in a row
at a time, use the set() method:
store.set(iter,...)
where iter specifies the store row and
... is one or more column number - value pairs
indicating the column and and value to set. For example, the following
call:
store.set(iter, 0, 'Foo', 5, 'Bar', 1, 123)
sets the first column to 'Foo', the sixth column to 'Bar' and
the second column to 123 in the store row specified
by iter.
Individual ListStore rows can be moved
using one of the following methods that are available in PyGTK 2.2 and
above:
liststore.swap(a,b) liststore.move_after(iter,position) liststore.move_before(iter,position)
swap() swaps the locations of the rows
referenced by the TreeIters a
and b. move_after() and
move_before() move the row referenced by the
TreeIter iter after or before
the row referenced by the TreeIter
position. If position is
None, move_after() will place
the row at the beginning of the store while
move_before(), at the end of the store.
If you want to completely rearrange the
ListStore data rows, use the following method:
liststore.reorder(new_order)
where new_order is a list of integers
that specify the new row order. The child nodes will be rearranged so
that the liststore node that is at position
index new_order[i] will be
located at position index i.
For example, if liststore contained
four rows:
'one' 'two' 'three' 'four'
The method call:
liststore.reorder([2, 1, 3, 0])
would produce the resulting order:
'three' 'two' 'four' 'one'
These methods will only rearrange unsorted
ListStores.
If you want to rearrange rows in PyGTK 2.0 you have to remove and insert rows using the methods described in Section 14.2.4, “Adding Rows” and Section 14.2.5, “Removing Rows”.
The methods used to rearrange TreeStore
rows are similar to the ListStore methods except they
only affect the child rows of an implied parent row - it is not possible to,
say, swap rows with different parent rows.:
treestore.swap(a,b) treestore.move_after(iter,position) treestore.move_before(iter,position)
swap() swaps the locations of the child
rows referenced by the TreeIters a and
b. a and
b must both have the same parent
row. move_after() and
move_before() move the row referenced by the
TreeIter iter after or before
the row referenced by the TreeIter
position. iter and
position must both have the same parent row. If
position is None,
move_after() will place the row at the beginning of
the store while move_before(), at the end of the
store.
The reorder() method requires an
additional parameter specifying the parent row whose child rows will be
reordered:
treestore.reorder(parent,new_order)
where new_order is a list of integers
that specify the new child row order of the parent row specified by the
TreeIter parent as:
new_order[newpos] = oldpos
For example, if treestore contained
four rows:
'parent'
'one'
'two'
'three'
'four'
The method call:
treestore.reorder(parent, [2, 1, 3, 0])
would produce the resulting order:
'parent'
'three'
'two'
'four'
'one'
These methods will only rearrange unsorted
TreeStores.
One of the trickier aspects of dealing with
ListStores and TreeStores is
the operation on multiple rows, e.g. moving multiple rows, say, from one
parent row to another or removing rows based on certain criteria. The
difficulty arises from the need to use a TreeIter
that may become invalid as the result of the operation. For
ListStores and TreeStores the
TreeIters are persistent as can be checked by using the
get_flags() method and testing for the
gtk.TREE_MODEL_ITERS_PERSIST flag. However the stackable
TreeModelFilter and
TreeModelSort classes do not have persistent
TreeIters.
Assuming that TreeIters don't persist how
do we move all the child rows from one parent row to another? We have
to:
We can't rely on the remove() method to
return a valid TreeIter so we'll just ask for the
first child iter until it returns None. A possible
function to move child rows is:
def move_child_rows(treestore, from_parent, to_parent):
n_columns = treestore.get_n_columns()
iter = treestore.iter_children(from_parent)
while iter:
values = treestore.get(iter, *range(n_columns))
treestore.remove(iter)
treestore.append(to_parent, values)
iter = treestore.iter_children(from_parent)
return
The above function covers the simple case of moving all child
rows of a single parent row but what if you want to remove all rows in the
TreeStore based on some match criteria, say the first
column value? Here you might think that you could use the
foreach() method to iterate over all the rows and
remove the matching ones:
store.foreach(func,user_data)
where func is a function that is
invoked for each store row and has the signature:
def func(model,path,iter,user_data):
where model is the
TreeModel data store, path is
the tree path of a row in model,
iter is a TreeIter pointing at
path and user_data is the
passed in data. if func returns
TRUE the foreach() method will
cease iterating and return.
The problem with that is that changing the contents of the store
while the foreach() method is iterating over it may
have unpredictable results. Using the foreach()
method to create and save TreeRowReferences to the rows to be removed and
then removing them after the foreach() method
completes would be a good strategy except that it doesn't work for PyGTK 2.0
and 2.2 where TreeRowReferences are not
available.
A reliable strategy that covers all the PyGTK variants is to
use the foreach() method to gather the tree paths
of rows to be removed and then remove them in reverse order to preserve the
validity of the tree paths. An example code fragment utilizing this
strategy is:
...
# match if the value in the first column is >= the passed in value
# data is a tuple containing the match value and a list to save paths
def match_value_cb(model, path, iter, data):
if model.get_value(iter, 0) >= data[0]:
data[1].append(path)
return False # keep the foreach going
pathlist = []
treestore.foreach(match_value_cb, (10, pathlist))
# foreach works in a depth first fashion
pathlist.reverse()
for path in pathlist:
treestore.remove(treestore.get_iter(path))
...
If you want to search a TreeStore for the
first row that matches some criteria, you probably want to do the iteration
yourself using something like:
treestore = TreeStore(str)
...
def match_func(model, iter, data):
column, key = data # data is a tuple containing column number, key
value = model.get_value(iter, column)
return value == key
def search(model, iter, func, data):
while iter:
if func(model, iter, data):
return iter
result = search(model, model.iter_children(iter), func, data)
if result: return result
iter = model.iter_next(iter)
return None
...
match_iter = search(treestore, treestore.iter_children(None),
match_func, (0, 'foo'))
The search() function iterates recursively
over the row (specified by iter) and its siblings and
their child rows in a depth first fashion looking for a row that has a
column matching the given key string. The search terminates when a row is
found.
The classes that implement the TreeModel
interface (TreeStore and
ListStore and in PyGTK 2.4, also the
TreeModelSort and
TreeModelFilter) support the Python mapping and
iterator protocols. The iterator protocol allows you to use the Python
iter() function on a TreeModel
to create an iterator to be used to iterate over the top level rows in the
TreeModel. A more useful capability is to iterate
using the for statement or a list comprehension. For
example:
...
liststore = gtk.ListStore(str, str)
...
# add some rows to liststore
...
# for looping
for row in liststore:
# do individual row processing
...
# list comprehension returning a list of values in the first column
values = [ r[0] for r in liststore ]
...
Other parts of the mapping protocols that are supported are using
del to delete a row in the model and extracting a PyGTK
TreeModelRow from the model using a key value that is
a tree path or TreeIter. For example, the following
statements all return the first row in a TreeModel
and the final statement deletes the first child row of the first row:
row = model[0] row = model['0'] row = model["0"] row = model[(0,)] i = model.get_iter(0) row = model[i] del model[(0,0)]
In addition, you can set the values in an existing row similar to the following:
...
liststore = gtk.ListStore(str, int, object)
...
liststore[0] = ['Button', 23, gtk.Button('Label')]
A PyGTK TreeModelRow object supports the
Python sequence and iterator protocols. You can get an iterator to iterate
over the column values in the row or use the for statement or list
comprehension as well. A TreeModelRow uses the column
number as the index to extract a value. For example:
...
liststore = gtk.ListStore(str, int)
liststore.append(['Random string', 514])
...
row = liststore[0]
value1 = row[1]
value0 = liststore['0'][0]
for value in row:
print value
val0, val1 = row
...
Using the example from the previous section to iterate over a
TreeStore to locate a row containing a particular
value, the code becomes:
treestore = TreeStore(str)
...
def match_func(row, data):
column, key = data # data is a tuple containing column number, key
return row[column] == key
...
def search(rows, func, data):
if not rows: return None
for row in rows:
if func(row, data):
return row
result = search(row.iterchildren(), func, data)
if result: return result
return None
...
match_row = search(treestore, match_func, (0, 'foo'))
You can also set a value in an existing column using:
treestore[(1,0,1)][1] = 'abc'
The TreeModelRow also supports the
del statement and conversion to lists and tuples using
the Python list() and tuple()
functions. As illustrated in the above example the
TreeModelRow has the
iterchildren() method that returns an iterator for
iterating over the child rows of the
TreeModelRow.
Your application can track changes in a
TreeModel by connecting to the signals that are
emitted by the TreeModel: "row-changed",
"row-deleted", "row-inserted", "row-has-child-toggled" and
"rows-reordered". These signals are used by a
TreeView to track changes in its
TreeModel.
If you connect to these signals in your application, you may see clusters of signals when some methods are called. For example the call to add the first child row to a parent row:
treestore.append(parent, ['qwe', 'asd', 123])
will cause the following signal emissions:
parent didn't previously have any child
rows.Note that you can't retrieve the row order in the "rows-reordered" callback since the new row order is passed as an opaque pointer to an array of integers.
See the PyGTK
Reference Manual for more information on the
TreeModel signals.
The ListStore and
TreeStore objects implement the
TreeSortable interface that provides methods for
controlling the sorting of TreeModel rows. The key
element of the interface is a "sort column ID" which is an arbitrary integer
value referring to a sort comparison function and associated user data. A
sort column ID must be greater than or equal to zero. A sort column ID is
created by using the method:
treesortable.set_sort_func(sort_column_id,sort_func,user_data=None)
where sort_column_id is a programmer
assigned integer value, sort_func is a function or
method used to compare rows and user_data is context
data. sort_func has the signature:
def sort_func_function(model, iter1, iter2, data) def sort_func_method(self, model, iter1, iter2, data)
where model is the
TreeModel containing the rows pointed to by the
TreeIters iter1 and
iter2 and data is
user_data. sort_func should
return: -1 if the iter1 row should precede the
iter2 row; 0, if the rows are equal; and, 1 if the
iter2 row should precede the
iter1 row. The sort comparison function should always
assume that the sort order is gtk.SORT_ASCENDING as the
sort order will be taken into account by the
TreeSortable implementations.
The same sort comparison function can be used for multiple sort
column IDs by varying the user_data to provide context information. For
example, the user_data specified in the
set_sort_func() method could be the index of the
column to extract the sort data from.
Once a sort column ID is created a store can use it for sorting by calling the method:
treesortable.set_sort_column_id(sort_column_id,order)
where order is the sort order either
gtk.SORT_ASCENDING or
gtk.SORT_DESCENDING.
The sort column ID of -1 means that the store should use the default sort function that is set using the method:
treesortable.set_default_sort_func(sort_func,user_data=None)
You can check if a store has a default sort function using the method:
result = treesortable.has_default_sort_func()
which returns TRUE if a default sort function
has been set.
Once a sort column ID has been set on a
TreeModel implementing the
TreeSortable interface it cannot be returned to the
original unsorted state. You can change the sort function or use a default
sort function but you cannot set the TreeModel to
have no sort function.
When a ListStore or
TreeStore object is created it automatically sets up
sort column IDs corresponding to the columns in the store using the column
index number. For example, a ListStore with three
columns would have three sort column IDs (0, 1, 2) setup
automatically. These sort column IDs are associated with an internal sort
comparison function that handles the fundamental types:
Initially a ListStore or
TreeStore is set with a sort column ID of -2 that
indicates that no sort function is being used and that the store is
unsorted. Once you set a sort column ID on a
ListStore or TreeStore you
cannot set it back to -2.
If you want to maintain the default sort column IDs you can set up a sort column ID well out of the range of the number of columns such as 1000 and up. Then you can switch between the default sort function and your application sort functions as needed.