PyQt5—事件处理

事件处理


一、常见事件类型和处理方法

1、事件类型

  • 常见的事件有如下:
    • 键盘事件:按键按下和松开
    • 鼠标事件:鼠标指针移动、鼠标按键按下和松开
    • 拖放事件:用鼠标进行拖放
    • 滚轮事件:鼠标滚轮滚动
    • 绘屏事件:重绘屏幕的某些部分
    • 定时事件:定时器到时
    • 焦点事件:键盘焦点移动
    • 进入和离开事件:鼠标指针移入 Widget 内,或者移出
    • 移动事件:Widget 的位置改变
    • 大小改变事件:Widget 的大小改变。
    • 显示和隐藏事件:Widget 的显示和隐藏
    • 窗口事件:窗口是否为当前窗口

2、事件处理方法

  • PyQt5 中事件处理的方法有如下五种:

(1)重写事件函数

  • 将如 mousePressEvent()、keyPressEvent()、paintEvent() 这些事件函数重写。

(2)重新实现 QObject.event()

  • 对于某些事件没有内置提供的处理函数,可以采用此方法。

(3)安装事件过滤器

  • 对 QObject 调用 installEventFilter,让 QObject 的全部事件先传到事件过滤函数 eventFilter 中,然后再这个函数中对事件进行修改,并确定哪些事件使用自定义事件处理函数,那些事件使用默认事件处理函数。

(4)在 QApplication 中安装事件过滤器

  • QApplication 的时间过滤器可以第一时间捕获 QObject 的所有事件。

(5)重新实现 QApplication 的 notify() 方法

  • notify() 函数用于分发事件,可以达到在任何事件处理器之前捕获事件。

