- 事件消息
- 当一个控件被触发了一个特定的行为时, 就会调用特定的方法, 来将事件传递给开发人员, 方便处理
- 重写这些事件方法, 就可以监听相关的信息
- 当用户对控件进行了某些操作时,根据事件机制,会一步一步传递(分发),最终触发指定的事件
-
API
- 控件事件
- 控件显示和关闭事件
showEvent(QShowEvent) # 控件显示时调用; closeEvent(QCloseEvent) # 控件关闭时调用
- 控件移动事件
moveEvent(QMoveEvent) # 控件移动时调用 # 窗口显示时已经移动了两次,移动的判定是x、y是否变化。
- 控件调整大小
resizeEvent(QResizeEvent) # 控件调整大小时调用 # 窗口显示时已经改变了一次;
- 控件显示和关闭事件
- 鼠标事件
- 进入和离开事件
enterEvent(QEvent) # 鼠标进入时触发 leaveEvent(QEvent) # 鼠标离开时触发 # 可通过这个显示被选中的阴影效果。
- 鼠标按下时触发
mousePressEvent(QMouseEvent) # 鼠标按下时触发
- QMouseEvent事件对象相关属性
QMouseEvent.button # 返回产生事件的按钮 # 需要注意的是,对于鼠标移动事件,该函数返回值总是Qt.NoButton QMouseEvent.buttons # 返回产生事件的按钮状态 # 函数返回当前按下的所有按钮,按钮状态可以是 # Qt.LeftButton 左键 # Qt.RightButton 右键 # Qt.MidButton 中键 # 或运算组合 多键同时按下 QMouseEvent.flags QMouseEvent.globalX() # 获取该对象的全局X轴位置,相对于桌面而言 QMouseEvent.globalY() # 获取该对象的全局Y轴位置,相对于桌面而言 QMouseEvent.globalPos() # 获取该对象的全局位置(x, y),相对于桌面而言 QMouseEvent.screenPos() # 获取该对象的全局位置(x.0, y.0),相对于桌面而言 QMouseEvent.x() # 获取该对象的局部X轴位置,相对于父对象而言 QMouseEvent.y() # 获取该对象的局部y轴位置,相对于父对象而言 QMouseEvent.pos() # 获取该对象的局部位置(x, y),相对于父对象而言 QMouseEvent.localPos() # 获取该对象的局部位置(x.0, y.0),相对于父对象而言
- 鼠标释放时触发
mouseReleaseEvent(QMouseEvent) # 一次按下加一次释放(控件的空间范围内)就是一次单击。
- 鼠标双击时触发
mouseDoubleClickEvent(QMouseEvent)
- 鼠标按下后移动时触发
mouseMoveEvent(QMouseEvent) # setMouseTracking(True)设置鼠标跟踪后,没有按下的移动也能触发。
- 进入和离开事件
- 键盘事件
- 键盘按下和释放时触发
keyPressEvent(QKeyEvent) # 键盘按下时调用 keyReleaseEvent(QKeyEvent) # 键盘释放时调用 # 如果想监测是哪个按键被按下:查看QKeyEvent事件对象。 # 键盘事件中,用户按下的是哪个键会存储在事件函数的 QKeyEvent 事件对象中 QKeyEvent.count() # 按下/释放按键的个数 QKeyEvent.isAutoRepeat() # 是否自动重复(长按不松就是自动重复) QKeyEvent.key() # 获取用户按下的普通键(A、B...),返回是代号 QKeyEvent.matches() # QKeyEvent.modifiers() # 获取用户按下的修饰键(Ctrl、Shift、Alt... 按下后系统没有任何操作的键)。返回一个对象
- 获取到的普通键可以通过以下方法等进行判断
Qt.Key_Tab # Tab键 Qt.Key_A # A键 Qt_Key_0 # 0键 ...
- 获取到的修饰键可以通过以下方法进行判断
Qt.NoModifier # 没有修饰键 Qt.ShiftModifier # Shift键 Qt.Controlmodifier # Ctrl键 Qt.AltModifier # Alt键 ... # 多个修饰键之间使用或( '|' )运算
-
- 获取到的普通键可以通过以下方法等进行判断
- 要使用键盘事件,那么我们需要给控件设置捕获功能
- 捕获键盘:
控件.grabKeyboard()
- 捕获手势:
控件.grabGesture()
- 捕获鼠标:
控件.grabMouse()
- 捕获快捷键:
控件.grabShortcut()
- 捕获键盘:
- 键盘按下和释放时触发
- 焦点事件
- 焦点获取和失去时触发
focusInEvent(QFocusEvent) # 获取焦点时调用 focusOutEvent(QFocusEvent) # 失去焦点时调用
- 焦点获取和失去时触发
- 拖拽事件(可上传文件)
- 拖拽时触发
dragEnterEvent(QDragEnterEvent) # 拖拽进入控件时调用 dragLeaveEvent(QDragLeaveEvent) # 拖拽离开控件时调用 dragMoveEvent(QDragMoveEvent) # 拖拽在控件内移动时调用 dropEvent(QDropEvent) # 拖拽放下时调用
- 拖拽时触发
- 绘制事件(美化控件)
- 绘制控件时触发
paintEvent(QPaintEvent) # 显示控件, 更新控件时调用;
- 绘制控件时触发
- 改变事件(中英文切换)
- 窗体、字体改变时触发
changeEvent(QEvent) # 窗体改变, 字体改变时调用。
- 窗体、字体改变时触发
- 右键菜单
- 右键菜单时调用
contextMenuEvent(QContextMenuEvent) # 访问右键菜单时调用。
- 右键菜单时调用
- 输入法
- 输入法调用
inputMethodEvent(QInputMethodEvent) # 输入法调用
- 输入法调用
- 示例代码
# 0.导入包和模块 from PyQt5.Qt import * import sys # 继承类、定义自己的方法 class Window(QWidget): # 初始化 def __init__(self): super(Window, self).__init__() self.setWindowTitle("事件消息的学习") self.resize(500, 500) self.setup_ui() # 存放所有子控件以及子控件的配置操作 def setup_ui(self): label = QLabel(self) label.setText("标签签") def showEvent(self, a0: QShowEvent) -> None: print("窗口被展示") def closeEvent(self, a0: QCloseEvent) -> None: print("窗口被关闭") def moveEvent(self, a0: QMoveEvent) -> None: print("窗口被移动") def resizeEvent(self, a0: QResizeEvent) -> None: print("窗口改变了尺寸大小") def enterEvent(self, a0: QEvent) -> None: print("鼠标进来了") self.setStyleSheet("background-color: yellow;") def leaveEvent(self, a0: QEvent) -> None: print("鼠标离开了") self.setStyleSheet("background-color: green;") def mousePressEvent(self, a0: QMouseEvent) -> None: print("鼠标被按下") def mouseReleaseEvent(self, a0: QMouseEvent) -> None: print("鼠标被释放") def mouseDoubleClickEvent(self, a0: QMouseEvent) -> None: print("鼠标双击") def mouseMoveEvent(self, a0: QMouseEvent) -> None: print("鼠标移动") def keyPressEvent(self, a0: QKeyEvent) -> None: print("键盘某一按键被按下") def keyReleaseEvent(self, a0: QKeyEvent) -> None: print("键盘某一按键被释放") if __name__ == '__main__': import sys app = QApplication(sys.argv) window = Window() window.show() sys.exit(app.exec_())
- 应用场景
- 当一个控件被触发了一个特定的行为时, 就会调用特定的方法, 来将事件传递给开发人员, 方便处理。重写这些事件方法, 就可以监听相关的信息
事件转发
- 先回顾一下QObject基类的事件机制
- 事件机制:实际上是属于事件派发。
- 具体流程:
- 操作系统将事件消息分发给应用程序
- 应用程序的消息循环捕获到事件消息
- 消息循环将事件接收者(receiver)和事件对象(evt)包装成QEvent对象(事件消息对象),分发给QApplication的notify()方法
- notify()方法将消息事件分发给receiver对象(事件接收者)的event(evt)方法
- receiver对象(事件接收者)的event()方法,再根据evt的事件类型分发给receiver(事件接收者)对象的具体事件函数
- 通过自定义类继承父类,并重写该对象的事件函数可以监听该事件
- 事件转发:
- 如果这个控件没有处理事件,那就往上传给给父控件,如果这个父控件还是没有处理事件,那么会继续往上传给父控件的父控件
- 来个案例演示一下
- 创建一个窗体,再窗体内创建一个空白控件,再在空白控件内创建一个标签控件和一个按钮控件
- 此时,我们需要监听各个层级之间的事件,那么我们就需要通过自定义类来重写事件
from PyQt5.Qt import * import sys # 自定义Widget类,继承自QWidget class MyWidget(QWidget): def mousePressEvent(self, a0: QMouseEvent) -> None: print('顶层窗口被点击了') class midWindow(QWidget): def mousePressEvent(self, a0: QMouseEvent) -> None: print('中间控件被点击了') class MyLabel(QLabel): def mousePressEvent(self, ev: QMouseEvent) -> None: print('标签被点击了') app = QApplication(sys.argv) window = MyWidget() window.resize(500, 500) window.setWindowTitle('事件转发案例') mid_win = midWindow(window) mid_win.resize(300, 300) mid_win.move(100, 100) mid_win.setAttribute(Qt.WA_StyledBackground, True) mid_win.setStyleSheet("background-color:yellow;") label = MyLabel(mid_win) label.setText('我是一个标签') label.move(100,150) label.setStyleSheet("background-color: red;") window.show() sys.exit(app.exec_())
- 此时,我们各个控件都对鼠标按下事件进行了处理,所以并不会网上转发给父控件
- 当我们取消label控件对象的鼠标按下事件(那么label控件将不再处理鼠标按下事件),当我们在label控件上按下鼠标时,控件本身将不再处理,事件消息会往上转发给他的父控件(中间控件),并打印出“中间控件被点击了”
class MyLabel(QLabel): pass # def mousePressEvent(self, ev: QMouseEvent) -> None: # print('标签被点击了')
- 当我们将标签控件和其父控件的鼠标按下事件都取消,我们在标签上按下鼠标的时候,标签本身不处理该事件,那么会往上传递给他的父控件,他的父控件也不处理该事件,会继续往上传递,传递给到父控件的父控件(即顶层窗口),此时打印“顶层窗口被点击了”
class midWindow(QWidget): pass # def mousePressEvent(self, a0: QMouseEvent) -> None: # print('中间控件被点击了')
-
- 我们再添加一个按钮控件,看一下按钮控件和标签控件有什么区别
from PyQt5.Qt import * import sys # 自定义Widget类,继承自QWidget class MyWidget(QWidget): def mousePressEvent(self, a0: QMouseEvent) -> None: print('顶层窗口被点击了') class midWindow(QWidget): def mousePressEvent(self, a0: QMouseEvent) -> None: print('中间控件被点击了') class MyLabel(QLabel): pass # def mousePressEvent(self, ev: QMouseEvent) -> None: # print('标签被点击了') class MyButton(QPushButton): pass app = QApplication(sys.argv) window = MyWidget() window.resize(500, 500) window.setWindowTitle('事件转发案例') mid_win = midWindow(window) mid_win.resize(300, 300) mid_win.move(100, 100) mid_win.setAttribute(Qt.WA_StyledBackground, True) mid_win.setStyleSheet("background-color:yellow;") label = MyLabel(mid_win) label.setText('我是一个标签') label.move(100,150) label.setStyleSheet("background-color: red;") btn = MyButton(mid_win) btn.setText('我是一个按钮') btn.move(100, 50) window.show() sys.exit(app.exec_())
- 此时我们可以看到,自定义的按钮对象和标签对象一样都没有处理鼠标按下事件,但是当鼠标按下按钮时并没有往上转发触发父控件的鼠标按下事件;而标签控件上鼠标按下时,由于自身没有处理鼠标按下事件,所以将事件往上转发给父控件,并触发父控件的鼠标按下事件
- 原因:
- 自定义按钮对象的父对象(QPushButton)有鼠标按下事件(mousePressEvent),当子对象没有事件方法时候,由于有继承关系,会到他的父对象中找该事件方法
- 自定义标签对象的父对象(QLabel)本身就没有鼠标按下事件(mousePressEvent)。所以当这个事件传递到自定义标签对象时,它并没有这个事件,然后会去它的父对象中找,结果父对象中也没有。那么就会通过事件转发,网上转发给到标签的父控件
- 事件对象有两个方法来标记事件处理状态
evt.ignore():标记事件被忽略,继续上传给父控件。 evt.accept():标记事件被处理,不会继续上传给父控件。 evt.isAccepted():看事件是否被处理,返回True或False
- 我们通过示例来看下效果
class MyLabel(QLabel): def mousePressEvent(self, ev: QMouseEvent) -> None: # pass print('标签被点击了') # 标记事件被忽略,继续往上转发给父控件 ev.ignore() # 获取事件处理状态 print('事件处理状态:', ev.isAccepted())
- 当我们在label对象的mousePressEvent事件中加上ev.ignore(),标记该事件被忽略,那么这个事件会继续往上转发给父控件
- 鼠标点击label标签输出结果
-
- 当我们再修改一下代码,将label标签的mousePressEvent事件加上ev.accept(),标记该事件被处理,不再继续往上转发给父控件
class MyLabel(QLabel): def mousePressEvent(self, ev: QMouseEvent) -> None: # pass print('标签被点击了') # 标记事件被忽略,继续往上转发给父控件 # ev.ignore() # 标记事件被处理,不再继续往上转发给父控件 ev.accept() # 获取事件处理状态 print('事件处理状态:', ev.isAccepted())
-
事件消息案例
- 案例1:创建一个窗口,包含一个标签。
- 要求:鼠标进入标签时候,展示“欢迎光临”,鼠标离开标签时,展示“谢谢惠顾”
from PyQt5.Qt import * import sys class MyLabel(QLabel): def enterEvent(self, a0: QEvent) -> None: # 鼠标进入时触发 self.setText('欢迎光临') def leaveEvent(self, a0: QEvent): # 鼠标离开时触发 self.setText('谢谢惠顾') app = QApplication(sys.argv) window = QWidget() window.resize(500, 500) window.setWindowTitle('事件消息案例1') label = MyLabel(window) label.move(100, 100) label.resize(300, 100) label.setStyleSheet("background-color: cyan;font-size:40px;") window.show() sys.exit(app.exec_())
- 案例2:创建一个窗口,包含一个标签,监听用户按键
- 要求:监听用户输入Tab键、Ctrl+s组合键、Ctrl+Shift+A
from PyQt5.Qt import * import sys class MyLabel(QLabel): def keyPressEvent(self, ev: QKeyEvent) -> None: # 判断键盘按下的普通键是 Tab if ev.key() == Qt.Key_Tab: self.setText("Tab") # 判断键盘按下的修饰键是 Ctrl 并且 普通键是 S elif ev.modifiers() == Qt.ControlModifier and ev.key() == Qt.Key_S: self.setText("Ctrl + S") # 判断键盘按下的修饰键是 Ctrl 和 Shift 并且 普通键是 A elif ev.modifiers() == Qt.ControlModifier | Qt.ShiftModifier and ev.key() == Qt.Key_A: self.setText("Ctrl + Shift + A") app = QApplication(sys.argv) window = QWidget() window.resize(500, 500) window.setWindowTitle("事件消息案例2") # keyPressEvent(QKeyEvent) # 键盘按下时调用 # keyReleaseEvent(QKeyEvent) # 键盘释放时调用 label = MyLabel(window) label.resize(400, 100) label.move(50, 100) label.setStyleSheet("background-color: cyan;font-size:40px;") # 设置控件捕获键盘 label.grabKeyboard() window.show() sys.exit(app.exec_())
- 案例3:创建一个窗口,鼠标点击用户区域拖拽也可以移动窗口
- 知识点:鼠标按下事件,鼠标移动事件、鼠标释放事件
from PyQt5.Qt import * import sys class MyWidget(QWidget): def __init__(self): super().__init__() self.mous_y = 0 self.mous_x = 0 self.setWindowTitle('事件消息案例3') self.resize(500, 500) def mousePressEvent(self, ev: QMouseEvent) -> None: # 获取鼠标点击时相对于控件的x、y坐标 self.mous_x = ev.x() self.mous_y = ev.y() def mouseMoveEvent(self, ev: QMouseEvent) -> None: # 获取鼠标在x轴和y轴方向移动的距离 move_x = ev.x() - self.mous_x move_y = ev.y() - self.mous_y # 设置窗口的x坐标和y坐标。 self.move(self.x()+move_x, self.y()+move_y) def mouseReleaseEvent(self, ev: QMouseEvent) -> None: print('鼠标被释放了') app = QApplication(sys.argv) window = MyWidget() window.show() sys.exit(app.exec_())
- 运行上面的代码,看上去已经满足了案例要求。但是当这个控件设置鼠标跟踪之后,就有问题了(当鼠标进入控件,移动鼠标时窗口就会跟着移动)
from PyQt5.Qt import * import sys class MyWidget(QWidget): def __init__(self): super().__init__() self.mouse_y = 0 self.mouse_x = 0 self.setWindowTitle('事件消息案例3') self.resize(500, 500) def mousePressEvent(self, ev: QMouseEvent) -> None: # 获取鼠标点击时相对于控件的x、y坐标 self.mouse_x = ev.x() self.mouse_y = ev.y() def mouseMoveEvent(self, ev: QMouseEvent) -> None: # 获取鼠标在x轴和y轴方向移动的距离 move_x = ev.x() - self.mouse_x move_y = ev.y() - self.mouse_y # 设置窗口的x坐标和y坐标。 self.move(self.x()+move_x, self.y()+move_y) def mouseReleaseEvent(self, ev: QMouseEvent) -> None: print('鼠标被释放了') app = QApplication(sys.argv) window = MyWidget() window.setMouseTracking(True) window.show() sys.exit(app.exec_())
- 解决方法:在控件初始化时候,设置一个标记属性为False,鼠标点击事件中将该标记修改为True,鼠标移动事件中添加判断,当标记为True时,才执行窗口移动
from PyQt5.Qt import * import sys class MyWidget(QWidget): def __init__(self): super().__init__() self.state = False self.mouse_y = 0 self.mouse_x = 0 self.setWindowTitle('事件消息案例3') self.resize(500, 500) def mousePressEvent(self, ev: QMouseEvent) -> None: # 获取鼠标点击时相对于控件的x、y坐标 self.mouse_x = ev.x() self.mouse_y = ev.y() self.state = True def mouseMoveEvent(self, ev: QMouseEvent) -> None: if self.state: # 获取鼠标在x轴和y轴方向移动的距离 move_x = ev.x() - self.mouse_x move_y = ev.y() - self.mouse_y # 设置窗口的x坐标和y坐标。 self.move(self.x()+move_x, self.y()+move_y) def mouseReleaseEvent(self, ev: QMouseEvent) -> None: print('鼠标被释放了') app = QApplication(sys.argv) window = MyWidget() window.setMouseTracking(True) window.show() sys.exit(app.exec_())
- 设置完成。新问题又来了...鼠标进入窗口后,窗口并不会随着鼠标移动而移动了。当我们按下鼠标后可以正常移动窗口了。但是,当我们松开鼠标按键后,窗口会一直跟随鼠标移动
- 解决方法:在鼠标释放事件中,我们再将标记修改为False
from PyQt5.Qt import * import sys class MyWidget(QWidget): def __init__(self): super().__init__() self.state = False self.mouse_y = 0 self.mouse_x = 0 self.setWindowTitle('事件消息案例3') self.resize(500, 500) def mousePressEvent(self, ev: QMouseEvent) -> None: # 获取鼠标点击时相对于控件的x、y坐标 self.mouse_x = ev.x() self.mouse_y = ev.y() self.state = True def mouseMoveEvent(self, ev: QMouseEvent) -> None: if self.state: # 获取鼠标在x轴和y轴方向移动的距离 move_x = ev.x() - self.mouse_x move_y = ev.y() - self.mouse_y # 设置窗口的x坐标和y坐标。 self.move(self.x()+move_x, self.y()+move_y) def mouseReleaseEvent(self, ev: QMouseEvent) -> None: self.state = False app = QApplication(sys.argv) window = MyWidget() window.setMouseTracking(True) window.show() sys.exit(app.exec_())
- 前面的问题都解决了,新问题又来了。此时不管我们按下的是鼠标左键还是右键,移动鼠标的时候,窗口都会跟随移动
- 解决方法:在鼠标按下事件中,添加判断。当鼠标按下的是左键的时候才执行后面的代码
from PyQt5.Qt import * import sys class MyWidget(QWidget): def __init__(self): super().__init__() self.state = False self.mouse_y = 0 self.mouse_x = 0 self.setWindowTitle('事件消息案例3') self.resize(500, 500) def mousePressEvent(self, ev: QMouseEvent) -> None: if ev.button() == Qt.LeftButton: # 获取鼠标点击时相对于控件的x、y坐标 self.mouse_x = ev.x() self.mouse_y = ev.y() self.state = True def mouseMoveEvent(self, ev: QMouseEvent) -> None: if self.state: # 获取鼠标在x轴和y轴方向移动的距离 move_x = ev.x() - self.mouse_x move_y = ev.y() - self.mouse_y # 设置窗口的x坐标和y坐标。 self.move(self.x()+move_x, self.y()+move_y) def mouseReleaseEvent(self, ev: QMouseEvent) -> None: self.state = False app = QApplication(sys.argv) window = MyWidget() window.setMouseTracking(True) window.show() sys.exit(app.exec_())
- 此时,才算完美解决了案例要求。