Jump to content

GTK+ By Example/Tree View/Tree Models

From Wikibooks, open books for an open world

GtkTreeModels for Data Storage: GtkListStore and GtkTreeStore

[edit | edit source]

It is important to realise what GtkTreeModel is and what it is not. GtkTreeModel is basically just an 'interface' to the data store, meaning that it is a standardised set of functions that allows a GtkTreeView widget (and the application programmer) to query certain characteristics of a data store, for example how many rows there are, which rows have children, and how many children a particular row has. It also provides functions to retrieve data from the data store, and tell the tree view what type of data is stored in the model. Every data store must implement the GtkTreeModel interface and provide these functions, which you can use by casting a store to a tree model with GTK_TREE_MODEL(store). GtkTreeModel itself only provides a way to query a data store's characteristics and to retrieve existing data, it does not provide a way to remove or add rows to the store or put data into the store. This is done using the specific store's functions.

Gtk+ comes with two built-in data stores (models): GtkListStore and GtkTreeStore. As the names imply, GtkListStore is used for simple lists of data items where items have no hierarchical parent-child relationships, and GtkTreeStore is used for tree-like data structures, where items can have parent-child relationships. A list of files in a directory would be an example of a simple list structure, whereas a directory tree is an example for a tree structure. A list is basically just a special case of a tree with none of the items having any children, so one could use a tree store to maintain a simple list of items as well. The only reason GtkListStore exists is in order to provide an easier interface that does not need to cater for child-parent relationships, and because a simple list model can be optimised for the special case where no children exist, which makes it faster and more efficient.

GtkListStore and GtkTreeStore should cater for most types of data an application developer might want to display in a GtkTreeView. However, it should be noted that GtkListStore and GtkTreeStore have been designed with flexibility in mind. If you plan to store a lot of data, or have a large number of rows, you should consider implementing your own custom model that stores and manipulates data your own way and implements the GtkTreeModel interface. This will not only be more efficient, but probably also lead to saner code in the long run, and give you more control over your data. See below for more details on how to implement custom models.

Tree model implementations like GtkListStore and GtkTreeStore will take care of the view side for you once you have configured the GtkTreeView to display what you want. If you change data in the store, the model will notify the tree view and your data display will be updated. If you add or remove rows, the model will also notify the store, and your row will appear in or disappear from the view as well. 3.1. How Data is Organised in a Store

A model (data store) has model columns and rows. While a tree view will display each row in the model as a row in the view, the model's columns are not to be confused with a view's columns. A model column represents a certain data field of an item that has a fixed data type. You need to know what kind of data you want to store when you create a list store or a tree store, as you can not add new fields later on.

For example, we might want to display a list of files. We would create a list store with two fields: a field that stores the filename (i.e. a string) and a field that stores the file size (i.e. an unsigned integer). The filename would be stored in column 0 of the model, and the file size would be stored in column 1 of the model. For each file we would add a row to the list store, and set the row's fields to the filename and the file size.

The GLib type system (GType) is used to indicate what type of data is stored in a model column. These are the most commonly used types:

  • G_TYPE_BOOLEAN
  • G_TYPE_INT, G_TYPE_UINT
  • G_TYPE_LONG, G_TYPE_ULONG, G_TYPE_INT64, G_TYPE_UINT64 (these are not supported in early gtk+-2.0.x versions)
  • G_TYPE_FLOAT, G_TYPE_DOUBLE
  • G_TYPE_STRING - stores a string in the store (makes a copy of the original string)
  • G_TYPE_POINTER - stores a pointer value (does not copy any data into the store, just stores the pointer value!)
  • GDK_TYPE_PIXBUF - stores a GdkPixbuf in the store (increases the pixbuf's refcount, see below)

You do not need to understand the type system, it will usually suffice to know the above types, so you can tell a list store or tree store what kind of data you want to store. Advanced users can derive their own types from the fundamental GLib types. For simple structures you could register a new boxed type for example, but that is usually not necessary. G_TYPE_POINTER will often do as well, you will just need to take care of memory allocation and freeing yourself then.

Storing GObject-derived types (most GDK_TYPE_FOO and GTK_TYPE_FOO) is a special case that is dealt with further below.

Here is an example of how to create a list store:

  GtkListStore *list_store;

  list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_UINT);

