Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Native Development

Reply
Developer
testinz
Posts: 180
Registered: ‎09-03-2012
My Device: Blackberry 10
Accepted Solution

Connect signal in QML from C++?

Hello,

 

Sorry in advance for this noobish question :smileyhappy:

 

From my QML, I would like to call C++ function to invoke camera and then upon photo saved, I would to save the url for further processing.

 

The invocation and the capturing of URL is working properly.

 

The problem is that I cannot send a signal from C++ to QML in order to send the url to QML for further processing.

 

Trigger from QML:

Button {
   text: "Snapshot"
   onClicked: {
	app.invokeCamera();
   }
}

void App::invokeCamera() {
	InvokeRequest request;
	request.setTarget("sys.camera.card");
	request.setAction("bb.action.CAPTURE");
	QByteArray data;
	data.append("full");
	request.setData(getShareRecipeData());
	mInvokeManager->invoke(request);
}

void App::childCardDone(
		const bb::system::CardDoneMessage &doneMessage) {
	QString url = "";
	qDebug() << "childCardDone reason:" << doneMessage.reason();
	qDebug() << "childCardDone data&colon;" << doneMessage.data();

	if (doneMessage.reason().compare("save") == 0) {
		url = "file://" + doneMessage.data();
	} else if (doneMessage.reason().compare("save") == 0) {
		showError(doneMessage.data());
	}
// This doesn't seem to send to QML
	emit cameraCaptureCompleted(url);
}

In C++:

signals:
	void cameraCaptureCompleted(const QString &url);

 In QML for signal received (somehow this needs to be duplicated???)

 

    signal cameraCaptureCompleted(string imageLink)
    onCameraCaptureCompleted: {
        console.debug("image link: " + imageLink);

    }

 

The onCameraCaptureCompleted never triggered. The console.debug("image link: " + imageLink); didn't show anything.

 

Thanks for your help!

 

 

Please use plain text.
Developer
greenmr
Posts: 882
Registered: ‎03-20-2013
My Device: Red LE Developer Z10

Re: Connect signal in QML from C++?

[ Edited ]

First off, I trust that you are declaring the Q_OBJECT macro in your App class. If not then your C++ signals on that class are not exposed to QML.

 

Second, you don't need to (and shouldn't) redeclare the signal in your QML, and I suspect that is fooling the compiler into connecting the QML version of the signal to onCameraCaptureCompleted (which is just a slot) rather than the one from the App class, which is what you want. When you put "signal cameraCaptureCompleted(string imageLink)" in your QML you are creating a completely new signal with that name on the QML object. When the QML is loaded by the app it is compiled into C++ class instances on the fly. I bet the runtime QML compiler sees your redefinition of cameraCaptureCompleted and connects it to the onCameraCaptureCompleted slot instead of the signal from your C++ App class.

 

Can you post more complete examples of your App class definition and the QML object with the onCameraCaptureCompleted slot? It is hard to diagnose a problem with such limited snippets of code.

 

EDIT: I was thinking about this after posting, and I realized that if you had to declare "signal cameraCaptureCompleted(string imageLink)" in your QML to get it to accept the "onCameraCaptureCompleted" slot definition it means you aren't exposing the C++ class to QML. Did you use "qmlRegisterType()" to expose your App class to QML? If so is the "onCameraCaptureCompleted" declaration in QML on the QML representation of the exposed App class? You can't just declare that slot on any QML object and expect it to connect to the slot on the App class.

 

Forgive me if this is all stuff you already know, but the basic steps are:

 

  1. Declare your App class in C++
  2. Make sure you include the Q_OBJECT macro in the class declaration
  3. Declare your signal in the signals: section of the class declaration.
  4. Expose the class to QML with something like this - qmlRegisterType<App>("uri",1,0,"App");
  5. Declare an "App" in your QML code (see below).
  6. Declare your slot on the QML App object. (onCameraCaptureCompleted)

