Blackberry 10:C++和QML集成

C++ and QML integration

http://developer.blackberry.com/cascades/reference/bb__cascades__abstractpane.html

QML is designed so that data can easily be passed to and from C++. You can use the            classes in theQt            Declarative module to load and modify QML documents from C++. In addition, QML and C++            objects can easily communicate using Qt signals and slots.

There are a number of reasons that you might want to mix QML and C++ in your application.            For example, you might have a data source for a list that you define in C++, while the            visuals for the list are in QML. Or you might define your UI in QML, but you must access            functionality in the Qt Declarative module or anotherQt class.

Loading a QML document from C++

      

Loading a QML document into C++ is a common way that Cascades applications            are created. You can define your UI in QML, and then load it into your C++ application            to be displayed for the user.

To load a QML document into an application, you create a             QmlDocument object by calling QmlDocument::create() and specifying the QML            asset that you want to load. To display the content of the QmlDocument,            you retrieve the root node usingQmlDocument::createRootObject(), and display            the content usingApplication::setScene().

The following example demonstrates how to create a Cascades app using            QML for the UI:

main.cpp

int main( int argc, char **argv)
{
     Application app(argc, argv);
  
     QmlDocument *qml = QmlDocument::create( "asset:///main.qml" ).parent(&app);
     AbstractPane *root = qml->createRootObject<AbstractPane>();
     app.setScene(root);
  
     return Application::exec();
}

main.qml

