Monday, May 13, 2013

Signals and slots in QML

As like our previous discussion on Signals and slots mechanism, we will follow the same example with a different approach. Approach in the sense, making the signal and slot mechanism work from QML.

Our example output will be like : 



Upon button click signal, change label text

T

The completed mechanism is done in QML. The QML file will look like:

import bb.cascades 1.0

Page {
    Container {
        Button {
            id: myButton
            text : "emit signal"
            horizontalAlignment: HorizontalAlignment.Left
            signal buttonClicked(string text)   //  declare signal
            onClicked: {
                myButton.buttonClicked.connect(label.changeText);   //  connect signal and slot
                myButton.buttonClicked("Button Clicked")    //  Call slot.
            }
        }   //  end button
        Label {
            id: label
            horizontalAlignment: HorizontalAlignment.Right
            text: "Im a label"
            function changeText(text) {
                label.text = text;
            }
        }   //  end label
    }   //  end contianer
}   //  end page

Here comes the detailed explanation in 4 steps :

1)In Button, I'm declaring the signal for the buttonclick.

signal buttonClicked(string text)   //  declare signal

2)Write a slot function to listen the signal.

function changeText(text) {
                label.text = text;
            }

3) Connect the signal and slot.

 myButton.buttonClicked.connect(label.changeText);   //  connect signal and 

4) Call slot method

myButton.buttonClicked("Button Clicked")    //  Call slot.


By this way, we can use signals and slots mechanism from QML.

Sunday, May 5, 2013

BB10 Signal's and slot's.

Signal and Slot mechanism replace listener concept in Android or Java development.
Yes they serve the same purpose!!

To know what Signal means, let us consider a button, and if a click event occurs on it, it is specified as Signal. So, it is a clicked() Signal.

Coming to Slot, considering the above button, if we are interested in writing some functionality on emitting a signal. Here comes Slot i.e to catch a signal we use slot, technically slot is a method which is called upon emission of signal. So, on emission of clicked() signal our slot method will be called.

Ok, I'll show you some sample out for a clear understanding.Check the output images below.




The first screenshot shows a button and label. When button is clicked I'm changing the text of label using signal and slot mechanism's.Code snippets are explained below.

Below is the QML file.

import bb.cascades 1.0

Page {
    Container {
        background: Color.White
        Button {
            objectName: "button"
            verticalAlignment: VerticalAlignment.Center
            horizontalAlignment: HorizontalAlignment.Left
            text: "Emit signal"
        } // end button
        
        Label {
            objectName: "label"
            text : "I'm a label"
            textStyle{
                color: Color.Black
            }
            horizontalAlignment: HorizontalAlignment.Right
        } // end label
    } // end container
}  // end page


Let us look into .hpp file

#include <QObject>
#include <bb/cascades/AbstractPane>
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/Page>
#include <bb/cascades/Button>
#include <bb/cascades/Label>

using namespace bb::cascades;

class SampleSig : public QObject {
    Q_OBJECT
public:
    SampleSig();
    virtual ~SampleSig();
    Button *btn;
    Label *label;

public slots:
    void buttonClickedSlot();
};

Here slots are separately declared under "public slots" and thus they gain a special identity. The methods which are defined under slots have a special affinity to register for a signal emission.

Now, let us see how we are connecting the signal's and slots. Below is .cpp file.

/*
 * SampleSig.cpp
 *
 *  Created on: 05-May-2013
 *      Author: Sree Harsha
 */

#include "SampleSig.h"

SampleSig::SampleSig(): QObject(Application::instance()){
    // create scene document from main.qml asset
    // set parent to created document to ensure it exists for the whole application lifetime
    QmlDocument *qml = QmlDocument::create("asset:///signal.qml").parent(this);

    // create root object for the UI
    AbstractPane *root= qml->createRootObject<AbstractPane>();
    // set created root object as a scene
    Application *application = Application::instance();
    application->setScene(root);

    btn = root->findChild<Button*>("button");
    label = root->findChild<Label*>("label");

    //  connect button clicked to this objects buttonClcikedSlot
    connect(btn, SIGNAL(clicked()), this, SLOT(buttonClickedSlot()));
}

SampleSig::~SampleSig() {

}

void SampleSig::buttonClickedSlot() {
    label->setText("My slot is called !!");
}



In the last line of constructor, we can see the connect method is used to register the Slot method's with signal's.


connect(btn, SIGNAL(clicked()), this, SLOT(buttonClickedSlot()));


Zooming into the above line, it contain's 4 param's. 
  1. 1st Param, from which object signal is raised, in our case button.
  2. 2nd param, Signal method, here it is button clicked().
  3. 3rd param, which object's slot need to be connected.
  4. 4th param, which slot need to be raised on signal emission.
Totally, on click signal of button we are calling buttonClickedSlot() method of the class object.

Finally, in the slot I just changed the text  of the label.

We can register n number of slot's to a single signal.

Happy learning's :)

Navigation pane in BB10

In BB10, if you want to set UI on to the screen, we need to go for AbstractPane. It is better to learn a bit about abstract pane to know why we really need NavigationPane.

