Xfce Foundation Classes
« Main Page | Index
# The TreeView Widget ### Table of Contents Cell Renderers Tree Models Tree Selections Creating a Model Creating the TreeView Creating a TreeViewColumn Managing a Selection TreeView Example 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: Gtk::TreeView - the tree view widget. Gtk::TreeViewColumn - the view column. Gtk::CellRenderer - the cell renderers. Gtk::TreeModel - the tree model interface. 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 Renderers CellRenderer 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+: Gtk::CellRendererPixbuf - renders images, either user-defined or one of the stock icons that come with GTK+. Gtk::CellRendererText - renders strings, numbers or bool values as text. Gtk::CellRendererToggle - renders a bool value in form of a check button or as a radio button. and their respective constructors are: `CellRendererPixbuf(); CellRendererText(); CellRendererToggle();` If you want to have editable text cells, use CellRendererText and make sure the 'editable' property is set: `Gtk::CellRendererText *cell = new Gtk::CellRendererText; cell->property_editable() = true;` ## Tree Models 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. `Gtk::CellRendererText *cell = new Gtk::CellRendererText; cell->property_editable() = true;` 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; bool get_iter(Gtk::TreeIter& iter, const String& 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. 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: `GtkTreeIter iter; // First method: get the iterator from a path. A smart pointer is used because our tree path must be unreferenced. Pointer<Gtk::TreePath> path = new Gtk::TreePath("3:2:5"); bool success = model->get_iter(iter, *path); // Second method: get the iterator from a string bool success = model->get_iter(iter, "3:2:5");` 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; Gtk::TreeIter parent_iter; bool success = model->iterate_nth_child(iter, 0, 3); if (success) { parent_iter = iter; model->iterate_nth_child(iter, &parent_iter, 2); parent_iter = iter; model->iterate_nth_child(iter, 0, 5); }` 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> enum { STRING_COLUMN, INT_COLUMN, N_COLUMNS }; Gtk::TreeIter iter; int row_count = 0; // Get the first iter in the list bool success = model->get_iter_first(iter); while (success) { // Walk through the list, reading each row String str_data; model->get_value(iter, STRING_COLUMN, str_data); int int_data; model->get_value(iter, INT_COLUMN, int_data); // Do something with the data std::cout << "Row " << row_count << ": (" << str_data << "," << int_data << ")" << std::endl; row_count++; success = model->iterate_next(iter); }` ## Tree Selections 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. The TreeSelection object can be retrieved from a TreeView widget by calling: `Gtk::TreeSelection* get_selection() const;` 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. 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 TreeModel 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: `ListStore(int n_columns, ...); ListStore(int n_columns, const std::vector<GType>& types);` 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; types.push_back(G_TYPE_INT); types.push_back(G_TYPE_STRING); types.push_back(G_TYPE_BOOLEAN); Gtk::ListStore *model = new Gtk::ListStore(3, 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 { TITLE_COLUMN, // Book title AUTHOR_COLUMN, // Author CHECKED_COLUMN, // Is checked out? N_COLUMNS // Total number of columns }; Gtk::TreeStore *model = new Gtk::TreeStore(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);` 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); Gtk::TreeIter insert_before(Gtk::TreeIter& sibling); Gtk::TreeIter insert_after(Gtk::TreeIter& sibling); Gtk::TreeIter prepend(); Gtk::TreeIter append();` 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); Gtk::TreeIter insert_before(Gtk::TreeIter& sibling, Gtk::TreeIter *parent = 0); Gtk::TreeIter insert_after(Gtk::TreeIter& sibling, Gtk::TreeIter *parent = 0); Gtk::TreeIter prepend(Gtk::TreeIter *parent = 0); Gtk::TreeIter append(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); void set_value(Gtk::TreeIter& iter, int column, const char *str); template<typename DataType> void set_value(const TreeIter& iter, int column, const DataType& data); template<typename DataType> void set_enum(const TreeIter& iter, int column, const DataType& data); template<typename DataType> void set_object(const TreeIter& iter, int column, const DataType& data); template<typename DataType> void set_pointer(const TreeIter& iter, int column, const DataType& data)` 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; bool get_value(const TreeIter& iter, int column, String& str) const; template<typename DataType> void get_value(const TreeIter& iter, int column, DataType& data) const; template<typename DataType, typename ValueType> void get_enum(const TreeIter& iter, int column, DataType& data) const; template<typename DataType> void get_object(const TreeIter& iter, int column, DataType& data) const; template<typename DataType> void get_pointer(const TreeIter& iter, int column, DataType& data) const;` To get the value set for the G_TYPE_BOOLEAN column above you would do this: `bool value; model->get_value(iter, column_number, value);` and to get the value set for the G_TYPE_ENUM column above you would do this: `Gtk::StateType value; model->get_enum(iter, column_number, value);` So, putting it all together, consider the initial example: `// Acquire an iterator Gtk::TreeIter iter = model->append(); // Set the cell values model->set_value(iter, TITLE_COLUMN, "The Principle of Reason"); model->set_value(iter, AUTHOR_COLUMN, "Martin Heidegger"); model->set_value(iter, CHECKED_COLUMN, false);` 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 Gtk::TreeIter parent_iter = model->append(); // Set the cell values model->set_value(parent_iter, TITLE_COLUMN, "The Art of Computer Programming"); model->set_value(parent_iter, AUTHOR_COLUMN, "Donald E. Knuth"); model->set_value(parent_iter, CHECKED_COLUMN, false); // Acquire a child iterator and set the values for the child cell Gtk::TreeIter child_iter = model->append(&parent_iter); model->set_value(child_iter, TITLE_COLUMN, "Volume 1: Fundamental Algorithms"); child_iter = model->append(&parent_iter); model->set_value(child_iter, TITLE_COLUMN, "Volume 2: Seminumerical Algorithms"); child_iter = model->append(&parent_iter); model->set_value(child_iter, TITLE_COLUMN, "Volume 3: Sorting and Searching");` ## Creating a TreeView While 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(); TreeView(Gtk::TreeModel& model);` 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); Gtk::TreeModel* get_model() const;` 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 TreeViewColumn A 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(); TreeViewColumn(const String& title); TreeViewColumn(const String& title, Gtk::CellRenderer& cell, const String& attribute, int column); TreeViewColumn(const String& title, CellRenderer& cell, const CellColumnAttributes& attributes);` 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; Gtk::TreeViewColumn *column = new Gtk::TreeViewColumn ("Author", cell, "text", AUTHOR_COLUMN); tree_view->append_column(*column);` Here is an example that uses a CellColumnAttributes object to set several attributes: `enum { TEXT_COLUMN, COLOR_COLUMN, N_COLUMNS }; Gtk::CellRendererText *cell = new Gtk::CellRendererText(); Gtk::CellColumnAttributes attributes; attributes.add("text", TEXT_COLUMN); attributes.add("foreground", COLOR_COLUMN); Gtk::TreeViewColumn *column = new Gtk::TreeViewColumn("Title", cell, attributes); tree_view->append_column(*column);` ## Managing a Selection At 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(); selection->set_mode(Gtk::SELECTION_SINGLE); selection->signal_changed().connect(sigc::mem_fun(this, &MyWindow::changed_handler));` where the 'changed_handler' has the prototype: `` `void MyWindow::changed_handler();` Then, you retrieve the row data in the 'changed_handler' like this: `void MyWindow::changed_handler() { Gtk::TreeModel *model = 0; Gtk::TreeIter iter; if (selection->get_selected(&iter, &model)) { String author; model->get_value(iter, AUTHOR_COLUMN, author); g_print ("You selected a book by %s\n", author); } }` ## TreeView Example Here 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> #include <xfc/gtk/treestore.hh> #include <xfc/gtk/treeview.hh> #include <xfc/gtk/window.hh> using namespace Xfc; class TreeViewWindow : public Gtk::Window { Pointer<Gtk::TreeStore> model; Gtk::TreeView *tree_view; protected: void on_selection(); void on_toggled(const char *path_str); public: TreeViewWindow(); virtual ~TreeViewWindow(); };` and the source file is <treeview.cc>: `#include "treeview.hh" #include <xfc/gtk/cellrenderertext.hh> #include <xfc/gtk/cellrenderertoggle.hh> #include <iostream> enum { TITLE_COLUMN, AUTHOR_COLUMN, CHECKED_COLUMN, N_COLUMNS }; TreeViewWindow::TreeViewWindow() { set_title("TreeView Example"); // We'll create a TreeStore model but this could be any model. The model pointer // is a smart pointer because we own the initial reference and it must be freed. model = new Gtk::TreeStore(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN); // Populate the model with data. First acquire a top-level iterator. Gtk::TreeIter parent_iter = model->append(); // Then, set the cell values model->set_value(parent_iter, TITLE_COLUMN, "The Art of Computer Programming"); model->set_value(parent_iter, AUTHOR_COLUMN, "Donald E. Knuth"); model->set_value(parent_iter, CHECKED_COLUMN, false); // Then, acquire a child iterator and set the values for the child cells Gtk::TreeIter child_iter = model->append(&parent_iter); model->set_value(child_iter, TITLE_COLUMN, "Volume 1: Fundamental Algorithms"); model->set_value(child_iter, CHECKED_COLUMN, false); child_iter = model->append(&parent_iter); model->set_value(child_iter, TITLE_COLUMN, "Volume 2: Seminumerical Algorithms"); model->set_value(child_iter, CHECKED_COLUMN, false); child_iter = model->append(&parent_iter); model->set_value(child_iter, TITLE_COLUMN, "Volume 3: Sorting and Searching"); model->set_value(child_iter, CHECKED_COLUMN, false); // Create a TreeView tree_view = new Gtk::TreeView(*model); // You can't create a TreeSelection, you have to retrieve it from the TreeView. Gtk::TreeSelection *selection = tree_view->get_selection(); selection->set_mode (Gtk::SELECTION_SINGLE); selection->signal_changed().connect(sigc::mem_fun(this, &TreeViewWindow::on_selection)); // Create a text cell render and arbitrarily make it red for demonstration purposes. Gtk::CellRendererText *renderer = new Gtk::CellRendererText; renderer->property_foreground().set("red"); // Create first column, associating the "text" attribute of the cell_renderer with the first column of the model Gtk::TreeViewColumn *column = new Gtk::TreeViewColumn("Author", renderer, "text", AUTHOR_COLUMN); // Add the column to the view. tree_view->append_column(*column); // Create second column. The title of the book. renderer = new Gtk::CellRendererText; column = new Gtk::TreeViewColumn("Title", renderer, "text", TITLE_COLUMN); tree_view->append_column(*column); // Create last column. Whether a book is checked out. Gtk::CellRendererToggle *toggle_renderer = new Gtk::CellRendererToggle; toggle_renderer->signal_toggled().connect(sigc::mem_fun(this, &TreeViewWindow::on_toggled)); column = new Gtk::TreeViewColumn("Check out", toggle_renderer, "active", CHECKED_COLUMN); tree_view->append_column(*column); // Now we can manipulate the view just like any other widget add(*tree_view); tree_view->show_all(); } TreeViewWindow::~TreeViewWindow() { } void TreeViewWindow::on_selection() { Gtk::TreeIter iter; Gtk::TreeSelection *selection = tree_view->get_selection(); if (selection->get_selected(&iter)) { String title; model->get_value(iter, TITLE_COLUMN, title); std::cout << "You selected a book with the title \"" << title << "\"" << std::endl; } } void TreeViewWindow::on_toggled(const char *path_str) { Gtk::TreeIter iter; Pointer<Gtk::TreePath> path = new Gtk::TreePath(path_str); // Get toggled iter model->get_iter(iter, *path); bool checked; model->get_value(iter, CHECKED_COLUMN, checked); // Do something with the value checked ^= 1; // Set new value for the CHECKED_COLUMN model->set_value(iter, CHECKED_COLUMN, checked); } int main (int argc, char *argv[]) { using namespace Main; init(&argc, &argv); TreeViewWindow window; window.signal_destroy().connect(sigc::ptr_fun(&Xfc::Main::quit)); window.show(); run(); return 0; }` ## 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). To create a Makefile for TreeView, add the following lines to a new text file and save it using the name "Makefile": `CC = g++ CFLAGS = -Wall -O2 treeview: treeview.cc treeview.hh $(CC) treeview.cc -o treeview $(CFLAGS) `pkg-config xfcui-X.X --cflags --libs` clean: rm -f *.o treeview` If you cut and paste these lines make sure the whitespace before $(CC) and rm is a tab character. When you compile and run this program you will see the following window appear: The window displays a simple tree view with one parent row and three child rows. Click on the row arrow to display and hide the child rows. You can also check the checkbuttons at the end of each row. When you select a row its title string is written to the standard output, usually the screen.
Copyright © 2004-2005 The XFC Development Team | Top | XFC 4.4