3、示例

  • 示例代码如下:
    # -*- coding:utf-8 -*-
    # Time : 2019/09/16 下午 5:05 
    # Author : 御承扬
    # e-mail:[email protected]
    # project:  PyQt5
    # File : Event.py 
    # @software: PyCharm
    
    
    import sys
    from PyQt5.QtCore import (QEvent, QTimer, Qt)
    from PyQt5.QtWidgets import (QApplication, QMenu, QWidget)
    from PyQt5.QtGui import QPainter, QIcon
    
    
    class Widget(QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent)
            self.setWindowIcon(QIcon("./images/Python2.ico"))
            self.setWindowTitle("鼠标事件示例")
            self.justDoubleClicked = False
            self.key = ""
            self.text = ""
            self.message = ""
            self.resize(400, 300)
            self.move(100, 100)
            self.setWindowTitle("Events")
            QTimer.singleShot(0, self.giveHelp)  # 避免窗口大小重绘事件的影响,可以把参数0改变成3000(3秒),然后在运行,就可以明白这行代码的意思。
    
        def giveHelp(self):
            self.text = "请点击这里触发追踪鼠标功能"
            self.update()  # 重绘事件,也就是触发paintEvent函数。
    
        '''重新实现关闭事件'''
    
        def closeEvent(self, event):
            print("Closed")
    
        '''重新实现上下文菜单事件'''
    
        def contextMenuEvent(self, event):
            menu = QMenu(self)
            oneAction = menu.addAction("&One")
            twoAction = menu.addAction("&Two")
            oneAction.triggered.connect(self.one)
            twoAction.triggered.connect(self.two)
            if not self.message:
                menu.addSeparator()
                threeAction = menu.addAction("Thre&e")
                threeAction.triggered.connect(self.three)
            menu.exec_(event.globalPos())
    
        '''上下文菜单槽函数'''
    
        def one(self):
            self.message = "Menu option One"
            self.update()
    
        def two(self):
            self.message = "Menu option Two"
            self.update()
    
        def three(self):
            self.message = "Menu option Three"
            self.update()
    
        '''重新实现绘制事件'''
    
        def paintEvent(self, event):
            text = self.text
            i = text.find("\n\n")
            if i >= 0:
                text = text[0:i]
            if self.key:  # 若触发了键盘按钮,则在文本信息中记录这个按钮信息。
                text += "\n\n你按下了: {0}".format(self.key)
            painter = QPainter(self)
            painter.setRenderHint(QPainter.TextAntialiasing)
            painter.drawText(self.rect(), Qt.AlignCenter, text)  # 绘制信息文本的内容
            if self.message:  # 若消息文本存在则在底部居中绘制消息,5秒钟后清空消息文本并重绘。
                painter.drawText(self.rect(), Qt.AlignBottom | Qt.AlignHCenter,
                                 self.message)
                QTimer.singleShot(5000, self.clearMessage)
                QTimer.singleShot(5000, self.update)
    
        '''清空消息文本的槽函数'''
    
        def clearMessage(self):
            self.message = ""
    
        '''重新实现调整窗口大小事件'''
    
        def resizeEvent(self, event):
            self.text = "调整窗口大小为: QSize({0}, {1})".format(
                event.size().width(), event.size().height())
            self.update()
    
        '''重新实现鼠标释放事件'''
    
        def mouseReleaseEvent(self, event):
            # 若鼠标释放为双击释放,则不跟踪鼠标移动
            # 若鼠标释放为单击释放,则需要改变跟踪功能的状态,如果开启跟踪功能的话就跟踪,不开启跟踪功能就不跟踪
            if self.justDoubleClicked:
                self.justDoubleClicked = False
            else:
                self.setMouseTracking(not self.hasMouseTracking())  # 单击鼠标
                if self.hasMouseTracking():
                    self.text = "开启鼠标跟踪功能.\n" + \
                                "请移动一下鼠标!\n" + \
                                "单击鼠标可以关闭这个功能"
                else:
                    self.text = "关闭鼠标跟踪功能.\n" + \
                                "单击鼠标可以开启这个功能"
                self.update()
    
        '''重新实现鼠标移动事件'''
    
        def mouseMoveEvent(self, event):
            if not self.justDoubleClicked:
                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.justDoubleClicked = 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()
            if self.key:
                self.key = self.key
                self.update()
            else:
                QWidget.keyPressEvent(self, event)
    
        '''重新实现其他事件,适用于PyQt没有提供该事件的处理函数的情况,Tab键由于涉及焦点切换,不会传递给keyPressEvent,因此,需要在这里重新定义。'''
    
        def event(self, event):
            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)
        form = Widget()
        form.show()
        app.exec_()
    
  • 效果如下:
    PyQt5—事件处理_第1张图片
  • 使用事件过滤器的案例代码入下:
    # -*- coding:utf-8 -*-
    # Time : 2019/09/16 下午 7:16 
    # Author : 御承扬
    # e-mail:[email protected]
    # project:  PyQt5
    # File : Event02.py 
    # @software: PyCharm
    
    
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    import sys
    
    
    class EventFilter(QDialog):
        def __init__(self, parent=None):
            super(EventFilter, self).__init__(parent)
            self.setWindowTitle("事件过滤器")
            self.setWindowIcon(QIcon("./images/Python2.ico"))
            self.label1 = QLabel("请点击")
            self.label2 = QLabel("请点击")
            self.label3 = QLabel("请点击")
            self.LabelState = QLabel("test")
            self.image1 = QImage("images/cartoon1.ico")
            self.image2 = QImage("images/cartoon2.ico")
            self.image3 = QImage("images/cartoon3.ico")
            self.width = 600
            self.height = 300
            self.resize(self.width, self.height)
            self.label1.installEventFilter(self)
            self.label2.installEventFilter(self)
            self.label3.installEventFilter(self)
            mainLayout = QGridLayout(self)
            mainLayout.addWidget(self.label1, 500, 0)
            mainLayout.addWidget(self.label2, 500, 1)
            mainLayout.addWidget(self.label3, 500, 2)
            mainLayout.addWidget(self.LabelState, 600, 1)
            self.setLayout(mainLayout)
    
        def eventFilter(self, watched, event):
            # 只对 label1 的点击事件进行过滤,重写其行为,其他事件忽略
            if watched == self.label1:
                if event.type() == QEvent.MouseButtonPress:
                    mouseEvent = QMouseEvent(event)
                    if mouseEvent.buttons() == Qt.LeftButton:
                        self.LabelState.setText("按下鼠标左键")
                    elif mouseEvent.button() == Qt.MidButton:
                        self.LabelState.setText("按下鼠标中键")
                    elif mouseEvent.buttons() == Qt.RightButton:
                        self.LabelState.setText("按下了鼠标右键")
    
                    '''装换图片大小'''
                    transform = QTransform()
                    transform.scale(0.5, 0.5)
                    tmp = self.image1.transformed(transform)
                    self.label1.setPixmap(QPixmap.fromImage(tmp))
                if event.type() == QEvent.MouseButtonRelease:
                    self.LabelState.setText("释放鼠标键")
                    self.label1.setPixmap(QPixmap.fromImage(self.image2))
            elif watched == self.label3:
                if event.type() == QEvent.MouseButtonPress:
                    mouseEvent = QMouseEvent(event)
                    if mouseEvent.buttons() == Qt.LeftButton:
                        self.LabelState.setText("按下鼠标左键")
                    elif mouseEvent.button() == Qt.MidButton:
                        self.LabelState.setText("按下鼠标中键")
                    elif mouseEvent.buttons() == Qt.RightButton:
                        self.LabelState.setText("按下了鼠标右键")
    
                    '''装换图片大小'''
                    transform = QTransform()
                    transform.scale(0.5, 0.5)
                    tmp = self.image2.transformed(transform)
                    self.label3.setPixmap(QPixmap.fromImage(tmp))
                if event.type() == QEvent.MouseButtonRelease:
                    self.LabelState.setText("释放鼠标键")
                    self.label3.setPixmap(QPixmap.fromImage(self.image3))
            return QDialog.eventFilter(self, watched, event)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        dialog = EventFilter()
        dialog.show()
        sys.exit(app.exec_())
    
  • 运行效果如下:
    PyQt5—事件处理_第2张图片

