0

I got a QML object in the QObject shape. With ->setProperty(..., ...) I can change properties, but how can I execute slots without signals?

Currently I declare a signal in my C++ class:

signals:
  void testSignal();

and signal/slot in QML object:

signal executeTestFunc(); onExecuteTestFunc: {
  testAnimation.start();
}

Then I connect these two:

QObject::connect(this, SIGNAL(testSignal()),
                 QmlObject, SIGNAL(executeTestFunc()));

But this is not clean, as I can see:

  1. De facto this is strange SIGNAL/SIGNAL connection.
  2. I do not want to use SIGNAL/SLOT mechanism, unless I have to, due to performance and long code.

Is there a way to execute QML onExecuteTestFunc(); from QObject directly?

  • 1
    slot is a regular function, you can execute it as all other functions. btw, `QObject::connect` should connect slot to signal, not signal to signal, probably you have a typo here. – folibis Jul 13 '20 at 10:44
  • @folibis, I know that signal should be connected to slot, but I found this is the only way to do that :-(. Can you tell how I can execute QML slot from C++ code other then signal/slot mechanism? – Alexander Zolkin Jul 13 '20 at 11:04
  • considering that QML slot is a regular function you can just call it from C++ using [QMetaObject::invokeMethod](https://doc.qt.io/qt-5/qtqml-cppintegration-interactqmlfromcpp.html#invoking-qml-methods) – folibis Jul 13 '20 at 11:15
  • @folibis signals _can_ actually be connected to other signals, and there are good use cases for that. Do *not* use things like QMetaObject::invokeMethod to call from C++ into QML. It works, but it yields unflexible and unmaintainable code. Your C++ should not be aware of the structure of your QML. – André Jul 14 '20 at 06:20
  • the best answer: https://stackoverflow.com/a/71060709/8873508 – p-a-o-l-o Jun 23 '22 at 10:06

2 Answers2

3

You can create a C++ class that will emit a signal. That signal will be caught in QML. No explicit connection with SIGNAL/SLOT is needed. Example:

C++:

class Presenter : public QObject
{
    Q_OBJECT
public:
    explicit Presenter()
    {
        QTimer *timer = new QTimer();
        timer->setInterval(500);
        connect(timer, &QTimer::timeout,
                this, &Presenter::timeout);
        timer->start();
    }

    Q_SIGNAL void timeout();
};

QML:

Window {
    ...
    Presenter {
        onTimeout: {
            console.log("called from c++")
        }
    }
}

Result:

qml: called from c++
qml: called from c++
qml: called from c++
qml: called from c++
...
pklimczu
  • 626
  • 1
  • 6
  • 15
2

@Thomenson has already given a good answer on how you can connect to a signal from C++ and act on it in QML. His solution works if you can create the C++ from QML, which may not always be the case.

Connections element

There are two other options you might consider. First, if you have an object that was not created from QML but was put into it in some other way (QML context, static object, returned from an invokable...) you may use the Connections element:

Connections {
    target: theObjectWithTheSignal
    onSignalName: {doSomething();}
}

This is a flexible way to react to signals from object that you did not create in QML, or even objects that were created elsewhere in QML.

Callback using JSValue

If you really insist on avoiding signal/slot (though I don't understand why; Qt and QML are build around it and trying to avoid it is fighting against the framework instead of using its strenghts), there is another way. You can, on the C++ object that is exposed to QML, create a property of type QJSValue. Then, in C++ on setting, check that whatever was set is callable (using QJSValue::isCallable()). Then as the point you wish to trigger whatever you want to execute in your QML, call it using QJSValue::call.

On the QML side, you can simply assign or bind something callable, just like you'd do for a signal handler.

Anti pattern: QMetaObject::invokeMethod

There is another way, which I will only include as a warning against an anti-pattern. You can call into the QML from C++ using Qt's introspection mechanism. You can find an object by it's set objectName and call any (invokable) method on it using QMetaObject::invokeMethod, read and write any property and listen to all signals. That includes calling methods you defined in QML. Using this, you can manipulate your QML from your C++. Don't do this. It leads to inflexible and unmaintainable code.

André
  • 570
  • 3
  • 7
  • As far as I know, you should not use signals with high frequencies or transfer large amount of data through it. Isn't right? – Alexander Zolkin Jul 14 '20 at 13:25
  • Signal/slot can choke up with high frequencies and/or high quantities of data if you use them to communicate across threads. In that case, the call and the arguments are marshaled into an event and the data wrapped in QVariants before being unpacked again at the receiving objects thread. However, in this case the connection would be direct, which results in a simple method call. However, you probably don't want too high-frequency and high data volume calls into QML at all. That's another story though. Note QJSValue also requires you to wrap your arguments into a list of QJSValues. – André Jul 14 '20 at 15:18