0051: MVC IV – The ComboBox with Text

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

Today starts a mini-series within our MVC series in which we look at a simple ComboBox example to reproduce what we’ve already done with the ComboBoxText.

So… unlike the ComboBoxText—which you more or less just throw strings at—the ComboBox needs an actual Model (ListStore or TreeStore) to draw text strings from. This is a little more work than it was with the ComboBoxText, so let’s dig in…

The Model

We start with a ListStore class:

class SignListStore : ListStore
{
	string[] items = ["bike", "bump", "cow", "deer", "crumbling cliff", "man with a stop sign", "skidding vehicle"];
	TreeIter treeIter;
	
	this()
	{
		super([GType.STRING]);
		
		foreach(ulong i; 0..items.length)
		{
			string message = items[i];
			treeIter = createIter();
			setValue(treeIter, 0, message);
		}

	} // this()

} // class SignListStore

Now, there are similarities. We still have a string array, but how we handle it is quite different.

The Model Constructor

The ListStore constructor takes an array of GTypes and these define the data types each column in the ListStore will hold. The GType enum, found in generated/gtkd/gobject/c/types.d, defines all the built-in types we can use here. Later, when we look at more complex examples, we’ll go over how to deal with more complex types, but for now, these will do.

You’ll note that the call to super() still gets an array for an argument, even though we’re only using one data type. And, of course, because there’s only one element in the array, there will be only one column.

The foreach() loop steps through the array, picks one of the items, instantiates a TreeIter, and then sets the value in the ListStore row.

The setValue() arguments are:

  • treeIter – a pointer to the ListStore row where we’ll store the current data,
  • 0 – the column number (in this case the only column we have) within the ListStore where the data will end up, and
  • message – the string data we’re storing.

The View/Control

The ComboBox acts as both View and Control. Keep in mind that it’s based on the CellLayout interface and so it a non-standard implementation of the MVC paradigm. But, no matter. The results are so similar, they make no real difference, so let’s carry on.

Let’s look at the SignComboBox a bit at a time starting with…

The Initialization Chunk

class SignComboBox : ComboBox
{
	private:
	bool entryOn = false;
	SignListStore _signListStore;
	CellRendererText cellRendererText;
	int visibleColumn = 0;
	int activeItem = 0;

Here’s what these are:

  • entryOn we’ve used before and with it being false, it stops the ComboBox from including an Entry widget,
  • _signListStore is just a convenient (and local) place to keep a pointer to the ListStore,
  • cellRendererText tells the ComboBox that we’ll be working with and displaying text items,
  • visibleColumn is the ListStore column number from which we’ll draw data, and
  • activeItem is the ListStore row number (index) that’ll be selected by default.

We’ll talk more about the visibleColumn variable and CellRenderers of various types when we look at other examples later in this mini-series.

The Constructor

public:
this(SignListStore signListStore)
{
	super(entryOn);
	
	// set up the ComboBox's column to render text
	cellRendererText = new CellRendererText();
	packStart(cellRendererText, false);
	addAttribute(cellRendererText, "text", visibleColumn);
	
	// set up and bring in the store
	_signListStore = signListStore;
	setModel(_signListStore);
	setActive(activeItem);
	
	addOnChanged(&doSomething);
	
} // this()

After the instantiation of the super-class, we have three stages to this constructor:

  • setting up and packing the CellRenderer,
  • initializing the ListStore (Model), and
  • hooking up the signal.

Stage 1: CellRendererText

In the introduction to this series, I mentioned that one or more CellRenderers are packed into a TreeViewColumn so it knows how to display its contents. With a ComboBox, we don’t have a TreeViewColumn. Instead, as I also said earlier, the ComboBox is an implementation of the CellLayout interface. This interface is also implemented by the TreeViewColumn which means the ComboBox acts as its own TreeViewColumn of a sort. From a practical point of view, all this means is that you can treat the ComboBox as if it has a TreeViewColumn… sort of. Later on, we’ll dig into this a bit and see how flexible this can be.

For now, though, this is what happens in the first stage of the constructor:

  • the CellRenderer is instantiated,
  • its packed into the ComboBox, and
  • we use addAttribute() to tell the ComboBox:
    • what its single column will display,
    • which CellRenderer to use, and
    • which column will be visible (in this case, the only column we have).

Stage 2: Initializing the Model

Not a big deal, we just:

  • assign a local pointer to the ListStore,
  • use setModel() to tell the ComboBox where to look for its data, and
  • pre-select one of the items, using setActive(), so the ComboBox shows a default value.

Moving on…

Stage 3: The Callback

And the last line of the constructor hooks up the callback signal, but that’s straightforward, so let’s look at the callback code itself:

void doSomething(ComboBox cb)
{
	string data;
	TreeIter treeIter;

	write("index of selection: ", getActive(), ", ");
	
	if(getActiveIter(treeIter) == true)
	{
		data = getModel().getValueString(treeIter, 0);
		writeln("data: ", data);
	}

} // doSomething()

Again, we define a TreeIter which we’ll go over in a moment.

The first action we take is to get the index of the currently-selected item. This is here purely for completeness sake. It really has nothing to do with the next step…

which is where we use the TreeIter, not to stuff data into the ListStore, but to retrieve it. The getActiveIter() function returns a Boolean to indicate success or failure, so we can predicate further action on whether or not the TreeIter gets initialized here. And yes, it’s one of those D-language situations where the function definition looks like this:

public bool getActiveIter(out TreeIter iter)

And if you don’t yet know, that’s D’s way of asking a function to assign value to an argument. And to make things easy for this worker function, D has the ability to hand it the argument using the out qualifier.

Anyway, if the TreeIter gets instantiated by getActiveIter(), we then:

  • use getModel() to grab the ListStore’s TreeModel so we can
  • use its getValueString() function to grab the data stored in the first (0th) column of the Model.

It looks and sounds far more complex than it actually is. We could have done the same thing like this:

   model = getModel();
   data = model.getValueString(treeIter, 0);

But, whatever. From there, we can do whatever we want with the fetched data. In this case, we just echo it to the terminal.

Conclusion

Okay, so there we have a reproduction of the ComboBoxText using a ComboBox and—who’d-a thunk it—some text. Sure, it’s more work, but as we’ll see in the rest of this mini-series within a series, when we turn to non-string data, we need to know this stuff.

See you next time when we tackle a ComboBox with integers.

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