0053: MVC VI - A ComboBox with Images

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

And here’s a link to the zip file containing images used in this example.

So, we’ve done a two-column ListStore, how about one with four columns? And this time, let’s throw in a Pixbuf… which BTW isn’t defined in the GType enum.

The SignListStore Class

Here’s the initialization section:

class SignListStore : ListStore
{
	string[] items = ["bike", "bump", "cow", "deer", "crumbling cliff", "man with a stop sign", "skidding vehicle"];
	int[] signNumbers = [1, 2, 3, 4, 5, 6, 7];
	string[] images = ["_images/bicycle.png",
			"_images/bump.png", 
			"_images/cattle.png", 
			"_images/deer.png", 
			"_images/falling_rocks.png", 
			"_images/road_crew.png", 
			"_images/slippery_road.png"];
	string[] descriptions = ["Bicycles present",
				"Bump ahead",
				"Cattle crossing",
				"Deer crossing",
				"Falling rocks ahead",
				"Road crew ahead",
				"Slippery when wet"];
	enum Column
	{
		THEME_COLUMN = 0,
		NUMBER_COLUMN = 1,
		IMAGE_COLUMN = 2,
		DESCRIPTION_COLUMN = 3
		
	} // enum Column
	
	TreeIter treeIter;

And the definitions for the four columns mean we’re working with:

  • items: one-word text tags for each image,
  • signNumbers: numbers associated with each image,
  • images: relative paths and file names for each image, and
  • descriptions: strings with more complete descriptions of each sign.

You’ll also notice there’s an enum (Column) and we’ll see in a moment how these values are used, in the constructors for both the SignListStore and the SignComboBox.

Making the Column enum part of SignListStore means that anywhere we can access the ListStore, we’ll be able to access these static column values. And if you take a peek at the AppBox class…

class AppBox : Box
{
	SignComboBox signComboBox;
	SignListStore signListStore;
	
	this()
	{
		super(Orientation.VERTICAL, 10);
		
		signListStore = new SignListStore();
		signComboBox = new SignComboBox(signListStore);
		packStart(signComboBox, false, false, 0);
		
	} // this()

} // class AppBox

… you’ll see that a pointer to signListStore is passed into the SignComboBox constructor.

Now let’s look at the SignListStore constructor:

this()
{
	string item, imageName, description;
	int number;

	super([GType.STRING, GType.INT, Pixbuf.getType(), GType.STRING]);
		
	foreach(ulong i; 0..items.length)
	{
		item = items[i];
		number = signNumbers[i];
		imageName = images[i];
		description = descriptions[i];
			
		treeIter = createIter();
		setValue(treeIter, Column.THEME_COLUMN, item);
		setValue(treeIter, Column.NUMBER_COLUMN, number);
		setValue(treeIter, Column.IMAGE_COLUMN, new Pixbuf(imageName));
		setValue(treeIter, Column.DESCRIPTION_COLUMN, description);
	}

} // this()

The first thing I’ll point out is the array we’re passing to super(). See that Pixbuf.getType() argument? It needs a bit of explanation, so here goes…

Non-GTypes

Any visible GTK/GDK/Pango object class—such as Pixbuf, Color, RBGA, PgFontDescription, along with a whole raft of others—can be used as ListStore column data types. Some—like Color, RBGA, and PgFontDescription—can do no more than decorate other columns, but others—like the Pixbuf—can be visible in the ComboBox. And we get the correct type to pass to the ListStore constructor by asking the data class—not an instantiation of the class, but the base class itself—what type it is, kind of a “who goes there” approach:

Pixbuf.getType()

And that does the job. Include that right in the array we pass to the super-class constructor and it’s accepted as just another GType.

Note: Because getType() is a function call, it can’t be read at compile time, so we can’t predefine the array like we can if the columns all hold standard GTypes. The array has to be defined in the constructor at the very earliest, or written out as it’s passed to the super-class constructor as can be seen here:

super([GType.STRING, GType.INT, Pixbuf.getType(), GType.STRING]);

And the foreach() loop that calls setValue() can now stuff everything into the ListStore:

foreach(ulong i; 0..items.length)
{
	item = items[i];
	number = signNumbers[i];
	imageName = images[i];
	description = descriptions[i];
			
	treeIter = createIter();
	setValue(treeIter, Column.THEME_COLUMN, item);
	setValue(treeIter, Column.NUMBER_COLUMN, number);
	setValue(treeIter, Column.IMAGE_COLUMN, new Pixbuf(imageName));
	setValue(treeIter, Column.DESCRIPTION_COLUMN, description);
}

Two things of note here:

