Create your UI with C++

In this part of the tutorial, we're going to create the app UI using C++.

In the src folder, double-click the applicationui.cpp file to open it in the editor.

#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/LocaleHandler>

using namespace bb::cascades;

ApplicationUI::ApplicationUI() :
        QObject()
{
    // prepare the localization
    m_pTranslator = new QTranslator(this);
    m_pLocaleHandler = new LocaleHandler(this);

    bool res = QObject::connect(m_pLocaleHandler, 
            SIGNAL(systemLanguageChanged()), 
            this, 
            SLOT(onSystemLanguageChanged()));
    
    // This is only available in Debug builds
    Q_ASSERT(res);
    // Since the variable is not used in the app, this is added to avoid a
    // compiler warning
    Q_UNUSED(res);

    // initial load
    onSystemLanguageChanged();

    // Create scene document from main.qml asset, the parent is set
    // to ensure the document gets destroyed properly at shut down.
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);

    // Create root object for the UI
    AbstractPane *root = qml->createRootObject<AbstractPane>();

    // Set created root object as the application scene
    Application::instance()->setScene(root);
}

void ApplicationUI::onSystemLanguageChanged()
{
    QCoreApplication::instance()->removeTranslator(m_pTranslator);
    // Initiate, load and install the application translation files.
    QString locale_string = QLocale().name();
    QString file_name = QString("YoApp_%1").arg(locale_string);
    if (m_pTranslator->load(file_name, "app/native/qm")) {
        QCoreApplication::instance()->installTranslator(m_pTranslator);
    }
}

Because we're not importing any QML into our app, most of the code in the constructor can be removed. We need to leave the localization setup and slot function in there but we can take out the code that loads the QML file into our app. We also need to make sure that the class contains the following include statements:

#include <bb/cascades/Application>
#include <bb/cascades/LocaleHandler>
#include <bb/cascades/Color>
#include <bb/cascades/Container>
#include <bb/cascades/DockLayout>
#include <bb/cascades/ImageView>
#include <bb/cascades/Page>
#include <bb/cascades/Slider>
#include <bb/cascades/StackLayout>
#include <bb/cascades/StackLayoutProperties>

When you're done, the App class should look something like this sample:

#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/LocaleHandler>
#include <bb/cascades/Color>
#include <bb/cascades/Container>
#include <bb/cascades/DockLayout>
#include <bb/cascades/ImageView>
#include <bb/cascades/Page>
#include <bb/cascades/Slider>
#include <bb/cascades/StackLayout>
#include <bb/cascades/StackLayoutProperties>

using namespace bb::cascades;

ApplicationUI::ApplicationUI() :
        QObject()
{
    // prepare the localization
    m_pTranslator = new QTranslator(this);
    m_pLocaleHandler = new LocaleHandler(this);

    bool res = QObject::connect(m_pLocaleHandler,
    		SIGNAL(systemLanguageChanged()),
    		this,
    		SLOT(onSystemLanguageChanged()));

    // This is only available in Debug builds
    Q_ASSERT(res);
    // Since the variable is not used in the app,
    // this is added to avoid a compiler warning.
    Q_UNUSED(res);

    // initial load
    onSystemLanguageChanged();

}

void ApplicationUI::onSystemLanguageChanged()
{
    QCoreApplication::instance()->removeTranslator(m_pTranslator);
    // Initiate, load and install the application translation files.
    QString locale_string = QLocale().name();
    QString file_name =
    		QString("MyFirstApp_%1").arg(locale_string);
    if (m_pTranslator->load(file_name, "app/native/qm")) {
        QCoreApplication::instance()->
        	installTranslator(m_pTranslator);
    }
}

Before we get started creating the UI, let's do a recap and look at the different pieces we need to create and position:

  • A root container
  • An upper container with two layered images (night image and day image)
  • A lower container with a slider and two icons (moon and sun)
Screen showing the Lightning Crossfade sample layout.

Create the root container

In the constructor for ApplicationUI (right after the preparation for localization), the first thing we do is create the root container that is going to hold the background and the rest of our UI components. This container is used to hold two child containers, one for our images and one for the slider. We want to use design units to set the padding for this container so we need to get the UI configuration properties of the container. We use the UIConfig object to set padding on our container in C++.

    // Create the main UI in a container with a StackLayout
    Container *contentContainer = new Container();

    // Get the UIConfig object to use resolution independent sizes
    UIConfig *ui = contentContainer->ui();
    contentContainer->setTopPadding(ui->du(2.0));
    contentContainer->setLeftPadding(ui->du(2.0));
    contentContainer->setRightPadding(ui->du(2.0));

