一、引言
基于窗体(Widget)的应用程序都是由事件(event)驱动的,鼠标单击、按下某个按键、重绘某个组件、最小化窗口都会产生相应的事件,应用程序对这些事件作出相应的响应处理以实现程序的功能。
二、应用程序的事件循环
事件主要由操作系统的窗口系统产生的,产生的事件进入一个事件队列,由应用程序的事件循环进行处理。以下是PyQt5应用程序的主程序结构:
app = QApplication(sys.argv)
mainform = QmyWidget()
mainform.show()
sys.exit(app.exec_())
app是创建的应用程序对象,最后执行的app.exec_()开启了应用程序的事件处理循环。应用程序会对事件队列中排队的事件进行处理,还可以对相同事件进行合并处理。例如一个界面组件的重绘事件paintEvent,如果在事件队列中重复出现同一事件,应用程序就会合并处理,所以,事件处理是一种异步处理机制。
三、事件类型及默认的事件处理函数
在PyQt5中,事件是一种对象,由抽象类QEvent表示。QEvent还有很多子类表示具体的事件,如QKeyEvent表示按键事件,QMouseEvent表示鼠标事件,QPaintEvent表示窗体绘制事件。
当一个事件发生时,PyQt5会根据事件的具体类型用QEvent相应的子类创建一个事件实例对象,然后传递给产生事件的对象的event()函数进行处理。
QObject类及其子类都可以进行事件的处理,但主要还是窗体类(QWidget及其子类)中用到事件处理。以下是QObject类的event()函数的原型:
event(self, <em>e</em>)
参数e是QEvent类型,QEvent类主要有以下3个接口函数:
(1)accept():表示事件接收者接受此事件,对此事件进行处理,接受的事件不会再传播给上层容器组件。
(2)ignore():表示事件接收者忽略次事件,忽略的事件将传播给上层容器组件。
(3)type():返回事件的类型,是枚举类型QEvent.Type,这个枚举类型由100多个值,表示100多个类型的事件。
枚举类型QEvent.Type的每个值都对应于一个事件类,例如QEvent.KeyPress类型表示的是按键事件,对应的事件类是QKeyEvent。
一个类接收到事件后,首先会触发其event()函数,如果event()函数不做任何处理,就会自动触发默认的事件处理函数。
QWidget类是所有界面组件类的父类,它定义了各种事件的默认处理函数,例如鼠标双击事件的枚举类型是QEvent.MouseButtonDblClick,默认的事件处理函数是mouseDoubleClickEvent(),其函数原型是:
mouseDoubleClickEvent(self, event)
参数event是QMouseEvent类型,这是鼠标类型对应的类。
QWidget定义了很多的默认事件处理函数,都会传递一个event参数,但是event的类型由具体事件类型决定。常用的默认事件处理函数如表所示(表中只列出了函数名称,未列出函数输入参数)
默认函数名称 | 触发时机 | 参数event类型 |
---|---|---|
mousePressEvent() | 鼠标按键按下时触发 | QMouseEvent |
mouseReleaseEvent() | 鼠标按键释放时触发 | QMouseEvent |
mouseMoveEvent() | 鼠标移动时触发 | QMouseEvent |
mouseDoubleClickEvent() | 鼠标双击时触发 | QMouseEvent |
keyPressEvent() | 键盘按键按下时触发 | QKeyEvent |
keyReleaseEvent() | 键盘按键释放时触发 | QKeyEvent |
paintEvent() | 在界面需要重新绘制时触发 | QPaintEvent |
closeEvent() | 一个窗体关闭时触发 | QCloseEvent |
showEvent() | 一个窗体显示时触发 | QShowEvent |
hideEvent() | 一个窗体隐藏时触发 | QHideEvent |
resizeEvent() | 组件改变大小时触发,如一个窗口改变大小时 | QResizeEvent |
focusInEvent() | 当一个组件获得键盘焦点时触发,如一个QLineEdit组件获得输入焦点 | QFocusEvent |
focusOutEvent() | 当一个组件失去键盘焦点时触发,如一个QLineEdit组件失去输入焦点 | QFocusEvent |
enterEvent() | 当鼠标进入组件的屏幕空间时触发,如鼠标移动到一个QPushButton组件上 | QEvent |
leaveEvent() | 当鼠标离开组件的屏幕空间时触发,如鼠标离开一个QPushButton组件 | QEvent |
dragEnterEvent() | 拖动操作正在进行,鼠标移动到组件上方时触发 | QDragEnterEvent |
dragLeaveEvent() | 拖动操作正在进行,鼠标移出组件上方时触发 | QDragLeaveEvent |
dragMoveEvent() | 拖动操作正在进行,鼠标移动时触发 | QDragMoveEvent |
dropEvent() | 当拖动操作在某个组件上放下时触发 | QDropEvent |
用户在继承于QWidget或其子类的自定义类中可以重新实现这些默认的事件处理函数,从而实现一些需要的功能。例如,QWidget没有clicked()信号,那么就不能通过信号与槽的方式实现对鼠标单击的处理,但是可以重新实现mouseReleaseEvent()函数对鼠标单击事件进行处理。
下列例子对基于项目模板widgetApp的默认事件处理作说明:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox
from PyQt5.QtCore import pyqtSlot, Qt, QEvent
from PyQt5.QtGui import QPainter, QPixmap
from ui_Widget import Ui_Widget
class QmyWidgrt(QWidget):
def __init__(self, parent = None):
super().__init__(parent) #调用父类构造函数,创建窗体
self.ui = Ui_Widget() #创建UI对象
self.ui.SetupUi(self) #构造UI
然后在QmyWidget类中重新实现了以下一些典型事件的默认处理函数。
(1)paintEvent()
在界面需要重新绘制时触发,在此事件函数里可以实现一些自定义的绘制功能。绘制窗体背景图片:
def paintEvent(self, event):
painter = QPainter(self)
pic = QPixmap("sea1.jpg")
painter.drawPixmap(0, 0, self.width(), self.height(), pic)
super().paintEvent(event)
函数的功能是将一个图片文件sea1.jpg绘制到窗体的整个区域,绘图时使用了窗体的画笔对象painter。图片文件sea1.jpg必须与使用其的脚本文件在同一个目录里。最后一行语句表示再执行父类的paintEvent()事件处理函数,以便父类执行其内建的一些操作。
(2)resizeEvent()
在窗体改变大小时触发,在此事件函数里,根据窗体大小,使一个按钮btnTest总是居于窗体的中央。
def resizeEvent(self, event):
W = self.width()
H = self.height()
Wbtn = self.ui.btnTest.width()
Hbtn = self.ui.btnTest.height()
self.ui.btnTest.setGeometry((W-Wbtn)/2, (H-Hbtn)/2, Wbtn, Hbtn)
(3)closeEvent()
在窗体关闭时触发,在此事件函数里可以使用一个对话框询问是否关闭窗体,代码如下:
def closeEvent(self, event):
dlgTitle = "Question消息框"
strInfo = "closeEvent事件触发,确定要退出吗?"
defaultBtn = QMessageBox.NoButton
result = QMessageBox.question(self, dlgTitle, strInfo, QMessageBox.Yes | QMessageBox.No, defaultBtn)
if(result == QMessageBox.Yes):
event.accept() #窗体可关闭
else:
event.ignore() #窗体不可关闭
使用了对话框QMessageBox询问是否关闭窗体。
closeEvent(event)函数的参数event是QCloseEvent类型,根据对话框的返回结果调用QCloseEvent的accept()函数可以关闭窗体,ignore()函数则不关闭窗体。
(4)mousePressEvent()
在鼠标按键按下时触发,在此事件函数里判断鼠标左键是否按下,如果是左键按下就显示鼠标光标处的屏幕坐标,即:
def mousePressEvent(self, event):
pt = event.pos() #鼠标位置,QPoint
if(event.button() == Qt.LeftButton): #鼠标左键按下
self.ui.LabMove.setText("(x, y) = (%d, %d)" %(pt.x(), pt.y()))
rect = self.ui.LabMove.geometry()
self.ui.LabMove.setGeometry(pt.x(), pt.y(), rect.width(), rect.height())
super().mousePressEvent(event)
参数event是QMouseEvent类型,QMouseEvent有以下几个接口函数,表示按下的按键的信息和鼠标坐标信息。
if(event.buttons() & Qt.LeftButton) and (event.buttons() & Qt.RightButton):
(5)keyPressEvent()或keyReleaseEvent()
keyPressEvent()在键盘上的按键按下时触发,keyReleaseEvent()在按键释放时触发,为keyPressEvent()编写的代码如下:
def keyPressEvent(self, event):
rect = self.ui.btnMove.geometry()
if event.key() in set([Qt.Key_A, Qt.Key_Left]):
self.ui.btnMove.setGeometry(rect.left() - 20, rect.top(), rect.width(), rect.height())
elif event.key() in set([Qt.Key_D, Qt.Key_Right]):
self.ui.btnMove.setGeometry(rect.left() + 20, rect.top(), rect.width(), rect.height())
elif event.key() in set([Qt.Key_W, Qt.Key_Up]):
self.ui.btnMove.setGeometry(rect.left, rect.top() - 20, rect.width(), rect.height())
elif event.key() in set([Qt.Key_S, Qt.Key_Down]):
self.ui.btnMove.setGeometry(rect.left, rect.top() + 20, rect.width(), rect.height())
keyPressEvent(event)函数的参数event是QKeyEvent类型,它有以下两个主要的接口函数,表示按下的按键的信息。
if(event.key() == Qt.Key_Q) and (event.modifiers() & Qt.ControlModifier):
这段程序是期望在按下W、A、S、D或上、下、左、右方向键时,窗体上的按钮btnMove能上下左右移动位置。但是却会发现在使用keyPressEvent()事件函数时,只有W、A、S、D有效,而如果使用的是keyReleaseEvent()函数,则W、A、S、D和上、下、左、右方向键都有效。这说明上、下、左、右方向键按下时不会产生QEvent.KeyPress类型的事件。