Tutorial: Orientation changes

This tutorial shows you how to create a simple application that changes the layout of some containers and a text label based on the orientation of the device.

You will learn to:

  • Configure the bar-descriptor.xml file to support all orientations
  • Use the OrientationSupport class to change the appearance of the UI when orientation changes occur
  • Handle orientation changes in both QML and C++
Screen showing orientation application

Before you begin

You should have the following things ready:

  • The BlackBerry 10 Native SDK
  • A device or simulator running BlackBerry 10

You can click here to download the tools that you need and learn how to create, build, and run your first Cascades project.

If you're using the BlackBerry 10 Device Simulator and you want to test orientation changes, you can rotate the simulator by using the controller. For more information, see Simulating device movement.

Set up your project

Before we start creating our application, create an empty project in the Momentics IDE, making sure to select the Standard empty project template. If you're not sure how to do this, take a look at Creating a Cascades project.

Create a simple UI

We're going to build our own simple UI, so start by removing the code in the main.qml file. Create a new UI that consists of five containers and one label:

  • A root container to hold all the components
  • Two smaller containers that are placed in the upper-left and upper-right of the root container
  • One container in the center of the root container that itself holds another container and the label

The root container requires an id so that we can modify its padding properties when an orientation change occurs. The label also needs an id so that we can change its text to state the current orientation. You can find the code for this UI below and, in the image to the right, you can see the relationships between the UI components in the Outline view of the Momentics IDE.

This UI looks almost like a face, with two eyes, a mouth, and the label acting as a mustache.

Screen showing Outline view
import bb.cascades 1.3

Page {
    // Root container
    Container { 
        id: rootContainer
        // Root container layout, giving padding to all controls
        // within it
        layout: DockLayout {}
        leftPadding: ui.sdu(12.5)
        rightPadding: ui.sdu(12.5)
        topPadding: ui.sdu(12.5)
        
        // Left eye container
        Container { 
            background: Color.create("#005c7b")
            preferredWidth: ui.sdu(18.75)
            preferredHeight: ui.sdu(12.5)
            
            horizontalAlignment: HorizontalAlignment.Left
            verticalAlignment: VerticalAlignment.Top
        }
        
        // Right eye container
        Container {
            background: Color.create("#005c7b")
            preferredWidth: ui.sdu(18.75)
            preferredHeight: ui.sdu(12.5)
            
            horizontalAlignment: HorizontalAlignment.Right
            verticalAlignment: VerticalAlignment.Top
        }
        
        // Container for mouth and mustache
        Container {
            layout: StackLayout {
            }
            
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
            
            
            // Mustache text
            Label { 
                // Change label text based on current orientation
                text: newOrientation == "landscape" ?
                "landscape" : "portrait"
                
                horizontalAlignment: HorizontalAlignment.Center
                verticalAlignment: VerticalAlignment.Center
            }
            
            // Mouth container
            Container {
                background: Color.create("#8c8880")
                preferredWidth: ui.sdu(37.5)
                preferredHeight: ui.sdu(12.5)
            }
        }
    }
}

The code for the label text uses a method called binding to link the text to the current orientation. This is discussed below.

Add support for orientation changes

Now that the UI is created, we must tell the OS that the application supports orientation changes by using the SupportedDisplayOrientation class. This class contains a set of enums that identify the types of orientations that the app supports. A good place to set the supported orientation types for the app is within the onCreationCompleted signal handler, which handles the creationCompleted() signal that's emitted on the completion of UI initialization. In this signal handler, we specify the supported orientations other than the orientation that the application starts in. In this case, we want to support all orientations.

// Root container
Container {
 
    // Background, layout, and any other properties of the container
     
    onCreationCompleted: {
        OrientationSupport.supportedDisplayOrientation = 
            SupportedDisplayOrientation.All;
    }
     
    // Children of the container
     
} // end of root container

Finally, we must configure the bar-descriptor.xml file to support all orientations:

  1. In the Project Explorer view, double-click the bar-descriptor.xml file.
  2. On the Application tab, in the Orientation drop-down list, select Auto-orient.

If you set the supported orientation to Auto-orient in the bar-descriptor.xml file, you don't need to set it programmatically. It is shown here for illustrative purposes.

Your application is now ready to receive orientation updates.

Add the orientation handler

At this point, the application orients itself automatically when a user changes the orientation of the device. Great! However, we must think about how the UI looks when the orientation changes. There's a chance that the layout looks good in landscape orientation but doesn't look good in portrait orientation, so some adjustments may be required. We can use the OrientationHandler class to respond to orientation changes and update our UI accordingly. You add this handler using the  attachedObjects property of the Page control of the application. We give this handler an id so it can be referenced later.

attachedObjects: [
    OrientationHandler {
        id: handler
     
    }
]

