Tutorial: The ComboBox class

Table of Contents


This tutorial introduces the ComboBox class, which is a component for displaying lists of items to the user. The contents of a ComboBox object can be modified dynamically, and can be used for text input, too.

Level: Beginner

Platforms: Windows, macOS, Linux, iOS, Android

Classes: ComboBox, Label, Font, Colour, Colours

Getting started

Download the demo project for this tutorial here: PIP | ZIP. Unzip the project and open it in your IDE.

If you need help with this step, see Tutorial: Projucer Part 1: Getting started with the Projucer.

The demo project

The demo project displays a piece of text at the top of the window within a Label component (see Tutorial: The Label class). A ComboBox component contains the items Plain, Bold, and Italic. The user can select one of these items to change the style of the text within the label.

tutorial_combo_box_screenshot1.png
Selecting font styles

The ComboBox class

This tutorial introduces many of the features of the ComboBox class. A ComboBox component contains a list of text strings. Each of these text strings is associated with an ID number (an int value). You can query which item is currently selected, either by:

The ComboBox class is also a broadcaster. To listen for changes you can register a ComboBox::Listener class (see Tutorial: Listeners and Broadcasters) or alternatively use a Lambda function with the ComboBox::onChange helper object.

Let's examine the demo project. In our MainContentComponent class we have four private members:

Label textLabel { {}, "The quick brown fox jumps over the lazy dog." };
Font textFont { 12.0f };
ComboBox styleMenu;

The Label and Font objects are configured in the constructor:

MainContentComponent()
{
addAndMakeVisible (textLabel);
textLabel.setFont (textFont);
//...

Adding items

Items can be added to a ComboBox object one at a time using the ComboBox::addItem() function. Here we add the "Plain", "Bold", and "Italic" items with ID numbers 1, 2, and 3 respectively:

addAndMakeVisible (styleMenu);
// add items to the combo-box
styleMenu.addItem ("Plain", 1);
styleMenu.addItem ("Bold", 2);
styleMenu.addItem ("Italic", 3);
styleMenu.onChange = [this] { styleMenuChanged(); };
styleMenu.setSelectedId (1);

Responding to changes

We could have registered our MainContentComponent object as a listener to get notified when the styleMenu ComboBox object is changed by the user in the application. But in this case we implement the ComboBox::onChange helper object to directly call the styleMenuChanged() function:

styleMenu.onChange = [this] { styleMenuChanged(); };

The styleMenuChanged() function handles changes to the styleMenu object:

void styleMenuChanged()
{
switch (styleMenu.getSelectedId())
{
case 1: textFont.setStyleFlags (Font::plain); break;
case 2: textFont.setStyleFlags (Font::bold); break;
case 3: textFont.setStyleFlags (Font::italic); break;
}
textLabel.setFont (textFont);
}

Here you can see that we set the textFont Font object appropriately based on the user's selection. Then we use this font to update the textLabel Label object's font.

Using item ID numbers

You can use any integer as an item ID except zero. Zero has a special meaning. It is used to indicate that none of the items are selected (either an item hasn't been selected yet or the ComboBox object is displaying some other custom text).

With a small selection of items using simple numbers like 1, 2, and 3 in your code is easy to manage. This would quickly become unmanageable as you develop your app further. In our case it would be clearer to use an enum. Let's add a private enum for our styles:

enum FontStyles
{
stylePlain = 1,
styleBold,
styleItalic,
numberOfStyles
};

Then we can use these values when setting up the ComboBox object:

addAndMakeVisible (styleMenu);
styleMenu.addItem ("Plain", stylePlain);
styleMenu.addItem ("Bold", styleBold);
styleMenu.addItem ("Italic", styleItalic);
styleMenu.onChange = [this] { styleMenuChanged(); };
styleMenu.setSelectedId (stylePlain);

And in our styleMenuChanged() function:

void styleMenuChanged()
{
switch (styleMenu.getSelectedId())
{
case stylePlain: textFont.setStyleFlags (Font::plain); break;
case styleBold: textFont.setStyleFlags (Font::bold); break;
case styleItalic: textFont.setStyleFlags (Font::italic); break;
}
textLabel.setFont (textFont);
}

At the very least this make the code much more readable.

Other common strategies for using ID numbers are:

Sections and dividers

The list of items in a combo-box can contain dividers and section headings. This is especially useful in very long lists. Let's add a combo-box to our app that changes the text colour. First, let's add another enum for our colours:

enum TextColours
{
black = 1,
white,
red,
darkred,
indianred,
green,
darkgreen,
lightgreen,
blue,
darkblue,
lightblue,
numberOfColours
};

And a new member to our MainContentComponent class:

ComboBox coloursMenu;

In the MainContentComponent constructor we need to add the code to set up the new combo-box. Here we introduce two new functions, ComboBox::addSeparator() and ComboBox::addSectionHeading():

addAndMakeVisible (coloursMenu);
coloursMenu.addItem ("Black", black);
coloursMenu.addItem ("White", white);
coloursMenu.addSeparator();
coloursMenu.addSectionHeading ("Reds");
coloursMenu.addItem ("Red", red);
coloursMenu.addItem ("Dark Red", darkred);
coloursMenu.addItem ("Indian Red", indianred);
coloursMenu.addSeparator();
coloursMenu.addSectionHeading ("Greens");
coloursMenu.addItem ("Green", green);
coloursMenu.addItem ("Dark Green", darkgreen);
coloursMenu.addItem ("Light Green", lightgreen);
coloursMenu.addSeparator();
coloursMenu.addSectionHeading ("Blues");
coloursMenu.addItem ("Blue", blue);
coloursMenu.addItem ("Dark Blue", darkblue);
coloursMenu.addItem ("Light Blue", lightblue);
coloursMenu.onChange = [this] { coloursMenuChanged(); };
coloursMenu.setSelectedId (black);

You can use separators without section headings and section headings without separators but they do work well together.

Note
Separators and section headings can't be selected and don't have associated ID numbers.

We need to implement a new function to handle the changes to the coloursMenu object:

void coloursMenuChanged()
{
Colour textColour;
switch (coloursMenu.getSelectedId())
{
case black: textColour = Colours::black; break;
case white: textColour = Colours::white; break;
case red: textColour = Colours::red; break;
case darkred: textColour = Colours::darkred; break;
case indianred: textColour = Colours::indianred; break;
case green: textColour = Colours::green; break;
case darkgreen: textColour = Colours::darkgreen; break;
case lightgreen: textColour = Colours::lightgreen; break;
case blue: textColour = Colours::blue; break;
case darkblue: textColour = Colours::darkblue; break;
case lightblue: textColour = Colours::lightblue; break;
}
textLabel.setColour (Label::textColourId, textColour);
}

Don't forget to set the bounds of the coloursMenu object in the resized() function:

coloursMenu.setBounds (10, 70, getWidth() - 20, 20);

Run the project and the window should look something like this:

tutorial_combo_box_screenshot2.png
Selecting colours and styles

You can now change the colour of the label text.

Text entry

The default behaviour of a combo-box is to allow the user to select only the items listed. But, you can make the combo-box editable to allow the user to enter other text. In the MainContentComponent constructor let's make our coloursMenu object editable:

coloursMenu.setEditableText (true);

Now let's add a function that takes the text from the coloursMenu object and treats the text as a hexadecimal colour code. The colour code is in the ARGB format (for example, "ff888888"" would be an opaque mid-grey colour):

Colour handleColourText()
{
auto menuText = coloursMenu.getText();
auto valueFromHex = menuText.getHexValue32();
coloursMenu.setText (String::toHexString ((int) valueFromHex));
return Colour (valueFromHex);
}

Notice that we convert the string to a hex value then convert it back to a string, setting the combo-box text. This is a simple way to filter the text entered.

Note
A more sophisticated method to filter input text would be to implement a custom class that derives from the ComboBox class. Then you can override its Label::Listener::editorShown() virtual function to customise its text editor. You can use the TextEditor::setInputFilter() function to use a TextEditor::InputFilter object to filter the text entered before it even appears on the screen.

In our coloursMenuChanged() function we need to update the switch..case to call our handleColourText() function in the default case:

switch (coloursMenu.getSelectedId())
{
case black: textColour = Colours::black; break;
case white: textColour = Colours::white; break;
case red: textColour = Colours::red; break;
case darkred: textColour = Colours::darkred; break;
case indianred: textColour = Colours::indianred; break;
case green: textColour = Colours::green; break;
case darkgreen: textColour = Colours::darkgreen; break;
case lightgreen: textColour = Colours::lightgreen; break;
case blue: textColour = Colours::blue; break;
case darkblue: textColour = Colours::darkblue; break;
case lightblue: textColour = Colours::lightblue; break;
default: textColour = handleColourText(); break;
}
tutorial_combo_box_screenshot3.png
Entering custom text

Run the application and you should be able to enter custom colours in the six-digit hex format (RGB).

Exercise
Modify the code to add any custom hex colours to the coloursMenu combo-box so that the user can choose these items again if they wish.

Disabling and enabling items

Items in a combo-box can be disabled and enabled (they are, of course, enabled by default). When they are disabled they will still be visible but shown greyed-out. Let's change the code so that none of the lighter versions colours (and white) can be selected if the bold style is selected. First, let's add a function to disable or enable the lighter colours using the ComboBox::setItemEnabled() function:

void setLightColoursEnabled (const bool shouldBeEnabled)
{
coloursMenu.setItemEnabled (white, shouldBeEnabled);
coloursMenu.setItemEnabled (indianred, shouldBeEnabled);
coloursMenu.setItemEnabled (lightgreen, shouldBeEnabled);
coloursMenu.setItemEnabled (lightblue, shouldBeEnabled);
}

Now let's refactor our styleMenuChanged() function and make use of the setLightColoursEnabled() function:

void styleMenuChanged()
{
switch (styleMenu.getSelectedId())
{
case stylePlain: setStylePlain(); break;
case styleBold: setStyleBold(); break;
case styleItalic: setStyleItalic(); break;
}
textLabel.setFont (textFont);
}
void setStylePlain()
{
setLightColoursEnabled (true);
textFont.setStyleFlags (Font::plain);
}
void setStyleBold()
{
setLightColoursEnabled (false);
textFont.setStyleFlags (Font::bold);
}
void setStyleItalic()
{
setLightColoursEnabled (true);
textFont.setStyleFlags (Font::italic);
}

Notice that in the setStyleBold() function we disable the lighter colours and we need to enable them in the other cases. Run the application and you should now see that the lighter colours are disabled when the Bold style is selected.

tutorial_combo_box_screenshot4.png
Disabling combo-box items
Exercise
One problem here is that you can select a lighter colour then choose the bold style. Modify the code to change the colour back to black if one of the lighter colours is selected when the user selects the Bold style.

Notes

The final implementation of the code for this tutorial can be found in the ComboBoxTutorial_02.h file of the demo project.

Summary

This tutorial has introduced the ComboBox class. After reading this tutorial you should be able to:

See also