Page {
     property alias labelText: label.text     
     Container {           
         Label {
             id: label
             text: "Label"
        
                        
         Button {
             objectName: "button"
             text: "Button"
         }                              
     }
}

After you load a QML document into C++, you can modify its properties using                QObject::setProperty(). The following example shows how you can            change the text for thelabelText property:

root->setProperty( "labelText" , "New text" );

In this example, we can change the text because we define an alias to                the text property for theLabel at the root of the QML document. Without            an alias to the label'stext property, the value is not accessible            because it's not located at the root of the QML document.

When you set the scene in an application, the root object must be a subclass of             AbstractPane. However, the root node of the QML            document that you want to load doesn't need to be an            AbstractPane subclass. For example, if your main.qml file includes a                Container control at its root, you can load            this document and create a scene to display it as follows:

QmlDocument *qml = QmlDocument::create( "asset:///main.qml" );
Container *c = qml->createRootObject<Container>();
Application::instance()->setScene(Page::create().content(c));

Accessing child objects

Since QML documents are organized in a tree hierarchy, another option for accessing child            properties and objects is to search for the object by using                QObject::findChild(). In this example, we do a search for the            button and change its text property:

QObject *newButton = root->findChild<QObject*>( "button" );
  
if (newButton)
     newButton->setProperty( "text" , "New button text" );

To be able to search for the object using findChild(), the button must            have an objectName.

Exposing C++ values and objects to QML

      

There are often cases that require you to embed data from C++ into QML that you loaded            into your application. A convenient way of passing C++ data or objects to QML is by            using theQDeclarativePropertyMap class. This allows you to define            key-value pairs in C++ that you can bind to QML properties.

For example, this is how you could use QDeclarativePropertyMap to pass a            name and phone number to QML.

QmlDocument *qml = QmlDocument::create( "asset:///main.qml" );
  
QDeclarativePropertyMap* propertyMap = new QDeclarativePropertyMap;
propertyMap->insert( "name" , QVariant(QString( "Wes Barichak" )));
propertyMap->insert( "phone" , QVariant(QString( "519-555-0199" )));
  
qml->setContextProperty( "propertyMap" , propertyMap);

In your QML document, you can access the values by specifying the property name and the            key that you define in C++.

Label {
     text: "User name: " + propertyMap.name
}
Label {
     text: "Phone number: " + propertyMap.phone
}

Since QDeclarativePropertyMap accepts QVariant objects,            you can pass a QObject to QML this way as well.

Exposing C++ objects to QML

      

In an application, it's possible to create a C++ object and expose it to QML. Before you            can pass a C++ object to QML, there are some importantQt macros that you            need to implement in the header file for the C++ class.

  • Q_OBJECT is a required macro for classes that use the signals and                slots mechanism.
  • Q_PROPERTY exposes a class property to QML. It also defines the                read and write values from and to the property, as well as the signal that is                emitted with the property is changed.
  • Q_INVOKABLE exposes member functions so that they can be invoked                from QML.

In the example header below, the class has                a Q_PROPERTY called value. It also has three            functions;value(), setValue(),                and reset(). The reset() function is invokable            from QML using theQ_INVOKABLE macro. The header also defines the            signal that is emitted when the value property changes.

#ifndef MYCPPCLASS_H_
#define MYCPPCLASS_H_
  
#include <QObject>
#include <QMetaType>
  
class MyCppClass : public QObject {
     Q_OBJECT
     Q_PROPERTY( int value READ value WRITE setValue NOTIFY valueChanged)
  
public :
     MyCppClass();
     virtual ~MyCppClass();
  
     Q_INVOKABLE void reset();
  
     int value();
     void setValue( int i);
  
signals:
     void valueChanged( int );
  
private :
     int m_iValue;
};
  
#endif

And this is how you could implement the resulting class.

#include "MyCppClass.h"
  
MyCppClass::MyCppClass()
{
     m_iValue = 0;
}
  
MyCppClass::~MyCppClass()
{
}
  
int MyCppClass::value()
{
     return m_iValue;
}
  
void MyCppClass::setValue( int i)
{
     m_iValue = i;
     emit valueChanged(m_iValue);
}
  
void MyCppClass::reset()
{
     m_iValue = 0;
     emit valueChanged(m_iValue);
}

To expose MyCppClass to QML, you create an instance of the class and use                setContextProperty() to expose it.

MyCppClass *cppObject = new MyCppClass();
qml->setContextProperty( "cppObject" , cppObject);

From QML, you can access the Q_PROPERTY by using the property name that            is defined in the header file. In the example below, the value property            is displayed in the text property of a label.

Label {
   text: "Value of cppObject: " +  cppObject.value
}

From QML, you can also change the value of a Q_PROPERTY, connect slots            to its signals, and callQ_INVOKABLE functions. In the example below, a            button is used to increase the value property each time the user            presses it. The button also defines a custom slot and connects it to the property's                 valueChanged() signal. When the slot is invoked, the slot changes            the text on the button. A reset button is also used, which calls                MyCppClass::reset() to reset            the value property.

Button {
     id: increaseButton
     text: "Increase the value"
     onClicked: {
         cppObject.valueChanged.connect
                 (increaseButton.onCppValueChanged);
         cppObject.value = cppObject.value + 1;
     }
     function onCppValueChanged (val) {
         increaseButton.text = "Set value to " + (val + 1);
     }
}
Button {
     id: resetButton
     text: "Reset the value"
     onClicked: {
         cppObject.reset ()
     }
}
Label {
     id: valueLabel
     text: "The value is " + cppObject.value
}

Using C++ classes in QML

In some instances, instead of passing a C++ object or values to QML, you may want to use            the C++ class in QML directly. In theCascades framework,            there are a few ways that you can use a C++ class in QML directly, whether it's a class            from the coreQt            library, or a class that you define yourself:

  • Attach a C++ class to an existing QML component using                UIObject::attachedObjects (the class must                inherit QObject).
  • Extend a C++ class using                CustomControl and use it in QML                directly.

In both cases, you must register the class for QML, by                using qmlRegisterType().

Using the attachedObjects property

      

Since the attachedObjects property is a member of UIObject, you can use it to attach a QObject to almost any Cascades QML            component that has a visual representation. For example, if you want access to the            functionality in theQTimer class, this is how you would register the class            for use in QML:

qmlRegisterType<QTimer>( "my.library" , 1, 0, "QTimer" );

After you register the QTimer class, you can create and                manipulate QTimer objects within your QML document. In the            following example, aQTimer object is attached to aLabel. When the page is created, the timer is            started. When the timer is complete, the text for the label is updated with new            text.

import bb.cascades 1.0
import my.library 1.0
  
Page {
     Label {
         id: timerTestLabel
         text: "Hello world"
              
         attachedObjects: [
             QTimer {
                 id: timer
                 interval: 1000
                 onTimeout :{
                     timerTestLabel.text = "Timer triggered"      
                 }
             }
         ]
     }
          
     onCreationCompleted: {
         timer.start();
     }
}

For another example demonstrating how to use the attachedObjects property in an application, see            theSignals and slots tutorial.

Subclassing CustomControl

      

By subclassing             CustomControl, you can use your C++ class in a            QML document without attaching it to another component using             attachedObjects. In the following header file,            a simple class calledTextControl is                declared. TextControl is composed of a text            property, functions to set and get the text property, and a signal that            is emitted when the text changes.

#include <QObject>
#include <QString>
#include <bb/cascades/CustomControl>
  
class TextControl : public bb::cascades::CustomControl
{
     Q_OBJECT
     Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
  
public :
     QString text();
     void setText(QString text);
  
signals:
     void textChanged();
};

To use the class in QML, first you must register the class.

qmlRegisterType<TextControl>( "my.library" , 1, 0, "TextControl" );

After you register the class, you can import its library into a QML document, and use it            the way you use any other QML component:

import bb.cascades 1.0
import my.library 1.0
  
Page {
     TextControl {
         text: "Custom text control"
     }
}

Injecting C++ objects into QML

      

In previous sections, we've looked at how to define values and objects in C++ and use            them in QML. We've also looked at how to expose C++ classes to QML as attached objects            or custom controls. In this section, we'll see how to create objects in C++ and inject            them dynamically into QML.

Being able to inject C++ content into to QML is useful if you have a UI that is            dynamically generated. For example, you might want the ability to add or remove            containers from your screen during run time.

The first step is to provide an objectName for the QML component that            you want to add content to (or remove from). In this case, content is going to be added            to a             Container.

Page {
     Container {
         objectName: "rootContainer"
     }
}

Next, you load the QML file into C++ and create a Page object using the root from the QML file.            Once you have the root of the QML file, you use findChild() to search            for the Container with the specified object name.

QmlDocument *qml = QmlDocument::create( "main.qml" );
 
// Sets the context property that we want to use from within QML.
// Functions exposed via Q_INVOKABLE will be found with this
// property name and the name of the function.
qml->setContextProperty( "injection" , this );
 
// Creates the root using the page node
Page *appPage = qml->createRootNode<Page>();
 
// Retrieves the root container from the page
Container *container = appPage->findChild<Container*>( "rootContainer" );

Now that you have the container you want to add content to, it's just a matter of adding            your content. Here's how to add another container as the last child in the parent.

container->add(Container::create()
     .background(image)
     .preferredSize(200,200));

You aren't restricted to just adding content though. If you wanted, you could remove            components, replace components, or insert components at a specific index.

Here's an example that demonstrates how to create containers in C++ and inject them into            QML. The QML file contains a button that when clicked, calls a function in C++ that            creates a new container and adds it to QML.

main.qml

import bb.cascades 1.0
  
Page {
     // Allows the user to scroll vertically
     ScrollView {
         scrollViewProperties {
             scrollMode: ScrollMode.Vertical
         }
         // Root container that containers from C++ are added to
         Container {
             objectName: "rootContainer"      
             layout: StackLayout {}
             // Button that calls the C++ function to add a
             // new container. The selectedIndex from the drop down
             // is passed to C++.
             Button {
                 text: "Add container"
                 onClicked: {
                     injection.injectContainer();       
                 }
             }
         }
     }
}

TestApp.cpp

#include "TestApp.hpp"
  
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
  
using namespace bb::cascades;
  
TestApp::TestApp(bb::cascades::Application *app)
: QObject(app)
{
     QmlDocument *qml = QmlDocument::create( "asset:///main.qml" ).parent( this );
     qml->setContextProperty( "injection" , this );
     AbstractPane *root = qml->createRootObject<AbstractPane>();
     app->setScene(root);
}
  
void TestApp::injectContainer()
{
     // Creates the container and adds it to the root
     // container in qml
     mRootContainer->add(Container::create()
         .background(Color::Red)
         .preferredSize(200,200)
         .bottomMargin(20)
         .horizontal(HorizontalAlignment::Center));       
}

TestApp.hpp

#ifndef TestApp_HPP_
#define TestApp_HPP_
  
#include <QObject>
  
namespace bb { namespace cascades { class Application; }}
  
class TestApp : public QObject
{
     Q_OBJECT
public :
     TestApp(bb::cascades::Application *app);
     virtual ~Test() {}
      
     // By using Q_INVOKABLE we can call it from qml
     Q_INVOKABLE void injectContainer();
  
private :
     Page *appPage;
     Container *mRootContainer;
};
  
#endif /* Test_Hpp_ */

你可能感兴趣的:(Blackberry 10:C++和QML集成)