警告
本节包含自动从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 季刊》文章 重新审视事件 则更详细地描述了。在这里,我们将解释足够95%的应用程序需要的内容。
事件处理程序#
事件通常是通过调用虚拟函数来传递的。例如,通过调用 QWidget::paintEvent() 来传递 QPaintEvent。这个虚拟函数负责适当地做出反应,通常是通过重绘小部件。如果你没有在虚拟函数的实现中完成所有必要的操作,你可能需要调用基类的实现。
例如,以下代码处理了自定义复选框小部件上的鼠标左键单击,并将所有其他按钮的单击传递给基类的 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
值会阻止事件发送到其他对象。
事件过滤器#
有时一个对象需要查看,也许还需要拦截传递给其他对象的某些事件。例如,对话框通常需要过滤某些小部件的按键,例如,修改 Return 键的处理。
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
上述代码展示了另一种拦截发送到特定目标小部件的表单键按下事件的另一种方式。在这种情况下,过滤器处理相关事件,并返回 true
以阻止其进一步处理。所有其他事件都被忽略,过滤器返回 false
以允许它们通过安装在其上的任何其他事件过滤器发送到目标小部件。
也可以通过在 QApplication 或 QCoreApplication
对象上安装事件过滤器来过滤整个应用程序的 所有 事件。此类全局事件过滤器在对象特定过滤器之前调用。这非常强大,但它也会减慢整个应用程序中每个单个事件的传递;一般应使用所讨论的其他技术。
发送事件
许多应用程序都希望创建并发送它们自己的事件。您可以通过构造适当的事件对象并使用 sendEvent()
和 postEvent()
将它们发送,以与 Qt 自己的事件循环完全一样的方式发送事件。
sendEvent()
立即处理事件。当它返回时,事件过滤器或对象本身已经处理了事件。对于许多事件类,有一个名为 isAccepted()
的函数,它会告知您事件是否被最后调用的处理程序接收或拒绝。
postEvent()
将事件发布到队列中以便稍后分发。下次 Qt 的主事件循环运行时,它会以一些优化为所有已发布的事件分发。例如,如果有几个调整大小的事件,它们会被压缩成一个。这同样适用于绘图事件:QWidget::update() 调用 postEvent()
,消除了闪烁并提高了速度,因为它避免了多次重新绘制。
postEvent()
在对象初始化期间也使用,因为事件通常会在对象初始化完成后不久分发。实现小部件时,重要的是要认识到事件可以在其生命周期非常早期就会被传递,因此在其构造函数中,请确保成员变量在有可能接收事件之前尽早初始化。
要创建自定义类型的事件,您需要定义一个事件编号,该编号必须大于 User
,并且可能需要继承 QEvent
以传递关于您的自定义事件的特定信息。有关详细信息,请参阅 QEvent
文档。