首页 文章

在Qt / PyQt中将自定义QEvent传播到父窗口小部件

提问于
浏览
9

拳头,对问题的长度道歉 .

我试图将自定义Qt事件从子窗口小部件传播到顶部父窗口小部件,以便根据事件类型而不是链接信号触发某些操作 .

Qt docs建议用 postEvent() 发布的每个具有 accept()ignore() 方法的事件都可以传播(意味着每个 QEvent 子类) .

我试图覆盖 customEvents 方法而不是 events 但无济于事 .

Python

我在Python中使用PyQt4尝试了这个(Qt版本是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_()

在这个示例中, Bar.someEventHandler() 只会在事件以 self.parent() 作为第一个参数发布时触发,如下所示:

def doEvent(self):
    QApplication.postEvent(self.parent(), QEvent(12345))

这是可以理解的,因为事件直接传递给接收对象 .

C.

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();
}

与python中的相同,只有在事件直接传递给对象时才有效 .

void Foo::doEvent() {
    QEvent *event = new QEvent(QEvent::User);
    QApplication::postEvent(this->parentWidget(), event);
}

也许我错过了这一点,是否有可能只向上传播Key和Mouse事件?

3 回答

  • 5

    我花了一些时间浏览Qt源代码来回答你的问题,我最终提出的是:事件传播到父窗口小部件是由 QApplication::notify 执行的,基本上是一个带有各种事件的长 switch 语句 . 例如,这是 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();
                }
            }
    ...
    

    现在,重点是:这是为用户定义的事件(以及许多其他显式处理的标准Qt事件)执行的 not ,因为 default 子句是:

    default:
            res = d->notify_helper(receiver, e);
            break;
    

    并且 notify_helper 不会传播事件 . 所以,我的答案是:显然,用户定义的事件不会传播到父窗口小部件,你必须自己做(或更好:覆盖 QApplication::notify (它是一个虚拟公共成员)并为你的事件添加事件传播) .

    我希望有所帮助 .

  • 18

    在Python中重新实现 QApplication.notify ,它将传播自定义事件 .

    在Qt notfy_helper 确保调用事件过滤器(QApplications和接收器),但我跳过它,因为我不需要它们, notfy_helper 是私有成员 .

    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)
    

    而不是使用QApplication的实例,我们使用我们的子类的实例 .

    import sys
    if __name__=='__main__':
        app = MyApp(sys.argv)
    
  • 1

    作为rebus答案的替代方案,此代码段实现了事件的手动传播:

    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()
    

    请注意,这使用sendEvent,因此必须在目标对象所在的线程上调用 . 这种限制可以通过更多间接来解决 .

    请注意,您需要自己调用_manual_propagate /而不是/ sendEvent . 这是rebus技术的“自动化程度较低”版本 .

    此外,由于此版本使用sendEvent,因此可以正确调用对象上的事件过滤器 .


    自定义事件不会传播的相关原因,至少在Qt 4.8中,是这样的:

    //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 */)
    {
    }
    

    也就是说, QObject::event 在收到自定义事件时调用另一个方法,然后 break 从它的 switch 语句中调用并执行 return true;' . 这个 return true 向呼叫者发出信号(通常 QCoreApplication::notify 表示该事件已被处理 .

相关问题