Qt-based BB10 API Examples Documentation

Contents

Loader Example

Files:

Description

The Loader example demonstrates how to use QDeclarativeComponent to extend Cascades with a powerful mechanism to load/unload controls from the UI at runtime.

Overview

In this example we'll learn how to use the QDeclarativeComponent class to extend Cascades with a custom element named Loader that allows the user to load and unload Controls from an external QML file or from an internal component at runtime.

The UI

The UI of this sample application consists of a TabbedPane with two Pages. On the first page the user can switch between a set of colored rectangles by moving a Slider. The colored rectangles are implemented in external QML files and are loaded/unloaded on-the-fly by a Loader element depending on the value of the Slider.

    Container {
        id: pageContainer

        property variant pageNames: ['', 'Page1.qml', 'Page2.qml', 'Page3.qml', 'Page4.qml', 'Page5.qml']

        horizontalAlignment: HorizontalAlignment.Center
        verticalAlignment: VerticalAlignment.Center

        Loader {
            horizontalAlignment: HorizontalAlignment.Center

            // Update the source of the loader depending on the position of the slider
            source: pageContainer.pageNames[Math.round(slider.immediateValue)]
        }

        Slider {
            id: slider

            topMargin: 50

            fromValue: 0
            toValue: 5
            value: 0
        }
    }

The second page defines a couple of colored rectangles inside Components. These rectangles won't be visible on screen until the Loader loads them explicitly.

    attachedObjects: [
        QtBase.Component {
            id: redSquare
            ColoredRectangle {
                property string colorName: qsTr ("red")
                color: Color.Red
                title: qsTr ("Red")
            }
        },
        QtBase.Component {
            id: greenSquare
            ColoredRectangle {
                property string colorName: qsTr ("green")
                color: Color.Green
                title: qsTr ("Green")
            }
        },

The user can change the component that should be loaded by the Loader by clicking on the visible rectangle.

    Loader {
        id: rectLoader

        property int counter: 0

        horizontalAlignment: HorizontalAlignment.Center
        topMargin: 10

        // Bind one of the internal components to 'sourceComponent' property depeding on the counter
        sourceComponent:   counter == 0 ? redSquare
                         : counter == 1 ? greenSquare
                         : counter == 2 ? blueSquare
                         : yellowSquare

        // The Loader provides all the functionality of a standard Control e.g. the touch signal
        onTouch: {
            if (event.isDown()) {
                // Increment counter and wrap if > 3
                counter = ((counter + 1) % 4)
            }
        }
    }

Loader

The Loader class is a custom Cascades control that embeds a QDeclarativeComponent. The component can either be an external QML file or a QtQuick.Component declared inside the QML file where the Loader is used. The embedded component can be loaded/unloaded at runtime and allows therefor to unitize the UI in smaller parts.

    class Loader : public bb::cascades::CustomControl
    {
        Q_OBJECT
        Q_ENUMS(Status)

        // The URL to an external QML file that is used as embedded component
        Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)

        // An internal component that is used as embedded component
        Q_PROPERTY(QDeclarativeComponent *sourceComponent READ sourceComponent WRITE setSourceComponent RESET resetSourceComponent NOTIFY sourceChanged)

        // The Cascades control that represents the embedded component
        Q_PROPERTY(bb::cascades::Control* control READ control NOTIFY controlChanged)

        // The current status of the loader
        Q_PROPERTY(Status status READ status NOTIFY statusChanged)

        // The loading progress of the embedded component
        Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged)

    public:
        /**
         * Describes the status of the Loader
         */
        enum Status
        {
            Null,     ///< No embedded component is loaded
            Ready,    ///< The embedded component has been loaded successfully
            Loading,  ///< The embedded component is still loading
            Error     ///< The embedded component could not be loaded
        };

        Loader(bb::cascades::Container *parent = 0);
        ~Loader();

        // The accessor methods for the properties
        QUrl source() const;
        void setSource(const QUrl &url);

        QDeclarativeComponent *sourceComponent() const;
        void setSourceComponent(QDeclarativeComponent *component);
        void resetSourceComponent();

        Status status() const;
        qreal progress() const;

        bb::cascades::Control *control() const;

    Q_SIGNALS:
        // The change notification signals for the properties
        void controlChanged();
        void sourceChanged();
        void statusChanged();
        void progressChanged();

        // This signal is emitted when the embedded component has been loaded
        void loaded();

    private Q_SLOTS:
        // This slot is invoked when the declarative component has been loaded
        void sourceLoaded();

    private:
        // A helper method to clean up a previously loaded component
        void clear();

        // A helper method that does the actual loading of the embedded component
        void load();

        // The URL to an external QML file that is used as embedded component
        QUrl m_source;

        // The Cascades control that represents the embedded component
        bb::cascades::Control *m_control;

        // The actual declarative component that has been loaded
        QDeclarativeComponent *m_component;

        // A flag that defines whether this class owns the declarative component
        bool m_ownsComponent;
    };

The Loader inhertits from bb::cascades::CustomControl, so it behaves like any other Cascades control and provides the same basic properties. Inside the constructor of Loader we set its 'visible' property to false, because the Loader should be hidden as long as it hasn't loaded any component.

    Loader::Loader(bb::cascades::Container *parent)
        : CustomControl(parent)
        , m_control(0)
        , m_component(0)
        , m_ownsComponent(false)
    {
        /**
         * The Loader should only be visible if a embedded component has been loaded,
         * so hide it by default.
         */
        setVisible(false);
    }

