Satya's blog - Treeviews in PyGTk

Oct 10 2007 17:39 Treeviews in PyGTk

Let's say you want to display a nice list of cities, grouped by state, like so:

StateCity
FL 
 Miami
 Tampa
SC 
 Columbia

In a GUI window of a Python PyGTk app, of course. Let's also say that the list will have that little collapsing triangle next to each state, so you can close and open the sub-list of cities. As a bonus, let's make the columns sortable. What you need is GtkTreeView. What you want is something simpler. Sucks to be you.

Assuming you know how to do the rest, let's concentrate on the TreeView itself. It's sort-of MVC-ish, so your TreeView needs a TreeModel or rather, a TreeStore:

treestore=gtk.TreeStore(str,str)
for state in states:
    piter=treestore.append(None, [state['name'], None])
    for city in states['cities']:
        treestore.append(piter, [None, city])

So, we instantiate a treestore object and tell it that we want two string columns. Next we loop through the list of states. Each item is a dictionary, the name and the cities list. The line that starts piter= appends the state name and a blank cell to the root of the TreeStore (the first None). (piter is Parent Iter, by the way. This Treeview stuff talks a lot about iters and paths, which are both ways to address a cell in our table.) Then we loop through the cities, appending them to piter.

Now we set up a sortable tree model using this treestore, and a treeview using the sortable tree model:

tmsort = gtk.TreeModelSort(treestore) # produce a sortable treemodel
treeview = gtk.TreeView(tmsort) # the tree view

Now, for each column (State and City), we do the following:

  • instantiate a TreeViewColumn and add it to the treeview.
  • Instantiate a CellRendererText object (basically, a cell in the table) and add it to the column,
  • and bind the view's numbered column (i) to the TreeViewColumn object.

The last two lines make the column searchable and sortable.

tvcolumns={} # the columns
cells={} # the cells
i=0
for c in ('State','City'): # the actual column headers
    # instantiate TVC
    tvcolumns[c] = gtk.TreeViewColumn(c)

    # add to the treeview
    treeview.append_column(tvcolumns[c])

    # instantiate and add the cell object
    cells[c]=gtk.CellRendererText()
    tvcolumns[c].pack_start(cells[c], True) #add the cell to the column, allow it to expand

    # now set the cell's text attribute to the treeview's column i (0,1)
    tvcolumns[c].add_attribute(cells[c], 'text', i)

    #make it searchable and sortable
    treeview.set_search_column(i)
    tvcolumns[c].set_sort_column_id(i)
    i+=1

Whee! Now we just need to put this in a scrolled window in case it's too big for our containing widget:

treeview.show()
scrolled_window = gtk.ScrolledWindow()
scrolled_window.add_with_viewport(treeview)
scrolled_window.set_size_request(300,200)
scrolled_window.show()

Suppose we want to do something, like open a window showing the city weather, when a city is double-clicked. Let us connect a signal handler to the treeview (I believe it might be easier to connect one to each row or cell, but less efficient). The signal we want to look for is "row-activated". We want to call a method city_clicked, and presumably this is all in a class so we want *our* city-clicked method, i.e. self.city_clicked. We can pass in any extra data to this method, but there is None that we want right now.

treeview.connect("row-activated", self.city_clicked, None)

And that's it. That should show our nice grouped table when run (and added to a main window, etc).

But the most confusing and poorly documented bit for me was the signal handler, city_clicked. That's the reason for this blog post. It's a confusing maze of iters and paths and views, or it is if you believe the so-called tutorial. Here's how it really works:

def city_clicked(self, treeview, iter, tvc, foo):
    model=treeview.get_model()
    iter = model.get_iter(iter)
    city_name = model.get_value(iter, 1) # column id 1 contains the city name

The first parameter to city_clicked is self, because we're in a class, remember? The next two are the treeview object and the iter object that was activated. Next is tvc, the TreeViewColumn object for the cell that was clicked. The last one, foo, is the extra data, in this case None.

So we get the TreeView's underlying model. The iter param is usually just a tuple, so we convert it into a TreeIter or similar object using get_iter() on the model. Then we can use get_value() on the model and pass in the TreeIter, as well as the column number (which was the 'i' variable when we were building the TreeView). That nets us the content of that column from the activated row. Yay.

I should write a wrapper class for this stuff. It's way too much work as it stands.

Update: Or, you can use glade. It's a user interface layout designer thingy. See http://www.learningpython.com/...g-pygtk-and-glade/ On the other hand, all that does is build the interface, it will NOT populate your treeview. That's not a UI function -- not in the view, in MVC terms -- it's in the Model or Controller, i.e. in your python code, as above.

Last updated: Oct 13 2007 10:36

Tag: python pygtk howto