This creates a new list store with two columns. Column 0 stores a string and column 1 stores an unsigned integer for each row. At this point the model has no rows yet of course. Before we start to add rows, let's have a look at the different ways used to refer to a particular row.

Referring to Rows: GtkTreeIter, GtkTreePath, GtkTreeRowReference

[edit | edit source]

There are different ways to refer to a specific row. The two you will have to deal with are GtkTreeIter and GtkTreePath.

GtkTreePath

[edit | edit source]

Describing a row 'geographically'

A GtkTreePath is a comparatively straightforward way to describe the logical position of a row in the model. As a GtkTreeView always displays all rows in a model, a tree path always describes the same row in both model and view.

Figure 3-1.

The picture shows the tree path in string form next to the label. Basically, it just counts the children from the imaginary root of the tree view. An empty tree path string would specify that imaginary invisible root. Now 'Songs' is the first child (from the root) and thus its tree path is just "0". 'Videos' is the second child from the root, and its tree path is "1". 'oggs' is the second child of the first item from the root, so its tree path is "0:1". So you just count your way down from the root to the row in question, and you get your tree path.

To clarify this, a tree path of "3:9:4:1" would basically mean in human language (attention - this is not what it really means!) something along the lines of: go to the 3rd top-level row. Now go to the 9th child of that row. Proceed to the 4th child of the previous row. Then continue to the 1st child of that. Now you are at the row this tree path describes. This is not what it means for Gtk+ though. While humans start counting at 1, computers usually start counting at 0. So the real meaning of the tree path "3:9:4:1" is: Go to the 4th top-level row. Then go to the 10th child of that row. Pick the 5th child of that row. Then proceed to the 2nd child of the previous row. Now you are at the row this tree path describes. :)

The implication of this way of referring to rows is as follows: if you insert or delete rows in the middle or if the rows are resorted, a tree path might suddenly refer to a completely different row than it referred to before the insertion/deletion/resorting. This is important to keep in mind. (See the section on GtkTreeRowReferences below for a tree path that keeps updating itself to make sure it always refers to the same row when the model changes).

This effect becomes apparent if you imagine what would happen if we were to delete the row entitled 'funny clips' from the tree in the above picture. The row 'movie trailers' would suddenly be the first and only child of 'clips', and be described by the tree path that formerly belonged to 'funny clips', i.e. "1:0:0".

You can get a new GtkTreePath from a path in string form using gtk_tree_path_new_from_string, and you can convert a given GtkTreePath into its string notation with gtk_tree_path_to_string. Usually you will rarely have to handle the string notation, it is described here merely to demonstrate the concept of tree paths.

Instead of the string notation, GtkTreePath uses an integer array internally. You can get the depth (i.e. the nesting level) of a tree path with gtk_tree_path_get_depth. A depth of 0 is the imaginary invisible root node of the tree view and model. A depth of 1 means that the tree path describes a top-level row. As lists are just trees without child nodes, all rows in a list always have tree paths of depth 1. gtk_tree_path_get_indices returns the internal integer array of a tree path. You will rarely need to operate with those either.

If you operate with tree paths, you are most likely to use a given tree path, and use functions like gtk_tree_path_up, gtk_tree_path_down, gtk_tree_path_next, gtk_tree_path_prev, gtk_tree_path_is_ancestor, or gtk_tree_path_is_descendant. Note that this way you can construct and operate on tree paths that refer to rows that do not exist in model or view! The only way to check whether a path is valid for a specific model (i.e. the row described by the path exists) is to convert the path into an iter using gtk_tree_model_get_iter.

GtkTreePath is an opaque structure, with its details hidden from the compiler. If you need to make a copy of a tree path, use gtk_tree_path_copy.

GtkTreeIter

[edit | edit source]

Referring to a row in model-speak

Another way to refer to a row in a list or tree is GtkTreeIter. A tree iter is just a structure that contains a couple of pointers that mean something to the model you are using. Tree iters are used internally by models, and they often contain a direct pointer to the internal data of the row in question. You should never look at the content of a tree iter and you must not modify it directly either.

