PyQt5

PyQt5实现文件传输程序(四):事件、信号与槽

PyQt提供了两种通信机制:底层事件处理机制,类似于所有其他GUI库提供的机制,以及Trolltech(Qt的制造商)所称的高级机制 “信号和槽”。“信号与槽”的通信机制是Qt最明显、最强大的特性之一,本文就重点讲一讲PyQt中的信号与槽,以及如何利用信号与槽实现界面的跳转。

一、什么是信号与槽?

在绝大多数 GUI 库中,每个事件的发生往往伴随十分详细的信息,以供开发人员使用。
例如鼠标点击的精确坐标、按钮是通过鼠标按下还是通过空格键按下等等。
然而很多情况下,我们并不需要知道这些行为具体是如何发生的,只需要知道用户想要做些什么。
例如,我们只关心哪个按钮被按下,而不用知道用户是如何按下这个按钮的。
也就是说,对于低层次的具体细节,我们并不需要知道那么多。
相反,我们关心的是高层次的行为事件。
为此,Qt 提出了信号与槽的概念,抛开具体的细节,将一些行为抽象为某个信号;
而针对这些行为做出的反应,我们称之为槽。
简单来说,用户做出的某些行为会发出特定信号,而槽就是用来处理这些信号的函数。

二、如何使用信号与槽?

部件发出的信号有两种,一种是部件本身就已经绑定的信号,如QPushButton的点击事件就会自动发出一个clicked的信号,所以,对于按钮点击之后要执行的动作,我们只需要这样写就可以。

btn = QPushButton('选择')

# 处理函数无参数时,为按钮绑定点击事件
btn.clicked.connect(deal1)  #传入一个函数名,作为处理函数

# 处理函数
def deal1() :
    # 执行动作

# 处理函数有参数时,为按钮绑定点击事件,推荐使用如下方法
btn.clicked.connect(lambda: deal2(参数))  #传入一个函数名,作为处理函数

def deal2(*args):
    # 执行动作

但是,对于我们想要的动作,但是没有对应的信号怎么办呢?我们可以自定义一个。

from PyQt5.QtCore import pyqtSignal

sendSignal = pyqtSignal(dict)   # 定义一个信号,信号携带一个字典类型的参数

# 将信号与槽函数绑定
sendSignal.connect(send)

# 发生一些事件

sendSignal.emit(data) # 发送一个信号,data是一个字典,将被传递给槽函数

# 槽函数, 对传递来的数据进行处理并执行一些动作
def send(data):
    q.put(data)

三、我能用信号与槽做什么?

从表面上看上去,信号与槽和其他GUI程序的通信机制并无不同,但实际上信号与槽强大的地方在于它是可以跨界面(类)、跨进程的,即一个界面或一个进程的信号可以连接到另一个界面或进程的槽函数上,并且一个信号可以绑定多个槽函数,实现一次发射,多个响应。下面,我们就利用信号与槽来实现登录界面与注册界面的跳转。

四、利用信号与槽实现界面跳转

首先定义好登录界面和注册界面

from rnd import *
from plabel import PLable
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QLabel, QLineEdit, QGridLayout, QPushButton

class Login(QWidget):

    rgsSignal = pyqtSignal()    # 跳转至注册界面的信号
    statSignal = pyqtSignal(str)    # 更新面板状态栏的信号

    def __init__(self):
        super().__init__()

        self.initUI()
        #t2 = Thread(target=self.startservice, args=(q, ))
        #t2.start()

    # 绘制图形界面
    def initUI(self):
        grid = QGridLayout()
        grid.setSpacing(10)

        # 用户名输入
        lbu = QLabel('用户名:', self)
        self.ur = QLineEdit(self)
        grid.addWidget(lbu, 1, 0, 1, 2)
        grid.addWidget(self.ur, 1, 1, 1, 2)

        # 密码输入
        lbp = QLabel('密码:', self)
        self.pw = QLineEdit(self)
        self.pw.setEchoMode(QLineEdit.Password)
        grid.addWidget(lbp, 2, 0, 1, 1)
        grid.addWidget(self.pw, 2, 1, 1, 2)

        # 验证码输入
        lbrn = QLabel('请输入验证码:', self)
        self.irn = QLineEdit(self)
        self.lbrnp = PLable()
        self.rnd = get_rnd(30, 60)  # 获取验证码
        pic = QPixmap('code.png')
        self.lbrnp.setPixmap(pic)
        grid.addWidget(lbrn, 3, 0, 1, 1)
        grid.addWidget(self.irn, 3, 1, 1, 1)
        grid.addWidget(self.lbrnp, 3, 2, 1, 1)
        self.lbrnp.clicked.connect(self.change_rnd)

        # 登录按钮
        bt1 = QPushButton('登录')
        grid.addWidget(bt1, 4, 2)

        # 注册账号
        bt2 = QPushButton('注册账号')
        grid.addWidget(bt2, 4, 1)
        bt2.clicked.connect(self.go_rgs)

        # 设置布局
        self.setLayout(grid)
        # self.resize(400, 300)
        # self.setWindowTitle('登录')
        # self.show()

    # 当用户点击或登录报错时更换验证码
    def change_rnd(self):
        self.irn.clear()
        self.rnd = get_rnd(30, 60)
        pic = QPixmap('code.png')
        self.lbrnp.setPixmap(pic)

    # 跳转至注册页面
    def go_rgs(self):
        self.close()    # 关闭界面
        self.rgsSignal.emit()   # 发出跳转信号

