Propagate QML events to C++ - c++

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.

Related

How to execute QML slot from C++ without a signal?

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.

Changing the behavior of a widget type in another class

I have a MainWindow class in Qt, in which several checkable QGroupBox widgets are placed.
What I want to do is, to trigger a general onClick(bool checked) slot whenever one (any) of the QGroupBox objects are clicked, identify the sender and trigger some code.
I need to capture the objects' "clicked" signal in order to prevent a disabling action it performs on its children when the control is clicked.
This is the signal I'm trying to handle:
class Q_WIDGETS_EXPORT QGroupBox : public QWidget
{
...
Q_SIGNALS:
void clicked(bool checked = false);
...
};
I tried adding a custom slot like this and tried connecting it with the signal above but since QGroupBox at its own is not an object or pointer, the operation fails.
void MainWindow::onClick(bool clicked)
{
qDebug()<<"Custom slot triggered";
}
Long story short, I need to handle a control type's default behavior within my MainWindow class.
Thanks for any ideas in advance.
I need to capture the objects' "clicked" signal in order to prevent a disabling action it performs on its children when the control is clicked.
Perhaps, but I'm smelling an XY Problem here. You can certainly prevent emission of signals by invoking blockSignals() on the widget. But that's a hack - it will also prevent Qt's internals from acting on the object's destroyed() signal, and you might subtly break other users of the object's signals.
Instead, one could make the UI stateful and have the controller implement the stateful aspect of the button's interaction with the rest of the application. Currently, the button's clicked() signal is connected directly to other users. Instead, connect the button to the controller, and have the controller disable the children only when it's appropriate to do so. One could use the QStateMachine to make this stateful behavior explicit in terms of states.
#Kuba Ober, maybe you're right, I could not state my problem and the facts around it seperately and clearly. Thank you for pointing that out.
After checking out the QGroupBox class' source, I discovered that a "toggle" signal is emitted after every other operation in a "clicked" event.
http://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/widgets/qgroupbox.cpp
void QGroupBox::setChecked(bool b)
{
Q_D(QGroupBox);
if (d->checkable && b != d->checked) {
update();
d->checked = b;
d->_q_setChildrenEnabled(b);
#ifndef QT_NO_ACCESSIBILITY
QAccessible::State st;
st.checked = true;
QAccessibleStateChangeEvent e(this, st);
QAccessible::updateAccessibility(&e);
#endif
emit toggled(b);
}
}
By combining this information with that of #rbaleksandar 's suggestion, I managed to obtain a solution.
I iterated through the controls of my MainWindow class and connected every QGroupBox's "toggle" signal with a common "MainWindow::onToggle" slot in which I could handle the post-event operations I desire.
As for my previous approach, I could've created a class which inherits QGroupBox and override any methods I wish to. But that would be overkill compared to my current solution. The fix I've came up with did the trick perfectly.
Thanks everyone for their insight and help.

Handling Qt C++ events in QML context

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.

Qt Signal Slot Architecture Unwanted Infinite Loop

I've problem with qt signal-slot system.
First I've created a class which is called System in Singleton pattern, so I can access it's instance where I want. System has a signal SelectionChanged.
I've a list widget and I am connecting it's itemSelectionChanged signal to my custom slot which is called onSelectionChanged. In onSelectionChanged slot, I am emitting System's SelectionChanged signal. There is no problem yet.
In my software design, a selection of object(s) can be used by many GUI widgets or custom classes and System's SelectionChanged signal can be emited by widgets other then the list widget.
So I am creating a slot called OnSystemSelectionChanged in the list widget then connect it to the System's SelectionChanged signal. The OnSystemSelectionChangedSlot is like this.
void MyListWidget::OnSystemSelectionChanged(QObject *sender)
{
if (sender == this) return;
// Then I want to get a list of selected objects and set them as selection of this widget like this:
this->SetSelection(System::Instance()->GetSelectedObjects());
}
But the problem is when I start to set the list widget's selected items, it is going to emit itemSelectionChanged signal and my onSelectionChanged slot will be called. Then the slot will emit System's SelectionChanged signal and then OnSystemSelectionChanged will be called too. It will stop through sender parameter but there is no method for setting list widget's selected items at once.
How can I figure this problem out.
I hope I did explain my problem well. Thanks in advance.
Edit: Spelling and grammer errors are corrected.
There are a few ways of dealing with this in Qt.
Idioms
Use multiple views with one underlying model. This handles propagation of changes to multiple view controls automatically and you don't need to do anything extra. You can use QDataWidgetMapper to link "plain old" widgets to the data elements in a model. I'd say that this should be the preferred way of doing things. Having an underlying model for all of your UI is a step in the direction of good software design anyway.
When propagating changes between data models, implement both a DisplayRole and an EditRole. The views will nominally modify the models using one of the roles (say, the EditRole), while you can, programmatically, modify the models using the other role (say, the DisplayRole). You handle the dataChanged signals from the model in your own slot, properly dealing with the roles, and call setData on the other models with the other role. This prevents the loops.
For controls that are not QAbstractItemViews, implement two signals: one emitted on any change, another one emitted only on changes based on keyboard/mouse input. This is the interface exposed by QAbstractButton, for example: the toggled(bool) signal is the former, the clicked() is the latter. You then only connect to the input-based signals.
Your own code must propagate programmatic changes to all the interlinked controls, since changing one control from your code won't modify the others. This should not be a problem, since well designed code should encapsulate the implementation details of UI controls from rest of the code. Your dialog/window class will thus expose its properties in a way that's not coupled to the number of controls showing a particular property.
Hackish Let's-Hope-They-Won't-Become Idioms
Use a flag inhibiting signal emission (Bartosz's answer).
Break the signal/slot connections for the duration of the change (Bartosz's answer).
Use QObject::blockSignals().
There are two possible solutions I can think of:
add a flag which makes possible to ignore particular signals:
void MyListWidget::OnSystemSelectionChanged(QObject *sender)
{
if (sender == this || inhibitSelectionChanged)
return;
this->inhibitSelectionChanged = true;
this->SetSelection(System::Instance()->GetSelectedObjects());
this->inhibitSelectionChanged = false;
}
disconnect the slot from the signal, and reconnect it after changing the selection:
void MyListWidget::OnSystemSelectionChanged(QObject *sender)
{
if (sender == this)
return;
this->disconnect(SIGNAL(SelectionChanged()));
this->SetSelection(System::Instance()->GetSelectedObjects());
this->connect(
this, SIGNAL(SelectionChanged()),
this, SLOT(OnSystemSelectionChanged(QObject*)));
}
I found my solution in QObject::blockSignals() method. It will prevent emitting signals from the list widget while I am setting selected items.
Thanks for all the answers and solutions especialy for BartoszKP's. This solution is looks like the official way of his first solution.
The problem: you've tried to cut corners and created a singleton. Not a classic case for singleton.
Signals and slots are used for notifications, each object notifies interested objects about what it did or to reflect its new state.
I'm suggesting changing the design as follows:
No singleton signal.
Each Object has its own signal and slot for a relevant event (e.g. selection change).
The application or a higher level object (that created the widgets/objects) performs the signal to slot connection. If those widgets are placed in a list, this is very simple.

How to call QMessageBox Static API outside of a QWidget Sub-Class

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.