TreeView Tutorial
[email protected] ("Brian Tipton") Fri, 17 Nov 2006 16:43:06 -0600
| Newsgroups | php.gtk.doc |
|---|---|
| Message-ID | <[email protected]> |
Attached is a GtkTreeView tutorial ported to PHP-GTK2 from PyGTK. Original can be found here: http://liw.iki.fi/liw/texts/gtktreeview-tutorial.html As this is my first attempt, I am sure there are errors. Please correct me where I have made mistakes and I'll do my best to get them fixed as soon as possible. Please respond to this address as I am not subscribed to the list. Thanks, Brian
treeview.xml
(text/xml, 13.2 KB)
<?xml version="1.0" encoding="utf-8" ?>
<!-- $Revision: 1.7 $ -->
<chapter id="tutorials.treeview">
<title>GtkTreeView T4utorial (basic)</title>
<para>
Thanks to Lars Wirzenius for the
<ulink url="http://liw.iki.fi/liw/texts/gtktreeview-tutorial.html">original version</ulink>
of this tutorial written for PyGtk. Changes to the document were only to
reflect the current state of the Gtk+ codebase and to be applicable to PHP.
</para>
<simpara>
Version 2.0 of Gtk+ introduces its third generation of tree
and list widgets. Version 1.0 had GtkList and GtkTree, version 1.2
had GtkCList and GtkCTree, and now version 2.0 has GtkTreeView,
which servers both as a list and a tree. With each version, the
power and usefulness of the widgets have increased.
</simpara>
<simpara>
GtkTreeView uses a Model/View/Controller approach. This
means that the code is divided into a data structure representing
user's data ("the model"), the widgets that display the data and
interact with the user ("the view"), and some logic to tie things
nicely together ("the controller"). The model is implemented by
GtkTreeModel (actually, classes implementing that interface), the
view by GtkTreeView with some helpers, and the controller by the
user code.
</simpara>
<simpara>
This sounds unnecessarily complicated, but the complexity
is local, and this aproach actually simplifies overall program
structure. For example, it is often necessary to view the same data
(i.e., model) in different ways, or in different windows. Think,
for example, of a programmer's editor: the same source code may
be viewed in several windows at the same time, and changes in one
window should be shown in all the others, as well. Thus, it makes
sense to separate the storage of the text from its display, rather
than storing the text in each window widget.
</simpara>
<simpara>
The example application in this article lets the user manage
a folder tree. The folders are virtual, not real directories in
the filesystem, to keep the code simpler. The example is actually
derived from Lodju, in which the folders have nothing to do with
the filesystem.
</simpara>
<para>
The <ulink url="http://developer.gnome.org/doc/API/">official Gtk+ 2.0 API reference documentation for GtkTreeView</ulink>
should be read together with this tutorial, even if it is a bit sparse in
some details.
</para>
<simpara>
I thank the people on the Gtk+ developer IRC channel for
encouragement and feedback.
</simpara>
<para>
<note>The model: GtkTreeModel, GtkTreeStore</note>
</para>
<simpara>
GtkTreeModel is the interface definition for the model part of
GtkTreeView. The application programmer could define his own model,
but GtkTreeView provides the two most popular ones: a linear list
with GtkListStore, and a hierarchical tree with GtkTreeStore. The
GtkTreeView reference manual claims that few application programmers
will need to use anything else, and they may well be right. Since I
have little experience with GtkListStore, this article will discuss
GtkTreeStore only.
</simpara>
<simpara>
The first step in using GtkTreeView, then, is to set up a
GtkTreeStore to keep your data. The model contains some number of
rows and each row contains the same number of columns. The cells
in each column contain the same type of data. These columns are
declared when the model is created, though they can be changed later,
if necessary.
</simpara>
<example>
<title>Model</title>
<programlisting role="php"><![CDATA[$model = new GtkTreeStore(Gtk::TYPE_PHP_VALUE, Gtk::TYPE_STRING);]]></programlisting>
</example>
<simpara>
Here we create a GtkTreeStore with two columns. The first
column contains a PHP variable (of any type), the second a
string. There are several different types available, see the
GTK Enums. We will be using the first column to hold an array
containing all the information about a folder and the second
column to display its name.
</simpara>
<simpara>
Note difference in naming. GtkTreeView is the C type
name and GtkTreeStore is the PHP class that corresponds
to GtkTreeView. For more information, see the PHP-GTK2
documentation.
</simpara>
<simpara>
Note that it does not matter in which order you define
the columns. The order in which GtkTreeStore knows them does not
affect how they are displayed to the user. It is not even necessary
to display all columns to the user.
</simpara>
<simpara>
Nodes can be added to a GtkTreeStore in several ways. My
preference is to use the gtk_tree_store_insert_before function,
known as the insert_before method in PHP.
</simpara>
<example>
<title>Create Row</title>
<programlisting role="php"><![CDATA[$folder = new_folder();
$iter = $model->insert_before(null, null);
$model->set($iter, 0, $folder);
$model->set($iter, 1, $folder['name']);]]></programlisting>
</example>
<simpara>
This code first inserts an empty row into the model,
then sets the values in both cells for the row.
</simpara>
<simpara>
A GtkTreeModel lets the programmer refer to rows in various
ways. We see here a GtkTreeIter object, which essentially works as a
pointer to the row. We then use this pointer, plus a column number,
to refer to a cell.
</simpara>
<simpara>
GtkTreeIter objects are also used as arguments to
gtk_tree_store_insert_before, to define the parent of the new node,
and the sibling that is to follow the new node. If the parent is NULL,
as in the example above, the new node is added to the top level,
and if sibling is NULL, then the new node becomes
the last child of the parent.
</simpara>
<simpara>
The GtkTreeIter objects may be temporary, and may become
invalidated as the tree is modified by adding or removing nodes. It
is probably a bad idea to store them.
</simpara>
<simpara>
You can also remove rows (gtk_tree_store_remove a.k.a. $model->remove()).
For other operations, please see the API reference documentation.
</simpara>
<para>
<note>The view: GtkTreeView, GtkTreeViewColumn, GtkCellRenderer</note>
</para>
<simpara>
The GtkTreeView widget is the view in the Module/View/Controller
approach. It takes care of displaying the data stored in a model
(GtkTreeStore or GtkListStore) to the user. You can have several
GtkTreeViews for a single model, and changes to the model will be
displayed immediately in all of them.
</simpara>
<simpara>
To use GtkTreeView, you need to create the widget itself, then create
GtkTreeViewColumns for the columns to display and GtkCellRenderers
to tell the columns how to display a cell in the column.
</simpara>
<example>
<title>TreeView and Renderer</title>
<programlisting role="php"><![CDATA[$view = new GtkTreeView($model);
$renderer = new GtkCellRendererText();
$column = new GtkTreeViewColumn("Folder", $renderer, "text", 1);
$view->append_column($column);]]></programlisting>
</example>
<simpara>
This code creates a new GtkTreeView and attaches it to a model.
Then it creates a text cell renderer and a column and adds those to
the view. "Folder" is the name of the column, displayed at the top
of the column.
</simpara>
<simpara>
There are several GtkCellRenderers in the Gtk+ library, and
you could write your own, if those are not enough.
</simpara>
<simpara>
The user's selections in a GtkTreeView (per view) are tracked
using the GtkTreeSelection object. If your code needs to notice when
the user changes the selection, connect to the GtkTreeSelection's
"changed" signal. You can also have the selection object call a
function for each selected node, or programmatically change the
selection. See the API for details.
</simpara>
<example>
<title>Selection Changed Signal</title>
<programlisting role="php"><![CDATA[$selection = $view->get_selection();
$selection->connect("changed", "display_selected_folder");]]></programlisting>
</example>
<simpara>
This code connects the "changed" signal to a function (display_selected_folder())
that displays the contents of a selected folder.
</simpara>
<simpara>
You need to tell the GtkTreeView explicitly that the user is
allowed to rearrange the folder tree using drag-and-drop. Fortunately,
after telling this once, the widget takes care of the rest.
</simpara>
<example>
<title>Drag-and-Drop Reorder</title>
<programlisting role="php"><![CDATA[$view->set_reorderable(true);]]></programlisting>
</example>
<simpara>
This is all it takes to make the widget drag-and-drop enabled.
</simpara>
<simpara>
The same thing can also be done by manually should you choose not to
enable drag-and-drop by removing the child node from the tree and inserting it
back in as a child of another node.
</simpara>
<example>
<title>Manual Reorder</title>
<programlisting role="php"><![CDATA[$folder = $model->get_value($old_iter, 0);
$model->remove($old_iter);
$new_iter = $model->insert_before($new_parent, null);
$model->set($new_iter, 0, $folder);
$model->set($new_iter, 1, $folder['name']);]]></programlisting>
</example>
<simpara>
This code moves a node from old_iter to be the last child
of the new_parent node.
</simpara>
<simpara>
At the time this tutorial was written/ported there is a bug in PHP-GTK2 that
parameter order for insert_before() and insert_after() has been switched. The
above code will not work (nor the example code below) without switching the
order of the parameters. As of php-gtk-2.0.0 alpha the order is still (sibling,
parent) and it should be (parent, sibling). This has already been changed in CVS.
</simpara>
<example>
<title>Example Source Code</title>
<programlisting role="php"><![CDATA[<?php
// This is an example for demonstrating use of the GtkTreeView widget.
// The code in this example is not particularly good: it is written to
// concentrate on widget usage demonstration, not for maintainability.
$view = null;
$choose_parent_view = null;
$dialog = null;
function move ($old_iter=null, $new_parent, $model)
{
if ($old_iter)
{
$folder = $model->get_value($old_iter, 0);
$model->remove($old_iter);
$new_iter = $model->insert_before($new_parent, null);
$model->set($new_iter, 0, $folder);
$model->set($new_iter, 1, $folder['name']);
}
}
function dialog_ok ($args)
{
global $dialog, $choose_parent_view, $view;
$dialog->hide();
list($model, $parent_iter) = $choose_parent_view->get_selection()->get_selected();
list($model, $old_iter) = $view->get_selection()->get_selected();
if ($parent_iter && $old_iter)
{
move($old_iter, $parent_iter, $model);
}
}
function dialog_cancel ($args)
{
global $dialog;
$dialog->hide();
}
function choose_parent ($args)
{
global $dialog;
$dialog->show();
}
function move_to_bottom ($args)
{
global $view;
list ($model, $old_iter) = $view->get_selection()->get_selected();
if ($old_iter)
{
move($old_iter, null, $model);
}
}
function quit ($args)
{
Gtk::main_quit();
}
function make_view ($model)
{
$view = new GtkTreeView($model);
$view->set_reorderable(true);
$renderer = new GtkCellRendererText();
$column = new GtkTreeViewColumn("Folder", $renderer, "text", 1);
$view->append_column($column);
$view->show();
$scrolled = new GtkScrolledWindow();
$scrolled->add($view);
$scrolled->show();
return array($view, $scrolled);
}
function make_buttons ($list)
{
$buttonbox = new GtkHBox();
foreach ($list as $label => $func)
{
$button = new GtkButton();
$button->set_label($label);
$button->connect("clicked", $func);
$button->show();
$buttonbox->pack_start($button, false, false);
}
$buttonbox->show();
return $buttonbox;
}
$model = new GtkTreeStore(Gtk::TYPE_PHP_VALUE, Gtk::TYPE_STRING);
for ($i=0; $i<100; $i++)
{
$folder = array("name" => "folder $i", $files => array("foo", "bar"));
$iter = $model->insert_before(null, null);
$model->set($iter, 0, $folder);
$model->set($iter, 1, $folder['name']);
}
list($view, $scrolled) = make_view($model);
$view->set_reorderable(true);
$buttons = array
(
"Quit" => "quit",
"Choose parent" => "choose_parent",
"Move to bottom" => "move_to_bottom"
);
$buttonbox = make_buttons($buttons);
$vbox = new GtkVBox();
$vbox->pack_start($buttonbox, false, false);
$vbox->pack_start($scrolled, true, true);
$vbox->show();
$win = new GtkWindow(Gtk::WINDOW_TOPLEVEL);
$win->connect("delete_event", "quit");
$win->add($vbox);
$win->show();
$win->resize(300, 500);
list($choose_parent_view, $scrolled) = make_view($model);
$buttons = array
(
"OK" => "dialog_ok",
"Cancel" => "dialog_cancel"
);
$buttonbox = make_buttons($buttons);
$vbox = new GtkVBox();
$vbox->pack_start($scrolled, true, true);
$vbox->pack_start($buttonbox, false, false);
$vbox->show();
$dialog = new GtkWindow(Gtk::WINDOW_TOPLEVEL);
$dialog->set_default_size(200, 400);
$dialog->add($vbox);
Gtk::main();
>?]]></programlisting>
</example>
</chapter>