All tree models (and therefore also GtkListStore and GtkTreeStore) must support the GtkTreeModel functions that operate on tree iters (e.g. get the tree iter for the first child of the row specified by a given tree iter, get the first row in the list/tree, get the n-th child of a given iter etc.). Some of these functions are:

  • gtk_tree_model_get_iter_first - sets the given iter to the first top-level item in the list or tree
  • gtk_tree_model_iter_next - sets the given iter to the next item at the current level in a list or tree.
  • gtk_tree_model_iter_children - sets the first given iter to the first child of the row referenced by the second iter (not very useful for lists, mostly useful for trees).
  • gtk_tree_model_iter_n_children - returns the number of children the row referenced by the provided iter has. If you pass NULL instead of a pointer to an iter structure, this function will return the number of top-level rows. You can also use this function to count the number of items in a list store.
  • gtk_tree_model_iter_nth_child - sets the first iter to the n-th child of the row referenced by the second iter. If you pass NULL instead of a pointer to an iter structure as the second iter, you can get the first iter set to the n-th row of a list.
  • gtk_tree_model_iter_parent - sets the first iter to the parent of the row referenced by the second iter (does nothing for lists, only useful for trees).

Almost all of those functions return TRUE if the requested operation succeeded, and return FALSE otherwise. There are more functions that operate on iters. Check out the GtkTreeModel API reference for details.

You might notice that there is no gtk_tree_model_iter_prev. This is unlikely to be implemented for a variety of reasons. It should be fairly simple to write a helper function that provides this functionality though once you have read this section.

Tree iters are used to retrieve data from the store, and to put data into the store. You also get a tree iter as result if you add a new row to the store using gtk_list_store_append or gtk_tree_store_append.

Tree iters are often only valid for a short time, and might become invalid if the store changes with some models. It is therefore usually a bad idea to store tree iters, unless you really know what you are doing. You can use gtk_tree_model_get_flags to get a model's flags, and check whether the GTK_TREE_MODEL_ITERS_PERSIST flag is set (in which case a tree iter will be valid as long as a row exists), yet still it is not advisable to store iter structures unless you really mean to do that. There is a better way to keep track of a row over time: GtkTreeRowReference

GtkTreeRowReference

[edit | edit source]

Keeping track of rows even when the model changes

A GtkTreeRowReference is basically an object that takes a tree path, and watches a model for changes. If anything changes, like rows getting inserted or removed, or rows getting re-ordered, the tree row reference object will keep the given tree path up to date, so that it always points to the same row as before. In case the given row is removed, the tree row reference will become invalid.

A new tree row reference can be created with gtk_tree_row_reference_new, given a model and a tree path. After that, the tree row reference will keep updating the path whenever the model changes. The current tree path of the row originally referred to when the tree row reference was created can be retrieved with gtk_tree_row_reference_get_path. If the row has been deleted, NULL will be returned instead of a tree path. The tree path returned is a copy, so it will need to be freed with gtk_tree_path_free when it is no longer needed.

You can check whether the row referenced still exists with gtk_tree_row_reference_valid, and free it with when no longer needed.

For the curious: internally, the tree row reference connects to the tree model's "row-inserted", "row-deleted", and "rows-reordered" signals and updates its internal tree path whenever something happened to the model that affects the position of the referenced row.

Note that using tree row references entails a small overhead. This is hardly significant for 99.9% of all applications out there, but when you have multiple thousands of rows and/or row references, this might be something to keep in mind (because whenever rows are inserted, removed, or reordered, a signal will be sent out and processed for each row reference).

If you have read the tutorial only up to here so far, it is hard to explain really what tree row references are good for. An example where tree row references come in handy can be found further below in the section on removing multiple rows in one go.

In practice, a programmer can either use tree row references to keep track of rows over time, or store tree iters directly (if, and only if, the model has persistent iters). Both GtkListStore and GtkTreeStore have persistent iters, so storing iters is possible. However, using tree row references is definitively the Right Way(tm) to do things, even though it comes with some overhead that might impact performance in case of trees that have a very large number of rows (in that case it might be preferable to write a custom model anyway though). Especially beginners might find it easier to handle and store tree row references than iters, because tree row references are handled by pointer value, which you can easily add to a GList or pointer array, while it is easy to store tree iters in a wrong way.

Usage

[edit | edit source]

Tree iters can easily be converted into tree paths using gtk_tree_model_get_path, and tree paths can easily be converted into tree iters using gtk_tree_model_get_iter. Here is an example that shows how to get the iter from the tree path that is passed to us from the tree view in the "row-activated" signal callback. We need the iter here to retrieve data from the store

