018.PyQt5_QWidget_事件消息

  • 事件消息
  • 当一个控件被触发了一个特定的行为时, 就会调用特定的方法, 来将事件传递给开发人员, 方便处理
  • 重写这些事件方法, 就可以监听相关的信息
  • 当用户对控件进行了某些操作时,根据事件机制,会一步一步传递(分发),最终触发指定的事件


API

  • 控件事件
    1. 控件显示和关闭事件
      showEvent(QShowEvent)        # 控件显示时调用;
      closeEvent(QCloseEvent)      # 控件关闭时调用
      
    2. 控件移动事件
      moveEvent(QMoveEvent)        # 控件移动时调用
      
      # 窗口显示时已经移动了两次,移动的判定是x、y是否变化。
      
    3. 控件调整大小
      resizeEvent(QResizeEvent)      # 控件调整大小时调用
      
      # 窗口显示时已经改变了一次;
      
  • 鼠标事件
    1. 进入和离开事件
      enterEvent(QEvent)      # 鼠标进入时触发
      leaveEvent(QEvent)      # 鼠标离开时触发
      
      # 可通过这个显示被选中的阴影效果。
      
    2. 鼠标按下时触发
      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),相对于父对象而言
      
    3. 鼠标释放时触发
      mouseReleaseEvent(QMouseEvent)      # 一次按下加一次释放(控件的空间范围内)就是一次单击。
      
    4. 鼠标双击时触发
      mouseDoubleClickEvent(QMouseEvent)  
      
    5. 鼠标按下后移动时触发
      mouseMoveEvent(QMouseEvent)
      
      # setMouseTracking(True)设置鼠标跟踪后,没有按下的移动也能触发。
      
  • 键盘事件
    1. 键盘按下和释放时触发
      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键
        ...
        
        # 多个修饰键之间使用或( '|' )运算
        
    2. 要使用键盘事件,那么我们需要给控件设置捕获功能
      • 捕获键盘: 控件.grabKeyboard()
      • 捕获手势: 控件.grabGesture()
      • 捕获鼠标: 控件.grabMouse()
      • 捕获快捷键:控件.grabShortcut()
  • 焦点事件
    1. 焦点获取和失去时触发
      focusInEvent(QFocusEvent)       # 获取焦点时调用
      focusOutEvent(QFocusEvent)       # 失去焦点时调用
      
  • 拖拽事件(可上传文件)
    1. 拖拽时触发
      dragEnterEvent(QDragEnterEvent)       # 拖拽进入控件时调用
      dragLeaveEvent(QDragLeaveEvent)       # 拖拽离开控件时调用
      dragMoveEvent(QDragMoveEvent)         # 拖拽在控件内移动时调用
      dropEvent(QDropEvent)                 # 拖拽放下时调用
      
  • 绘制事件(美化控件)
    1. 绘制控件时触发
      paintEvent(QPaintEvent)         # 显示控件, 更新控件时调用;
      
  • 改变事件(中英文切换)
    1. 窗体、字体改变时触发
      changeEvent(QEvent)         # 窗体改变, 字体改变时调用。
      
  • 右键菜单
    1. 右键菜单时调用
      contextMenuEvent(QContextMenuEvent)     # 访问右键菜单时调用。
      
  • 输入法
    1. 输入法调用
      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基类的事件机制
    • 事件机制:实际上是属于事件派发。
    • 具体流程:
      1. 操作系统将事件消息分发给应用程序
      2. 应用程序的消息循环捕获到事件消息
      3. 消息循环将事件接收者(receiver)和事件对象(evt)包装成QEvent对象(事件消息对象),分发给QApplication的notify()方法
      4. notify()方法将消息事件分发给receiver对象(事件接收者)的event(evt)方法
      5. receiver对象(事件接收者)的event()方法,再根据evt的事件类型分发给receiver(事件接收者)对象的具体事件函数
      6. 通过自定义类继承父类,并重写该对象的事件函数可以监听该事件
  • 事件转发:
    • 如果这个控件没有处理事件,那就往上传给给父控件,如果这个父控件还是没有处理事件,那么会继续往上传给父控件的父控件
  • 来个案例演示一下
    • 创建一个窗体,再窗体内创建一个空白控件,再在空白控件内创建一个标签控件和一个按钮控件
    • 此时,我们需要监听各个层级之间的事件,那么我们就需要通过自定义类来重写事件
    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_())
    
    
  • 此时,才算完美解决了案例要求。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失心疯_2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值