Trees and Tables
The tree view is the widget for structured data: a list of rows with columns, where the rows can be flat (a table) or hierarchical (a tree), and each cell is rendered by a cell renderer that maps a model value to the cell's appearance. The model-view separation is the architecture: the data lives in a TreeModel (the ListStore for the flat table, the TreeStore for the hierarchy), the TreeView displays it, and the columns are built from CellRenderers bound to the model's columns. This chapter covers the models (the ListStore and the TreeStore, their column types, the iteration), the view's construction (the columns, the renderers, the attributes), the selection, and the running example's file-list table.
The Models: ListStore and TreeStore
import gtk
# the ListStore - the FLAT table model:
# the constructor takes the Python types of each column
store = gtk.ListStore(str, int, bool)
# column 0: a string (the name), 1: an int (the size),
# 2: a bool (the selected flag)
# appending rows - one value per column, in order:
store.append(["report.pdf", 204800, True])
store.append(["notes.txt", 4200, False])
store.append(["image.png", 1048576, False])
# a row is a TreeIter - the handle for the row:
it = store[0] # the first row, by index
print(store[it][0]) # the column 0 value: "report.pdf"
store[it][1] = 999 # set a cell through the iter
# the TreeStore - the HIERARCHICAL model: same API, plus
# the parent argument for the nesting
tstore = gtk.TreeStore(str)
root = tstore.append(None, ["Documents"]) # top-level row
child = tstore.append(root, ["Projects"]) # nested under root
print(tstore[child][0]) # "Projects"
# iteration: the model's rows, in order
count = len(store)
path = store.get_path(store[1]) # the row's TreePath
The model is where the data lives, and the two standard models are the ListStore (the flat table - a list of rows) and the TreeStore (the hierarchy - rows that can have children). Both take the column types in the constructor (the Python types that define the columns: str, int, bool, or a custom type), and both append rows - the ListStore with the row's values, the TreeStore with a parent iter (None for a top-level row, a row's iter for a nested one). A row is addressed by a TreeIter (the handle) or a TreePath (the position), and the cells are read and written through the iter by column index. The model is pure data - it has no rendering, no selection, no view - and that separation is the architecture: the same model can back several views, and the view's job is to display the model's data through its renderers.
The View: Columns and Renderers
import gtk
# the TreeView - the display of a model:
view = gtk.TreeView(model=store)
view.set_headers_clickable(True) # the sort-on-click
# a column is: a renderer + the model attribute it displays
# the CellRendererText renders a string (or a formatted value)
name_renderer = gtk.CellRendererText()
name_col = gtk.TreeViewColumn("Name", name_renderer, text=0)
view.append_column(name_col)
# the size column: the int, rendered as text (the data_type
# attribute formats the value)
size_renderer = gtk.CellRendererText()
size_renderer.set_property("xalign", 1.0) # right-align
size_col = gtk.TreeViewColumn("Size", size_renderer, text=1)
view.append_column(size_col)
# the checkbox column: the bool, rendered as a toggle
check_renderer = gtk.CellRendererToggle()
check_col = gtk.TreeViewColumn("", check_renderer,
active=2)
view.append_column(check_col)
# the view in a scrolled window (the standard wrapper):
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
sw.add(view)
sw.set_size_request(400, 300)
# the column's "sort" is the model sort (below); the header
# click sorts when the column is sortable
The TreeView is the display, constructed with the model it shows, and its columns are the unit of the mapping: a TreeViewColumn pairs a CellRenderer (how a cell is drawn) with the model attribute it reads (the text=0 binds the renderer's text to column 0). The CellRendererText renders the string (or a formatted value from a non-string column), the CellRendererToggle renders the checkbox (the active attribute bound to the bool column), and the renderer's properties (the xalign for the alignment) shape the cell. The view is wrapped in a ScrolledWindow (the standard container that adds the scrollbars when the content exceeds the visible area), and the set_headers_clickable enables the sort-on-header-click. The architecture is the one to hold: the model is the data, the renderers are the per-cell display, the columns are the bindings, and the view is the arrangement - and a change to the model (an append, a cell write through the iter) is reflected in the view automatically, because the view observes the model.
Selection and the Row Signals
import gtk
# the selection: the view's selection object
sel = view.get_selection()
sel.set_mode(gtk.SELECTION_MULTIPLE) # SINGLE, BROWSE,
# MULTIPLE, EXTENDED
# read the selected rows:
def get_selected():
model, iters = sel.get_selected_rows()
return [(model[it][0], model[it][1]) for it in iters]
# the row signals:
def on_row_activated(view, path, column):
# the double-click (or the activater key) on a row:
print("activated row at", path)
view.connect("row-activated", on_row_activated)
def on_cursor_changed(sel):
# the selection changed:
model, iters = sel.get_selected_rows()
if iters:
print("selected:", model[iters[0]][0])
sel.connect("changed", on_cursor_changed)
# the programmatic selection:
sel.select_path(store.get_path(store[1]))
sel.unselect_all()
# the tree expansion (the TreeStore case):
view.set_level_indentation(16) # the per-level indent
view.expand_row(gtk.TreePath("[0]"), True) # expand a row
The selection is the view's get_selection() object, with the mode (the SINGLE, BROWSE, MULTIPLE, EXTENDED - the number and kind of rows selectable) and the read (get_selected_rows returns the model and the selected iters, from which the row data is read). The row signals are the interaction: the row-activated (the double-click or the activator key on a row - the "open this file" trigger in the file list) and the selection's changed (the selection changed - the hook for the "show the selected file's details" behavior). The programmatic selection - the select_path, the unselect_all - is the app selecting rows on the user's behalf (the search that highlights the match). For the TreeStore hierarchy, the expansion is the view's state - the set_level_indentation sets the per-level indent, and the expand_row/collapse_row open and close the children, which is the tree's reveal/hide control the table does not have.
The Running Example: The File List
import gtk
import os
def build_file_list(directory):
store = gtk.ListStore(str, str, int) # name, type, size
for entry in sorted(os.listdir(directory)):
full = os.path.join(directory, entry)
if os.path.isdir(full):
store.append([entry, "dir", 0])
else:
store.append([entry, "file", os.path.getsize(full)])
view = gtk.TreeView(model=store)
view.set_headers_clickable(True)
# the type column: a small icon next to the name
icon_render = gtk.CellRendererPixbuf()
name_render = gtk.CellRendererText()
name_col = gtk.TreeViewColumn("Name")
name_col.pack_start(icon_render, False)
name_col.pack_start(name_render, True)
# the icon is set per-row (the dir vs file icon)
for it in store:
icon_render.set_property("icon-name",
"folder" if it[1] == "dir" else "text-x-generic")
size_col = gtk.TreeViewColumn("Size",
gtk.CellRendererText(), text=2)
view.append_column(name_col)
view.append_column(size_col)
def on_activate(view, path, column):
it = store.get_iter(path)
open_path(os.path.join(directory, it[0]))
view.connect("row-activated", on_activate)
sw = gtk.ScrolledWindow()
sw.add(view)
return sw
# the file list is the composition: the model filled from the
# directory, the view with the icon+name and size columns,
# the row-activated as the open trigger, the scrolled window
# as the wrapper - the table pattern for any list-of-records UI
The running example is the table pattern applied to a file list: the ListStore filled from the directory (the name, the type, the size per entry), the view with two columns - the name column composed of a CellRendererPixbuf (the per-row icon, set through the iter loop) plus the CellRendererText (the name), and the size column bound to the int - and the row-activated signal as the open trigger (the double-click on a row opens the file). The pack_start on the column is the multi-renderer composition - a column can carry several renderers side by side (the icon plus the text), each bound to its data. The pattern generalizes to any list-of-records UI: the model filled from the data source, the columns as the renderers bound to the fields, the row signal as the action, the scrolled window as the wrapper. The tree view is the workhorse of the data-display UI, and this four-part structure is its standard form.
The Working Rules
- The model is the data, the view is the display. The
ListStore/TreeStoreholds the rows; theTreeViewrenders them. A model change is reflected in the view automatically - the view observes the model, and the two are not synced by hand. - The column types are declared in the model's constructor. The
str,int,boollist defines the columns, and the renderers bind to them by index - a type mismatch is a rendering failure, not a crash, which is why the declaration is the contract. - Wrap the view in a
ScrolledWindow. The tree view is the content, the scrolled window is the scrollable frame - the standard pair for any list that may exceed the visible area. - Resolve the row by the
TreePathor the iter from the signal, not a cached index. Therow-activatedand the selection signals carry the path/iter; the cached row number is the wrong-row bug after an insert or a remove. - The multi-renderer column is the icon-plus-text composition. The
pack_starton the column adds the renderers side by side - the file list's icon-and-name is the canonical use, and each renderer binds to its own data.
Summary
The tree view is the structured-data widget, and its architecture is the model-view separation: the ListStore (the flat table) or the TreeStore (the hierarchy) holds the rows, typed by the column list in the constructor, addressed by the TreeIter and the TreePath; the TreeView displays the model, its columns pairing a CellRenderer (the Text for the values, the Toggle for the checkboxes, the Pixbuf for the icons) with the model attribute by index; and the view is wrapped in the ScrolledWindow. The selection object reads and sets the selected rows (the mode controlling how many), the row-activated signal is the row's action trigger, and the TreeStore adds the expansion control. The file-list example is the standard composition - the model from the data, the icon-plus-name and size columns, the row-activated as the open trigger - and the rules keep it correct: the model as the data the view observes, the row resolved by the signal's path not a cached index, and the column types declared as the contract.