  1. I mentioned earlier that we’d see how the Column enum values are used and here’s the first of those uses, to identify which column is being filled for each pass through the foreach() loop, and
  2. the call to setValue() that handles the Pixbuf column makes a call to the Pixbuf constructor, passing along the file name grabbed from the imageName array.

Note: When your ListStore is to contain only string data, you can use set() instead of setValue() and thereby plug data into all the columns of a single row in one statement, but when using non-string data types, doing a setValue() on columns one at a time is your only option.

The SignComboBox Class

Rather than reproduce the entire SignComboBox class here, we’ll just look at the bits and pieces we haven’t seen before…

Because we want to show images instead of text, we need to declare a CellRendererPixbuf in the initialization section of the SignComboBox class:

CellRendererPixbuf cellRendererPixbuf;

And in the constructor we have the instantiation, the packing, and the adding of attributes:

cellRendererPixbuf = new CellRendererPixbuf();
packStart(cellRendererPixbuf, false);
addAttribute(cellRendererPixbuf, "pixbuf", _signListStore.Column.IMAGE_COLUMN);

You’ll note that whereas with the CellRendererText we used “text” as an attribute name, here we use “pixbuf” for the CellRendererPixbuf. Stands to reason, right?

Note: There is a bit of terminology confusion with the second argument to the addAttribute() statement. In the addAttribute() function definition, this argument is called an attribute. But when you look in the online reference or one of the books written about GTK, they’re called properties.

Anyway, if you’re curious about this type of attribute/property, go to the GTK Reference Manual online and search in the page for CellRenderer. Click on any of them and you’ll find a Properties section not too far down the page.

As we encounter each type of CellRenderer, we’ll cover which property/attribute is used with each one.

The other thing to note about the call to addAttribute() is its last argument:

_signListStore.Column.IMAGE_COLUMN

This is the second (and final) use of the Column enum and it’s why the Column enum was made part of the SignListStore class. A pointer to the store itself gets passed into the SignComboBox constructor, so we have access to it here without any major fiddling around.

Bonus Example – A 3-Column ComboBox

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

The folder/directory of images is used with this example as well.

This post is running a bit long, but this example really won’t take much explanation, so here goes…

There are only two differences between this example and the last. One’s in the constructor and it’s merely how many CellRenderers we stuff into the SignComboBox. It happens in these lines in the constructor:

cellRendererInt = new CellRendererText();
packStart(cellRendererInt, false);
addAttribute(cellRendererInt, "text", _signListStore.Column.NUMBER_COLUMN);
	
cellRendererText = new CellRendererText();
packStart(cellRendererText, false);
addAttribute(cellRendererText, "text", _signListStore.Column.THEME_COLUMN);

cellRendererPixbuf = new CellRendererPixbuf();
packStart(cellRendererPixbuf, false);
addAttribute(cellRendererPixbuf, "pixbuf", _signListStore.Column.IMAGE_COLUMN);

The ComboBox is derived from the Bin class and so all we have to do is pack in three CellRenderers, making sure we use an appropriate attribute (in this case, “text” for the first two columns and “pixbuf” for the last) and make sure they’re pointing to columns with matching data.

The other difference is…

There’s no visibleColumn variable because they’re all visible.

So remember:

  • you can have as many columns visible in the ComboBox as you wish, and
  • multiple calls to addAttribute() will make this happen.

Conclusion

And that wraps up our look at ComboBoxes with images. Next time we’ll start looking at the TreeView.

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