/************************************************************
 *                                                          *
 * Converting a GtkTreePath into a GtkTreeIter              *
 *                                                          *
 ************************************************************/

/************************************************************
 *
 * onTreeViewRowActivated: a row has been double-clicked
 *
 ************************************************************/

void
onTreeViewRowActivated (GtkTreeView *view, GtkTreePath *path,
                        GtkTreeViewColumn *col, gpointer userdata)
{
	GtkTreeIter   iter;
  GtkTreeModel *model;

  model = gtk_tree_view_get_model(view);

  if (gtk_tree_model_get_iter(model, &iter, path))
  {
    gchar *name;

    gtk_tree_model_get(model, &iter, COL_NAME, &name, -1);

    g_print ("The row containing the name '%s' has been double-clicked.\n", name);

    g_free(name);
  }
}

Tree row references reveal the current path of a row with gtk_tree_row_reference_get_path. There is no direct way to get a tree iter from a tree row reference, you have to retrieve the tree row reference's path first and then convert that into a tree iter.

As tree iters are only valid for a short time, they are usually allocated on the stack, as in the following example (keep in mind that GtkTreeIter is just a structure that contains data fields you do not need to know anything about):

 /************************************************************
  *                                                          *
  *  Going through every row in a list store                 *
  *                                                          *
  ************************************************************/

  void
  traverse_list_store (GtkListStore *liststore)
  {
    GtkTreeIter  iter;
    gboolean     valid;

    g_return_if_fail ( liststore != NULL );

    /* Get first row in list store */
    valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(liststore), &iter);

    while (valid)
    {
       /* ... do something with that row using the iter ...          */
       /* (Here column 0 of the list store is of type G_TYPE_STRING) */
       gtk_list_store_set(liststore, &iter, 0, "Joe", -1);

       /* Make iter point to the next row in the list store */
       valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(liststore), &iter);
    }
  }

The code above asks the model to fill the iter structure to make it point to the first row in the list store. If there is a first row and the list store is not empty, the iter will be set, and gtk_tree_model_get_iter_first will return TRUE. If there is no first row, it will just return FALSE. If a first row exists, the while loop will be entered and we change some of the first row's data. Then we ask the model to make the given iter point to the next row, until there are no more rows, which is when gtk_tree_model_iter_next returns FALSE. Instead of traversing the list store we could also have used gtk_tree_model_foreach

Adding Rows to a Store

[edit | edit source]

Adding Rows to a List Store

[edit | edit source]

Rows are added to a list store with gtk_list_store_append. This will insert a new empty row at the end of the list. There are other functions, documented in the GtkListStore API reference, that give you more control about where exactly the new row is inserted, but as they work very similar to gtk_list_store_append and are fairly straightforward to use, we will not deal with them here.

Here is a simple example of how to create a list store and add some (empty) rows to it:

  GtkListStore *liststore;
  GtkTreeIter   iter;

  liststore = gtk_list_store_new(1, G_TYPE_STRING);

  /* Append an empty row to the list store. Iter will point to the new row */
  gtk_list_store_append(liststore, &iter);

  /* Append an empty row to the list store. Iter will point to the new row */
  gtk_list_store_append(liststore, &iter);

  /* Append an empty row to the list store. Iter will point to the new row */
  gtk_list_store_append(liststore, &iter);

This in itself is not very useful yet of course. We will add data to the rows in the next section.

Adding Rows to a Tree Store

[edit | edit source]

Adding rows to a tree store works similar to adding rows to a list store, only that gtk_tree_store_append is the function to use and one more argument is required, namely the tree iter to the parent of the row to insert. If you supply NULL instead of providing the tree iter of another row, a new top-level row will be inserted. If you do provide a parent tree iter, the new empty row will be inserted after any already existing children of the parent. Again, there are other ways to insert a row into the tree store and they are documented in the GtkTreeStore API reference manual. Another short example:

  GtkListStore *treestore;
  GtkTreeIter   iter, child;

  treestore = gtk_tree_store_new(1, G_TYPE_STRING);

  /* Append an empty top-level row to the tree store.
   *  Iter will point to the new row */
  gtk_tree_store_append(treestore, &iter, NULL);

  /* Append another empty top-level row to the tree store.
   *  Iter will point to the new row */
  gtk_tree_store_append(treestore, &iter, NULL);

  /* Append a child to the row we just added.
   *  Child will point to the new row */
  gtk_tree_store_append(treestore, &child, &iter);

  /* Get the first row, and add a child to it as well (could have been done
   *  right away earlier of course, this is just for demonstration purposes) */
  if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(treestore), &iter))
  {
    /* Child will point to new row */
    gtk_tree_store_append(treestore, &child, &iter);
  }
  else
  {
    g_error("Oops, we should have a first row in the tree store!\n");
  }

