Tutorial
|
|
|
|
Creating a Model
Inti 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 model call one of the following constructors:
ListStore(int n_columns, ...);
ListStore(const std::vector<GType>&
types); |
The first constructor creates a liststore of
n_columns, each of the types 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. Since the number of types is known the
n_columns argument is not required. 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(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
Gtk::ListStore or Gtk::TreeStore set_() methods, depending upon which
sort of model was created. 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, Gtk::TreeStore::set_() 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 the new 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 new row, except there is
an
extra argument: a pointer to a parent iterator. Remember, a TreeStore
implements a tree-like hierachical structure so you can can have
toplevel 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 toplevel, otherwise the row is added to the list of child
rows for the parent. By default, rows are added to the
toplevel.
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)
{
G::Value
value(gtk_list_store()->column_headers[column]);
value.set(data);
gtk_list_store_set_value(gtk_tree_store(), iter,
column, value.g_value());
}
template<typename
DataType>
void set_enum(const
TreeIter& iter, int column, const DataType& data)
{
G::Value
value(gtk_list_store()->column_headers[column]);
value.set((int)data);
gtk_tree_list_set_value(gtk_tree_store(), iter,
column, value.g_value());
}
template<typename DataType>
void set_object(const TreeIter& iter, int column, const DataType& data)
{
G::Value
value(gtk_list_store()->column_headers[column]);
value.set((G::Object*)data);
gtk_list_store_set_value(gtk_list_store(), iter,
column, value.g_value());
}
template<typename DataType>
void set_pointer(const TreeIter& iter, int column, const DataType& data)
{
G::Value
value(gtk_list_store()->column_headers[column]);
value.set((void*)data);
gtk_list_store_set_value(gtk_list_store(), iter,
column, value.g_value());
}
|
The first two methods are ordinary functions. The
iter
argument is a valid iterator for 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. As you can see from the inline definitions
these methods supply the G::Value for you.
Before going any further, just a word about G::Values and why there are
four template functions. The G::Value class is declared in
<inti/glib/value.h>. 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 Inti::String. For set_enum() it can be any
enumeration type. From the code you can see the enum is cast to an int.
That's because G::Value handles enums internally as an int. For
get_object() DataType must be a pointer to an object derived
from G::Object. For get_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 do something like
this:
model->set_value(iter,
column_number, false); |
and to set a G_TYPE_ENUM column, say Gtk::StateType, you would have to
do this:
model->set_enum(iter,
column_number, Gtk::STATE_ACTIVE);
|
There are five corresponding methods that can be used to get values
from a ListStore or TreeStore:
void
get_value(const TreeIter& iter, int column, G::Value& value) const;
template<typename
DataType>
void get_value(const TreeIter& iter, int
column, DataType& data) const
{
G::Value value;
gtk_tree_model_get_value(gtk_tree_model(), iter,
column, value.g_value());
value.get(data);
}
template<typename
DataType, typename ValueType>
void get_enum(const TreeIter& iter, int column, DataType& data) const
{
G::Value value;
gtk_tree_model_get_value(gtk_tree_model(), iter,
column, value.g_value());
int
tmp_data;
value.get(tmp_data);
data = static_cast<DataType>(tmp_data);
}
template<typename DataType>
void get_object(const TreeIter& iter, int column, DataType& data) const
{
G::Value value;
gtk_tree_model_get_value(gtk_tree_model(), iter,
column, value.g_value());
G::Object *object;
value.get(object);
data = static_cast<DataType>(object);
}
template<typename DataType>
void get_pointer(const TreeIter& iter, int column, DataType& data) const
{
G::Value value;
gtk_tree_model_get_value(gtk_tree_model(), iter,
column, value.g_value());
void
*tmp_data = 0;
value.get(tmp_data);
data = static_cast<DataType>(tmp_data);
}
|
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 to Gtk::TreeStore::append() is the
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. Consider the following
example:
//
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");
|