二、窗口数据传递

  • 一个应用往往有一个以上的窗口,窗口本身以及窗口之间往往需要进行数据传递。
  • 对于多窗口的窗口之间的数据传递,一种是主窗口获取子窗口中控件的属性;另一种通过信号与槽机制,具体就是子窗口发射信号,主窗口的槽函数接收信号。

1、同个窗口的不同控件数据传递

  • 在典型应用中,一个窗口控件的变化往往会引起另一个窗口控件的变化,这种操作可以利用信号与槽机制实现,示例代码如下:
    # -*- coding:utf-8 -*-
    # Time : 2019/09/16 下午 8:21 
    # Author : 御承扬
    # e-mail:[email protected]
    # project:  PyQt5
    # File : SignalSlot07.py 
    # @software: PyCharm
    
    
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    import sys
    
    
    class WinForm(QWidget):
        def __init__(self):
            super(WinForm, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowIcon(QIcon("./images/Python2.ico"))
            self.setWindowTitle("信号与槽:连接滑块 LCD")
            self.setGeometry(300, 300, 350, 150)
            lcd = QLCDNumber(self)
            slider = QSlider(Qt.Horizontal, self)
            vBox = QVBoxLayout()
            vBox.addWidget(lcd)
            vBox.addWidget(slider)
            self.setLayout(vBox)
            slider.valueChanged.connect(lcd.display)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        win = WinForm()
        win.show()
        sys.exit(app.exec_())
    
  • 效果如下:
    PyQt5—事件处理_第3张图片

2、多窗口传递数据:属性

  • 一般是主窗口调用一个子窗口,子窗口关闭时将数据传输给主窗口。
  • 主窗口代码如下:
    # -*- coding:utf-8 -*-
    # Time : 2019/09/16 下午 9:01 
    # Author : 御承扬
    # e-mail:[email protected]
    # project:  PyQt5
    # File : CAllDialogMainWin.py 
    # @software: PyCharm
    
    
    from 信号与槽.DateDialog import DateDialog
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    import sys
    
    
    class MainWin(QWidget):
        def __init__(self, parent=None):
            super(MainWin, self).__init__(parent)
            self.resize(400, 90)
            self.setWindowIcon(QIcon("./images/Python2.ico"))
            self.setWindowTitle("窗口传递数据")
            self.lineEdit = QLineEdit(self)
            self.button1 = QPushButton("弹出对话框1")
            self.button1.clicked.connect(self.onButton1Click)
            self.button2 = QPushButton('弹出对话框2')
            self.button2.clicked.connect(self.onButton2Click)
            gridLayout = QGridLayout()
            gridLayout.addWidget(self.lineEdit)
            gridLayout.addWidget(self.button1)
            gridLayout.addWidget(self.button2)
            self.setLayout(gridLayout)
    
        def onButton1Click(self):
            dialog = DateDialog(self)
            result = dialog.exec_()
            date = dialog.dateTime()
            self.lineEdit.setText(date.date().toString())
            print('\n 日期对话框的返回值')
            print('date=%s' % str(date.date()))
            print('time=%s' % str(date.time()))
            print('result=%s' % result)
            dialog.destroy()
    
        def onButton2Click(self):
            date, time, result = DateDialog.getDateTime()
            self.lineEdit.setText(date.toString())
            print('\n 日期对话框的返回值')
            print('date=%s' % str(date))
            print('time=%s' % str(date))
            print('result=%s' % result)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        form = MainWin()
        form.show()
        sys.exit(app.exec_())
    
  • 子窗口代码如下:
    # -*- coding:utf-8 -*-
    # Time : 2019/09/16 下午 8:50 
    # Author : 御承扬
    # e-mail:[email protected]
    # project:  PyQt5
    # File : DateDialog.py 
    # @software: PyCharm
    
    
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    
    
    class DateDialog(QDialog):
        def __init__(self, parent=None):
            super(DateDialog, self).__init__(parent)
            self.setWindowTitle('DateDialog')
            self.setWindowIcon(QIcon("./images/Python2.ico"))
            self.resize(100, 100)
    
            layout = QVBoxLayout(self)
            self.datetime = QDateTimeEdit(self)
            self.datetime.resize(150, 20)
            self.datetime.setCalendarPopup(True)
            self.datetime.setDateTime(QDateTime.currentDateTime())
    
            buttons = QDialogButtonBox(
                QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
                Qt.Horizontal, self)
            buttons.accepted.connect(self.accept)
            buttons.rejected.connect(self.reject)
            layout.addWidget(buttons)
    
        def dateTime(self):
            return self.datetime.dateTime()
    
        @staticmethod
        def getDateTime(parent=None):
            dialog = DateDialog(parent)
            result = dialog.exec_()
            date = dialog.dateTime()
            return (date.date(), date.time(), result == QDialog.Accepted)
    
  • 运行效果:
    PyQt5—事件处理_第4张图片

3、多窗口数据传递:信号与槽

  • 这种模式下,一般是子窗口发射信号,主窗口的槽函数捕捉信号,然后获取信号中携带的数据;信号分为 PyQt5 的内置信号和自定义信号。
  • 示例代码如下:
    # -*- coding:utf-8 -*-
    # Time : 2019/09/20 下午 8:37 
    # Author : 御承扬
    # e-mail:[email protected]
    # project:  PyQt5
    # File : DateDialog02.py 
    # @software: PyCharm
    
    
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    
    
    class DateDialog(QDialog):
        Signal_OneParameter = pyqtSignal(str)
    
        def __init__(self, parent=None):
            super(DateDialog, self).__init__(parent)
            self.setWindowIcon(QIcon("./images/Python2.ico"))
            self.setWindowTitle('Child Dialog: Submit Signal')
    
            layout = QVBoxLayout(self)
    
            self.label = QLabel(self)
            self.label.setText('前者发射内置信号\n后者发射自定义信号')
    
            self.datetime_inner = QDateTimeEdit(self)
            self.datetime_inner.setCalendarPopup(True)
            self.datetime_inner.setDateTime(QDateTime.currentDateTime())
    
            self.datetime_emit = QDateTimeEdit(self)
            self.datetime_emit.setCalendarPopup(True)
            self.datetime_emit.setDateTime(QDateTime.currentDateTime())
    
            layout.addWidget(self.label)
            layout.addWidget(self.datetime_inner)
            layout.addWidget(self.datetime_emit)
    
            buttons = QDialogButtonBox(
                QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
                Qt.Horizontal, self)
    
            buttons.accepted.connect(self.accept)
            buttons.rejected.connect(self.reject)
            layout.addWidget(buttons)
    
            self.datetime_emit.dateTimeChanged.connect(self.emit_signal)
    
        def emit_signal(self):
            date_str = self.datetime_emit.dateTime().toString()
            self.Signal_OneParameter.emit(date_str)
    
    # -*- coding:utf-8 -*-
    # Time : 2019/09/20 下午 8:56 
    # Author : 御承扬
    # e-mail:[email protected]
    # project:  PyQt5
    # File : CallDialogMainWin2.py 
    # @software: PyCharm
    
    
    from 信号与槽.DateDialog02 import DateDialog
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    import sys
    
    
    class WinForm(QWidget):
        def __init__(self, parent=None):
            super(WinForm, self).__init__(parent)
            self.resize(400, 90)
            self.setWindowTitle('信号与槽传递参数示例')
            self.setWindowIcon(QIcon("./images/Python2.ico"))
            self.open_btn = QPushButton('获取时间')
            self.lineEdit_inner = QLineEdit(self)
            self.lineEdit_emit = QLineEdit(self)
            self.open_btn.clicked.connect(self.openDialog)
            self.lineEdit_inner.setText('接收子窗口内置信号的时间')
            self.lineEdit_emit.setText('接收子窗口自定义信号的时间')
            grid = QGridLayout()
            grid.addWidget(self.lineEdit_inner)
            grid.addWidget(self.lineEdit_emit)
            grid.addWidget(self.open_btn)
            self.setLayout(grid)
    
        def openDialog(self):
            dialog = DateDialog(self)
            dialog.datetime_inner.dateChanged.connect(self.deal_inner_slot)
            dialog.Signal_OneParameter.connect(self.deal_emit_slot)
            dialog.show()
    
        def deal_inner_slot(self, date):
            self.lineEdit_inner.setText(date.toString())
    
        def deal_emit_slot(self, dateStr):
            self.lineEdit_emit.setText(dateStr)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        form = WinForm()
        form.show()
        sys.exit(app.exec_())
    
  • 效果如下:
    PyQt5—事件处理_第5张图片
    PyQt5—事件处理_第6张图片

你可能感兴趣的:(PyQt5)