Xfce
Foundation Classes |
|||
« Main Page | Index | |||
The TreeView WidgetTable 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.
Cell RenderersCellRenderer is the base class of a set of objects used for rendering data to a cell in a TreeViewColumn. Typically, one cell renderer is used to draw many cells on the screen. To this extent, it isn't expected that a cell renderer keep any permanent state around. Instead, any state is set just prior to use, using its properties. The purpose of the cell renderers is to provide extensibility to the widget and to allow multiple ways of rendering the same type of data. For example, consider how to render a bool variable. Should you render it as a string of "true" or "false", "On" or "Off", or should you render it as a check button? There are three standard cell renderers
that
come with GTK+:
CellRendererPixbuf(); If you want to have editable text cells, use CellRendererText and make sure the 'editable' property is set: Gtk::CellRendererText
*cell = new
Gtk::CellRendererText; Tree ModelsA
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. Gtk::CellRendererText
*cell = new
Gtk::CellRendererText; 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: bool
get_iter(Gtk::TreeIter& iter, const
Gtk::TreePath& path) const; 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. There are three ways of getting
the iter pointing to a location. The
first two use the above get_iter() methods:
GtkTreeIter
iter; The third method involves walking
the tree to
find the iterator. To do this you need to use the TreeModel
iterate_nth_child() method:
bool
iterate_nth_child(Gtk::TreeIter& iter, const
Gtk::TreeIter *parent, int n); 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: Gtk::TreeIter
iter; 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.
#include <iostream> Tree SelectionsTreeSelection 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: Gtk::TreeSelection*
get_selection() const; 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). Creating a TreeModelXFC 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: ListStore(int n_columns, ...); 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:
Gtk::ListStore
*model = new Gtk::ListStore(3,
G_TYPE_INT,
G_TYPE_STRING,
G_TYPE_BOOLEAN); The second constructor does the
same thing except
you pass a vector of types. For example:
std::vector<GType>
types; 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.
enum 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: Gtk::TreeIter
insert(int position); 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: Gtk::TreeIter
insert(int position, Gtk::TreeIter *parent
= 0); 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: void
set_value(Gtk::TreeIter& iter, int
column, const G::Value& value); 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: model->set_value(iter,
column_number, false); and to set a G_TYPE_ENUM column, with say a Gtk::StateType, you would write: model->set_enum(iter,
column_number, Gtk::STATE_ACTIVE); There are six corresponding methods declared in TreeModel that can be used to get values from a ListStore or TreeStore: void
get_value(const TreeIter& iter, int column, G::Value& value)
const; To get the value set for the G_TYPE_BOOLEAN column above you would do this: bool
value; and to get the value set for the G_TYPE_ENUM column above you would do this: Gtk::StateType
value; So, putting it all together, consider the initial example: //
Acquire an iterator 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: //
Acquire a top-level iterator Creating a TreeViewWhile there are several different models to choose from, there is only one view widget to deal with. It works with either a ListStore or TreeStore model. Setting up a TreeView is not difficult but it does need a TreeModel to know where to retrieve its data from.You can create a TreeView with one of the following constructors: TreeView(); 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: void
set_model(Gtk::TreeModel *model); 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. Creating a TreeViewColumnA TreeViewColumn is the object that TreeView uses to organize the vertical columns in the tree view. It needs to know the name of the column to label for the user, what type of cell renderer to use, and which piece of data to retrieve from the model for a given row.You can create a TreeViewColumn by calling one of the following constructors: TreeViewColumn(); 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: Gtk::CellRendererText
*cell = new Gtk::CellRendererText; Here is an example that uses a CellColumnAttributes object to set several attributes: enum { Managing a SelectionAt this point, all the steps in creating a displayable tree have been covered. The model is created, data is stored in it, a tree view is created and columns are added to it. 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 a slot to its 'changed' signal.You would connect a slot the changed signal like this: Gtk::TreeSelection *selection =
tree_view->get_selection(); where the 'changed_handler' has the prototype: void
MyWindow::changed_handler(); Then, you retrieve the row data in the 'changed_handler' like this: void TreeView ExampleHere is an simple example of using a TreeView widget that creates a simple model and view, and puts them together. The model is populated with the data from the section: 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>: #include <xfc/main.hh> and the source file is <treeview.cc>: #include "treeview.hh" Compiling TreeView
If you compiled and installed XFC yourself, you will find the source
code for TreeView in the
<examples/treeview> source directory along with a Makefile. If
XFC came pre-installed, or you installed it from an RPM package, you
will
find the source code in the
</usr/share/doc/xfcui-X.X/examples/treeview> subdirectory. In
this case you will have to create the Makefile yourself (replace X.X
with the
version number of the libXFCui library you have installed). |
Copyright © 2004-2005 The XFC Development Team | Top |
XFC
4.4 |