Fist, apologies for the length of the question.
I am trying to propagate custom Qt event from child widgets to a top parent widget in order to trigger some action based on event type instead of linking signals.
Qt docs suggests that every event posted with postEvent()
that have accept()
and ignore()
methods can be propagated (meaning each QEvent
subclass).
I have tried to override customEvents
method instead of events
but to no avail.
I've tried this in Python using PyQt4 (Qt version is 4.6).
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Foo(QWidget):
def doEvent(self):
QApplication.postEvent(self, QEvent(12345))
def event(self, event):
event.ignore()
return False
class Bar(QWidget):
def __init__(self, *args, **kwargs):
super(Bar, self).__init__(*args, **kwargs)
self.foo = Foo(self)
layout = QHBoxLayout()
layout.addWidget(self.foo)
self.setLayout(layout)
def event(self, event):
if event.type() == 12345:
self.someEventHandler()
return True
def someEventHandler(self):
print 'Handler in {0}'.format(self.__class__.__name__)
if __name__=='__main__':
app = QApplication([''])
bar = Bar()
bar.show()
bar.foo.doEvent()
app.exec_()
In this example Bar.someEventHandler()
would only trigger if event was posted with self.parent()
as the first argument like so:
def doEvent(self):
QApplication.postEvent(self.parent(), QEvent(12345))
Which is understandable since the event is passed directly to receiving object.
Similar example in C++:
foobar.h
#ifndef FOOBAR_H
#define FOOBAR_H
#include <QtGui>
class Foo : public QWidget
{
Q_OBJECT
public:
Foo(QWidget *parent = 0);
void doEvent();
bool event(QEvent *);
};
class Bar : public QWidget
{
Q_OBJECT
public:
Bar(QWidget *parent = 0);
Foo *foo;
bool event(QEvent *);
};
#endif // FOOBAR_H
foobar.cpp
#include "foobar.h"
Foo::Foo(QWidget *parent)
: QWidget(parent) {}
void Foo::doEvent() {
QEvent *event = new QEvent(QEvent::User);
QApplication::postEvent(this, event);
}
bool Foo::event(QEvent *event)
{
event->ignore();
return QWidget::event(event);
}
Bar::Bar(QWidget *parent)
: QWidget(parent)
{
foo = new Foo(this);
}
bool Bar::event(QEvent *event)
{
if (event->type() == QEvent::User) {
qDebug() << "Handler triggered";
return true;
}
return QWidget::event(event);
}
main.cpp
#include <QtGui>
#include "foobar.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Bar bar(0);
bar.show();
bar.foo->doEvent();
return app.exec();
}
Same as in python this only works if event is passed directly to an object.
void Foo::doEvent() {
QEvent *event = new QEvent(QEvent::User);
QApplication::postEvent(this->parentWidget(), event);
}
Perhaps I missed the point, is it possible that only Key and Mouse events are propagated upwards?
I've spent some time browsing the Qt source in order to answer your question, and what I finally came up with is this: propagation of events to parent widgets is performed by QApplication::notify
, basically a long switch
statement with all sorts of events. For example, this is how it's done for QEvent::WhatsThisClicked
:
...
case QEvent::WhatsThisClicked:
{
QWidget *w = static_cast<QWidget *>(receiver);
while (w) {
res = d->notify_helper(w, e);
if ((res && e->isAccepted()) || w->isWindow())
break;
w = w->parentWidget();
}
}
...
Now, the salient point: this is not performed for user defined events (and many other explicitly handled standard Qt events, as well), since the default
clause is:
default:
res = d->notify_helper(receiver, e);
break;
And notify_helper
doesn't propagate events. So, my answer is: apparently, user defined events are not propagated to parent widgets, you will have to do it yourself (or better: override QApplication::notify
(it's a virtual public member) and add event propagation for your event(s)).
I hope that helps.
Re-implementation of QApplication.notify
in Python which would propagate custom events.
In Qt notfy_helper
makes sure the event filters are called (both QApplications and receivers) but I skipped that as I don't need them and notfy_helper
is private member.
from PyQt4.QtCore import QEvent
from PyQt4.QtGui import QApplication
class MyApp(QApplication):
def notify(self, receiver, event):
if event.type() > QEvent.User:
w = receiver
while(w):
# Note that this calls `event` method directly thus bypassing
# calling qApplications and receivers event filters
res = w.event(event);
if res and event.isAccepted():
return res
w = w.parent()
return super(MyApp, self).notify(receiver, event)
And instead of using instance of QApplication we use instance of our subclass.
import sys
if __name__=='__main__':
app = MyApp(sys.argv)
As an alternative to rebus's answer, this snippet implements manual propagation of an event:
def _manual_propagate(target, evt):
app = QtGui.QApplication.instance()
while target:
app.sendEvent(target, evt)
if not evt.isAccepted():
if hasattr(target, 'parent'):
target = target.parent()
else:
target = None
return evt.isAccepted()
Note that this uses sendEvent, and thus must be called on the thread that the target object lives on. That limitation can be worked around with more indirection.
Note that you'll need to call _manual_propagate /instead of/ sendEvent yourself. This is a "less automatic" version of rebus's technique.
In addition, because this version uses sendEvent, event filters on objects are properly called.
The relevant reason custom events are not propagated, at least in Qt 4.8, is this:
//qobject.cpp
bool QObject::event(QEvent *e)
{
switch (e->type()) {
//several cases skipped
default:
if (e->type() >= QEvent::User) {
customEvent(e);
break;
}
return false;
}
return true;
}
//more skips
/*!
This event handler can be reimplemented in a subclass to receive
custom events. Custom events are user-defined events with a type
value at least as large as the QEvent::User item of the
QEvent::Type enum, and is typically a QEvent subclass. The event
is passed in the \a event parameter.
\sa event(), QEvent
*/
void QObject::customEvent(QEvent * /* event */)
{
}
That is, QObject::event
calls another method when a custom event is received, and then break
s out of it's switch
statement and executes return true;'
. This return true
signals the caller (generally QCoreApplication::notify
that the event was handled.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With