Create your UI with C++

In this part of the tutorial we're going to create the application 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(bb::cascades::Application *app) :
        QObject(app)
{
    // 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
    app->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("MyFirstApp_%1").arg(locale_string);
    if (m_pTranslator->load(file_name, "app/native/qm")) {
        QCoreApplication::instance()->
            installTranslator(m_pTranslator);
    }
}

Since 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:

#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(bb::cascades::Application *app) :
        QObject(app)
{
    // 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 take a 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. Since we want to stack the images container above the slider container, we need to use a StackLayout.

// Create the root container
Container *contentContainer = new Container();
contentContainer->setLayout(StackLayout::create());
contentContainer->setTopPadding(20.0f);

Next, we set the background color for the Container.

// Set the background to a dark color
contentContainer->setBackground(Color::fromARGB(0xff262626));

Add images

Next, let's create the child container that will hold our images. Since 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.

// Create the images container
Container *imageContainer = new Container();
imageContainer->setLayout(new DockLayout());
imageContainer->setHorizontalAlignment(HorizontalAlignment::Center);

Next, we create all of our images. To create 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.

You don't have to specify that you want to use alternate images if you are testing your app on a device with a physical keyboard. The static asset selector does all the work for you. For more information about this feature, see Static Asset Selection.

// Create the night image
ImageView* night = ImageView::create("asset:///images/night.jpg");
night->setHorizontalAlignment(HorizontalAlignment::Center);
night->setVerticalAlignment(VerticalAlignment::Center);
 
// Create the day image
ImageView* day = 
    ImageView::create("asset:///images/day.jpg").opacity(0);
day->setHorizontalAlignment(HorizontalAlignment::Center);
day->setVerticalAlignment(VerticalAlignment::Center);
 
// Add the images to the image container
imageContainer->add(night);
imageContainer->add(day);

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

Add a slider

Now that the images container is finished, we can start creating the slider container. Since 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 also set its alignment so that it is centered within its parent container.

// Create the slider container
Container* sliderContainer = Container::create()
        .left(20.0f).right(20.0f).top(25.0f).bottom(25.0f);
sliderContainer->setLayout(StackLayout::create()
        .orientation(LayoutOrientation::LeftToRight));
sliderContainer->setHorizontalAlignment(HorizontalAlignment::Center);

Next, we create our two image icons and our slider. Since 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 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 is important because we can use the slider value to change an image's opacity directly, since opacity is also on a scale from 0.0 to 1.0.

// Create the slider
Slider* opacitySlider = Slider::create()
        .leftMargin(20.0f).rightMargin(20.0f);
 
opacitySlider->setLayoutProperties(StackLayoutProperties::create()
        .spaceQuota(1.0f));
opacitySlider->setHorizontalAlignment(HorizontalAlignment::Fill);
 
// Create the moon icon
ImageView* moon = ImageView::create("asset:///images/moon.png");
moon->setVerticalAlignment(VerticalAlignment::Center);
 
// Create the sun icon
ImageView* sun = ImageView::create("asset:///images/sun.png");
sun->setVerticalAlignment(VerticalAlignment::Center);

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

Think of a slot as a function that you want to invoke at runtime in response to some sort of event. A signal is just a function that, when called, will invoke 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 will succeed, but if it doesn't it means that something is wrong with our app.

// Connect the immediateValueChanged() signal 
// to the setOpacity() function.

// The connect) function returns a Boolean value. 
// Make sure to test the return value to detect any errors.

bool result = connect(opacitySlider, 
     SIGNAL(immediateValueChanged(float)),
     day, 
     SLOT(setOpacity(float))); 
 
// The Q_ASSERT statement indicates that the slot failed to  
// connect to the signal. This Q_ASSERT statement is only present in 
// debug loads. Make sure you know exactly why this has happened.
// This is not normal, and will cause your app to stop working!
Q_ASSERT(result);
 
// Indicate that the variable "result" isn't used in the rest 
// of the app. This prevents a compiler warning.
Q_UNUSED(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 slider and icons to the slider container
    sliderContainer->add(moon);
    sliderContainer->add(opacitySlider);
    sliderContainer->add(sun);
 
    // Add the image and slider container to the content container,
    // then add the content container to the root
    contentContainer->add(imageContainer);
    contentContainer->add(sliderContainer);
 
    // Create a page using the root container and set the scene
    Page *page = new Page();
    page->setContent(contentContainer);
    app->setScene(page);  
}  

Just build and run your application one last time and you're done.

#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(bb::cascades::Application *app) :
        QObject(app)
{
    // 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 root container
    Container *contentContainer = new Container();
    contentContainer->setLayout(StackLayout::create());
    contentContainer->setTopPadding(20.0f);

    // Set the background to a dark color
    contentContainer->setBackground(Color::fromARGB(0xff262626));

    // Create the images container
    Container *imageContainer = new Container();
    imageContainer->setLayout(new DockLayout());
    imageContainer->setHorizontalAlignment(HorizontalAlignment::Center);

    // Create the night image
    ImageView* night = ImageView::create("asset:///images/night.jpg");
    night->setHorizontalAlignment(HorizontalAlignment::Center);
    night->setVerticalAlignment(VerticalAlignment::Center);

    // Create the day image
    ImageView* day =
        ImageView::create("asset:///images/day.jpg").opacity(0);
    day->setHorizontalAlignment(HorizontalAlignment::Center);
    day->setVerticalAlignment(VerticalAlignment::Center);

    // Add the images to the image container
    imageContainer->add(night);
    imageContainer->add(day);


    // Create the slider container
    Container* sliderContainer = Container::create()
            .left(20.0f).right(20.0f).top(25.0f).bottom(25.0f);
    sliderContainer->setLayout(StackLayout::create()
            .orientation(LayoutOrientation::LeftToRight));
    sliderContainer->setHorizontalAlignment(HorizontalAlignment::Center);

    // Create the slider
    Slider* opacitySlider = Slider::create()
            .leftMargin(20.0f).rightMargin(20.0f);

    opacitySlider->setLayoutProperties(StackLayoutProperties::create()
            .spaceQuota(1.0f));
    opacitySlider->setHorizontalAlignment(HorizontalAlignment::Fill);

    // Create the moon icon
    ImageView* moon = ImageView::create("asset:///images/moon.png");
    moon->setVerticalAlignment(VerticalAlignment::Center);

    // Create the sun icon
    ImageView* sun = ImageView::create("asset:///images/sun.png");
    sun->setVerticalAlignment(VerticalAlignment::Center);

    // Connect the immediateValueChanged() signal
    // to the setOpacity() function.

    // The connect) function returns a Boolean value.
    // Make sure to test the return value to detect any errors.

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

    // The Q_ASSERT statement indicates that the slot failed to
    // connect to the signal. This Q_ASSERT statement is only present in
    // debug loads. Make sure you know exactly why this has happened.
    // This is not normal, and will cause your app to stop working!
    Q_ASSERT(result);

    // Indicate that the variable "result" isn't used in the rest
    // of the app. This prevents a compiler warning.
    Q_UNUSED(result);

    // Add the slider and icons to the slider container
    sliderContainer->add(moon);
    sliderContainer->add(opacitySlider);
    sliderContainer->add(sun);

    // Add the image and slider container to the content container,
    // then add the content container to the root
    contentContainer->add(imageContainer);
    contentContainer->add(sliderContainer);

    // Create a page using the root container and set the scene
    Page *page = new Page();
    page->setContent(contentContainer);
    app->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("MyFirstApp_%1").arg(locale_string);
    if (m_pTranslator->load(file_name, "app/native/qm")) {
        QCoreApplication::instance()->
        	installTranslator(m_pTranslator);
    }
}

Now that you're done

Now that you've created your first app, feel free to check out these topics in the documentation:

Screen showing the Lightning Crossfade sample running on an all-touch device.

Last modified: 2014-01-23

comments powered by Disqus