Because we want to stack the images container above the slider container, we need to use a StackLayout. And, we set the background color for the Container.

contentContainer->setLayout(StackLayout::create());
contentContainer->setBackground(Color::fromARGB(0xfff8f8f8));

Now we call two functions to set up our images container and our slider container. These functions do all the work of setting up the contents of the containers. We'll learn more about these two functions later. When the two containers are set up, add them to the main UI container.

    // Inside the main UI container, create two containers
    // The first container contain the images
    Container *imageContainer = setUpImageContainer();
    imageContainer->setHorizontalAlignment(HorizontalAlignment::Center);

    // The second container contains the slider, which is
    // aligned to the center of the main UI container
    Container *sliderContainer = setUpSliderContainer(imageContainer);
    sliderContainer->setHorizontalAlignment(HorizontalAlignment::Center);

    // Add the two containers to the main UI container
    contentContainer->add(imageContainer);
    contentContainer->add(sliderContainer);

Now all we need to do to finish our UI is to create a Page, add the main UI container to that Page and then set the scene using this content.

    
    // Create a page with the main UI as the content
    Page *page = new Page();
    page->setContent(contentContainer);

    // Create the application scene
    Application::instance()->setScene(page);
}

Our UI is almost complete but our app doesn't compile right now. We haven't defined the two functions that we use to build our images and slider containers. Open your applicationui.hpp file and we add some declarations right below the private declarations of our translator and locale handler. We also need to include the Container and Application components at the top of our applicationui.hpp file. Your applicationui.hpp file should look something like this sample:

#ifndef ApplicationUI_HPP_
#define ApplicationUI_HPP_

#include <QObject>
#include <bb/cascades/Container>
#include <bb/cascades/Application>

using namespace bb::cascades;

namespace bb
{
    namespace cascades
    {
        class LocaleHandler;
    }
}

class QTranslator;

/*!
 * @brief Application UI object
 *
 * Use this object to create and init app UI, to create context objects, to register the new meta types etc.
 */
class ApplicationUI : public QObject
{
    Q_OBJECT
public:
    ApplicationUI();
    virtual ~ApplicationUI() {}
private slots:
    void onSystemLanguageChanged();
private:
    QTranslator* m_pTranslator;
    bb::cascades::LocaleHandler* m_pLocaleHandler;

    /**
     * This function creates a container with two images 
     * center aligned and on top of each other. 
     *
     * @return The Container containing the images.
     */
    Container *setUpImageContainer();

    /**
     * This function sets up the slider container 
     * with a Slider control and two icon images.
     * The slider is connected to an image that is
     * passed in as a parameter.
     *
     * @param  imageContainer The container with the image.
     * @return The Container containing the slider and its icons.
     */
    Container *setUpSliderContainer(Container *imageContainer);

};

#endif /* ApplicationUI_HPP_ */

And now we can go ahead and implement the two functions that add the images and the slider in our applicationui.cpp file.

Add images

Go ahead an open your applicationui.cpp file. Here we create a function to build the child container that holds our images. Because we want to layer the images on top of one another, we need to use a DockLayout. We set its horizontal alignment so that it is centered within its parent container.

