Create  Edit  FrontPage  Index  Search  Changes  History  RSS  Back  Login

tut-treeview-model-reference Diff - Ruby-GNOME2 Project Website

  • Added parts are displayed like this.
  • Deleted parts are displayed like this.

= Refering to Rows: Gtk::TreeIter, Gtk::TreePath, Gtk::TreeRowReference
{{link "tut-treeview-model-data", "tut-treeview-model", "tut-treeview", "tut-treeview-model-add"}}

There are different ways to refer to a specific row. The two you will have to deal with are Gtk::TreeIter and Gtk::TreePath.

:((*Tree Path*)):
A Gtk::TreePath is a comparatively straight-forward way to describe the logical position of a row in the model. As a Gtk::TreeView always displays all rows in a model, a tree path always describes the same row in both model and view.

{{image_right "treepath.png"}}

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. You can get a new Gtk::TreePath from a path in string form using Gtk::TreePath.new(path_in_string_notation). But if you already have a Gtk::TreePath object you can convert it into its string notation with Gtk::TreePath#to_str. 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, Gtk::TreePath uses an integer array internally. You can get the depth (ie. the nesting level) of a tree path with Gtk::TreePath#depth. A depth of 0 is the imaginary invisible root node of the tree view and model (do not be misled on the picture the imaginary root level is not present, hence Songs and Videos are on depth 1). 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::TreePath#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 methods like Gtk::TreePath#up!, Gtk::TreePath#down!, Gtk::TreePath#next!, Gtk::TreePath#prev!, Gtk::TreePath#ancestor?, Gtk::TreePath#decendent?. Note that this way you can construct and operate on tree paths that refer to rows that do not exist in model or view.

:((*Tree Iterator*)):
Another way to refer to a row in a list or tree model is Gtk::TreeIter. 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 provide methods that operate on tree iters (e.g. add or remove an item to or from the child of the row specified by a given tree iter, or move to the next element in the tree or list, etc.).

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::ListStore#append or Gtk::TreeStore#append.

Tree iters are often only valid for a short time, and might become invalid if the store changes. It is therefore usually a bad idea to store tree iters. You can use Gtk::TreeModel#flags to get a model's flags, and check whether the Gtk::TreeModel::ITERS_PERSIST flag is set (in which case a tree iter will be valid as long as a row exists), but there is a better way to keep track of a row: Gtk::TreeRowReference.

A GtkTreeRowReference is basically an object that takes a tree path, and watches the 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 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 Gtk::ListStore and Gtk::TreeStore have persistent iters. Using tree row references is definitively the right way to do things, but comes with considerable overhead that might impact performance in case of trees that have a large number of rows.

Tree iters can easily be converted into tree paths using Gtk::TreeIter#path, and tree paths can easily be converted into tree iters using Gtk::TreeModel#get_iter. For example:

path = an_iter.path
model = treeview.model
iter = model.get_iter(path)    # where path : The Gtk::TreePath or a String representation of a Gtk::TreePath.

Tree row references reveal the current path of a row with Gtk::TreeRowReference#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. For example following is a simulation showing how to  obtain an iter from a tree reference (see the example program at the end of the page):

row_ref = Gtk::TreeRowReference.new(treestore, Gtk::TreePath.new("2:0:1"))  # <--- pretend we have an arbitrary TreeReference
iter = row_ref.model.get_iter(row_ref.path)
puts "Iter from Ref: First Name: #{iter[0]}"

As tree iters are only valid for a short time, they are usually allocated on the heap, as in the following example:

def traverse_list_store (liststore)
   # get first row in list store
   return unless iter = liststore.iter_first

   begin  
     # ... do something with that row using the iter ...
     # (Here column 0 of the list store is of type String)
     iter[0] = "Joe"
   end while iter.next!

   true
end

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::TreeModel#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::TreeIter#next! returns FALSE. Instead of traversing the list store we could also have used Gtk::TreeModel#each

liststore.each { |model,path,iter| iter[0]= "Joe" }

To demonstrate some of the points learned here the following summary example is a bit more involved and contains features we have not yet talked about. If you are not comfortable reading it you can ignore it until you first read about building TreeStore in the following page, but just looking at the screenshot of the program may suffice to clarify some unclear issues, and indeed, I believe you will appreciate this program later when you have read the missing info.


((*Example employing some of the items discussed on this page:*))


{{image_right "TreeStore-n-Refs.png"}}

#!/usr/bin/env ruby
require 'gtk2'

