警告
本节包含从C++自动翻译到Python的代码片段,可能包含错误。
事件系统¶
Qt事件处理指南。
在Qt中,事件是从抽象类QEvent派生的对象,它们表示应用程序内部发生的事件或应用程序需要知道的外部活动的结果。任何QObject子类的实例都可以接收和处理事件,但它们尤其与部件相关。本文档描述了在典型应用程序中事件是如何传递和处理的。
事件如何传递¶
当事件发生时,Qt通过构造适当的QEvent子类的实例来创建一个事件对象,并通过调用其event()函数将其传递给QObject(或其子类之一)的特定实例。
此函数不处理事件本身;根据传递的事件类型,它会调用特定类型事件的事件处理程序,并根据事件是否被接受或忽略发送响应。
一些事件,例如 QMouseEvent 和 QKeyEvent,来自窗口系统;一些,例如 QTimerEvent,来自其他来源;一些来自应用程序本身。
事件类型¶
大多数事件类型都有特殊的类,特别是QResizeEvent、QPaintEvent、QMouseEvent、QKeyEvent和QCloseEvent。每个类都是QEvent的子类,并添加了特定于事件的函数。例如,QResizeEvent添加了size()和oldSize(),使小部件能够发现它们的尺寸是如何变化的。
一些类支持多种实际事件类型。QMouseEvent 支持鼠标按钮按下、双击、移动和其他相关操作。
每个事件都有一个关联的类型,定义在Type中,这可以作为运行时类型信息的便捷来源,用于快速确定给定事件对象是从哪个子类构造的。
由于程序需要以多种复杂的方式做出反应,Qt的事件传递机制非常灵活。notify()的文档简洁地讲述了整个故事;Qt Quarterly文章Another Look at Events则不那么简洁地重新阐述了它。在这里,我们将为95%的应用程序解释足够的内容。
事件处理程序¶
事件传递的正常方式是通过调用一个虚函数。例如,QPaintEvent 是通过调用 QWidget::paintEvent() 来传递的。这个虚函数负责适当地响应,通常是通过重新绘制小部件。如果你在虚函数的实现中没有执行所有必要的工作,你可能需要调用基类的实现。
例如,以下代码处理自定义复选框小部件上的左键鼠标点击,同时将所有其他按钮点击传递给基类 QCheckBox:
def mousePressEvent(self, event): if event.button() == Qt.LeftButton: # handle left mouse button here else: # pass on other buttons to base class QCheckBox.mousePressEvent(event)
如果你想替换基类的函数,你必须自己实现所有内容。然而,如果你只想扩展基类的功能,那么你实现你想要的内容,并调用基类以获得你不想处理的任何情况的默认行为。
偶尔,没有这样的事件特定函数,或者事件特定函数不够用。最常见的例子涉及Tab键的按下。通常,QWidget会拦截这些按键以移动键盘焦点,但一些小部件需要Tab键供自己使用。
这些对象可以重新实现event(),即通用事件处理程序,并且可以在常规处理之前或之后进行事件处理,或者完全替换该函数。一个非常不寻常的小部件,既解释Tab键又具有特定于应用程序的自定义事件,可能包含以下event()函数:
def event(self, QEvent event): if event.type() == QEvent.KeyPress: ke = QKeyEvent(event) if ke.key() == Qt.Key_Tab: # special tab handling here return True elif event.type() == MyCustomEventType: myEvent = MyCustomEvent(event) # custom event handling here return True return QWidget.event(event)
请注意,对于所有未处理的情况,仍然会调用QWidget::event(),并且返回值表示事件是否已被处理;true值会阻止事件被发送到其他对象。
事件过滤器¶
有时一个对象需要查看并可能拦截传递给另一个对象的事件。例如,对话框通常希望为某些小部件过滤按键;例如,修改回车键的处理。
installEventFilter() 函数通过设置一个事件过滤器来实现这一点,使得指定的过滤器对象在其eventFilter()函数中接收目标对象的事件。事件过滤器可以在目标对象处理事件之前处理这些事件,从而允许它根据需要检查和丢弃事件。可以使用removeEventFilter()函数移除现有的事件过滤器。
当过滤器对象的eventFilter()实现被调用时,它可以接受或拒绝事件,并允许或拒绝事件的进一步处理。如果所有事件过滤器都允许事件的进一步处理(通过每个返回false),事件将被发送到目标对象本身。如果其中一个过滤器停止处理(通过返回true),目标对象和任何后续的事件过滤器将完全看不到该事件。
def eventFilter(self, QObject object, QEvent event): if object == target and event.type() == QEvent.KeyPress: keyEvent = QKeyEvent(event) if keyEvent.key() == Qt.Key_Tab: # Special tab handling return True else: return False return False
上述代码展示了另一种拦截发送到特定目标小部件的Tab键按下事件的方法。在这种情况下,过滤器处理相关事件并返回true以阻止它们被进一步处理。所有其他事件都被忽略,过滤器返回false以允许它们通过安装在其上的任何其他事件过滤器发送到目标小部件。
也可以通过在整个应用程序的QApplication或QCoreApplication对象上安装事件过滤器来过滤所有事件。这种全局事件过滤器在特定对象的事件过滤器之前被调用。这非常强大,但它也会减慢整个应用程序中每个事件的事件传递速度;通常应使用讨论的其他技术来代替。
发送事件¶
许多应用程序希望创建并发送自己的事件。您可以通过构建合适的事件对象并使用sendEvent()和postEvent()发送它们,以与Qt自身的事件循环完全相同的方式发送事件。
sendEvent() 立即处理事件。当它返回时,事件过滤器和/或对象本身已经处理了事件。对于许多事件类,有一个名为 isAccepted() 的函数,它告诉你事件是否被最后一个调用的处理程序接受或拒绝。
postEvent() 将事件发布到队列中以供稍后分发。下次Qt的主事件循环运行时,它会分发所有发布的事件,并进行一些优化。例如,如果有多个调整大小事件,它们会被压缩成一个。同样适用于绘制事件:QWidget::update() 调用 postEvent(),通过避免多次重绘来消除闪烁并提高速度。
postEvent() 也在对象初始化期间使用,因为发布的事件通常会在对象初始化完成后很快被分发。在实现一个小部件时,重要的是要意识到事件可能会在其生命周期的早期被传递,因此在其构造函数中,请确保尽早初始化成员变量,以免在接收到事件之前有任何机会。
要创建自定义类型的事件,您需要定义一个事件编号,该编号必须大于User,并且您可能需要子类化QEvent以便传递有关自定义事件的特定信息。有关更多详细信息,请参阅QEvent文档。