Container *ApplicationUI::setUpImageContainer()
{
    // Create a container that contains two images and
    // uses a dock layout to align the images
    Container* nightAndDayContainer = new Container();
    nightAndDayContainer->setLayout(new DockLayout());

Next, we add all of our images. To add the images, we retrieve the source of the images, and we set their alignment so that they are centered horizontally and vertically within their parent container. After we've created the images, we add them to the image container.

If you are testing your app on a device with a different screen resolution, you don't have to specify that you want to use alternate images. The static asset selector does all the work for you. For more information about this feature, see Static asset selection.

The only real difference between the images is that the day image's opacity property is set to 0.0. The opacity of the day image is set to 0.0 so that when the app starts, the night image is visible underneath the day image.

    
    // Add the night image
    ImageView* night = ImageView::create("asset:///images/night.jpg");

    // Center the night image using alignment
    night->setHorizontalAlignment(HorizontalAlignment::Center);
    night->setVerticalAlignment(VerticalAlignment::Center);

    // Add the day image on top of the night image and
    // hide the day image by changing its opacity value to 0
    ImageView* day = ImageView::create("asset:///images/day.jpg").opacity(0);

    // Center the day image using alignment
    day->setHorizontalAlignment(HorizontalAlignment::Center);
    day->setVerticalAlignment(VerticalAlignment::Center);

Because we create the images in this function, we need to assign an object name to the day image so that our slider function can access it. Then, we need to add the night and day images to the main UI container.

    
    // Assign an object name to the day image so that it can
    // be found and attached to the slider later
    day->setObjectName("dayImage");

    // Add all the images to the Container
    nightAndDayContainer->add(night);
    nightAndDayContainer->add(day);

    return nightAndDayContainer;
}

Add a slider

Now that the images container is finished, we can start creating the function that creates the slider container.

Because we want to stack our icons and slider horizontally instead of vertically, we need to use a left-to-right StackLayout. We also apply some padding to each side of the container to create some space between it and the edges of the screen and the container above it. We use design units (du) so that our UI adapts to different screen densities. Design units are device-independent values that you use to assign dimensions to components in your UI. To learn more, see Design units.

Container *ApplicationUI::setUpSliderContainer(Container *imageContainer)
{
    // Create a container for the slider and sort the controls
    // in this container from left to right
    Container* sliderContainer = new Container();

    // Get the UIConfig object to use resolution independent sizes
    UIConfig *sliderui = sliderContainer->ui();

    sliderContainer->setLeftPadding(sliderui->du(1.0));
    sliderContainer->setRightPadding(sliderui->du(1.0));
    sliderContainer->setTopPadding(sliderui->du(4.0));
    sliderContainer->setBottomPadding(sliderui->du(2.0));

    sliderContainer->setLayout(StackLayout::create().orientation(LayoutOrientation::LeftToRight));

Next, we create our two image icons and our slider. Because we don't want our sun and moon icons flush with our slider, we need to apply some margins on both ends of the slider to push the icons out. We also need to set the alignment on the slider so that it is centered horizontally. And we set the weight so that it expands to take up all the available space in the container by using HorizontalAlignment::Fill.

The slider also has fromValue and toValue properties, which are used as a scale for the slider. When the slider is in the left-most position its value is 0.0, and when it is in the right-most position its value is 1.0. This concept is important because we can use the slider value to change an image's opacity directly, because opacity is also on a scale from 0.0 to 1.0.

    
    // Create the slider that you see at the bottom of the screen
    // When it gets an onImmediateValueChanged() signal,
    // it opacity of the night image to make it disappear
    Slider* opacitySlider = Slider::create().leftMargin(sliderui->du(1.0)).rightMargin(sliderui->du(1.0));

    // Center the slider in the stack layout
    // and give it a positive space quota, which
    // makes the slider opacity value less than
    // the moon and sun icon images when they
    // are displayed on screens with different widths
    opacitySlider->setLayoutProperties(StackLayoutProperties::create().spaceQuota(1.0f));
    opacitySlider->setHorizontalAlignment(HorizontalAlignment::Fill);

    // Add a moon and a sun image on either side of slider
    ImageView* moon = ImageView::create("asset:///images/moon.png");
    moon->setVerticalAlignment(VerticalAlignment::Center);
    ImageView* sun = ImageView::create("asset:///images/sun.png");
    sun->setVerticalAlignment(VerticalAlignment::Center);

    // Attach the day image to the slider using the
    // object ID from the image Container
    ImageView *dayImage = imageContainer->findChild<ImageView*>("dayImage");

Creating a slider is nice, but at the moment, it's not going to do anything except slide back and forth across the screen. We also need it to send events to the day image to let it know that it needs to change its opacity. We can achieve this result by using the signals and slots construct.

Think of a slot as a function that you want to invoke at runtime in response to an event. A signal is just a function that, when called, invokes the slots that it is connected to. In our case, the slot is our day image's setOpacity() function, which accepts a float as a parameter. The signal is our slider's immediateValueChanged() function, which accepts a float that represents the current slider position. Since both functions have the same signature, we know we can connect the two of them together.

We use the connect() function to connect a signal to a slot and we should always check the Boolean value that is returned. Usually the connect() function succeeds, but if it fails, it means that something is wrong with our app.

    
    // Connect the slider value directly to the
    // opacity property of the day image

    // Make sure to test the result  to detect any errors
    bool result;
    Q_UNUSED(result);

    result=connect(opacitySlider,
            SIGNAL(immediateValueChanged(float)),
            dayImage,
            SLOT(setOpacity(float)));

    Q_ASSERT(result);

Whenever the slider is moved, the immediateValueChanged() signal is emitted telling the slot to change the opacity of the image to reflect the new slider position.

Next, we need to add the slider and the moon and sun images to the slider container, and add all the containers to contentContainer. Finally, we create an instance of Page using contentContainer, and set the scene by calling Application::instance()->setScene(page).

    
    // Add the components to the slider container
    // (Remember that they will be stacked from left to right)
    sliderContainer->add(moon);
    sliderContainer->add(opacitySlider);
    sliderContainer->add(sun);

    return sliderContainer;
}