We can set our QML page to BB10 screen using Application::instance().setScene(AbstractPane).
If we use AbstractPane, the UI is set to the application, but if we want to maintain stack of all screens which are pushed on to the scene, it is not possible using AbstractPane. This requirement gave birth to NavigationPane.

So, If we want to maintain stack of screens which are pushed on to the screen, go and use NavigationPane which is child class of AbstractPane.

You can find the complete example in my previous post.

Practical explanation:
QML file with navpane is like

import bb.cascades 1.0
NavigationPane {
    // creates one page with a label
    Page {
        Container {
            layout: DockLayout {
            }
            Button {
                text: qsTr("Click me")
                verticalAlignment: VerticalAlignment.Center
                horizontalAlignment: HorizontalAlignment.Center
                onClicked: {
                    cppObj.onButtonClicked();
                }  // end onclicked
            }
        }  //end button
    }  // contianer ends

} // Nav pane ends

The nav pane is set to cpp file like this

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

    // create root object for the UI
    NavigationPane *pane = qml->createRootObject();
// set created root object as a scene
    app->setScene(pane);

Use pane object to push the future pages.

QmlDocument *qml = QmlDocument::create("asset:///buttonclicked.qml").parent(this);
Page* root = qml->createRootObject();
pane->push(root);

Once you push a page using NavigationPage, a bar will appear for navigating to previous screens.
Further detailed explanation is well documented in BB website.

Using Q_INVOKABLE with BB10

Ever used Q_INVOKABLE ?

OK !! Let's know the use behind this.

If you want to communicate b/w QML file and cpp file, i.e if you like to call a method of cpp file from QML, then we have to take help of Q_INVOKABLE.In-order to let QML file know( discover ) the method in cpp file, we have to declare the method as Q_INVOKABLE.

Practically,In hpp file, declare the method like this.

Q_INVOKABLE void onButtonClicked();

In cpp file, define this method and make cpp file object available to QML using context property of QMLDocument.

QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);
// create root object for the UI
pane = qml->createRootObject(); // set created root object as a scene
app->setScene(pane);

qml->setContextProperty("cppObj", this);

In the last line of above code, I'm referencing cpp class object as "cppObj". So with this name, i can call method's of this object.

Lastly, in QML file make a call for the method.

Button {
    text: qsTr("Click me")
    verticalAlignment: VerticalAlignment.Center
    horizontalAlignment: HorizontalAlignment.Center
    onClicked: {
       cppObj.onButtonClicked();
     }
  }


Don't forget, decalring Q_OJECT in .hpp file.

The complete example is illustrated in this post.

Power of Q_OBJECT in cascades

Q_OBJECT is a powerful macro which is responsible for establishing communication between QML files and native Cpp files.

 From the QT Documentation
  
The Meta-Object Compiler, moc, is the program that handles Qt's C++ extensions.
The moc tool reads a C++ header file. If it finds one or more class declarations that contain the Q_OBJECT macro, it produces a C++ source file containing the meta-object code for those classes. Among other things, meta-object code is required for the signals and slots mechanism, the run-time type information, and the dynamic property system.
Let us make an illustration of Q_OBJECT with a simple button.I'll write down my code here.

Firstly, in my main.qml file I used a button and called cpp method on click event. Find it below


import bb.cascades 1.0
NavigationPane {
    // creates one page with a label
    Page {
        Container {
            layout: DockLayout {
            }
            Button {
                text: qsTr("Click me")
                verticalAlignment: VerticalAlignment.Center
                horizontalAlignment: HorizontalAlignment.Center
                onClicked: {
                    cppObj.onButtonClicked();
                }
            }
        }
    }
}
Here is the applicationui.hpp file


#include <QObject>
#include <bb/cascades/NavigationPane>
#include <bb/cascades/Page>

using namespace bb::cascades;

namespace bb { namespace cascades { class Application; }}

/*!
 * @brief Application pane object
 *
 *Use this object to create and init app UI, to create context objects, to register the new meta types etc.
 */
class ApplicationUI : public QObject
{
    Q_OBJECT
public:
    ApplicationUI(bb::cascades::Application *app);
    virtual ~ApplicationUI() {}
    Q_INVOKABLE void onButtonClicked();
    NavigationPane *pane;

};

Finally my applicationui.cpp goes here.
// Default empty project template
#include "applicationui.hpp"

#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>
using namespace bb::cascades;

ApplicationUI::ApplicationUI(bb::cascades::Application *app) :
        QObject(app) {
    // create scene document from main.qml asset
    // set parent to created document to ensure it exists for the whole application lifetime
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);

    // create root object for the UI
    pane = qml->createRootObject();
    // set created root object as a scene
    app->setScene(pane);

    qml->setContextProperty("cppObj", this);
}

/**
 * Q_INVOKABLE method which can be called from attached QML file.
 * Here in case main.qml
 */
void ApplicationUI::onButtonClicked() {
    // create scene document from buttonclicked.qml asset
    // set parent to created document to ensure it exists for the whole application lifetime
    QmlDocument *qml = QmlDocument::create("asset:///buttonclicked.qml").parent(this);
    Page* root = qml->createRootObject();
    pane->push(root);
}

The above code calls the onButtonClicked() of cpp file from QML.
To check the use of Q_OBJECT, remove Q_OBJECT in .hpp file. The button will not be clicked then after.