Drop-down menus

A DropDown allows users to select an Option from a drop-down list. This control consists of a title bar and an expandable list of options. The list expands when the title bar is tapped, and collapses when a user selects an option. The size of a DropDown is affected by the screen size of the device, as shown in the image below.


Screen showing the collapsed and expanded state of a drop-down menu on an all-touch device and a device with a physical keyboard.

An expanded DropDown displays the title bar and a list of options. The content of the title bar is specified using the title property, and the content of each Option is specified using the text, description, and imageSource properties.

If no option is selected, a collapsed DropDown displays the title of the control.

If an option is selected, a collapsed DropDown displays the title of the control left-aligned and the text of the selection option right-aligned in the title bar, and both share the available width of the title bar. Text can be truncated if the width is smaller than is necessary to fit the content.

Sizing

  • The width is manually adjustable using preferredWidth, minWidth, and maxWidth.
  • The height of an expanded DropDown varies based on the number of options, and the device. An all-touch device displays up to four options before scrolling, and a device with a physical keyboard displays up to three.
  • The height of a row is fixed and can't be changed.
  • The leftPadding, rightPadding, topPadding, and bottomPadding properties can't be used on this control.

Responding to value changes

When an option is selected, the DropDown emits the selectedValueChanged() signal if the value of the option differs from the previously selected option.

In QML, you can use the onSelectedValueChanged signal handler to capture the signal and respond to it accordingly. The following example uses the value of a selection to enable or disable a ToggleButton:

// Create a DropDown with two options
DropDown {
    Option {
        text: "Enabled"
        value: 1
    }
    Option {
        text: "Disabled"
        value: 2
    }
    onSelectedValueChanged: {
        // Use an if statement to enable or disable
        // a ToggleButton based on the value of the 
        // current selection
        if (selectedValue == 1) {
            myToggleButton.enabled = true
        } else {
            myToggleButton.enabled = false
        }
    }
}
ToggleButton {
    id: myToggleButton
}

Here's how to do the same thing in C++:

#include <bb/cascades/DropDown>
#include <bb/cascades/ToggleButton>
#include <bb/cascades/Option>
//...

// Create a ToggleButton. The myToggleButton
// object is declared in the header as follows:
// ToggleButton *myToggleButton;
myToggleButton = ToggleButton::create()
        .enabled(false)

// Create a DropDown with two options
DropDown *myDropDown = DropDown::create().title("DropDown")
    .add(Option::create().text("Enabled").value(1))
    .add(Option::create().text("Disabled").value(2));


// If any Q_ASSERT statement(s) indicate that the slot failed 
// to connect to the signal, make sure you know exactly why
// this has happened. This is not normal, and will cause your
// app to stop working
bool connectResult;

// Since the variable is not used in the app, this is 
// added to avoid a compiler warning
Q_UNUSED(connectResult);

// Connect the selectedValueChanged() signal to the
// handleValueChanged() slot
connectResult = connect(myDropDown, 
                       SIGNAL(selectedValueChanged(const QVariant)), 
                       this, 
                       SLOT(handleValueChanged(const QVariant)));
    
// This is only available in Debug builds
Q_ASSERT(connectResult);
    
//...

// Create the handleValueChanged() slot to capture the 
// selectedValueChanged() signal emitted by the drop-down menu
void DropDownRecipe::handleValueChanged(const QVariant & selectedValue)
{
    // Use an if statement to enable or disable a ToggleButton based
    // on the value of the current selection 
    if (selectedValue == 1) {
        myToggleButton->setEnabled(true);
    } else {
        myToggleButton->setEnabled(false);
    }
}

For more information on responding to signals, see Signals and slots.

Drop-down menu example

The following example shows how to use a DropDown to modify the amount of ingredients required for a beer recipe. The sample app contains a DropDown, an ImageView, and two TextArea controls.

When you select an option, the ingredients in the recipe are updated to reflect the number of pints that you select. The ImageView also updates the source image to reflect the current selection.

You can download the images for the sample here: images.zip

Animation showing a list of ingredients being updated by selecting a value in a drop-down menu.

main.qml

import bb.cascades 1.0
 
// A recipe showing how the DropDown control is used to make a selection.
// In this case, different selections result in a recipe
// with different amounts of delicious beer
 