In OrientationHandler, you add code to perform actions based on signals that are emitted during orientation changes. For this application, we make small changes to the layout and update the label text using the orientationAboutToChange() signal. The preferred method of updating UI controls based on orientation is to use a property to bind the control to the current orientation. This requires a property and a function that updates the property that are declared as part of the page:

Page {
    
    // Property to track current orientation
    property string newOrientation;
    
    // Set current orientation
    function updateNewOrientation(orientation) {
        newOrientation = 
            (orientation == UIOrientation.Landscape ?
                "landscape" : "portrait");
    }

    ...

} // end of Page

This property needs an initial value on start up, so we set its value in the onCreationCompleted signal handler:

    onCreationCompleted: {
        OrientationSupport.supportedDisplayOrientation = 
            SupportedDisplayOrientation.All;
        updateNewOrientation(handler.orientation)
    }

Now, we can update the UI controls to ensure they look good in both orientations. For landscape orientation, we add more padding to the left and right sides of the root container and less padding at the top as compared to portrait orientation. Also, we update the text for the label to reflect the current orientation.

        onOrientationAboutToChange: {
            updateNewOrientation(orientation)
            if (orientation == UIOrientation.Landscape) {
                // Change the padding
                rootContainer.leftPadding = ui.sdu(37.5)
                rootContainer.rightPadding = ui.sdu(37.5)
                rootContainer.topPadding = ui.sdu(28.75)
            } else {
                // Change the padding
                rootContainer.leftPadding = ui.sdu(12.5)
                rootContainer.rightPadding = ui.sdu(12.5)
                rootContainer.topPadding = ui.sdu(37.5)
            }
        }
    }
] // end of attachedObjects

There are two methods used here to update the UI controls: the declarative approach of changing the label text by binding to the newOrientation property and the imperative approach by changing the padding explicitly. The preferred approach for QML is declarative.

Screens showing different orientations

You can also use the declarative approach to load assets that are specific to an orientation. For example, to use different background images for landscape and portrait orientations, create an ImageView instance and use the newOrientation property to specify part of the asset name:

        ImageView {
            id: image
            // load different resources based on orientation
            imageSource: "asset:///image_" + newOrientation + ".png"
        }

We use the newOrientation property to bind imageSource to the current orientation and the image updates automatically as the orientation changes. In this example, you also need to provide two image assets: image_landscape.png and image_portrait.png.

We now have an app that responds to orientation changes by updating its UI controls to reflect the current orientation. This is a simple example, but you can use these principles to decide how to best handle orientation in your own application.

  • View the main.qml file

    import bb.cascades 1.3
    
    Page {
        // Property to track current orientation
        property string newOrientation;
        
        // Set current orientation
        function updateNewOrientation(orientation) {
            newOrientation = 
            (orientation == UIOrientation.Landscape ?
            "landscape" : "portrait");
        }
        attachedObjects: [
            OrientationHandler {
                id: handler
                onOrientationAboutToChange: {
                    updateNewOrientation(orientation)
                    if (orientation == UIOrientation.Landscape) {
                        // Change the padding
                        rootContainer.leftPadding = ui.sdu(37.5)
                        rootContainer.rightPadding = ui.sdu(37.5)
                        rootContainer.topPadding = ui.sdu(28.75)
                    } else {
                        // Change the padding
                        rootContainer.leftPadding = ui.sdu(12.5)
                        rootContainer.rightPadding = ui.sdu(12.5)
                        rootContainer.topPadding = ui.sdu(37.5)
                    }
                }
            }
        ] // End of attachedObjects
        Container { 
            id: rootContainer
            // Root container layout, giving padding to all controls
            // within it
            layout: DockLayout {}
            leftPadding: ui.sdu(12.5)
            rightPadding: ui.sdu(12.5)
            topPadding: ui.sdu(12.5)
            
            onCreationCompleted: {
                OrientationSupport.supportedDisplayOrientation = 
                SupportedDisplayOrientation.All;
                updateNewOrientation(handler.orientation)
            }
            
            // Left eye container
            Container { 
                background: Color.create("#005c7b")
                preferredWidth: ui.sdu(18.75)
                preferredHeight: ui.sdu(12.5)
                
                horizontalAlignment: HorizontalAlignment.Left
                verticalAlignment: VerticalAlignment.Top
            }
            
            // Right eye container
            Container {
                background: Color.create("#005c7b")
                preferredWidth: ui.sdu(18.75)
                preferredHeight: ui.sdu(12.5)
                
                horizontalAlignment: HorizontalAlignment.Right
                verticalAlignment: VerticalAlignment.Top
            }
            
            // Container for mouth and mustache
            Container {
                layout: StackLayout {
                }
                
                horizontalAlignment: HorizontalAlignment.Center
                verticalAlignment: VerticalAlignment.Center
                
                
                // Mustache text
                Label { 
                    // Change label text based on current orientation
                    text: newOrientation == "landscape" ?
                    "landscape" : "portrait"
                    
                    horizontalAlignment: HorizontalAlignment.Center
                    verticalAlignment: VerticalAlignment.Center
                }
                
                // Mouth container
                Container {
                    background: Color.create("#8c8880")
                    preferredWidth: ui.sdu(37.5)
                    preferredHeight: ui.sdu(12.5)
                }
            }
        }
    }

