|« Main Page | Index|
The TreeView Widget
Table of Contents
To create a tree or list widget in XFC you need to use the TreeModel interface in conjunction with a TreeView widget. Designed around the Model/View/Controller (MVC) architecture this widget consists of four major parts:
The View is composed of the first three, while the last is the Model. One of the prime benefits of the MVC design is that multiple views can be created of a single model. For example, a model mapping the file system could be created for a file manager. Many views could be created to display various parts of the file system, but only one copy need be kept in memory.
There are three standard cell renderers that come with GTK+:
If you want to have editable text cells, use CellRendererText and make sure the 'editable' property is set:
A TreeModel defines a generic interface for use by the TreeView widget. It is an abstract interface, and is designed to be usable with any appropriate class. The programmer just has to derive a new class that inherits from this interface for it to be viewable in a TreeView widget.
A TreeModel is represented as a hierarchical tree of strongly-typed, columned data. In other words, the model can be seen as a tree where every node has different values depending on which column is being queried. The type of data found in a column is determined by using the GType system (i.e. G_TYPE_INT, GTK_TYPE_BUTTON, G_TYPE_POINTER, etc.). The types are homogeneous per column across all nodes. It is important to note that this interface only provides a way of examining a model and observing changes. The implementation of each individual model decides how and if changes are made.
In order to make life simpler for programmers who do not need to write their own specialized model, two generic models are provided: ListStore and TreeStore. To use these, the developer simply pushes data into these models as necessary. These models provide the data structure as well as all appropriate tree interfaces. As a result, implementing drag and drop, sorting, and storing data is trivial. For the vast majority of trees and lists, these two models are sufficient.
Models are accessed on a node/column level of granularity. One can query for the value of a model at a certain node and a certain column on that node. There are two structures used to reference a particular node in a model. They are the TreePath and the TreeIter (iter is short for iterator). Most of the interface consists of operations on a TreeIter.
A TreePath is essentially a potential node. It is a location on a model that may or may not actually correspond to a node on a specific model. The TreePath class has two methods that can return a path either as a String or as a vector of integers.
The first method, to_string(), returns the string form which is a list of numbers separated by a colon. Each number (a zero-based index) 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. The second method, get_indices(), returns the current indices of the path as a vector of integers, each integer representing a node in the tree.
A TreeIter is a reference to a specific node on a specific model. It is a generic class that represents an integer and three generic pointers. These are filled in by the model in a model-specific way. One can convert a path to an iterator by calling either of the following TreeModel methods:
The 'iter' argument is an uninitialized TreeIter. In the first method 'path' is a TreePath and in the second it is the string representation of a TreePath. Both methods set iter to a valid iterator pointing to the specified path, if it exists and returns true. Otherwise, iter is left invalid and false is returned.
These iterators are the primary way of accessing a model and are similar to the iterators used by text buffers. They are generally statically allocated on the heap and only used for a short time. The model interface defines a set of operations using them for navigating the model.
It is expected that models fill in the iterator with private data. For example, the ListStore model, which is internally a simple linked list, stores a list node in one of the pointers. The TreeModelSort stores an array and an offset in two of the pointers. Additionally, there is an integer field. This field is generally filled with a unique stamp per model. This stamp is for catching errors resulting from using invalid iterators with a model.
The life cycle of an iterator can be a little confusing at first. Iterators are expected to always be valid for as long as the model is unchanged (and doesn't emit a signal). The model is considered to own all outstanding iterators and nothing needs to be done to free them from the user's point of view. Additionally, some models guarantee that an iterator is valid for as long as the node it refers to is valid (most notably the ListStore and TreeStore). Although generally uninteresting, as one always has to allow for the case where iterators do not persist beyond a signal, some very important performance enhancements were made in the sort model. As a result, the TREE_MODEL_ITERS_PERSIST flag was added to indicate this behavior.
To help show some common operations on a model, here are two examples. The first example shows three ways of getting the iter at the location "3:2:5". While the first method shown is easier, the second is much more common, as you often get paths from signal handlers.
Example 1. Acquiring a Gtk::TreeIter.
There are three ways of getting the iter pointing to a location. The first two use the above get_iter() methods:
The third method involves walking the tree to find the iterator. To do this you need to use the TreeModel iterate_nth_child() method:
The 'iter' argument is an uninitialized TreeIter, 'parent' is the TreeIter to get the child from, and 'n' is the index of the desired child. This method sets iter to be the child of parent, using the given index. The first index is 0. If index is too big, or parent has no children, iter is set to an invalid iterator and false is returned. The parent will remain a valid node after this method has been called. As a special case, if parent is null, then the nth root node is set.
To find an iterator by walking the tree you would do this:
Example 2. Reading data from a TreeModel
This second example shows a quick way of iterating through a list and getting a string and an integer from each row. The model used is a ListStore with two columns: a string column and an integer column.
TreeSelection is a helper object to manage the selection for a TreeView widget. The TreeSelection object is automatically created when a new TreeView widget is created, and cannot exist independently of this widget. The primary reason the TreeSelection object exists is for cleanliness of code and API. That is, there is no conceptual reason the TreeSelection functions could not be methods of the TreeView widget instead.TreeSelection can be manipulated to check the selection status of the tree, as well as select and de-select individual rows. Selection is done completely view side. As a result, multiple views of the same model can have completely different selections. Additionally, you cannot change the selection of a row on the model that is not currently displayed by the view without expanding its parents first.
The TreeSelection object can be retrieved from a TreeView widget by calling:
One of the important things to remember when monitoring the selection of a view is that the 'changed' signal is mostly a hint. That is, it may only emit one signal when a range of rows is selected. Additionally, it may on occasion emit a changed signal when nothing has happened (mostly as a result of programmers calling select_row on an already selected row).
XFC provides two types of models that can be used: ListStore and TreeStore. ListStore is used to model columned list widgets, while TreeStore models columned tree widgets. It is possible to develop a new type of model, but the existing models should be satisfactory for all but the most specialized of situations.
To create a new ListStore model call one of the following constructors:
The first constructor creates a list store 'n_columns' in length, where each column is of the type passed in the variable argument list. For example:
The second constructor does the same thing except you pass a vector of types. For example:
Both examples create a list store with three columns: an integer column, a string column and a boolean column. The columns appear in the view in the order of declaration. Typically the 3 is never passed directly like that; usually an enum is created wherein the different columns are enumerated, followed by a token that represents the total number of columns. The next example will illustrate this, only using a tree store instead of a list store. Creating a tree store operates almost exactly the same.
Adding data to the model is done using one of the ListStore or TreeStore set methods. To do this, a TreeIter must be acquired. The iterator points to the location where data will be added. Once an iterator has been acquired, the set method is used to apply data to the part of the model that the iterator points to.
For a ListStore you can acquire an iterator with one of the following methods:
All these methods add a new row to the model and return an iterator set to this row. The first method inserts the row at the specified position. The next two methods insert the row before and after the row identified by 'sibling'. The fourth method prepends the row to the beginning of the list and the the last method appends the row to the end of the list.
For a TreeStore you can acquire an iterator with one of the following methods:
As with ListStore, these methods add a new row to the model and return an iterator set to this row, except there is an extra argument: a pointer to a parent iterator. Remember, a TreeStore implements a tree-like hierarchical structure so you can can have top level rows, parent rows and child rows. A Child row itself can be a parent to a list of child rows which get displayed when the user clicks on the row's arrow indicator.
In the TreeStore methods above, if 'parent' is null the row is added to the top level, otherwise the row is added to the list of child rows for the parent. By default, rows are added to the top level.
There are six methods that can be used to set values in a ListStore or TreeStore:
The first two methods are ordinary functions. The 'iter' argument is a valid iterator pointing to the row being modified and 'column' is the column number to modify. In the first method 'value' is a G::Value that holds the new value for a cell. The second method lets you easily set string literals without using a template. The other four methods are template functions. They let you set cell values directly without using a G::Value.
Before going any further, just a word about G::Values and why there are four template functions. The G::Value class is declared in <xfc/glib/value.hh>. G:Value has 16 overloaded get() and set() methods. Most of these methods handle unique data types, like bool, gchar, gint, glong and gfloat without any trouble. However, enums and flags fall victim to compiler conversions and require special handling to prevent compiler errors. Rather than duplicate code, G::Value handles theses two types internally. All you are required to do is call the right method. For enums, you must cast the enum to an integer before calling the G::Value get() or set() methods. For flags, you must cast the flag to an unsigned integer before calling get() or set().
That brings us back to the four template functions. All the template functions require a typename 'DataType'. DataType is the actual data type you are passing. For the set_value() template it must be one of the standard data types, such as bool, int, float, double, or an Xfc::String. For set_enum() it can be any enumeration type. For set_object() DataType must be a pointer to an object derived from G::Object. For set_pointer() DataType can be a pointer to any object. Such pointers are handled internally as a generic (void*) pointer, without any interpretation.
For example, to set a G_TYPE_BOOLEAN column, you would write:
and to set a G_TYPE_ENUM column, with say a Gtk::StateType, you would write:
There are six corresponding methods declared in TreeModel that can be used to get values from a ListStore or TreeStore:
To get the value set for the G_TYPE_BOOLEAN column above you would do this:
and to get the value set for the G_TYPE_ENUM column above you would do this:
So, putting it all together, consider the initial example:
The argument passed to the TreeStore append method is a parent iterator. It is used to add a row to a TreeStore as a child of an existing row. This means that the new row will only be visible when its parent is visible and in its expanded state.
Here is a TreeStore example that uses child iterators:
You can create a TreeView with one of the following constructors:
If you call the first constructor you will need to explicitly set the model.
You can set and get the model for the TreeView by calling these two methods:
Once the TreeView widget has a model, it will need to know how to display the model. It does this with columns and cell renderers.
You can create a TreeViewColumn by calling one of the following constructors:
The 'title' argument is the title of the tree column, 'cell' is the cell renderer to pack into the beginning of the tree column, 'attribute' is an attribute of the cell renderer, and 'column' is the column position on the model to get the attribute from. In the last constructor the 'attributes' argument is a CellColumnAttributes object containing the tree column attributes to set.
Here is a simple example that uses the third constructor to set one attribute column:
Here is an example that uses a CellColumnAttributes object to set several attributes:
You would connect a slot the changed signal like this:
where the 'changed_handler' has the prototype:
Then, you retrieve the row data in the 'changed_handler' like this:
Creating a Model. More information can be found on this in the Tree Models section.
The header file for the TreeView example is <treeview.hh>:
and the source file is <treeview.cc>:
If you compiled and installed XFC yourself, you will find the source
code for TreeView in the
<examples/howto/treeview> source directory along with a Makefile. If
XFC came pre-installed, or you installed it from an RPM package, you
find the source code in the
</usr/share/doc/xfcui-X.X/examples/howto/treeview> subdirectory. In
this case you will have to create the Makefile yourself (replace X.X
version number of the libXFCui library you have installed).
|Copyright © 2004-2005 The XFC Development Team||Top