With a name like App I wonder if the class you are declaring your signal on is your main UI class? If so, you can't connect to the signal in QML with "on<signalName>:" since there is no way to expose that class to QML that allows the "on<signalName>:" syntax. If this is the case, and the class with the signal you want to handle IS the main U class, do this:

 

  1. In App::App() expose the App class INSTANCE to QML with "qml->setContextProperty( "app", this );"
  2. Step #1 MUST happen after the "qml = QmlDocument::create( "asset:///main.qml" )" statement but BEFORE the "root = qml->createRootObject<AbstractPane>();" statement.
  3. Now in your QML create a JavaScript function on any object or control where you want to handle your signal (for instance - function camCapCompletedHandler(imageLink) {}.
  4. Connect the signal to the handler explicitly, perhaps in the onCreationCompleted script (app.onCameraCaptureCompleted.connect(someObject.camCapCompletedHandler)"
  5. Your signal should now trigger your handler function correctly.

If your App class is NOT your main UI class then how you get the signal connected to the slot depends on whether your App class is a visual or utility object. If the former:

 

  1. Register the class to expose to QML - qmlRegisterType<App>("uri",1,0,"App")
  2. On the page in your QML just place the App where you want it... ie...
Page{
   App {
      onCameraCaptureCompleted: {
      }
   }
}

 If App is a utitly class however you will need to attach it to the page instead:

 

Page {
   attachedObjects: [
      App {
id: myApp onCameraCaptureCompleted: { } } ] }

 Hope this helps somewhat.



Developer of Built for BlackBerry certified multiFEED RSS/Atom feed reader and aggregator.
Please use plain text.
Developer
testinz
Posts: 180
Registered: ‎09-03-2012
My Device: Blackberry 10

Re: Connect signal in QML from C++?

Thanks for your help:

The missing code (on intialization).

 

    mInvokeManager = new InvokeManager(this);
    bool ok = connect(mInvokeManager,
            SIGNAL(childCardDone(const bb::system::CardDoneMessage&)), this,
            SLOT(childCardDone(const bb::system::CardDoneMessage&)));
    if (!ok) {
        qDebug() << "CONNECT FAILS: childCardDone";
    }

 

My App class has this line in the header:

 

Q_OBJECT

Slots and signals defined in C++ header:

 

public Q_SLOTS:
    void childCardDone(const bb::system::CardDoneMessage &doneMessage);

Q_SIGNALS:
    void cameraCaptureCompleted(const QString &imageLink);

 

QML page listening to the signal:

 

import bb.cascades 1.0
import bb.cascades.pickers 1.0
import bb.cascades.multimedia 1.0
Page {
    id: selectPhotoPage
    //signal cameraCaptureCompleted(string imageLink)
    onCameraCaptureCompleted: {
        console.debug("image link: " + imageLink);
        selectPhotoPage.imageUrl = imageLink;
    }

Please use plain text.
Developer
testinz
Posts: 180
Registered: ‎09-03-2012
My Device: Blackberry 10

Re: Connect signal in QML from C++?

If I comment out this line from the QML file:

// signal cameraCaptureCompleted(string imageLink)

I get this error:

4e3ebe6_/native/assets//SelectPhoto.qml:12:5: Cannot assign to non-existent property "onCameraCaptureCompleted"
onCameraCaptureCompleted: {
^)
Please use plain text.
Developer
greenmr
Posts: 882
Registered: ‎03-20-2013
My Device: Red LE Developer Z10

Re: Connect signal in QML from C++?

Ok, I may have found your problem. I was messing with the Q_SLOTS and Q_SIGNALS macros just a few days ago and I found that for some reason they don't work. Instead use the other syntax:

 

public slots:
    void childCardDone(const bb::system::CardDoneMessage &doneMessage);

signals:
    void cameraCaptureCompleted(const QString &imageLink);

I suspect the MOC is not expanding the Q_SLOTS and Q_SIGNALS macros properly. The Q_XXXX macros are interesting in that in the C++ preprocessor they always expand to either nothing, or in the case of Q_SIGNALS it expands to "public:" which obviously doesn't expose the signal to QML. In the MOC preprocessor though these macros expand to something else entirely to implement the signals and slots mechanism in C++ code. Since Q_SIGNALS doesn't actually expose the signals my guess is that it isn't expanding properly in the MOC. The good news is that using to alternate syntax like above DOES work.



Developer of Built for BlackBerry certified multiFEED RSS/Atom feed reader and aggregator.
Please use plain text.
Developer
greenmr
Posts: 882
Registered: ‎03-20-2013
My Device: Red LE Developer Z10

Re: Connect signal in QML from C++?

That's because the signal hasn't been exposed to QML (see my previous post). When you redeclare the signal in your QML you are registering another, different, and unrelated signal with the same name which the compiler happily uses instead. Change your Q_SIGNALS to "signals:" and your signal should be exposed properly to QML so you won't need your redundant QML signal just to make the compiler happy.


testinz wrote:
If I comment out this line from the QML file:

// signal cameraCaptureCompleted(string imageLink)

I get this error:

4e3ebe6_/native/assets//SelectPhoto.qml:12:5: Cannot assign to non-existent property "onCameraCaptureCompleted"
onCameraCaptureCompleted: {
^)





Developer of Built for BlackBerry certified multiFEED RSS/Atom feed reader and aggregator.
Please use plain text.
Developer
testinz
Posts: 180
Registered: ‎09-03-2012
My Device: Blackberry 10

Re: Connect signal in QML from C++?

I tried signals and it didn't work. Nothing show in the onCamera... function..

 

Did I approach this the wrong way? All I needed is for C++ to let me know the image url after successfully triggering Camera Invocation from QML,

Please use plain text.
Developer
greenmr
Posts: 882
Registered: ‎03-20-2013
My Device: Red LE Developer Z10

Re: Connect signal in QML from C++?

[ Edited ]

Ok, i went back and looked at your supplied code again and I see that you are trying to put your onCameraCaptureCompleted slot on the Page, which definitely will not work, since Page is not the class that has the cameraCaptureCompleted signal. I suggest one of these three approaches:

 

Page {
id: myPage attachedObjects: [ App { id: myApp onCameraCaptureCompleted: { // ---Do your stuff } } } }

 ... or if you prefer the handler to be on the page do it this way:

 

Page {
   id: myPage
   attachedObjects: [
      App {
         id: myApp
      }
   }
   function cameraCaptureCompletedHandler( imageLink ) {
      // ---Do your stuff
   }
   onCreationCompleted: {
      myApp.cameraCaptureCompleted.connect(myPage.cameraCaptureCompletedHandler);
   }
}

If you don't want to mess with connect() you could do the last example this way too:

 

Page {
   id: myPage
   attachedObjects: [
      App {
         id: myApp
         onCameraCaptureCompleted: {
            myPage.cameraCaptureCompletedHandler( imageLink );
         }
      }
   }
   function cameraCaptureCompletedHandler( imageLink ) {
      // ---Do your stuff
   }
}

Of course, all of these examples assume you have properly exposed the App class to QML with qmlRegisterType(). Since the signal you want to respond to is on the App class, you must either place your slot there, or connect the App::cameraCaptureCompleted signal to your slot explicitly.

 

Hope it is clearer to you now.

 



Developer of Built for BlackBerry certified multiFEED RSS/Atom feed reader and aggregator.
Please use plain text.
Developer
testinz
Posts: 180
Registered: ‎09-03-2012
My Device: Blackberry 10

Re: Connect signal in QML from C++?

[ Edited ]

Thanks a lot for your help and your patience!

 

I finally got it to work with your help.


I also believe that App has to be a custom control for it to work as well.

 

I think the following are essentials.

 

class QCameraInvoke : public bb::cascades::CustomControl {
    Q_OBJECT;
    Q_PROPERTY(NOTIFY cameraCaptureCompleted);


The signal and slots tutorial and sample project helped a lot also

Please use plain text.
Developer
greenmr
Posts: 882
Registered: ‎03-20-2013
My Device: Red LE Developer Z10

Re: Connect signal in QML from C++?

[ Edited ]

I'm glad you got things working, but from your last post it is clear you don't really understand how you got there, and in fact your solution is incorrect. :No:

 

There is no need for App to be derived from CustomControl (although it could be). The requirement for signals and slots to work is that it be inherited from QObject. Inheriting from CustomControl worked for you only because that class itself is a descendent of QObject. You only need to inherit from CustomControl if your class is a visual one... that is to say that it has a visual representation that will be displayed on a page like a control.

 

Also, your Q_PROPERTY statement is unnecessary, and in fact does nothing since you didn't actually declare a property with it. The NOTIFY clause is used to tell the Cascades property system that is should AUTOMATICALLY emit the signal you specify when the property you defined is changed. Since you didn't specify a property with Q_PROPERTY cameraCaptureCompleted will never be emitted. The NOTIFY clause is a convenience since you don't actually need to use it to make the property emit a signal... instead you can just put an emit statement in the property setter function explicitly.

 

You are correct that the Q_OBJECT macro must be used in your class declaration for the Cascades property system and signals/slots to work, but the rest of the things you suggested were necessary are actually not. Here is the skeleton of a simple class declaration that has the bare minimum needed to get signals working:

 

class App : public QObject {
	Q_OBJECT
	
	// ---Declare a property that emits using NOTIFY
	int _propertyOne;
	Q_PROPERTY( int propertyOne READ propertyOne WRITE setPropertyOne NOTIFY propertyOneChanged )
	
	// ---Declare a property that emits explicitly
	int _propertyTwo;
	Q_PROPERTY( int propertyTwo READ propertyTwo WRITE setPropertyTwo )
public: App(); // ---Getters and Setters int propertyOne() { return _propertyOne; } void setPropertyOne( int newVal ) { _propertyOne = newVal; } int propertyTwo() { return _propertyTwo; } void setPropertyTwo( int newVal ) { if ( newVal != _propertyTwo ) { _propertyTwo = newVal; // ---Not using NOTIFY so emit the changed signal explicitly emit propertyTwoChanged( newVal ); } } // ---Other public functions void someOtherFunction() { ... ... ... emit cameraCaptureCompleted( url ); } signals: void propertyOneChanged( int propertyOne ); void propertyTwoChanged( int propertyTwo ); void cameraCaptureCompleted( const QString& url ); }

The getters and setters are declared public so you can access the properties via property name in QML and by the get/set functions in C++. They are named according to Cascades convention for getters and setters. If you don't need to manipulate the properties from other C++ code you can declare them private instead.

 

The functions are all declared with inline bodies for clarity, but normally of course you would only declare the prototypes here in the header file and would put the bodies in the CPP file.

 

I know that since it is working for you now the tendency will be to leave your code as-is, but I suggest you correct it with a better understanding of signals and slots in C++ else you fill find maintenance later to be more confusing than it needs to be.


testinz wrote:

Thanks a lot for your help and your patience!

 

I finally got it to work with your help.


I also believe that App has to be a custom control for it to work as well.

 

I think the following are essentials.

 

class QCameraInvoke : public bb::cascades::CustomControl {
    Q_OBJECT;
    Q_PROPERTY(NOTIFY cameraCaptureCompleted);


The signal and slots tutorial and sample project helped a lot also




Developer of Built for BlackBerry certified multiFEED RSS/Atom feed reader and aggregator.
Please use plain text.