将自定义 QEvent 传播到 Qt/PyQt 中的父小部件
Posted
技术标签:
【中文标题】将自定义 QEvent 传播到 Qt/PyQt 中的父小部件【英文标题】:Propagate custom QEvent to parent widget in Qt/PyQt 【发布时间】:2010-07-05 15:03:33 【问题描述】:拳头,为问题的长度道歉。
我正在尝试将自定义 Qt 事件从子小部件传播到***父小部件,以便根据事件类型而不是链接信号触发一些操作。
Qt 文档建议使用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 事件?
【问题讨论】:
【参考方案1】:为了回答你的问题,我花了一些时间浏览 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 事件)不执行,因为default
子句是:
default:
res = d->notify_helper(receiver, e);
break;
notify_helper
不会传播事件。所以,我的回答是:显然,用户定义的事件不会传播到父小部件,你必须自己做(或者更好:覆盖QApplication::notify
(它是一个虚拟公共成员)并为你的事件添加事件传播)。
希望对你有帮助。
【讨论】:
相关来源here 问题是QObject::event是protected的,notify_helper是private的,那怎么可能加上事件传播呢? 关于我之前的评论,调用基类是解决方案 QApplication::notify(widget,event)【参考方案2】:在 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)
【讨论】:
【参考方案3】:作为 rebus 答案的替代方案,这个 sn-p 实现了事件的手动传播:
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
s退出它的switch
语句并执行return true;'
。这个return true
向调用者发出信号(通常是QCoreApplication::notify
,表明事件已处理。
【讨论】:
以上是关于将自定义 QEvent 传播到 Qt/PyQt 中的父小部件的主要内容,如果未能解决你的问题,请参考以下文章
Qt5 / PyQt5 : 带有 QML 前端和 Python 后端的自定义 QML 组件