Handling orientation changes in C++

You can use C++ to achieve the same results as the above QML code, using the OrientationSupport class. First, create a file called app.cpp and add the following declarations to the top of the file:

#include "applicationui.hpp"

#include <bb/cascades/AbstractPane>
#include <bb/cascades/Application>
#include <bb/cascades/Color>
#include <bb/cascades/Container>
#include <bb/cascades/DockLayout>
#include <bb/cascades/HorizontalAlignment>
#include <bb/cascades/Label>
#include <bb/cascades/Page>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/UIConfig>
#include <bb/cascades/VerticalAlignment>

using namespace bb::cascades;

We create a simple UI, as described above, that has five containers and one label:

// Create the page and the root container, while retrieving the
    // current UI context
    Page *page = new Page();
    ui = page->ui();

    rootContainer = Container::create().top(ui->sdu(12.5))
            .left(ui->sdu(12.5))
            .right(ui->sdu(12.5));
    DockLayout *m_dockLayout = new DockLayout;
    rootContainer->setLayout(m_dockLayout);

    // Create the left eye container
    Container *lefteye = Container::create().preferredHeight(ui->sdu(12.5))
        .preferredWidth(ui->sdu(18.75)).background(Color::fromARGB(0xff005c7b))
        .horizontal(HorizontalAlignment::Left)
        .vertical(VerticalAlignment::Top);

    // Create the right eye container, and set the layout
    Container *righteye = Container::create().preferredHeight(ui->sdu(12.5))
        .preferredWidth(ui->sdu(18.75)).background(Color::fromARGB(0xff005c7b))
        .horizontal(HorizontalAlignment::Right)
        .vertical(VerticalAlignment::Top);

    // Create mouth and mustache container, and set the layout
    Container *mouth_moustache = Container::create()
        .horizontal(HorizontalAlignment::Center)
        .vertical(VerticalAlignment::Center);


    // Create the mouth container
    Container *mouth = Container::create().preferredHeight(ui->sdu(12.5))
        .preferredWidth(300).background(Color::fromARGB(0xff8c8880));

    // Create the label, and create and set the layout
    m_label = Label::create().text("start")
        .horizontal(HorizontalAlignment::Center)
        .vertical(VerticalAlignment::Center);

    // Add all the containers and label to the root container
    rootContainer->add(lefteye);
    rootContainer->add(righteye);
    rootContainer->add(mouth_moustache);
    mouth_moustache->add(m_label);
    mouth_moustache->add(mouth);
    page->setContent(rootContainer);

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

To set and handle orientation, create an instance of the OrientationSupport class and set it's supported orientation property. Here, we specify that all orientations are supported.

OrientationSupport *support = OrientationSupport::instance();
support->setSupportedDisplayOrientation(
    SupportedDisplayOrientation::All);

At this point, the application orients itself automatically and we must add code to handle orientation changes and modify the UI. To be informed of orientation changes, the orientationAboutToChange() signal must be connected to a slot:

bool res = QObject::connect(support,
    SIGNAL(orientationAboutToChange(
        bb::cascades::UIOrientation::Type)),
    this,
    SLOT(onOrientationAboutToChange(
        bb::cascades::UIOrientation::Type)));
    
// 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);

We use Q_ASSERT() to detect any errors in connecting the signal to the slot.

In the slot function, you add code to perform actions based on the orientationAboutToChange() signal and the new orientation. For this application, we make small changes to the UI. To make sure the components look good in landscape orientation, we add more padding to the left and right sides of the root container and less padding at the top as compared to portrait orientation. Also, we update the text for the label to reflect the current orientation.

void ApplicationUI::onOrientationAboutToChange(bb::cascades::UIOrientation::Type
        uiOrientation)
{
    // Change the text and padding to reflect a landscape orientation
    if(uiOrientation == UIOrientation::Landscape)
    {
        m_label->setText("landscape");
        rootContainer->setLeftPadding(ui->sdu(37.5));
        rootContainer->setRightPadding(ui->sdu(37.5));
        rootContainer->setTopPadding(ui->sdu(28.75));
    }

    // Change the text and padding to reflect a portrait orientation
    else
    {
        m_label->setText("portrait");
        rootContainer->setLeftPadding(ui->sdu(12.5));
        rootContainer->setRightPadding(ui->sdu(12.5));
        rootContainer->setTopPadding(ui->sdu(37.5));
    }
}