=begin
   1. create 3 levels deep TreeStore

   2. Implement {{ set_cell_data_func }}
  
   3. Demonstrate how a renderer property (foreground) is handled
      via {{r.foreground="red"}}, and {{fu_r.foreground_set=false}}
      inside (as well as outside) the {{ set_cell_data_func }}

   4. Use a virtual view column to dynamically display path and depth
  
   5. Test the feature showing how to obtain an iter from a tree reference
=end

treestore = Gtk::TreeStore.new(String, String, Integer)

# Append a toplevel row and fill in some data
parent = treestore.append(nil)
parent[0] = "Maria"
parent[1] = "Incognito"
# Append another toplevel row and fill in some data
parent = treestore.append(nil)
parent[0] = "Tony"
parent[1] = "Zus"
parent[2] = 1949
# Append a second toplevel row and fill in some data
parent = treestore.append(nil)
parent[0] = "Jane"
parent[1] = "Average"
parent[2] = 1962
# Append a child to the second toplevel row and fill in some data
child = treestore.append(parent)
child[0] = "Janinita"
child[1] = "Average"
child[2] = 1985
# Append a grandchild to the third level row and fill in some data
grandchild = treestore.append(child)
grandchild[0] = "Janinitica"
grandchild[1] = "Average"
grandchild[2] = 2002
# Append the 2nd grandchild to the third level row and fill in some data
grandchild = treestore.append(child)
grandchild[0] = "Janinitica2"
grandchild[1] = "Average"
grandchild[2] = 2003
# Append again a second toplevel row and fill in some data
parent = treestore.append(nil)
parent[0] = "Gustav"
parent[1] = "Crocky"
parent[2] = 1951
# Append a child to the second toplevel row and fill in some data
child = treestore.append(parent)
child[0] = "Gustlek"
child[1] = "Crocky"
child[2] = 1970
# Append a child to the second toplevel row and fill in some data
child = treestore.append(parent)
child[0] = "Zyla"
child[1] = "Crocky"
child[2] = 1973


view = Gtk::TreeView.new(treestore)
view.selection.mode = Gtk::SELECTION_NONE

# Create a renderer
renderer = Gtk::CellRendererText.new
# Add column using our renderer
col = Gtk::TreeViewColumn.new("First Name", renderer, :text => 0)
view.append_column(col)

# Create another renderer and set the weight property
renderer = Gtk::CellRendererText.new
renderer.weight = Pango::FontDescription::WEIGHT_BOLD
# Add column using the second renderer
col = Gtk::TreeViewColumn.new("Last Name", renderer, :text => 1)
view.append_column(col)

# Create one more renderer and set the foreground color to red
renderer = Gtk::CellRendererText.new
renderer.foreground = "red"
# Add column using the third renderer
col = Gtk::TreeViewColumn.new("Age", renderer)
view.append_column(col)

# Create a cell data function to calculate age
col.set_cell_data_func(renderer) do |col, renderer, model, iter|
   year_now = 2012 # To save code not relevent to the example
   year_born = iter[2]

   if (year_born <= year_now) && (year_born > 0)

     ### - NOTE: the following doesn't work {{ iter[2] === Integer }} !!!
     # iter[2] = sprintf("%i years old", year_now - year_born)
     ### - You must employ:  renderer.text = ...
     renderer.text = sprintf("%i years old", year_now - year_born)


     # render in default foreground color if we know the age
     renderer.foreground_set = false
   else
     renderer.text = "age unknown"
     # render with foreground color we set earlier if we don't know the age
     renderer.foreground_set = true
   end
end

# - add another (virtual) VIEW column to TreeView
# Create one last renderer and set its display value to row's path
renderer = Gtk::CellRendererText.new
# Add column using the third renderer
col = Gtk::TreeViewColumn.new("Path & Depth", renderer)
view.append_column(col)

# Create a cell data function to display row path and depth.
col.set_cell_data_func(renderer) do |col, renderer, model, iter|
  renderer.text = "P:#{iter.path.to_str} \tDepth:#{iter.path.depth}"
end

vbox = Gtk::VBox.new(homogeneous=false, spacing=nil)
button = Gtk::Button.new("Click to Test TreeReference")
button.signal_connect("clicked") do
   # pretend we have an arbitrary TreeReference
   row_ref = Gtk::TreeRowReference.new(treestore, Gtk::TreePath.new("2:0:1"))
   iter = row_ref.model.get_iter(row_ref.path)
   puts "Iter from Ref: First Name: #{iter[0]}"
end

vbox.pack_start_defaults(view)
vbox.pack_start_defaults(button)

window = Gtk::Window.new("Test Obtaining Iters from TreeReference")
window.signal_connect("destroy") { Gtk.main_quit }
window.add(vbox)
window.show_all
Gtk.main