The C++ API has QEvent along with multiple other classes derived from it (QMouseEvent, QGestureEvent etc.). QML on the other hand has events too. However I am struggling to find an elegant way of directly processing C++ events in QML.
Usually what I do is I create a custom QQuickWidget (or similar including QQmlEngine), override the QWidget::event(QEvent* event) and upon receiving a specific C++ event I propagate it through signals to QML slots with the QML code being loaded through that widget. This seems like a lot of work and I'm wondering if there is some sort of QML built-in event handling for events that come from C++ context.
In particular I'm interested in handling QGestureEvents in QML but I guess what works for this type of events should also work for any other type of event.
There is no direct support for event handling in QML, even keyboard and mouse are accessible through auxiliary objects.
QEvent itself is not a QObject derived, and as such, the same applies to all the derived events as well. That means no meta-information and no easy way to use from QML.
Since you are interested in a particular type of events, it would be easiest to create your own auxiliary object for those type of events, implement it in C++ and interface it to QML via signals you can attach handlers to.
If QGestureEvent can be copy-constructed you could simply create a Q_GADGET based adapter:
class QmlGestureEvent : public QGestureEvent
{
Q_GADGET
Q_PROPERTY(...) // for the things you want to access from QML
public:
QmlGestureEvent(const QGestureEvent &other) : QGestureEvent(other) {}
};
If it is not copy-constructable you'll have to add data members to the adapter and copy the values from the event.
Related
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:
De facto this is strange SIGNAL/SIGNAL connection.
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?
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++
...
#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.
So when I use a setText() on a QLabel for example, Qt automatically updates the view/gui for me and the new text is shown, but what happens behind the scenes? Is there an update function that gets called automatically when using functions like setText()?
Thanks!!
You should check the basic documentation in this link.
The internal system is a little bit more complex but in general, it follows the observer pattern. This mechanism allows the detection of a user action or changing state, and respond to this action.
Low-level interactions, like refreshing the screen are implemented via the Event System
In Qt, events are objects, derived from the abstract QEvent class, that represent things that have happened either within an application or as a result of outside activity that the application needs to know about. Events can be received and handled by any instance of a QObject subclass, but they are especially relevant to widgets. This document describes how events are delivered and handled in a typical application.
So, regarding the display process, there is a dedicated event. A QWidget object handles/subscribe to a PaintEvent, see QWidget::paintEvent.
This event handler can be reimplemented in a subclass to receive paint events passed in event. A paint event is a request to repaint all or part of a widget.
When you call, QLineEdit::setText(), the widget will be repainted the next time a display event is triggered, based in the OS configuration, refresh rate, etc.
For high-level interactions, Qt uses a similar pattern based in the signal/slot mechanism:
Observer pattern is used everywhere in GUI applications and often leads to some boilerplate code. Qt was created with the idea of removing this boilerplate code and providing a nice and clean syntax, and the signal and slots mechanism is the answer.
I have a UI written in QML. The UI contains a TextEdit nested somewhere deep in the tree. I want to connect the signal onTextChanged to my c++ logic in the background. How can I access the nested signal from c++?
Sounds like a design issue, you shouldn't really access QML from C++, it is best to keep the interaction one way - access the exposed C++ API from QML only.
In your case, instead of making the connection on the C++ side, you can simply install a handler for the signal in QML:
onTextChanged : cppLogic.callCPPfoo()
This is faster, easier, more flexible and can pass data even if the signal doesn't have data parameters.
I would like to propagate event from a QML signal handler to C++ procedure but don't know how to pass the "event object". Take a look at this situation and pay particular attention to SomeType.
First I create a custom QML item with a slot that can be called from QML:
class Tool : public QQuickItem
{
. . .
public slots:
virtual void onMousePositionChanged(SomeType *event);
}
Then, in QML, I create a MouseArea and an instance of my custom object to which I wanto to propagate events to.
MouseArea {
hoverEnabled: true
onPositionChanged: {
var event = . . .
tool.onMousePositionChanged(event);
}
}
Tool {
id: tool
}
And there is the problem. I don't know how to assemble an instance of SomeType to pass to the method. Actually I don't know what should I choose as SomeType. Ideally this would be QQuickMouseEvent so i could just call tool.onMousePositionChanged(mouse), but for some reason this type is not available for use in C++ code.
I've also considered QMouseEvent so I would just have to rewrite the properties, but this class in turn seems to be unavailable to QML code.
I'm new to Qt Quick so maybe I'm misusing it altogether. What I want to achieve is to have this base Tool class that has those virtual event handlers for mouse and keyboard and then create a collection of different tools that override them as necessary. They are to be displayed in some kind of toolbox, i.e. a grid with icons, from which the user can choose the current tool. The chosen tool is the one that receives the events. Any design suggestions are welcome.
Try QEvent, then use event->type() for filtering the event type.
I have a utility class in my Qt GUI application. However, in my convenience class I wanted to call a QMessageBox::critical(), warning(), etc. The class isn't a QWidget, and therefore I cannot pass this as the parent. My class is subclassed from QObject, however, so it can run things such as signals and slots. So to work around this--if it's possible to--should I maybe look at the property API instead of using the Static API?
Class declaration:
class NetworkManager : public QObject
And here's an example of a Static API call that fails:
QMessageBox::critical(this, tr("Network"), tr("Unable to connect to host.\n"),
QMessageBox::Ok | QMessageBox::Discard);
So, if I were to build a Property based API message box, would it be possible to call it in a QObject somehow? I haven't really used the Property Based API, but I understand from the documentation that it seems to use an event loop (i.e. exec()).
Just pass NULL for the first parameter:
QMessageBox::critical(NULL, QObject::tr("Error"), QObject::tr("..."));
A better way than passing nullptr is to use the qobject tree you are already using (assuming that the parent of the NetworkManager instance is a QWidget; adjust the number of parents according to whatever your qobject tree looks like)
QMessageBox::critical(qobject_cast<QWidget *> (parent()), "Title", "Message");
We use a qobject_cast<> instead of a C or C++ style cast is because it adds a little protection and will return 0 if it can't cast upward to the QWidget *.
If you use nullptr the QMessageBox will appear as centered over the topmost window (QWidget) rather than the window that actually appeared higher up in the qobject tree of your NetworkManager class. This really annoys people who have multiple monitors, lots of windows open, multiple windows from a single application spanning multiple monitors, etc.