Speed Issues when Adding a Lot of Rows

[edit | edit source]

A common scenario is that a model needs to be filled with a lot of rows at some point, either at start-up, or when some file is opened. An equally common scenario is that this takes an awfully long time even on powerful machines once the model contains more than a couple of thousand rows, with an exponentially decreasing rate of insertion. As already pointed out above, writing a custom model might be the best thing to do in this case. Nevertheless, there are some things you can do to work around this problem and speed things up a bit even with the stock Gtk+ models:

Firstly, you should detach your list store or tree store from the tree view before doing your mass insertions, then do your insertions, and only connect your store to the tree view again when you are done with your insertions. Like this:

  ...

  model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));

  g_object_ref(model); /* Make sure the model stays with us after the tree view unrefs it */

  gtk_tree_view_set_model(GTK_TREE_VIEW(view), NULL); /* Detach model from view */

  ... insert a couple of thousand rows ...

  gtk_tree_view_set_model(GTK_TREE_VIEW(view), model); /* Re-attach model to view */

  g_object_unref(model);

  ...

Secondly, you should make sure that sorting is disabled while you are doing your mass insertions, otherwise your store might be resorted after each and every single row insertion, which is going to be everything but fast.

Thirdly, you should not keep around a lot of tree row references if you have so many rows, because with each insertion (or removal) every single tree row reference will check whether its path needs to be updated or not.

Manipulating Row Data

[edit | edit source]

Adding empty rows to a data store is not terribly exciting, so let's see how we can add or change data in the store.

gtk_list_store_set and gtk_tree_store_set are used to manipulate a given row's data. There is also gtk_list_store_set_value and gtk_tree_store_set_value, but those should only be used by people familiar with GLib's GValue system.

Both gtk_list_store_set and gtk_tree_store_set take a variable number of arguments, and must be terminated with a -1 argument. The first two arguments are a pointer to the model, and the iter pointing to the row whose data we want to change. They are followed by a variable number of (column, data) argument pairs, terminated by a -1. The column refers to the model column number and is usually an enum value (to make the code more readable and to make changes easier). The data should be of the same data type as the model column.

Here is an example where we create a store that stores two strings and one integer for each row:

  enum
  {
    COL_FIRST_NAME = 0,
    COL_LAST_NAME,
    COL_YEAR_BORN,
    NUM_COLS
  };

  GtkListStore *liststore;
  GtkTreeIter   iter;

  liststore = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);

  /* Append an empty row to the list store. Iter will point to the new row */
  gtk_list_store_append(liststore, &iter);

  /* Fill fields with some data */
  gtk_list_store_set (liststore, &iter,
                      COL_FIRST_NAME, "Joe",
                      COL_LAST_NAME, "Average",
                      COL_YEAR_BORN, (guint) 1970,
                      -1);

You do not need to worry about allocating and freeing memory for the data to store. The model (or more precisely: the GLib/GObject GType and GValue system) will take care of that for you. If you store a string, for example, the model will make a copy of the string and store that. If you then set the field to a new string later on, the model will automatically free the old string and again make a copy of the new string and store the copy. This applies to almost all types, be it G_TYPE_STRING or GDK_TYPE_PIXBUF.

The exception to note is G_TYPE_POINTER. If you allocate a chunk of data or a complex structure and store it in a G_TYPE_POINTER field, only the pointer value is stored. The model does not know anything about the size or content of the data your pointer refers to, so it could not even make a copy if it wanted to, so you need to allocate and free the memory yourself in this case. However, if you do not want to do that yourself and want the model to take care of your custom data for you, then you need to register your own type and derive it from one of the GLib fundamental types (usually G_TYPE_BOXED). See the GObject GType reference manual for details. Making a copy of data involves memory allocation and other overhead of course, so one should consider the performance implications of using a custom GLib type over a G_TYPE_POINTER carefully before taking that approach. Again, a custom model might be the better alternative, depending on the overall amount of data to be stored (and retrieved).

