0065: MVC X – TreeStore Basics
About a month ago, we broke away from MVC to talk about drawing with Cairo. Time to go back and pick up where we left off…
TreeStore Modeling with append()
As mentioned in the introduction to this series, the TreeStore
isn’t populated in quite the same way as a ListStore
because a ListStore
is a flat model while a TreeStore
has a hierarchy. But the difference isn’t that drastic.
In a flat model store, each row iter is created without associating it with any other, but in a hierarchical store, we do this:
- create a row iter, and
- use that iter to create a child row iter.
And if you add a grandchild row or even more generations, it’s the same process all the way down.
Of course, we also have to populate the rows as we go along, so we stuff this extra step in between the other two:
- create a row iter,
- populate it,
- use the existing iter as a parent as we create and populate the child row.
There are two approaches we can use, one with append()
and prepend()
, the other with createIter()
, all of which are functions of the TreeStore
object.
The code for a simple parent/child relationship might look like this:
class DemoTreeStore : TreeStore
{
TreeIter parentIter, childIter;
string parentRowString = "Parent";
string childRowString = "Child";
this()
{
super([GType.STRING, GType.STRING]);
parentIter = append(null);
setValue(parentIter, 0, parentRowString);
childIter = append(parentIter);
setValue(childIter, 1, childRowString);
} // this()
} // class DemoTreeStore
The first step in the constructor is the same as any other store/model. The super-class constructor needs an array of types, one for each column.
But then we depart from the flat model procedure. Comparing the two calls to append()
:
- for the parent row,
append()
is passed anull
value, but - for the child row,
append()
is passed the parent row’s iter.
The three bits of knowledge to glean from all this are:
append()
always returns aTreeIter
,- a
null
value tellsappend()
to create a top-level row, and - a non-
null
value tellsappend()
that the row being created is a child.
But, one thing we can’t do with append(
) is populate the row, so we still need to do that with setValue()
.
In effect, this approach allows us to use whatever TreeIter
is returned by append(
) to create the next generation of rows, going from parent to child to grandchild, etc.
TreeStore with Multiple Top-level Rows
For completeness sake, here’s a second example that adds multiple children to each of three top-level rows. The relevant code looks like this:
this()
{
super([GType.STRING, GType.STRING]);
parentIter = append(null); // first header row
setValue(parentIter, parentColumn, "Mom #1");
append(childIter, parentIter);
setValue(childIter, childColumn, "Kid #1");
append(childIter, parentIter);
setValue(childIter, childColumn, "Kid #2");
parentIter = append(null); // first header row
setValue(parentIter, parentColumn, "Dad #1");
append(childIter, parentIter);
setValue(childIter, childColumn, "Kid #3");
append(childIter, parentIter);
setValue(childIter, childColumn, "Kid #4");
parentIter = append(null); // first header row
setValue(parentIter, parentColumn, "Mom #2");
append(childIter, parentIter);
setValue(childIter, childColumn, "Kid #5");
append(childIter, parentIter);
setValue(childIter, childColumn, "Kid #6");
append(childIter, parentIter);
setValue(childIter, childColumn, "Kid #7");
} // this()
This should be straightforward to work out… we’re appending a parent row followed by the children of that parent, then moving on to the next parent.
But I mentioned earlier that there’s a second way to do this, so let’s look at that…
TreeStore Modeling with createIter()
The main difference here is that createIter()
doesn’t need a null to know it’s creating a top-level row. Have a look:
class DemoTreeStore : TreeStore
{
TreeIter parentIter, childIter;
string parentRowString = "Parent";
string childRowString = "Child";
this()
{
super([GType.STRING, GType.STRING]);
parentIter = createIter();
setValue(parentIter, 0, parentRowString);
childIter = createIter(parentIter);
setValue(childIter, 1, childRowString);
} // this()
} // class DemoTreeStore
Everything else is the same with just that simple substitution. append()
becomes creatIter()
, but the createIter()
argument is either the parent iter or nothing at all.
Populating a TreeStore in a Loop with createIter()
One last example today and it uses createIter()
as we just did, but takes what we did with the multiple top-level rows example and shoves it into a foreach()
loop. Relevant code:
this()
{
super([GType.STRING, GType.STRING]);
foreach(ulong i; 0..parentHeaders.length)
{
string parentTitle = parentHeaders[i];
string[] childFamily = children[i];
parentIter = createIter(); // append an empty row to the top level and get an iter back
setValue(parentIter, 0, parentTitle);
foreach(ulong j; 0..childFamily.length)
{
childIter = createIter(parentIter); // passing in parentIter makes this a child row
string child = children[i][j];
setValue(childIter, 1, child);
}
}
} // this()
No real surprises here. In the outside foreach()
loop…
- the parent iter is created as an empty row,
- its label is populated with
setValue()
, - then the inner
foreach()
loop:- creates the
childIter
by passing inparentIter
, - picks the appropriate string from the
children
array, and - does a
setValue()
to fill in the child rows.
- creates the
Conclusion
And that’s the basics of the TreeStore
class.
Comments? Questions? Observations?
Did we miss a tidbit of information that would make this post even more informative? Let's talk about it in the comments.
- come on over to the D Language Forum and look for one of the gtkDcoding announcement posts,
- drop by the GtkD Forum,
- follow the link below to email me, or
- go to the gtkDcoding Facebook page.
You can also subscribe via RSS so you won't miss anything. Thank you very much for dropping by.
© Copyright 2024 Ron Tarrant