Page {
    titleBar: TitleBar {
        title: "DropDown"
    }
    Container {
        ScrollView {
            scrollViewProperties {
                scrollMode: ScrollMode.Vertical
            }
            Container {
                topPadding: 20
                rightPadding: topPadding
                leftPadding: rightPadding
 
                Label {
                    text: "Beer recipe"
                }
                // In the DropDown Control, we add a number of options
                // for selecting different values. We use them later to
                // check how many pints the recipe should make
                DropDown {
                    id: dropdown
 
                    title: "Number of pints"
                    Option {
                        value: "1"
                        text: value
                    }
                    Option {
                        value: "2"
                        text: value
                        selected: true
                    }
                    Option {
                        value: "3"
                        text: value
                    }
                    Option {
                        value: "4"
                        text: value
                    }
                    onSelectedValueChanged: {
                        // Illustrating usage of DropDown signal handler
                        console.debug("Number of pints 
                        changed to " + selectedValue)
                    }
                }
                // The recipe text is built up from three texts: a
                // text with the amount that contain the different measures
                // added to create the mix, a text with the ingredients
                // that is aligned to the amounts, and a text of the
                // formula describing the process
                Container {
                    topMargin: 20
                    TextArea {
                        editable: false
                        input.flags: TextInputFlag.SpellCheckOff
                        text: dropdown.selectedValue / 10 + 
                                 " kg\t\tPale Ale Malt\n" + 
                              dropdown.selectedValue * 1.0 + 
                                 " g     \t\tCascade Hops\n" + 
                              dropdown.selectedValue * 3 / 5 + 
                                 " g\t\tYeast\n" + 
                              dropdown.selectedValue * 1.5 + 
                                 " l     \t\tWater"
                        textStyle.base: SystemDefaults.TextStyles.TitleText
                    }
                    ImageView {
                        imageSource: "asset:///images/beer" + 
                            dropdown.selectedValue + ".png"
                        scalingMethod: ScalingMethod.AspectFit
                    }
                    TextArea {
                        editable: false
                        input.flags: TextInputFlag.SpellCheckOff
                        horizontalAlignment: HorizontalAlignment.Center
                        text: "1. Mash at 67°C for 60 min.\n2. Sparge
                        \n3. Boil the wort for 90 min.
                        \n4. Add hops after 30 min.
                        \n5. Add yeast, ferment 1-2 weeks.
                        \n6. Add sugar and ferment in bottles for 1 week.
                        \n7. Serve."
                    }
                }
            }
        }
    }
}

DropDownRecipe.h

#ifndef _DROPDOWNRECIPE_H_
#define _DROPDOWNRECIPE_H_

#include <bb/cascades/Page>
#include <bb/cascades/Container>
#include <QObject>
#include <bb/cascades/ImageView>
#include <bb/cascades/DropDown>
#include <bb/cascades/TextArea>

namespace bb { namespace cascades { class Application; }}

using namespace bb::cascades;

class DropDownRecipe: public QObject
{
    Q_OBJECT

public:

    DropDownRecipe(bb::cascades::Application *app);
    	virtual ~DropDownRecipe() {}

private slots:

    void onSelectedIndexChanged(int selectedIndex);

private:

    void updateAmounts(DropDown *dropDown);

    Container *createBeerRecipe();

    TextArea *mAmounts;

    ImageView *mBeers;

    Container *recipeContainer;
    Container *beerRecipe;

    QString optionText;
    QVariant value;
    bool selected;
};

#endif // ifndef _DROPDOWNRECIPE_H_

DropDownRecipe.cpp

#include "DropDownRecipe.h"

#include <bb/cascades/Application>
#include <bb/cascades/Container>
#include <bb/cascades/DropDown>
#include <bb/cascades/ImageView>
#include <bb/cascades/Label>
#include <bb/cascades/Option>
#include <bb/cascades/ScrollView>
#include <bb/cascades/ScrollViewProperties>
#include <bb/cascades/StackLayout>
#include <bb/cascades/StackLayoutProperties>
#include <bb/cascades/DockLayout>
#include <bb/cascades/SystemDefaults>
#include <bb/cascades/TextArea>
#include <bb/cascades/TextStyle>
#include <bb/cascades/TitleBar>

using namespace bb::cascades;

DropDownRecipe::DropDownRecipe(bb::cascades::Application *app)
: QObject(app)
{
    Page *page = new Page();

    TitleBar *tbar = TitleBar::create()
    	.title("DropDown");

    page->setTitleBar(tbar);

    // This Page does not fit on one screen, so a ScrollView is
    // set up so that the user can scroll the Page
    ScrollView *scrollView = new ScrollView();
    ScrollViewProperties* scrollViewProp =
        scrollView->scrollViewProperties();

    scrollViewProp->setScrollMode(ScrollMode::Vertical);

    Container *recipeContainer = Container::create()
    	.top(20.0f)
    	.left(20.0f)
    	.right(20.0f);

    Label *title = Label::create()
    	.text("Beer recipe")
    	.textStyle(SystemDefaults::TextStyles::bodyText());


    // Create a DropDown and add the options
    DropDown *dropDown = DropDown::create()
    	.title("Number of pints")
    	.add(Option::create().text("1").value(QVariant(1)).selected(false))
    	.add(Option::create().text("2").value(QVariant(2)).selected(true))
    	.add(Option::create().text("3").value(QVariant(3)).selected(false))
    	.add(Option::create().text("4").value(QVariant(4)).selected(false));


    // If any Q_ASSERT statement(s) indicate that the slot failed
    // to connect to the signal, make sure you know exactly why this
    // has happened. This is not normal, and will cause your app
    // to stop working
    bool connectResult;

    // Since the variable is not used in the app, this is added
    // to avoid a compiler warning
    Q_UNUSED(connectResult);

    // Connect the selectedIndexChanged() signal to
    // the onSelectedIndexChanged() slot
    connectResult = connect(dropDown, SIGNAL(selectedIndexChanged(int)),
                            this,
                            SLOT(onSelectedIndexChanged(int)));

    // This is only available in Debug builds
    Q_ASSERT(connectResult);

    // Create the Container containing the recipe text
    Container *beerRecipe = createBeerRecipe();
    updateAmounts(dropDown);

    recipeContainer->add(title);
    recipeContainer->add(dropDown);
    recipeContainer->add(beerRecipe);

    // Add the scrollable content to the ScrollView
    scrollView->setContent(recipeContainer);

    page->setContent(scrollView);
    app->setScene(page);
}

void DropDownRecipe::onSelectedIndexChanged(int selectedIndex)
{
    Q_UNUSED(selectedIndex);

    // When the selection in the DropDown changes, we update
    // the "amounts" text to reflect the number of pints selected
    DropDown* dropDown = dynamic_cast<DropDown*>(sender());
    updateAmounts(dropDown);
}

void DropDownRecipe::updateAmounts(DropDown *dropDown)
{
    Option* selectedOption = dropDown->at(dropDown->selectedIndex());
    float numberOfPints = (selectedOption->value()).toFloat();

    // Set up the new string for the amounts depending on
    // the selected option value
    QString amounts = QString("%1 kg\n%2 g\n%3 g\n%4 l").arg(
            numberOfPints * 0.1f).arg(numberOfPints * 1.0f).arg(
            numberOfPints * 0.6f).arg(numberOfPints * 1.5f);
    mAmounts->setText(amounts);

    // Update the image
    QString imageSource = QString("asset:///images/beer%1.png")
            .arg((int) numberOfPints);
    mBeers->setImage(Image(imageSource));
}

Container *DropDownRecipe::createBeerRecipe()
{

    // The recipe text is constructed from three texts:
    // an "amounts" text that contain the different measures
    // added to create the mix, an "ingredients" text that
    // is aligned to the amounts, and a formula describing
    // the process
    Container *recipe = new Container();
    recipe->setTopMargin(40.0f);

    Container *recipeMeasure = new Container(recipe);
    recipeMeasure->setLayout(StackLayout::create().orientation
        (LayoutOrientation::LeftToRight));

    mAmounts = new TextArea(recipeMeasure);
    mAmounts->setEditable(false);
    mAmounts->textStyle()->setBase
        (SystemDefaults::TextStyles::titleText());
    mAmounts->setLayoutProperties
        (StackLayoutProperties::create().spaceQuota(1));

    TextArea *ingredients = new TextArea(recipeMeasure);
    ingredients->setEditable(false);
    ingredients->setText
        ("Pale Ale Malt\nCascade Hops\nYeast\nWater");
    ingredients->textStyle()->setBase
        (SystemDefaults::TextStyles::titleText());
    ingredients->setLayoutProperties
        (StackLayoutProperties::create().spaceQuota(3));

    mBeers = ImageView::create("asset:///images/beer1");
    mBeers->setScalingMethod(ScalingMethod::AspectFit);
    recipe->add(mBeers);

    TextArea *formula = new TextArea(recipe);
    formula->setEditable(false);
    formula->setText(
            "1. Mash at 67°C for 60 min\n\
             2. Sparge.\n\
             3. Boil the wort for 90 min.\n\
             4. Add hops after 30 min.\n\
             5. Add yeast, ferment 1-2 weeks.\n\
             6. Add sugar and ferment in bottles for 1 week.\n\
             7. Serve.");
    formula->textStyle()->setBase
        (SystemDefaults::TextStyles::bodyText());
    formula->setLayoutProperties(StackLayoutProperties::create());

    return recipe;
}

main.cpp

#include "DropDownRecipe.h"
 
#include <bb/cascades/Application>
#include <Qt/qdeclarativedebug.h>
 
using namespace bb::cascades;
 
Q_DECL_EXPORT int main(int argc, char **argv)
{
    Application app(argc, argv);
 
    new DropDownRecipe(&app);
 
    return Application::exec();
}

Last modified: 2013-12-21

comments powered by Disqus