dirk/C++/gtkmm/treeview-lazy
Ravensburg, 10-02-06
treeview-lazy – Creation of Tree Nodes on Demand
Treeview examples often show how certain hierarchical data can be displayed in a Gtk::Treeview.
Thereby, a recursive method is used to traverse the whole data at once mapping it in the respective tree rows.
In real life, the amount of data can become very large.
A treeview may never map the whole data because neither the user has time to wait for this nor the user desires to see all the visual junk.
A wellknown example is a directory browser.
Therefore, a treeview is necessary which views initially only some selected rows (e.g. the root nodes and/or selected branches).
Additional rows may be created on demand i.e. when the user clicks the expand button of a row.
Source Code
treeview-lazy.cc |
|---|
/** @file sample for a tree view where rows are filled on expand
*
* Last CVS checkin:
* $Date: $
* $Author: scheff $
*/
/**************************************************************************/
// standard C/C++ header:
#include <assert.h>
// Gnome header:
#include <glibmm/ustring.h>
#include <glibmm/nodetree.h>
#include <gtkmm/box.h>
#include <gtkmm/main.h>
#include <gtkmm/treestore.h>
#include <gtkmm/treeview.h>
#include <gtkmm/window.h>
/**************************************************************************/
/// @name Convinience Typedefs
//@{
typedef unsigned int uint;
typedef Glib::NodeTree<Glib::ustring> Node;
//@}
/// depth of sample tree
const uint DepthTree = 4;
const uint NChildren = 3;//8;
/** create a (sub-)tree of a certain depth.
*
* RECURSIVE FUNCTION
*
* @param depth depth of tree
* @param name stub of tree node names
* @return pointer to root node of created tree
*/
static Node* buildTree(
int depth = DepthTree, const Glib::ustring &name = "1")
{
Node *pNode = new Node(name);
if (--depth) {
for (uint i = 0; i < NChildren; ++i) {
Glib::ustring nameChild = name;
nameChild += '-'; nameChild += '0' + i;
pNode->append(*buildTree(depth, nameChild));
}
}
return pNode;
}
/**************************************************************************/
/// provides a class for a customized tree view widget.
class TreeView: public Gtk::TreeView {
// types:
private:
/// provides the column model for the tree view.
struct ModelColumns: public Gtk::TreeModelColumnRecord {
/// @name Columns
//@{
Gtk::TreeModelColumn<Node*> pNode;
Gtk::TreeModelColumn<Glib::ustring> name;
//@}
/// constructs column model.
ModelColumns(void) { add(pNode); add(name); }
};
// constants:
private:
/// the column model for the tree view
const ModelColumns _modelColumns;
// variables:
private:
/// GTK+ TreeStore
Glib::RefPtr<Gtk::TreeStore> _refTreeStore;
// methods:
public:
/// @name Construction & Destruction
//@{
/** constructs a tree view for a certain tree.
*
* @param pNode pointer to root tree node
*/
TreeView(Node *pNode);
/// destroys tree view for a certain tree.
virtual ~TreeView(void);
//@}
protected:
/// @name Overloaded Event Handlers
//@{
/** called when user requested to expand a row.
*
* @param it tree model iterator for related row
* @param path tree model path to related row
*/
virtual void on_row_expanded(
const Gtk::TreeModel::iterator &it, const Gtk::TreeModel::Path &path);
//@}
private:
/// @name Internal Stuff
//@{
/** initializes a row for a certain node.
*
* @param row row
* @param pNode pointer to corresponding node
*/
void initRow(const Gtk::TreeModel::Row &row, Node *pNode);
//@}
};
/**************************************************************************/
// Construction & Destruction
TreeView::TreeView(Node *pNode): Gtk::TreeView(),
_refTreeStore(Gtk::TreeStore::create(_modelColumns))
{
// init
set_enable_tree_lines(true);
//set_headers_visible(false);
set_model(_refTreeStore);
append_column("Name", _modelColumns.name);
// make root row
initRow(*_refTreeStore->append(), pNode);
}
TreeView::~TreeView(void) { }
// Overloaded Event Handlers
void TreeView::on_row_expanded(
const Gtk::TreeModel::iterator &it, const Gtk::TreeModel::Path &path)
{
const Gtk::TreeModel::Row &row = *it;
assert(!row.children().empty());
/* check whether row contains a dummy child
* (recognized by NULL pointer for node)
*/
const Gtk::TreeRow &row1stChild = *row.children().begin();
if (!row1stChild[_modelColumns.pNode]) {
// remove dummy row
_refTreeStore->erase(row1stChild);
// create child rows for nodes
Node *pNode = row[_modelColumns.pNode];
for (pNode = pNode->first_child(); pNode; pNode = pNode->next_sibling())
initRow(*_refTreeStore->append(row.children()), pNode);
// expand row
expand_row(path, false);
} else Gtk::TreeView::on_row_expanded(it, path);
}
// Internal Stuff
void TreeView::initRow(const Gtk::TreeModel::Row &row, Node *pNode)
{
row[_modelColumns.pNode] = pNode;
row[_modelColumns.name] = pNode->data();
// if node has children
if (!pNode->is_leaf()) {
// create a dummy child row to force an expander icon
Gtk::TreeModel::Row rowDummy = *_refTreeStore->append(row.children());
rowDummy[_modelColumns.pNode] = 0; // to identify it as dummy
}
}
/**************************************************************************/
/** provides the main function of application.
*
* @param argc number of command line arguments
* @param argv pointer to array of pointers to strings with command line
* arguments
* @return 0 ... application exited regularly\n
* else ... execution of application aborted
*/
int main(int argc, char *argv[])
{
// init
Gtk::Main gtkMain(argc, argv);
// init a tree
Node *pNode = buildTree();
// init GUI
Gtk::Window gtkWin;
Gtk::VBox gtkVBox;
TreeView treeView(pNode);
gtkVBox.pack_start(treeView);
gtkWin.add(gtkVBox);
gtkWin.show_all();
// run
gtkMain.run(gtkWin);
// done
return 0;
}
|
Output
| Snapshot: |
 |
Conclusion
The tree view is filled lazy.
Thus, only displayed tree nodes are stored and managed for large data.
There are two tricks necessary to achieve lazy filling:
- A dummy child row is created for each new row representing a node with children.
- The
Gtk::TreeView::on_expand_row method is overloaded to fill a row just before expansion.
Concerning my on_expand_row:
I thought it is enough to call Gtk::TreeView::on_expand_row() after filling a row, but I had to click the expander icon twice for each row expanded the first time.
Therefore, Gtk::TreeView::expand is called after filling a row and this does exactly what expected.
Files
dirk/C++/gtkmm/treeview-lazy
Last modified: Thu Feb 25 17:33:06 2010