Build and run the app and you're done. To learn more, see Build your project.

#ifndef ApplicationUI_HPP_
#define ApplicationUI_HPP_

#include <QObject>
#include <bb/cascades/Container>
#include <bb/cascades/Application>

using namespace bb::cascades;

namespace bb
{
    namespace cascades
    {
        class LocaleHandler;
    }
}

class QTranslator;

/*!
 * @brief Application UI object
 *
 * Use this object to create and init app UI, to create context objects, to register the new meta types etc.
 */
class ApplicationUI : public QObject
{
    Q_OBJECT
public:
    ApplicationUI();
    virtual ~ApplicationUI() {}
private slots:
    void onSystemLanguageChanged();
private:
    QTranslator* m_pTranslator;
    bb::cascades::LocaleHandler* m_pLocaleHandler;

    /**
     * This function creates a container with two images 
     * center aligned and on top of each other. 
     *
     * @return The Container containing the images.
     */
    Container *setUpImageContainer();

    /**
     * This function sets up the slider container 
     * with a Slider control and two icon images.
     * The slider is connected to an image that is
     * passed in as a parameter.
     *
     * @param  imageContainer The container with the image.
     * @return The Container containing the slider and its icons.
     */
    Container *setUpSliderContainer(Container *imageContainer);

};

#endif /* ApplicationUI_HPP_ */
#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/LocaleHandler>
#include <bb/cascades/Color>
#include <bb/cascades/Container>
#include <bb/cascades/DockLayout>
#include <bb/cascades/ImageView>
#include <bb/cascades/Page>
#include <bb/cascades/Slider>
#include <bb/cascades/StackLayout>
#include <bb/cascades/StackLayoutProperties>

using namespace bb::cascades;

ApplicationUI::ApplicationUI() :
        QObject()
{
    // prepare the localization
    m_pTranslator = new QTranslator(this);
    m_pLocaleHandler = new LocaleHandler(this);

    bool res = QObject::connect(m_pLocaleHandler,
            SIGNAL(systemLanguageChanged()),
            this,
            SLOT(onSystemLanguageChanged()));

    // This is only available in Debug builds
    Q_ASSERT(res);
    // Since the variable is not used in the app,
    // this is added to avoid a compiler warning.
    Q_UNUSED(res);

    // initial load
    onSystemLanguageChanged();

    // Create the main UI in a container with a StackLayout
    Container *contentContainer = new Container();

    // Get the UIConfig object to use resolution independent sizes
    UIConfig *ui = contentContainer->ui();
    contentContainer->setTopPadding(ui->du(2.0));
    contentContainer->setLeftPadding(ui->du(2.0));
    contentContainer->setRightPadding(ui->du(2.0));

    contentContainer->setLayout(StackLayout::create());
    contentContainer->setBackground(Color::fromARGB(0xfff8f8f8));

    // Inside the main UI container, create two containers
    // The first container contain the images
    Container *imageContainer = setUpImageContainer();
    imageContainer->setHorizontalAlignment(HorizontalAlignment::Center);

    // The second container contains the slider, which is
    // aligned to the center of the main UI container
    Container *sliderContainer = setUpSliderContainer(imageContainer);
    sliderContainer->setHorizontalAlignment(HorizontalAlignment::Center);

    // Add the two containers to the main UI container
    contentContainer->add(imageContainer);
    contentContainer->add(sliderContainer);
    // Create a page with the main UI as the content
    Page *page = new Page();
    page->setContent(contentContainer);

    // Create the application scene
    Application::instance()->setScene(page);
}

