Tree and List Widgets ===================== A :class:`Gtk.TreeView` and its associated widgets are an extremely powerful way of displaying data. They are used in conjunction with a :class:`Gtk.ListStore` or :class:`Gtk.TreeStore` and provide a way of displaying and manipulating data in many ways, including: * Automatic updates when data is added, removed or edited * Drag and drop support * Sorting data * Embedding widgets such as check boxes, progress bars, etc. * Reorderable and resizable columns * Filtering data With the power and flexibility of a :class:`Gtk.TreeView` comes complexity. It is often difficult for beginner developers to be able to utilize it correctly due to the number of methods which are required. The Model --------- Each :class:`Gtk.TreeView` has an associated :class:`Gtk.TreeModel`, which contains the data displayed by the TreeView. Each :class:`Gtk.TreeModel` can be used by more than one :class:`Gtk.TreeView`. For instance, this allows the same underlying data to be displayed and edited in 2 different ways at the same time. Or the 2 Views might display different columns from the same Model data, in the same way that 2 SQL queries (or "views") might show different fields from the same database table. Although you can theoretically implement your own Model, you will normally use either the :class:`Gtk.ListStore` or :class:`Gtk.TreeStore` model classes. :class:`Gtk.ListStore` contains simple rows of data, and each row has no children, whereas :class:`Gtk.TreeStore` contains rows of data, and each row may have child rows. When constructing a model you have to specify the data types for each column the model holds. .. code-block:: python store = Gtk.ListStore(str, str, float) This creates a list store with three columns, two string columns, and a float column. Adding data to the model is done using :meth:`Gtk.ListStore.append` or :meth:`Gtk.TreeStore.append`, depending upon which sort of model was created. For a :class:`Gtk.ListStore`: .. code-block:: python treeiter = store.append(["The Art of Computer Programming", "Donald E. Knuth", 25.46]) For a :class:`Gtk.TreeStore` you must specify an existing row to append the new row to, using a :class:`Gtk.TreeIter`, or None for the top level of the tree: .. code-block:: python treeiter = store.append(None, ["The Art of Computer Programming", "Donald E. Knuth", 25.46]) Both methods return a :class:`Gtk.TreeIter` instance, which points to the location of the newly inserted row. You can retrieve a :class:`Gtk.TreeIter` by calling :meth:`Gtk.TreeModel.get_iter`. Once data has been inserted, you can retrieve or modify data using the tree iter and column index. .. code-block:: python print(store[treeiter][2]) # Prints value of third column store[treeiter][2] = 42.15 As with Python's built-in :class:`list` object you can use :func:`len` to get the number of rows and use slices to retrieve or set values. .. code-block:: python # Print number of rows print(len(store)) # Print all but first column print(store[treeiter][1:]) # Print last column print(store[treeiter][-1]) # Set last two columns store[treeiter][1:] = ["Donald Ervin Knuth", 41.99] Iterating over all rows of a tree model is very simple as well. .. code-block:: python for row in store: # Print values of all columns print(row[:]) Keep in mind, that if you use :class:`Gtk.TreeStore`, the above code will only iterate over the rows of the top level, but not the children of the nodes. To iterate over all rows, use :meth:`Gtk.TreeModel.foreach`. .. code-block:: python def print_row(store, treepath, treeiter): print("\t" * (treepath.get_depth() - 1), store[treeiter][:], sep="") store.foreach(print_row) Apart from accessing values stored in a :class:`Gtk.TreeModel` with the list-like method mentioned above, it is also possible to either use :class:`Gtk.TreeIter` or :class:`Gtk.TreePath` instances. Both reference a particular row in a tree model. One can convert a path to an iterator by calling :meth:`Gtk.TreeModel.get_iter`. As :class:`Gtk.ListStore` contains only one level, i.e. nodes do not have any child nodes, a path is essentially the index of the row you want to access. .. code-block:: python # Get path pointing to 6th row in list store path = Gtk.TreePath(5) treeiter = liststore.get_iter(path) # Get value at 2nd column value = liststore.get_value(treeiter, 1) In the case of :class:`Gtk.TreeStore`, a path is a list of indexes or a string. The string form is a list of numbers separated by a colon. Each number refers to the offset at that level. Thus, the path "0" refers to the root node and the path "2:4" refers to the fifth child of the third node. .. code-block:: python # Get path pointing to 5th child of 3rd row in tree store path = Gtk.TreePath([2, 4]) treeiter = treestore.get_iter(path) # Get value at 2nd column value = treestore.get_value(treeiter, 1) Instances of :class:`Gtk.TreePath` can be accessed like lists, i.e. ``len(treepath)`` returns the depth of the item ``treepath`` is pointing to, and ``treepath[i]`` returns the child's index on the *i*-th level. The View -------- While there are several different models to choose from, there is only one view widget to deal with. It works with either the list or the tree store. Setting up a :class:`Gtk.TreeView` is not a difficult matter. It needs a :class:`Gtk.TreeModel` to know where to retrieve its data from, either by passing it to the :class:`Gtk.TreeView` constructor, or by calling :meth:`Gtk.TreeView.set_model`. .. code-block:: python tree = Gtk.TreeView(model=store) Once the :class:`Gtk.TreeView` widget has a model, it will need to know how to display the model. It does this with columns and cell renderers. :data:`headers_visible ` controls whether it displays column headers. Cell renderers are used to draw the data in the tree model in a specific way. There are a number of cell renderers that come with GTK+, for instance :class:`Gtk.CellRendererText`, :class:`Gtk.CellRendererPixbuf` and :class:`Gtk.CellRendererToggle`. In addition, it is relatively easy to write a custom renderer yourself by subclassing a :class:`Gtk.CellRenderer`, and adding properties with :func:`GObject.Property`. A :class:`Gtk.TreeViewColumn` is the object that :class:`Gtk.TreeView` uses to organize the vertical columns in the tree view and hold one or more cell renderers. Each column may have a :data:`title ` that will be visible if the :class:`Gtk.TreeView` is showing column headers. The model is mapped to the column by using keyword arguments with properties of the renderer as the identifiers and indexes of the model columns as the arguments. .. code-block:: python renderer = Gtk.CellRendererPixbuf() column = Gtk.TreeViewColumn(cell_renderer=renderer, icon_name=3) tree.append_column(column) Positional arguments can be used for the column title and renderer. .. code-block:: python renderer = Gtk.CellRendererText() column = Gtk.TreeViewColumn("Title", renderer, text=0, weight=1) tree.append_column(column) To render more than one model column in a view column, you need to create a :class:`Gtk.TreeViewColumn` instance and use :meth:`Gtk.TreeViewColumn.pack_start` to add the model columns to it. .. code-block:: python column = Gtk.TreeViewColumn("Title and Author") title = Gtk.CellRendererText() author = Gtk.CellRendererText() column.pack_start(title, True) column.pack_start(author, True) column.add_attribute(title, "text", 0) column.add_attribute(author, "text", 1) tree.append_column(column) The Selection ------------- Most applications will need to not only deal with displaying data, but also receiving input events from users. To do this, simply get a reference to a selection object and connect to the "changed" signal. .. code-block:: python select = tree.get_selection() select.connect("changed", on_tree_selection_changed) Then to retrieve data for the row selected: .. code-block:: python def on_tree_selection_changed(selection): model, treeiter = selection.get_selected() if treeiter is not None: print("You selected", model[treeiter][0]) You can control what selections are allowed by calling :meth:`Gtk.TreeSelection.set_mode`. :meth:`Gtk.TreeSelection.get_selected` does not work if the selection mode is set to :attr:`Gtk.SelectionMode.MULTIPLE`, use :meth:`Gtk.TreeSelection.get_selected_rows` instead. Sorting ------- Sorting is an important feature for tree views and is supported by the standard tree models (:class:`Gtk.TreeStore` and :class:`Gtk.ListStore`), which implement the :class:`Gtk.TreeSortable` interface. Sorting by clicking on columns ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A column of a :class:`Gtk.TreeView` can easily made sortable with a call to :meth:`Gtk.TreeViewColumn.set_sort_column_id`. Afterwards the column can be sorted by clicking on its header. First we need a simple :class:`Gtk.TreeView` and a :class:`Gtk.ListStore` as a model. .. code-block:: python model = Gtk.ListStore(str) model.append(["Benjamin"]) model.append(["Charles"]) model.append(["alfred"]) model.append(["Alfred"]) model.append(["David"]) model.append(["charles"]) model.append(["david"]) model.append(["benjamin"]) treeView = Gtk.TreeView(model=model) cellRenderer = Gtk.CellRendererText() column = Gtk.TreeViewColumn("Title", renderer, text=0) The next step is to enable sorting. Note that the *column_id* (``0`` in the example) refers to the column of the model and **not** to the TreeView's column. .. code-block:: python column.set_sort_column_id(0) Setting a custom sort function ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ It is also possible to set a custom comparison function in order to change the sorting behaviour. As an example we will create a comparison function that sorts case-sensitive. In the example above the sorted list looked like:: alfred Alfred benjamin Benjamin charles Charles david David The case-sensitive sorted list will look like:: Alfred Benjamin Charles David alfred benjamin charles david First of all a comparison function is needed. This function gets two rows and has to return a negative integer if the first one should come before the second one, zero if they are equal and a positive integer if the second one should come before the first one. .. code-block:: python def compare(model, row1, row2, user_data): sort_column, _ = model.get_sort_column_id() value1 = model.get_value(row1, sort_column) value2 = model.get_value(row2, sort_column) if value1 < value2: return -1 elif value1 == value2: return 0 else: return 1 Then the sort function has to be set by :meth:`Gtk.TreeSortable.set_sort_func`. .. code-block:: python model.set_sort_func(0, compare, None) Filtering --------- Unlike sorting, filtering is not handled by the two models we previously saw, but by the :class:`Gtk.TreeModelFilter` class. This class, like :class:`Gtk.TreeStore` and :class:`Gtk.ListStore`, is a :class:`Gtk.TreeModel`. It acts as a layer between the "real" model (a :class:`Gtk.TreeStore` or a :class:`Gtk.ListStore`), hiding some elements to the view. In practice, it supplies the :class:`Gtk.TreeView` with a subset of the underlying model. Instances of :class:`Gtk.TreeModelFilter` can be stacked one onto another, to use multiple filters on the same model (in the same way you'd use "AND" clauses in a SQL request). They can also be chained with :class:`Gtk.TreeModelSort` instances. You can create a new instance of a :class:`Gtk.TreeModelFilter` and give it a model to filter, but the easiest way is to spawn it directly from the filtered model, using the :meth:`Gtk.TreeModel.filter_new` method. .. code-block:: python filter = model.filter_new() In the same way the sorting function works, the :class:`Gtk.TreeModelFilter` uses a "visibility" function, which, given a row from the underlying model, will return a boolean indicating if this row should be filtered out or not. It's set by :meth:`Gtk.TreeModelFilter.set_visible_func`: .. code-block:: python filter.set_visible_func(filter_func, data=None) The alternative to a "visibility" function is to use a boolean column in the model to specify which rows to filter. Choose which column with :meth:`Gtk.TreeModelFilter.set_visible_column`. Let's look at a full example which uses the whole :class:`Gtk.ListStore` - :class:`Gtk.TreeModelFilter` - :class:`Gtk.TreeModelFilter` - :class:`Gtk.TreeView` stack. .. image:: ../images/treeview_filter_example.png .. literalinclude:: ../examples/treeview_filter_example.py :linenos: