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()

Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)

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 a null 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 a TreeIter,
  • a null value tells append() to create a top-level row, and
  • a non-null value tells append() 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

Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)

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()

Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)

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()

Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)

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 in parentIter,
    • picks the appropriate string from the children array, and
    • does a setValue() to fill in the child rows.

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.

You can also subscribe via RSS so you won't miss anything. Thank you very much for dropping by.

© Copyright 2023 Ron Tarrant