void ApplicationUI::onSystemLanguageChanged()
{
    QCoreApplication::instance()->removeTranslator(m_pTranslator);
    // Initiate, load and install the application translation files.
    QString locale_string = QLocale().name();
    QString file_name = QString("YoApp_%1").arg(locale_string);
    if (m_pTranslator->load(file_name, "app/native/qm")) {
        QCoreApplication::instance()->installTranslator(m_pTranslator);
    }
}

Container *ApplicationUI::setUpImageContainer()
{
    // Create a container that contains two images and
    // uses a dock layout to align the images
    Container* nightAndDayContainer = new Container();
    nightAndDayContainer->setLayout(new DockLayout());

    // Add the night image
    ImageView* night = ImageView::create("asset:///images/night.jpg");

    // Center the night image using alignment
    night->setHorizontalAlignment(HorizontalAlignment::Center);
    night->setVerticalAlignment(VerticalAlignment::Center);

    // Overlay the day image on top of the night image and
    // hide the day image by changing its opacity value to 0
    ImageView* day = ImageView::create("asset:///images/day.jpg").opacity(0);

    // Center the day image using alignment
    day->setHorizontalAlignment(HorizontalAlignment::Center);
    day->setVerticalAlignment(VerticalAlignment::Center);


    // Assign an object name to the day image so that it can
    // be found and attached to the slider later
    day->setObjectName("dayImage");

    // Add all the images to the Container
    nightAndDayContainer->add(night);
    nightAndDayContainer->add(day);

    return nightAndDayContainer;
}

Container *ApplicationUI::setUpSliderContainer(Container *imageContainer)
{
    // Create a container for the slider and sort the controls
    // in this container from left to right
    Container* sliderContainer = new Container();

    // Get the UIConfig object to use resolution independent sizes
    UIConfig *sliderui = sliderContainer->ui();

    sliderContainer->setLeftPadding(sliderui->du(1.0));
    sliderContainer->setRightPadding(sliderui->du(1.0));
    sliderContainer->setTopPadding(sliderui->du(4.0));
    sliderContainer->setBottomPadding(sliderui->du(2.0));

    sliderContainer->setLayout(StackLayout::create().orientation(LayoutOrientation::LeftToRight));

    // Create the slider that you see at the bottom of the screen
    // When it gets an onImmediateValueChanged() signal,
    // it opacity of the night image to make it disappear
    Slider* opacitySlider = Slider::create().leftMargin(sliderui->du(1.0)).rightMargin(sliderui->du(1.0));

    // Center the slider in the stack layout
    // and give it a positive space quota, which
    // makes the slider opacity value less than
    // the moon and sun icon images when they
    // are displayed on screens with different widths
    opacitySlider->setLayoutProperties(StackLayoutProperties::create().spaceQuota(1.0f));
    opacitySlider->setHorizontalAlignment(HorizontalAlignment::Fill);

    // Add a moon and a sun image on either side of slider
    ImageView* moon = ImageView::create("asset:///images/moon.png");
    moon->setVerticalAlignment(VerticalAlignment::Center);
    ImageView* sun = ImageView::create("asset:///images/sun.png");
    sun->setVerticalAlignment(VerticalAlignment::Center);

    // Attach the day image to the slider using the
    // object ID from the image Container
    ImageView *dayImage = imageContainer->findChild<ImageView*>("dayImage");


    // Connect the slider value directly to the
    // opacity property of the day image

    // Make sure to test the result  to detect any errors
    bool result;
    Q_UNUSED(result);

    result=connect(opacitySlider,
            SIGNAL(immediateValueChanged(float)),
            dayImage,
            SLOT(setOpacity(float)));

    Q_ASSERT(result);

    // Add the components to the slider container
    // (Remember that they will be stacked from left to right)
    sliderContainer->add(moon);
    sliderContainer->add(opacitySlider);
    sliderContainer->add(sun);

    return sliderContainer;
}

Last modified: 2015-03-31



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus