DesirePRG
DesirePRG

Reputation: 6388

how to access events of a specific QML control from c++

Is there a way of accessing signals(such as clicked()) of a QML control such as a button, from c++. Assume that I have the memory address of that specific control. I just want to simulate a click event from c++ code.

Upvotes: 1

Views: 2215

Answers (3)

mike510a
mike510a

Reputation: 2168

Easy. You just create a slot in a C++ object that has a QObject base to it, make sure its registered as a QML type, then you "instantiate" it in the QML document, and connect the desired signal to the C++ object through QML using connect() and handle the logic from the C++ side.

Example:

I have a Rectangle I want to get the onWidthChanged signal from and use it in my class ShapeTracker which tracks when shapes change or whatever

in main.cpp:

#include "shapetracker.h"

int main(int argc, char *argv[])
{

    QGuiApplication app(argc, argv);



  QQmlApplicationEngine engine;



   /*  this is where you register ShapeTracker as a QML type that can be 
        accessed through the QML engine even though its a C++ QObject */

    qmlRegisterType<ShapeTracker>("my_cpp_classes", 1, 0, "ShapeTracker");




  engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
   return app.exec();
 } 

then in

main.qml

import QtQuick 2.6



/* import the C++ class you registered into this QML document */

import my_cpp_classes 1.0


Window {
    visible: true
    Item {


        /*  set up a parent item that has a signal which is exposed to 
             the ShapeTracker object and the Rectangle you want to track */
        id:  myRootItem    
        signal  rectangleChanged(var newWidth)



        /* Heres our special Rectangle from QML that will send a signal when
           its width changes */
        Rectangle {
           id: specialRectangle
            width:  250


         /*  send the signal rectangleChanged(width) from our parent item 
             root item whenever width changes
            */
            onWidthChanged:  function() { rectangleChanged(width);  }

        }


        /*  Special Button that when clicked enlarges the Rectangle but 
             10px */
        Button {
             id: mySpecialButton
             onClicked:  {  click_handler(mouse); }

             function click_handler(mouse): {
                  if (specialRectangle.width < 500) 
                      specialRectangle.width += 10;
             }
        }



        /* Heres the actual ShapeTracker instance, which exists 
          as a  QObject inside of the QML context, but has its methods 
          which   are declared in C++, and exposed to the QML engine */
        ShapeTracker {
            id: myShapeTracker


            /* similar to a constructor, but more like a callback */
           Component.onCompleted: {


           /*  connect our signal from the parent root Item  called 
              "rectangleChanged"  to the ShapeTracker's  Slot  called 
             "myRectangleChangeHandler"      */
                   myRootItem.rectangleChanged.connect(myShapeTracker.myRectangleChangeHandler);


              /* connect send_mouse_click to the click_handler of 
                 the button */                        
               myShapeTracker.send_mouse_click.connect(mySpecialButton.click_handler)

                }

       }

    }
}

in shapetracker.h you simply add a new slot with the name myRectangleChangeHandler and it will receive that signal whenever it is send via QML to be processed via C++

class ShapeTracker : public QObject {
    Q_OBJECT
public:
    ShapeTracker(QObject *parent = 0 );

signal: 

    void send_mouse_click(QMouseEvent *event);


public slots:
    void myRectangleChangeHandler(QVariant newWidth) {
           /*  Perform a mouse click on our QML Object mySpecialButton 
               using QQuickItem::mousePressEvent  and sending it via 
               signal back to QML */


           QMouseEvent myEvent(QEvent::MouseButtonPress, QPointF(1,1), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);

          QMouseEvent* pressEvent = QQuickItem::mousePressEvent(&myEvent);

           emit send_mouse_click(pressEvent);


    }

};


In Summary,

you expose a C++ QObject to QML, then you use

  object.signal.connect(cppObject.desired_slot)  

To connect them -- all the extra stuff was for a functional example in case anyone needs it later

In reality, you don't even need this functionality because anything happening in an onClick event could just as easily be put into any other property such on

Rectangle {
       id: rect
       signal customClick(var var1)
       onCustomClick : {  console.log(var1);   } 
    }

   Item {
         rect.customClick(1);   
    }

Upvotes: 3

Richard
Richard

Reputation: 8920

The easy way would be to call all the receiving SLOTS manually. But that would be tedious and error prone.

You might try implementing a sub-class of QObject that has one slot onClicked() which emits the signal clicked() and use it as a shim between the button and elements controlled by the button. Connect the button clicked() to the new object onClicked() and then connect the new object to the original receivers. Then calling onClicked() would trigger the behavior.

This is a very simple example, and I haven't run it through the compiler.

ButtonShim.hpp

#include <QObject>
class ButtonShim : public QObject {
  Q_OBJECT

public:
  ButtonShim(QObject *parent = 0);
  virtual ~ButtonShim();

public slots:
  void onClicked();

signals:
  void clicked();
};

ButtonShim.cpp

#include "ButtonShim.hpp"

ButtonShim::ButtonShim(QObject *parent) : QObject(parent) {
}

ButtonShim::~ButtonShim() {
}

void ButtonShim::onClicked() {
  // All we do here is emit the clicked signal.
  emit clicked();
}

SomeFile.cpp

#include <bb/cascades/Button>
#include "ButtonShim.hpp"

...

ButtonShim * pButtonShim = new ButtonShim(pButton); // pButtonShim will live as long as pButton

bool c = connect(pButton, SIGNAL(clicked()), pButtonShim, SLOT(onClicked()));

c = connect(pButtonShim, SIGNAL(clicked()), pSomeObject, SLOT(onButtonClicked()));

...

// to simulate a click of pButton
pButtonShim->onClicked();

SomeFile.qml

// assuming ButtonShim has been exposed to QML from your application

...

attachedObjects: [
  ButtonShim {
    id: buttonShim
    onClicked: {
      clickedLabel.text = "I've been clicked";
    }
  }
]

...

Label {
  id: clickedLabel
  text: "I haven't been clicked"
}

Button {
  text: "Click Me"
  onClicked: {
    buttonShim.onClicked();
  }
}

Upvotes: 1

Kakadu
Kakadu

Reputation: 2839

I think that you can look at code for tests. There they get object from QML file loaded into engine.

If you have an QObject you can just call signal because, AFAIR

public signals:
  void clicked();

is expanded by moc into

public:
  void clicked();

Upvotes: 0

Related Questions