from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QLabel, QLineEdit, QGridLayout, QPushButton, QRadioButton

class Regis(QWidget):

    lgSignal = pyqtSignal() # 跳转到注册页面信号
    statSignal = pyqtSignal(str)    # 更新状态栏的信号

    def __init__(self):
        super().__init__()

        self.initUI()

    # 绘制注册界面
    def initUI(self):
        grid = QGridLayout()
        grid.setSpacing(10)

        # 输入用户名
        lbu = QLabel('用户名:', self)
        self.ur = QLineEdit(self)
        grid.addWidget(lbu, 1, 0, 1, 2)
        grid.addWidget(self.ur, 1, 1, 1, 2)

        # 输入密码
        lbp = QLabel('密码:', self)
        self.pw = QLineEdit(self)
        self.pw.setEchoMode(QLineEdit.Password)
        grid.addWidget(lbp, 2, 0, 1, 1)
        grid.addWidget(self.pw, 2, 1, 1, 2)

        # 确认密码
        lbrp = QLabel('确认密码:', self)
        self.rpw = QLineEdit(self)
        self.rpw.setEchoMode(QLineEdit.Password)
        grid.addWidget(lbrp, 3, 0, 1, 1)
        grid.addWidget(self.rpw, 3, 1, 1, 2)

        # 同意服务协议
        self.bta = QRadioButton('我已阅读并同意本软件的服务协议')
        grid.addWidget(self.bta, 4, 0, 1, 3)
        self.bta.toggled.connect(self.btstate)

        # 注册按钮
        self.btr = QPushButton('注册')
        grid.addWidget(self.btr, 5, 2)
        self.btr.setEnabled(False)
        self.btr.clicked.connect(self.check)

        # 取消按钮
        btc = QPushButton('取消')
        grid.addWidget(btc, 5, 1)
        btc.clicked.connect(self.go_lg)

        self.setLayout(grid)
        # self.resize(400, 300)
        # self.setWindowTitle('注册')
        # self.show()

    # 进行用户输入验证
    def check(self):
        pw = self.pw.text()
        if pw != self.rpw.text():
            self.statSignal.emit('密码输入不一致')
            self.pw.clear()
            self.rpw.clear()
        else:
            # 发送数据给服务器端

    # 切换注册按钮状态
    def btstate(self):
        sender = self.sender()
        if sender.isChecked() == True:
            self.btr.setEnabled(True)
        else:
            self.btr.setEnabled(False)

    # 跳转至登录界面
    def go_lg(self):
        self.lgSignal.emit()
        self.close()

之后定义一个主窗口(QMainWindow)来管理切换界面布局。

import sys
import qdarkstyle
from login import Login
from cform import CForm
from queue import Queue
from client import Client
from register import Regis
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import pyqtSignal, QCoreApplication

class MForm(QMainWindow):

    def __init__(self):
        super().__init__()
        self.ur = ''
        self.stat = self.statusBar()    # 初始化状态栏
        self.initUI()

    # 初始化界面
    def initUI(self):
        self.resize(400, 300)
        self.to_lg()

    # 跳转至注册页面
    def to_rgs(self):
        self.rgs = Regis()  # 新建注册对象

        # 绑定信号与槽
        self.rgs.statSignal.connect(self.change_stat)
        self.rgs.lgSignal.connect(self.to_lg)

        self.setWindowTitle('注册')

        self.setCentralWidget(self.rgs)

    # 跳转至登录界面
    def to_lg(self):
        self.lg = Login()   # 新建登录对象

        # 绑定信号与槽
        self.lg.rgsSignal.connect(self.to_rgs)
        self.lg.statSignal.connect(self.change_stat)

        self.setWindowTitle('登录')
        self.setCentralWidget(self.lg)

    # 更新状态栏
    def change_stat(self, s):
        self.stat.showMessage(s)

# 启动程序
if __name__ == '__main__':
    QCoreApplication.addLibraryPath('.')
    app = QApplication(sys.argv)
    app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
    mf = MForm()
    mf.show()
    sys.exit(app.exec_())

上述代码经过了删减,主要体现利用信号与槽进行界面跳转的思想。
其实还有一个实现界面跳转的好方法,就是使用QStackedWidget或者QStackedLayout,这两个类提供了多页面切换的布局,一次只能看到一个界面。