Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Propagate custom QEvent to parent widget in Qt/PyQt

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.

Python

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.

C++

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?

like image 215
Davor Lucic Avatar asked Jul 05 '10 15:07

Davor Lucic


3 Answers

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.

like image 74
Greg S Avatar answered Oct 17 '22 11:10

Greg S


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)
like image 5
Davor Lucic Avatar answered Oct 17 '22 12:10

Davor Lucic


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 breaks out of it's switch statement and executes return true;'. This return true signals the caller (generally QCoreApplication::notify that the event was handled.

like image 1
EB. Avatar answered Oct 17 '22 11:10

EB.