Retrieving Row Data

[edit | edit source]

Storing data is not very useful if it cannot be retrieved again. This is done using gtk_tree_model_get, which takes similar arguments as gtk_list_store_set or gtk_tree_store_set do, only that it takes (column, pointer) arguments. The pointer must point to a variable that is of the same type as the data stored in that particular model column.

Here is the previous example extended to traverse the list store and print out the data stored. As an extra, we use gtk_tree_model_foreach to traverse the store and retrieve the row number from the GtkTreePath passed to us in the foreach callback function:

  #include <gtk/gtk.h>

  enum
  {
    COL_FIRST_NAME = 0,
    COL_LAST_NAME,
    COL_YEAR_BORN,
    NUM_COLS
  };

  gboolean
  foreach_func (GtkTreeModel *model,
                GtkTreePath  *path,
                GtkTreeIter  *iter,
                gpointer      user_data)
  {
    gchar *first_name, *last_name, *tree_path_str;
    guint  year_of_birth;

    /* Note: here we use 'iter' and not '&iter', because we did not allocate
     *  the iter on the stack and are already getting the pointer to a tree iter */

    gtk_tree_model_get (model, iter,
                        COL_FIRST_NAME, &first_name,
                        COL_LAST_NAME, &last_name,
                        COL_YEAR_BORN, &year_of_birth,
                        -1);

    tree_path_str = gtk_tree_path_to_string(path);

    g_print ("Row %s: %s %s, born %u\n", tree_path_str,
             first_name, last_name, year_of_birth);

    g_free(tree_path_str);

    g_free(first_name); /* gtk_tree_model_get made copies of       */
    g_free(last_name);  /* the strings for us when retrieving them */

    return FALSE; /* do not stop walking the store, call us with next row */
  }

  void
  create_and_fill_and_dump_store (void)
  {
    GtkListStore *liststore;
    GtkTreeIter   iter;

    liststore = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);

    /* Append an empty row to the list store. Iter will point to the new row */
    gtk_list_store_append(liststore, &iter);

    /* Fill fields with some data */
    gtk_list_store_set (liststore, &iter,
                        COL_FIRST_NAME, "Joe",
                        COL_LAST_NAME, "Average",
                        COL_YEAR_BORN, (guint) 1970,
                        -1);

    /* Append another row, and fill in some data */
    gtk_list_store_append(liststore, &iter);

    gtk_list_store_set (liststore, &iter,
                        COL_FIRST_NAME, "Jane",
                        COL_LAST_NAME, "Common",
                        COL_YEAR_BORN, (guint) 1967,
                        -1);

    /* Append yet another row, and fill it */
    gtk_list_store_append(liststore, &iter);

    gtk_list_store_set (liststore, &iter,
                        COL_FIRST_NAME, "Yo",
                        COL_LAST_NAME, "Da",
                        COL_YEAR_BORN, (guint) 1873,
                        -1);

    /* Now traverse the list */

    gtk_tree_model_foreach(GTK_TREE_MODEL(liststore), foreach_func, NULL);
  }

  int
  main (int argc, char **argv)
  {
    gtk_init(&argc, &argv);

    create_and_fill_and_dump_store();

    return 0;
  }

Note that when a new row is created, all fields of a row are set to a default NIL value appropriate for the data type in question. A field of type G_TYPE_INT will automatically contain the value 0 until it is set to a different value, and strings and all kind of pointer types will be NULL until set to something else. Those are valid contents for the model, and if you are not sure that row contents have been set to something, you need to be prepared to handle NULL pointers and the like in your code.

Run the above program with an additional empty row and look at the output to see this in effect.

Freeing Retrieved Row Data

[edit | edit source]

Unless you are dealing with a model column of type G_TYPE_POINTER, gtk_tree_model_get will always make copies of the data retrieved.

In the case of strings, this means that you need to g_free the string returned when you don't need it any longer, as in the example above.

If you retrieve a GObject such as a GdkPixbuf from the store, gtk_tree_model_get will automatically add a reference to it, so you need to call g_object_unref on the retrieved object once you are done with it:

  ...

  GdkPixbuf *pixbuf;

  gtk_tree_model_get (model, &iter, 
                      COL_PICTURE, &pixbuf, 
                      NULL);

  if (pixbuf != NULL)
  {
    do_something_with_pixbuf (pixbuf);
    g_object_unref (pixbuf);
  }

  ...