Whenever the 'source' property of the Loader is changed, the setSource() method is called.

    void Loader::setSource(const QUrl &url)
    {
        if (m_source == url)
            return;

        // If a new external source has been defined, clear the previous loaded component
        clear();
        m_source = url;

        if (m_source.isEmpty()) {
            /**
             * If the source is empty, the user only wants to unload the current component,
             * so we simply emit the change notifications and return.
             */
            emit sourceChanged();
            emit statusChanged();
            emit progressChanged();
            emit controlChanged();
            return;
        }

        // Let the QDeclarativeComponent do the actual loading of the external QML file
        m_component = new QDeclarativeComponent(qmlEngine(this), m_source, this);
        m_ownsComponent = true;

        // Start the loading
        load();
    }

Inside this method we clear any previously loaded component, update the internal property value and if the new source URL is not empty, we create a new QDeclarativeComponent object. This object takes the URL to the external QML file, which it will load and parse. Since we created this component object, we remember in a flag that we own that object. Afterwards we call the load() method that will handle the remaining, common loading steps.

If not an external QML file is set on the 'source' property, but an internal component set on the 'sourceComponent' property, the setSourceComponent() method is called.

    void Loader::setSourceComponent(QDeclarativeComponent *component)
    {
        if (m_component == component)
            return;

        // If a new component has been defined, clear the previous loaded component
        clear();

        // Use the passed component as our embedded component ...
        m_component = component;

        // ... however we do not own the component in this case
        m_ownsComponent = false;

        if (!m_component) {
            /**
             * If the component is @c null, the user only wants to unload the current component,
             * so we simply emit the change notifications and return.
             */
            emit sourceChanged();
            emit statusChanged();
            emit progressChanged();
            emit controlChanged();
            return;
        }

        // Start the loading
        load();
    }

Inside this method we clear any previously loaded component again, update the internal property value and if the new component is not a null object, we call the load() method.

    void Loader::load()
    {
        // Sanity check
        if (!m_component)
            return;

        if (!m_component->isLoading()) {
            // The component has finished loading already
            sourceLoaded();
        } else {
            // Setup the signal/slot connections to be informed about progress and state changes
            bool ok = connect(m_component, SIGNAL(statusChanged(QDeclarativeComponent::Status)),
                              this, SLOT(sourceLoaded()));
            Q_ASSERT(ok);
            ok = connect(m_component, SIGNAL(progressChanged(qreal)),
                         this, SIGNAL(progressChanged()));
            Q_ASSERT(ok);
            emit statusChanged();
            emit progressChanged();
            emit sourceChanged();
            emit controlChanged();
        }
    }

The load() method checks whether the component has finished loading already (and calls sourceLoaded() in that case) or sets up signal/slot connections to monitor the loading progress otherwise.

    void Loader::sourceLoaded()
    {
        if (m_component) { // Sanity check

            /**
             * If an error occured during loading, just emit the change notification
             * signals, the 'status' property will reflect that error state.
             */
            if (!m_component->errors().isEmpty()) {
                emit statusChanged();
                emit progressChanged();
                emit sourceChanged();
                emit controlChanged();
                return;
            }

            // Get the declarative context of the declarative component
            QDeclarativeContext *creationContext = m_component->creationContext();

            // If it has not been set, use the declarative context of the Loader element as fallback
            if (!creationContext)
                creationContext = QDeclarativeEngine::contextForObject(this);

            // Create a new child context that will be used as declarative context for the Cascades object
            QDeclarativeContext *ctxt = new QDeclarativeContext(creationContext);
            ctxt->setContextObject(this);

            // Load the Cascades object
            QObject *object = m_component->create(ctxt);
            if (object) {
                m_control = qobject_cast<bb::cascades::Control*>(object);
                if (m_control) {
                    // If the loaded Cascades object is a Control, we use it as root element for the Loader and make it visible
                    setRoot(m_control);
                    setVisible(true);
                } else {
                    // Otherwise report an error
                    qmlInfo(this) << tr("Loader does not support loading non-visual elements.");
                    delete object;
                    delete ctxt;
                }
            } else {
                // Cleanup if the loading failed
                delete object;
                delete ctxt;
                m_source = QUrl();
            }

            emit sourceChanged();
            emit statusChanged();
            emit progressChanged();
            emit controlChanged();
            emit loaded();
        }
    }

The sourceLoaded() method is called after the QDeclarativeComponent is fully configured, so in the next step the actual C++ object can be created from the QML description, which the QDeclarativeComponent represents. For that we first need a QDeclarativeContext object which we either get from the component or from the Loader object itself. On top of that QDeclarativeContext object we create a child QDeclarativeContext object. This one would allow us to publish new C++ objects to the QML environment which will only be visible inside the QML code of the loaded component.

The call to create() of QDeclarativeComponent creates the actual C++ object that represents the QML code inside the component. We check whether the returned object inherits from bb::cascades::Control, in that case we set it as root control of the Loader and make the Loader visible. Otherwise some error occurred and we clean up the context object and report an error message.

    void Loader::clear()
    {
        // If we own the declarative component, we have to delete it
        if (m_ownsComponent) {
            m_component->deleteLater();
            m_component = 0;
            m_ownsComponent = false;
        }

        m_source = QUrl();

        /**
         * Remove and delete the Cascades control that represents the loaded component
         * and hide the Loader.
         */
        if (m_control) {
            setRoot(0);
            setVisible(false);
            m_control->deleteLater();
            m_control = 0;
        }
    }

The clear() method is called to unload any loaded component. If we created the QDeclarativeComponent object ourself, we have to delete it explicitly here. If a Control had been loaded, we remove it as root object, hide the Loader object and delete the Control object afterwards.