Here is the complete code sample for handling orientation changes in C++:

#include "applicationui.hpp"

#include <bb/cascades/AbstractPane>
#include <bb/cascades/Application>
#include <bb/cascades/Color>
#include <bb/cascades/Container>
#include <bb/cascades/DockLayout>
#include <bb/cascades/HorizontalAlignment>
#include <bb/cascades/Label>
#include <bb/cascades/Page>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/UIConfig>
#include <bb/cascades/VerticalAlignment>

using namespace bb::cascades;

ApplicationUI::ApplicationUI() :
        QObject()
{
    // Create an instance of OrientationSupport and set the
    // supported orientation
    OrientationSupport *support = OrientationSupport::instance();
    support->setSupportedDisplayOrientation(
        SupportedDisplayOrientation::All);

    // Connect to the orientationAboutToChange() signal. Make sure to
    // test the return value to detect any errors
    // 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 res = QObject::connect(support,
            SIGNAL(orientationAboutToChange(
                bb::cascades::UIOrientation::Type)),
            this,
            SLOT(onOrientationAboutToChange(
                bb::cascades::UIOrientation::Type)));

    // 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);

    // Create the page and the root container, while retrieving the
    // current UI context
    Page *page = new Page();
    ui = page->ui();

    rootContainer = Container::create().top(ui->sdu(12.5))
            .left(ui->sdu(12.5))
            .right(ui->sdu(12.5));
    DockLayout *m_dockLayout = new DockLayout;
    rootContainer->setLayout(m_dockLayout);

    // Create the left eye container
    Container *lefteye = Container::create().preferredHeight(ui->sdu(12.5))
        .preferredWidth(ui->sdu(18.75)).background(Color::fromARGB(0xff005c7b))
        .horizontal(HorizontalAlignment::Left)
        .vertical(VerticalAlignment::Top);

    // Create the right eye container, and set the layout
    Container *righteye = Container::create().preferredHeight(ui->sdu(12.5))
        .preferredWidth(ui->sdu(18.75)).background(Color::fromARGB(0xff005c7b))
        .horizontal(HorizontalAlignment::Right)
        .vertical(VerticalAlignment::Top);

    // Create mouth and mustache container, and set the layout
    Container *mouth_moustache = Container::create()
        .horizontal(HorizontalAlignment::Center)
        .vertical(VerticalAlignment::Center);


    // Create the mouth container
    Container *mouth = Container::create().preferredHeight(ui->sdu(12.5))
        .preferredWidth(300).background(Color::fromARGB(0xff8c8880));

    // Create the label, and create and set the layout
    m_label = Label::create().text("start")
        .horizontal(HorizontalAlignment::Center)
        .vertical(VerticalAlignment::Center);

    // Add all the containers and label to the root container
    rootContainer->add(lefteye);
    rootContainer->add(righteye);
    rootContainer->add(mouth_moustache);
    mouth_moustache->add(m_label);
    mouth_moustache->add(mouth);
    page->setContent(rootContainer);

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

}

void ApplicationUI::onOrientationAboutToChange(bb::cascades::UIOrientation::Type
        uiOrientation)
{
    // Change the text and padding to reflect a landscape orientation
    if(uiOrientation == UIOrientation::Landscape)
    {
        m_label->setText("landscape");
        rootContainer->setLeftPadding(ui->sdu(37.5));
        rootContainer->setRightPadding(ui->sdu(37.5));
        rootContainer->setTopPadding(ui->sdu(28.75));
    }

    // Change the text and padding to reflect a portrait orientation
    else
    {
        m_label->setText("portrait");
        rootContainer->setLeftPadding(ui->sdu(12.5));
        rootContainer->setRightPadding(ui->sdu(12.5));
        rootContainer->setTopPadding(ui->sdu(37.5));
    }
}
#ifndef ApplicationUI_HPP_
#define ApplicationUI_HPP_

#include <QObject>
#include <bb/cascades/OrientationSupport>


namespace bb
{
    namespace cascades
    {
        class Container;
        class Label;
        class UIConfig;
    }
}

class ApplicationUI : public QObject
{
    Q_OBJECT
    
public:
    ApplicationUI();
    virtual ~ApplicationUI() {}
private slots:
    void onOrientationAboutToChange(bb::cascades::UIOrientation::Type uiOrientation);
private:
    bb::cascades::Container  *rootContainer;
    bb::cascades::Label      *m_label;
    bb::cascades::UIConfig   *ui;
};

#endif /* ApplicationUI_HPP_ */
#include "applicationui.hpp"
#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);

    // Create the Application UI object, this is where the main.qml file
    // is loaded and the application scene is set.
    ApplicationUI appui;

    // Enter the application main event loop.
    return Application::exec();
}

Last modified: 2015-05-21



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

comments powered by Disqus