57a54856ad2eb7c0b9a447868c62561b.png

Qt中的事件(event)

事件(event)由窗口系统或Qt自身产生,用以响应所发生的各类事情。例如:当用户按下键盘或者点击鼠标上的按键时,就是产生一个键盘或者鼠标事件;当某个窗口第一次显示的时候,就会产生一个绘制事件,告知窗口绘制它本身,使窗口可见。事件主要分两种情况:

  • 与用户交互时发生,比如按下鼠标(mousePressEvent),敲击键盘(keyPressEvent)等。
  • 系统内部自身产生,比如计时器事件(timerEvent)等。

QEvent类是所有事件类的基类,事件对象里包含事件参数。在Qt中,事件作为一个对象,继承自QEvent。

任何从QObject类派生的类的对象都可以通过QObject.event()函数来接收事件;事件产生时,Qt会创建一个合适的QEvent对象或其派生类对象,然后通过调用QObject类的event()函数将这个事件对象传递给特定的QObject对象或其派生类对象。

Qt 的主事件循环(QCoreApplication::exec())从事件队列中获取本地窗口系统事件,将它们转化为 QEvents,然后将转换后的事件发送给 QObjects。QObjects 通过调用它们的 QObjec.event() 函数接收事件。该函数可以在子类中重新实现,来处理自定义的事件以及添加额外的事件类型,QWidget.event() 就是一个很好的例子。

c80355fe34010e6be1f4dfa839d6c21d.png

Qt中的事件类

Qt中的常见事件类型:

  • 键盘事件:处理键盘相关事件,比如按键等;
  • 鼠标事件:处理鼠标相关事件,比如鼠标按下,释放,双击等事件;
  • 拖放事件:拖放事件处理;
  • 定时器事件:处理定时器事件;
  • 窗口相关事件:处理窗口绘制,移动,改变尺寸,关闭,右键菜单等事件。

事件分发event()函数

事件的分发函数称为事件处理器(event handler)。event()函数就是用来处理事件的分发。如果想在事件的分发之前进行一些操作,比如监听某个按键的按下,就可以在event()函数里处理:

class MyWidget(QWidget):         ......         def event(e):                  if e.key() == Qt.Key_Tab:                            print('按下了Tab键')                            return True                  #按原来的流程来处理事件的分发                  return QWidget.event(self, e)

在上面的代码中,MyWidget是QWidget的子类,它重新实现了event()函数,该函数带有一个QEvent类型的参数。当系统产生QEvent对象时,就会传入这个函数并调用。如果传入的事件被识别并处理,则返回True,表示这个事件已经处理完毕,Qt不会将这个事件再分发出去。否则,就继续将该事件分发出去。

Qt系统处理事件时,使用了一种机制,叫做事件传播机制。意思是在子类(比如说一个按钮QPushButton)中发生的事件,调用了子组件的event()函数之后,还会调用父组件(比如说QAbstractButton)的event()函数。event函数的返回值就用于控制这一传播过程。

f96f25f4b3df243c219a1a88045cd652.png

Qt中的事件传递过程

事件过滤器

在一些应用场景中,需要拦截某个组件发生的事件,让这个事件不再向其他组件传播,这是可以为这个组件或其父组件安装一个事件过滤器(eventFilter)来实现,在实际使用中。需要调用函数installEventFilter()为组件安装过滤器,才能使用事件过滤器这个机制。安装好事件过滤器之后,该组件和其子组件的事件就会被监听。然后重写其eventFilter函数,实现事件过滤。

QEvent中的成员函数

  • ignore(): 接收者忽略当前接收到的事件,但事件可能会传递给接收者的父组件;
  • accept(): 接收者期望处理当然事件;
  • isAccept(): 判断当前事件是否被处理;
  • registerEventType(): 注册并返回一个自定义事件类型;
  • spontaneous(): 如果事件由应用程序之外产生,比如一个系统事件,返回True,否则返回False;
  • type(): 返回事件的类型。QEvent.Type定义了Qt中有效的事件类型。

处理Qt事件的思路

在处理Qt的事件时,一般按照以下思路进行:

  • 重写paintEventmousePressEvent等事件处理函数。这是最常见、最简单的方式;
  • 重写event函数。 event函数是所有对象的事件入口,在QObject和QWidget中的实现中,默认是把事件传递给特定的事件处理函数;
  • 在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件;
  • QCoreApplication::instance()上面安装事件过滤器。该过滤器将过滤所有对象的所有事件;
  • 重写QCoreApplication::notify()函数。这是最强大的,和全局事件过滤器一样提供完全控制,并且不受线程的限制。

简单示例代码

示例代码演示了对一些基本事件的处理,完整代码如下:

import sysfrom PyQt5 import QtCore, QtGui, QtWidgetsfrom PyQt5.QtCore import Qt, QEvent, QTimerfrom PyQt5.QtGui import QPainterfrom PyQt5.QtWidgets import (QApplication, QWidget, QMenu, QMessageBox) class DemoEvent(QWidget):    def __init__(self, parent=None):        super(DemoEvent, self).__init__(parent)         # 设置窗口标题        self.setWindowTitle('实战PyQt5: QEvent事件演示')        # 设置窗口大小        self.resize(400, 320)         #初始化数据        self.doubleClicked = False   #鼠标双击        self.key=''                  #按键值        self.text=''                 #文本信息        self.message=''              #提示消息                #定时器,500毫秒后执行        QTimer.singleShot(500, self.onShowHelpInfo)            def onShowHelpInfo(self):        self.text = '点击这里触发追踪鼠标位置'        #触发重绘事件,即触发paintEvent函数        self.update()            #关闭应用时,会触发closeEvent    def closeEvent(self, event):        if QMessageBox.information(self, '关闭应用',  '点击按钮关闭应用'):            return True        #右键菜单事件    def contextMenuEvent(self, event):        #实例化一个菜单        menu = QMenu(self)                actionTest1 = menu.addAction('测试1')        actionTest1.triggered.connect(self.onMenuTest1)        actionTest2 = menu.addAction('测试2')        actionTest2.triggered.connect(self.onMenuTest2)                if not self.message:            menu.addSeparator()            actionTest3 = menu.addAction('测试3')            actionTest3.triggered.connect(self.onMenuTest3)                #在鼠标出现的位置显示菜单栏        menu.exec(event.globalPos())            def onMenuTest1(self):        self.message = '菜单选项1'        self.update()             def onMenuTest2(self):        self.message = '菜单选项2'        self.update()         def onMenuTest3(self):        self.message = '菜单选项3'        self.update()              #重绘窗口事件    def paintEvent(self, event):        text = self.text        pos = text.find('')        if pos >= 0:            text = text[0:pos]                #如果触发了键盘按键,则在文本信息中记录相应的按键信息        if self.key:            text += '按下了: {0}'.format(self.key)                    painter = QPainter(self)        painter.setRenderHint(QPainter.TextAntialiasing)                #居中绘制文本信息        painter.drawText(self.rect(), Qt.AlignCenter, text)                 #如果有消息文本,则在底部居中绘制消息文本,3秒钟口消息文本清空重绘        if self.message:            #显示消息文本            painter.drawText(self.rect(), Qt.AlignBottom|Qt.AlignLeft, self.message)                    #3秒后触发清空信息的函数,并重绘事件        QTimer.singleShot(3000, self.clearMessage)            def clearMessage(self):        self.message = ''        self.update()             #鼠标释放事件    def mouseReleaseEvent(self, event):        #如果是双击释放,就不跟踪鼠标移动        if self.doubleClicked:            self.doubleClicked = False        else:            self.setMouseTracking(not self.hasMouseTracking())            if self.hasMouseTracking():                self.text = '开启鼠标位置跟踪功能.' +                     '请移动一下鼠标!!!' +                     '单击鼠标可以关闭这个功能'            else:                self.text = '闭鼠标跟踪功能.' +                     '单击鼠标可以开启这个功能'        self.update()            #鼠标移动事件    def mouseMoveEvent(self, event):        if not self.doubleClicked:            #窗口坐标转换为屏幕坐标            globalPos = self.mapToGlobal(event.pos())            self.text = '鼠标位置: 窗口坐标为:QPoint({0}, {1}) 屏幕坐标为:QPoint({2}, {3})'                 .format(event.pos().x(), event.pos().y(), globalPos.x(), globalPos.y())            self.update()                #鼠标双击事件    def mouseDoubleClickEvent(self, event):        self.doubleClicked = True        self.text = '你双击了鼠标'        self.update()            #键盘按键事件    def keyPressEvent(self, event):        self.key = ''        if event.key() == Qt.Key_Home:            self.key = 'Home'        elif event.key() == Qt.Key_End:            self.key = 'End'        elif event.key() == Qt.Key_PageUp:            if event.modifiers() & Qt.ControlModifier:                self.key = "Ctrl+PageUp"            else:                self.key = "PageUp"        elif event.key() == Qt.Key_PageDown:            if event.modifiers() & Qt.ControlModifier:                self.key = "Ctrl+PageDown"            else:                self.key = "PageDown"        elif Qt.Key_A <= event.key() <= Qt.Key_Z:            if event.modifiers() & Qt.ShiftModifier:                self.key = "Shift+"            self.key += event.text()                #如果key有字符,不为空,则绘制字符        if self.key:            self.update()        #否则就继续监视这个事件        else:            QWidget.keyPressEvent(self, event)        '''    重新实现其他事件,适用于PyQt没有提供该事件的处理函数的情况,    Tab键由于涉及焦点切换,不会传递给keyPressEvent,    因此,需要在这里重新定义。      '''         def event(self, event):        #如果有按键按下,并且按键是tab键        if (event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab):            self.key = "在event()中捕获Tab键"            self.update()            return True        return QWidget.event(self, event) if __name__ == '__main__':    app = QApplication(sys.argv)    window = DemoEvent()    window.show()    sys.exit(app.exec())

运行效果如下图:

1b6d4e81054e0c7877d99f4613b93113.gif

QEvent事件演示

本文知识点

  • Qt的事件传播和处理机制;
  • Qt中的各种事件;
  • 事件发生时,会生成一个QEvent对象,需要even函数进行分发,来调用相应的事件处理器;
  • Qt事件可能在处理后传递给父组件对象;
  • 事件过滤器(evenFilter)可以令事件进行拦截,阻止其传播,从而实现某些功能。

喜欢本文内容就关注, 收藏,点赞,评论和转发。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