Similarly, GBoxed-derived types retrieved from a model need to be freed with g_boxed_free when done with them (don't worry if you have never heard of GBoxed).

If the model column is of type G_TYPE_POINTER, gtk_tree_model_get will simply copy the pointer value, but not the data (even if it wanted to, it couldn't copy the data, because it would not know how to copy it or what to copy exactly). If you store pointers to objects or strings in a pointer column (which you should not do unless you really know what you are doing and why you are doing it), you do not need to unref or free the returned values as described above, because gtk_tree_model_get would not know what kind of data they are and therefore won't ref or copy them on retrieval.

Removing Rows

[edit | edit source]

Rows can easily be removed with gtk_list_store_remove and gtk_tree_store_remove. The removed row will automatically be removed from the tree view as well, and all data stored will automatically be freed, with the exception of G_TYPE_POINTER columns (see above).

Removing a single row is fairly straight forward: you need to get the iter that identifies the row you want to remove, and then use one of the above functions. Here is a simple example that removes a row when you double-click on it (bad from a user interface point of view, but then it is just an example):

  static void
  onRowActivated (GtkTreeView        *view,
                  GtkTreePath        *path,
                  GtkTreeViewColumn  *col,
                  gpointer            user_data)
  {
    GtkTreeModel *model;
    GtkTreeIter   iter;

    g_print ("Row has been double-clicked. Removing row.\n");

    model = gtk_tree_view_get_model(view);

    if (!gtk_tree_model_get_iter(model, &iter, path))
      return; /* path describes a non-existing row - should not happen */

    gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
  }


  void
  create_treeview (void)
  {
    ...
    g_signal_connect(treeview, "row-activated", G_CALLBACK(onRowActivated), NULL);
    ...
  }

Note: gtk_list_store_remove and gtk_tree_store_remove both have slightly different semantics in Gtk+-2.0 and Gtk+-2.2 and later. In Gtk+-2.0, both functions do not return a value, while in later Gtk+ versions those functions return either TRUE or FALSE to indicate whether the iter given has been set to the next valid row (or invalidated if there is no next row). This is important to keep in mind when writing code that is supposed to work with all Gtk+-2.x versions. In that case you should just ignore the value returned (as in the call above) and check the iter with gtk_list_store_iter_is_valid if you need it.

If you want to remove the n-th row from a list (or the n-th child of a tree node), you have two approaches: either you first create a GtkTreePath that describes that row and then turn it into an iter and remove it; or you take the iter of the parent node and use gtk_tree_model_iter_nth_child (which will also work for list stores if you use NULL as the parent iter. Of course you could also start with the iter of the first top-level row, and then step-by-step move it to the row you want, although that seems a rather awkward way of doing it.

The following code snippet will remove the n-th row of a list if it exists:

   /******************************************************************
    *
    *  list_store_remove_nth_row
    *
    *  Removes the nth row of a list store if it exists.
    *
    *  Returns TRUE on success or FALSE if the row does not exist.
    *
    ******************************************************************/

   gboolean
   list_store_remove_nth_row (GtkListStore *store, gint n)
   {
     GtkTreeIter  iter;

     g_return_val_if_fail (GTK_IS_LIST_STORE(store), FALSE);

     /* NULL means the parent is the virtual root node, so the
      *  n-th top-level element is returned in iter, which is
      *  the n-th row in a list store (as a list store only has
      *  top-level elements, and no children) */
     if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(store), &iter, NULL, n))
     {
       gtk_list_store_remove(store, &iter);
       return TRUE;
     }

     return FALSE;
   }

Removing Multiple Rows

[edit | edit source]

Removing multiple rows at once can be a bit tricky at times, and requires some thought on how to do this best. For example, it is not possible to traverse a store with gtk_tree_model_foreach, check in the callback function whether the given row should be removed and then just remove it by calling one of the stores' remove functions. This will not work, because the model is changed from within the foreach loop, which might suddenly invalidate formerly valid tree iters in the foreach function, and thus lead to unpredictable results.

You could traverse the store in a while loop of course, and call gtk_list_store_remove or gtk_tree_store_remove whenever you want to remove a row, and then just continue if the remove functions returns TRUE (meaning that the iter is still valid and now points to the row after the row that was removed). However, this approach will only work with Gtk+-2.2 or later and will not work if you want your programs to compile and work with Gtk+-2.0 as well, for the reasons outlined above (in Gtk+-2.0 the remove functions did not set the passed iter to the next valid row). Also, while this approach might be feasible for a list store, it gets a bit awkward for a tree store.

Here is an example for an alternative approach to removing multiple rows in one go (here we want to remove all rows from the store that contain persons that have been born after 1980, but it could just as well be all selected rows or some other criterion):

 /******************************************************************
  *
  *  Removing multiple rows in one go
  *
  ******************************************************************/

  ...

  gboolean
  foreach_func (GtkTreeModel *model,
                GtkTreePath  *path,
                GtkTreeIter  *iter,
                GList       **rowref_list)
  {
    guint  year_of_birth;

    g_assert ( rowref_list != NULL );

    gtk_tree_model_get (model, iter, COL_YEAR_BORN, &year_of_birth, -1);

    if ( year_of_birth > 1980 )
    {
      GtkTreeRowReference  *rowref;

      rowref = gtk_tree_row_reference_new(model, path);

      *rowref_list = g_list_append(*rowref_list, rowref);
    }

    return FALSE; /* do not stop walking the store, call us with next row */
  }

  void
  remove_people_born_after_1980 (void)
  {
     GList *rr_list = NULL;    /* list of GtkTreeRowReferences to remove */
     GList *node;

     gtk_tree_model_foreach(GTK_TREE_MODEL(store),
                            (GtkTreeModelForeachFunc) foreach_func,
                            &rr_list);

     for ( node = rr_list;  node != NULL;  node = node->next )
     {
        GtkTreePath *path;

        path = gtk_tree_row_reference_get_path((GtkTreeRowReference*)node->data);

        if (path)
        {
           GtkTreeIter  iter;

           if (gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
           {
             gtk_list_store_remove(store, &iter);
           }

           /* FIXME/CHECK: Do we need to free the path here? */
        }
     }

     g_list_foreach(rr_list, (GFunc) gtk_tree_row_reference_free, NULL);
     g_list_free(rr_list);
  }

  ...


gtk_list_store_clear and gtk_tree_store_clear come in handy if you want to remove all rows.

Storing GObjects (Pixbufs etc.)

[edit | edit source]

A special case are GObject types, like GDK_TYPE_PIXBUF, that get stored in a list or tree store. The store will not make a copy of the object, rather it will increase the object's refcount. The store will then unref the object again if it is no longer needed (i.e. a new object is stored in the old object's place, the current value is replaced by NULL, the row is removed, or the store is destroyed).

From a developer perspective, this means that you need to g_object_unref an object that you have just added to the store if you want the store to automatically dispose of it when no longer needed. This is because on object creation, the object has an initial refcount of 1, which is "your" refcount, and the object will only be destroyed when it reaches a refcount of 0. Here is the life cycle of a pixbuf:

  GtkListStore *list_store;
  GtkTreeIter   iter;
  GdkPixbuf    *pixbuf;
  GError       *error = NULL;

  list_store = gtk_list_store_new (2, GDK_TYPE_PIXBUF, G_TYPE_STRING);

  pixbuf = gdk_pixbuf_new_from_file("icon.png", &error);

  /* pixbuf has a refcount of 1 after creation */

  if (error)
  {
    g_critical ("Could not load pixbuf: %s\n", error->message);
    g_error_free(error);
    return;
  }

  gtk_list_store_append(list_store, &iter);

  gtk_list_store_set(list_store, &iter, 0, pixbuf, 1, "foo", -1);

  /* pixbuf has a refcount of 2 now, as the list store has added its own reference */

  g_object_unref(pixbuf);

  /* pixbuf has a refcount of 1 now that we have released our initial reference */

  /* we don't want an icon in that row any longer */
  gtk_list_store_set(list_store, &iter, 0, NULL, -1);

  /* pixbuf has automatically been destroyed after its refcount has reached 0.
   *  The list store called g_object_unref() on the pixbuf when it replaced
   *  the object in the store with a new value (NULL). */

Having learned how to add, manipulate, and retrieve data from a store, the next step is to get that data displayed in a GtkTreeView widget.

Storing Data Structures: of Pointers, GBoxed Types, and GObject (TODO)

[edit | edit source]

Unfinished chapter.