Active Frames

Active Frames are dynamic covers that are displayed when your application is minimized on the home screen. Every application uses application covers by default. Just swipe up from the bottom bezel within an app and the application cover is displayed automatically. Tapping the cover takes you back into the application.Diagram showing how to initiate Active Frames in an app.

The application cover is a non-interactive scene that the Cascades framework automatically updates whenever it changes. By default, the cover displays a scaled-down version of the scene that's currently being displayed in the application. For many applications, using the default cover might be the best option since it displays the current visual state of the app for the user.

However, there might be instances where you want to display specific information in the cover. For example, in a weather application you could display the current temperature. In Cascades, you can create custom application covers using the SceneCover class.

For best practices for using application covers, see the Active Frames topic in the UI Guidelines.

Creating a cover

An application cover is similar to any other screen that you might display in your application. You can create the content for the cover using either QML or C++ and it can support whichever controls that you want to display.

Although the root node of the scene needs to be an AbstractPane subclass, the root of a cover can be a Control. The major difference is that users can't interact with a cover, and it doesn't support animations.

The Cascades Cookbook apps provide examples of how to create covers using both C++ and QML. You can download both the C++ and QML versions of the app from the Sample Apps page.

Since it doesn't have a custom cover, the application displays a scaled-down version of the current scene. Here's what the cover might look like when the home screen of the app is being displayed.

Screen showing the cover of the Cookbook sample.

Creating a cover in C++

To create the content for a cover in C++, you need to create the controls and add them to a root container. The custom cover for the Cascades Cookbook app contains a 334 x 396 pixel image for the background, a Label indicating the name of the cookbook, and various containers and layouts for positioning the controls.

The background image is designed specifically for the dimensions of the BlackBerry Z10. If your app also targets keyboard devices, you'll need another background image that is 310 x 211 pixels. Since the cover itself is quite small compared to a regular screen, the label uses a custom TextStyle with a Small FontSize.

Screen showing the Active Frame created from the sample code that follows.
// A small UI consisting of just an ImageView in a Container is set up
// and used as the cover for the application when running in minimized mode.
Container *coverContainer = Container::create().background(
		Color::fromRGBA(0, 0, 0, 1));
coverContainer->setLayout(new DockLayout());

// A background image for the app cover.
ImageView *coverImage = ImageView::create(
		"asset:///images/application-cover.png").scalingMethod(
		ScalingMethod::AspectFit);

Container *titleContainer = Container::create().bottom(31);
titleContainer->setHorizontalAlignment(HorizontalAlignment::Center);
titleContainer->setVerticalAlignment(VerticalAlignment::Bottom);

Container *labelContainer =
		Container::create().preferredSize(84, 42).background(
				Color::fromARGB(0xff121212));
labelContainer->setLayout(new DockLayout());

// A title for the "book" cover so that one can see that it is the
// C++ version of the cookbook that is running.
Label* title = Label::create("C++");
title->textStyle()->setColor(Color::fromARGB(0xffebebeb));
title->textStyle()->setFontSize(FontSize::PointValue);
title->textStyle()->setFontSizeValue(6);
title->setHorizontalAlignment(HorizontalAlignment::Center);
title->setVerticalAlignment(VerticalAlignment::Bottom);

// Setting up the title Container with a title
labelContainer->add(title);
titleContainer->add(labelContainer);

// Adding the background image and the title Container
coverContainer->add(coverImage);
coverContainer->add(titleContainer);

After you add the components to the root container, you create an instance of SceneCover using the root container, retrieve an instance of Application, and set the cover.

// Create a SceneCover and set the application cover
SceneCover *sceneCover = SceneCover::create().content(coverContainer);
Application::instance()->setCover(sceneCover);

When the user minimizes the application, the custom application cover is displayed instead of the default cover.

Creating a cover in QML

The process for creating a cover in QML is basically the same as C++. The only difference is that the UI is defined in QML.

First, you create the QML document while specifying the .qml file containing the UI for the cover. After you verify that the QML document is free from errors, you retrieve the root container, set it as the content for a SceneCover, and then set the cover on your application instance.

Screen showing the Active Frame created from the sample code that follows.
QmlDocument *qmlCover = QmlDocument::create("asset:///AppCover.qml").parent(
		this);

if (!qmlCover->hasErrors()) {
	// Create the QML Container from using the QMLDocument.
	Container *coverContainer = qmlCover->createRootObject<Container>();

	// Create a SceneCover and set the application cover
	SceneCover *sceneCover = SceneCover::create().content(coverContainer);
	Application::instance()->setCover(sceneCover);
}

Here's the QML that defines the components for the cover.

import bb.cascades 1.0

Container {
    Container {        
        layout: DockLayout {}
        background: Color.Black
        
        ImageView {
            imageSource: "asset:///images/application-cover.png"
            scalingMethod: ScalingMethod.AspectFit
        }
        
        Container {
            bottomPadding: 31
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Bottom
            
            Container {
                preferredWidth: 300
                preferredHeight: 42
                background: Color.create("#121212")
                layout: DockLayout {}
                
                Label {
                    objectName: "TheLabel"
                    horizontalAlignment: HorizontalAlignment.Center
                    verticalAlignment: VerticalAlignment.Center
                    text: "QML"
                    textStyle.color: Color.create("#ebebeb")
                    textStyle.fontSize: FontSize.PointValue
                    textStyle.fontSizeValue: 6
                }
            }
        }
    }
}

When the user minimizes the application, the custom application cover is displayed instead of the default cover.

Dynamic covers

The previous sections demonstrated how to create application covers using static images and controls. In the previous examples, the content of the covers never changed. This section shows how to create dynamic covers.

Dynamic covers are useful since they can provide the user with specific information regardless of the current application context. For example, a calendar application might show details about the next event.

The image on the right shows an application cover that contains dynamic content. The content of the cover contains information about recent updates from a user's friends.

Screen showing the Active Frame from BBM.

Creating a dynamic cover

Creating the content for a dynamic cover isn't very different from creating content for a static cover. You create the scene as you normally would, using C++ or QML, but now you have the ability to update the content.

A typical way to create a dynamic cover is to create a class that inherits from SceneCover. In this example, the class is called ActiveFrameQML. The content for the cover is defined in a separate QML file. In the constructor, findChild() is called on the root container to retrieve the Label component that is updated. The class also contains a Q_INVOKABLE function called update(), which is called from main.qml whenever updates need to occur. The content for the cover is set using setContent().

#include "ActiveFrameQML.h"

#include <bb/cascades/SceneCover>
#include <bb/cascades/Container>
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>

using namespace bb::cascades;

ActiveFrameQML::ActiveFrameQML(QObject *parent)
	: SceneCover(parent)
{
	QmlDocument *qml = QmlDocument::create("asset:///AppCover.qml")
            .parent(parent);
	Container *mainContainer = qml->createRootObject<Container>();
	setContent(mainContainer);

    // Retrieves the label from QML that we want to update
	m_coverLabel = mainContainer->findChild<Label*>("TheLabel");
	m_coverLabel->setParent(mainContainer);
}

void ActiveFrameQML::update(QString appText) {

	m_coverLabel->setText(appText);
}

The next step is setting the cover on the application. To set the cover, you create an instance of ActiveFrameQML and set it on the Application in the ApplicationUI constructor. You must also expose the ActiveFrameQML instance as a context object to the main.qml file, because that's where updates to the cover are initiated from.

ApplicationUI::ApplicationUI(bb::cascades::Application *app) :
		QObject(app) {

	QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);

	ActiveFrameQML *activeFrame = new ActiveFrameQML();
	Application::instance()->setCover(activeFrame);

	qml->setContextProperty("activeFrame", activeFrame);

	AbstractPane *root = qml->createRootObject<AbstractPane>();
	app->setScene(root);
}

To initiate updates, your application must listen for the Application::thumbnail() signal, which is emitted every time the application moves to the background. This example shows how to connect a function to thumbnail() from within main.qml. Connecting to the signal from QML is partly for convenience, because the data that's used to update the cover is also located within this file. Depending on your application, you might want to connect to thumbnail() from C++ instead.

Whenever thumbnail() is emitted, the update() function is called. The update() function passes the text property from the TextField to the ActiveFrameQML instance, which is then used to update the Label on the cover.

import bb.cascades 1.0

Page {
    Container {
        TextField {
            id: appLabel
            text: "Hello World"
            textStyle.base: SystemDefaults.TextStyles.BigText
        }
    }
    onCreationCompleted: {
        Application.thumbnail.connect(onThumbnail)
    }
    function onThumbnail() {
        activeFrame.update(appLabel.text);
    }
}

To see the complete code for this example, expand the files listed below.

#include <bb/cascades/Application>

#include "applicationui.hpp"

#include <Qt/qdeclarativedebug.h>

using namespace bb::cascades;

Q_DECL_EXPORT int main(int argc, char **argv)
{
    Application app(argc, argv);

    new ApplicationUI(&app);

    return Application::exec();
}
#ifndef ApplicationUI_HPP_
#define ApplicationUI_HPP_

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

using namespace ::bb::cascades;

class ApplicationUI : public QObject
{
    Q_OBJECT

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

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

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

using namespace bb::cascades;

ApplicationUI::ApplicationUI(bb::cascades::Application *app) :
		QObject(app) {

	QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);

	ActiveFrameQML *activeFrame = new ActiveFrameQML();
	Application::instance()->setCover(activeFrame);

	qml->setContextProperty("activeFrame", activeFrame);

	AbstractPane *root = qml->createRootObject<AbstractPane>();
	app->setScene(root);
}
/*
 * ActiveFrame.h
 *
 *  Created on: Apr 2, 2013
 *      Author: wbarichak
 */

#ifndef ACTIVEFRAMEQML_H_
#define ACTIVEFRAMEQML_H_

#include <QObject>
#include <bb/cascades/Label>
#include <bb/cascades/SceneCover>

using namespace ::bb::cascades;

class ActiveFrameQML: public SceneCover {
	Q_OBJECT

public:
	ActiveFrameQML(QObject *parent=0);

public slots:
	Q_INVOKABLE void update(QString appText);

private:
	bb::cascades::Label *m_coverLabel;
};

#endif /* ACTIVEFRAMEQML_H_ */
#include "ActiveFrameQML.h"

#include <bb/cascades/SceneCover>
#include <bb/cascades/Container>
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>

using namespace bb::cascades;

ActiveFrameQML::ActiveFrameQML(QObject *parent)
	: SceneCover(parent)
{
	QmlDocument *qml = QmlDocument::create("asset:///AppCover.qml")
            .parent(parent);
	Container *mainContainer = qml->createRootObject<Container>();
	setContent(mainContainer);

	m_coverLabel = mainContainer->findChild<Label*>("TheLabel");
	m_coverLabel->setParent(mainContainer);
}

void ActiveFrameQML::update(QString appText) {

	m_coverLabel->setText(appText);
}
import bb.cascades 1.0

Page {
    Container {
        TextField {
            id: appLabel
            text: "Hello World"
            textStyle.base: SystemDefaults.TextStyles.BigText
        }
    }
    onCreationCompleted: {
        Application.thumbnail.connect(onThumbnail)  
    }
    function onThumbnail() {
        activeFrame.update(appLabel.text);
    }
}
import bb.cascades 1.0

Container {    
    Container {        
        layout: DockLayout {}
        background: Color.Black
        
        ImageView {
            imageSource: "asset:///images/application-cover.png"
            scalingMethod: ScalingMethod.AspectFit
        }
        
        Container {
            bottomPadding: 31
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Bottom
            
            Container {
                preferredWidth: 300
                preferredHeight: 42
                background: Color.create("#121212")
                layout: DockLayout {}
                
                Label {
                    objectName: "TheLabel"
                    horizontalAlignment: HorizontalAlignment.Center
                    verticalAlignment: VerticalAlignment.Center
                    text: "QML"
                    textStyle.color: Color.create("#ebebeb")
                    textStyle.fontSize: FontSize.PointValue
                    textStyle.fontSizeValue: 6
                }
            }
        }
    }
}
Screen showing the image file used in the application cover example.

Using Active Frames efficiently

When you create dynamic covers, you should consider how often you update the covers, and how it might affect performance and battery life. For more information about creating covers efficiently, see Update Active Frames efficiently.

Multicovers

To specify more than one cover size for the Active Frames in your app, you can use the MultiCover class. Here's some sample code in C++ that shows you how to use multicovers:

MultiCoverTest()
{
    AbstractCover *cover = MultiCover::create()
        .add(SceneCover::create(), CoverDetailLevel::High)
        .add(ApplicationViewCover::create(), CoverDetailLevel::Medium);
    Application::instance()->setCover(cover);
}

The following sample code shows how to create multicovers in QML:

import bb.cascades 1.3

Page {
   objectName: "MyPage"
   content: Button {
       objectName: "MyButton"
       text: "My Button"
   }
   onCreationCompleted: {
       Application.setCover(multi)
   }

   MultiCover {
       SceneCover {
           MultiCover.level: CoverDetailLevel.High
           description: "My Application"
           content: Button {
               objectName: "MyCoverButton"
               text: "My Cover Button"
           }
       }
       ApplicationViewCover {
           MultiCover.level: CoverDetailLevel.Medium
           description: "My Application"
       }
   }
}

Related resources

Web-based training

Design guidelines

 

Last modified: 2014-06-26



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

comments powered by Disqus