• 企业400电话
  • 网络优化推广
  • AI电话机器人
  • 呼叫中心
  • 全 部 栏 目

    网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    PyQt5通过信号实现MVC的示例
    POST TIME:2021-10-18 14:58

    众所周知MVC是个好东西。前阵子网上搜了下,但关于用PyQt5实现MVC的中文文档缺少之又少,优质的文档只搜到了一篇。既然这样,来,开个坑,学习新知识,吸引流量。话说,关于PyQt5,布局那里需要好好看看,容器类控件需要好好看看,还有多线程和自动化测试那块。但要写出完美GUI需要大量的代码经验和文档查询的能力。然后,嗯,这部分坑就填完了。

    扯回正题:假设此时面临的场景是,一个软件涉及好几个页面,每个页面是单独的代码。且每个页面需要有自己的controller,最终所有的controller汇总到一起,统一管理。

    本文中,文字只是辅助理解,务必读懂代码。

    信号

    众所周知,GUI中当一个控件的状态改变时需要通知另一个控件,也就是实现了对象间的通信。当事件发生或状态改变时,就会发出信号,信号会触发与这个事件相关联的函数,我们这个函数为槽。信号与槽可以是多对多的关系。信号在类创建时定义,即需要在初始化的前面定义。

    自定义信号与槽

    别问,静静感受以下代码。以下的代码中,已经包含了信号的定义、指定参数的类型、发射、绑定槽函数等一系列过程。

    from PyQt5.QtCore import QObject, pyqtSignal
    
    # 信号对象
    class QSignal(QObject):
      # 定义信号
      # 在类创建时定义,不能在类创建后作为类的属性而添加
      # 指定信号传递参数的数量,类型等
      send_msg = pyqtSignal(str, str)
    
      def __init__(self):
        super(QSignal, self).__init__()
    
      def run(self):
        # 信号发射
        self.send_msg.emit('First arg', 'Second arg')
    
    # 槽对象
    class QSlot(QObject):
      def __init__(self):
        super(QSlot, self).__init__()
    
      def get(self, *args):
        # 信号接收
        print("Get message =>" + args[0], args[1], sep=', ')
    
    if __name__ == '__main__':
      send = QSignal()
      slot = QSlot()
    
      # 将信号与槽函数绑定
      send.send_msg.connect(slot.get)
      # 外部调用 发射信号
      send.run()
      # 信号与槽解除关联
      send.send_msg.disconnect(slot.get)
      send.run()

    内置信号绑定自定义槽

    这样,再来看一个和窗口结合的实例。窗口中有一个按钮,点击按钮就退出窗口。虽然这个例子很简单,不用信号和槽也能实现。但这里给个例子静心感受下:信号连接、发射、接收的全逻辑。

    import sys
    from functools import partial
    from PyQt5.QtCore import pyqtSignal
    from PyQt5.QtWidgets import (QMainWindow, QApplication, QPushButton, QWidget, 
                   QHBoxLayout)
    
    
    class MainWindow(QMainWindow):
      btn_signal = pyqtSignal()
      def __init__(self):
        super(MainWindow, self).__init__()
    
        a = QPushButton("退出")
        # 给绑定的槽函数增加额外信息
        a.clicked.connect(partial(self.btn_clicked, 1))
        self.btn_signal.connect(self.close)
    
        self.setWindowTitle("演示")
    
        main_widget = QWidget()
        layout = QHBoxLayout()
        layout.addWidget(a)
        main_widget.setLayout(layout)
        # QMainWindow 不能设置布局
        self.setCentralWidget(main_widget)
    
      def btn_clicked(self, n):
        print(n)
        self.btn_signal.emit()
    
      def close(self):
        app = QApplication.instance()
        app.quit()
    
    
    if __name__ == "__main__":
      # 在shell中执行
      app = QApplication(sys.argv)
      mywin = MainWindow()
      mywin.show()
      # 开始主循环,直到退出
      sys.exit(app.exec())

    这里,想给绑定的槽函数btn_clicked传递额外参数,但信号绑定时不能添加额外参数。对应到上述例子中,close()可以通过指定信号的参数和类型来增加参数,但btn_clicked()不能。一种解决方案是掏出万能的partial函数,将函数和参数绑定在一起。

    至此,应该了解了信号的工作方式和原理。而关于信号更多的内容,如重载、装饰器等,这里不做更多介绍,详情参考官方文档。话说,也佩服当年的学习方式:『把所有代码敲一遍』。时至今日也忘记了大多控件的含义和各种样式的代码,变成了:到时候去查API。

    MVC

    MVC的大名应该都听说过,model, view 和 control,即数据库、页面和处理逻辑相分离,这样写出来的代码更加专一化。这里给份代码感受下,三个内容用三个类所实现,个人不建议这样写,建议将文件放到三个文件夹下,而不是扔进一份代码里:

    import sys
    from PyQt5 import QtCore
    from PyQt5.QtWidgets import (QWidget, QHBoxLayout, QPushButton, QMessageBox, 
                   QLineEdit, QApplication)
    
    # View
    class MainWindow(QWidget):
      verifySignal = QtCore.pyqtSignal()
    
      def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.id_line = QLineEdit()
        self.id_line.setPlaceholderText("请输入账号")
        self.psd_line = QLineEdit()
        self.psd_line.setPlaceholderText("请输入密码")
    
        self.init()
    
      def init(self):
    
        layout = QHBoxLayout()
        self.setLayout(layout)
    
        self.button = QPushButton("登录")
        layout.addWidget(self.button)
    
        layout.addWidget(self.id_line)
        layout.addWidget(self.psd_line)
      
        # 连接定义的信号
        self.button.clicked.connect(self.verify_emit)
    
      def verify_emit(self):
        self.verifySignal.emit()
    
      def verify_ok(self):
        QMessageBox.about(self, "密码正确", "已经登录")
    
      def verify_no(self):
        QMessageBox.about(self, "你犯了一个粗误", "请重新检查输入")
    
    # model
    class Student(object):
    
      def __init__(self):
        self.name = "aaa"
        self.password = "aaa"
    
    # control
    class LoginControll(object):
    
      def __init__(self):
        # 不需要从命令行输入参数
        self._app = QApplication([])
        self._model = Student()
        self._view = MainWindow()
        self.init()
    
      def init(self):
        self._view.verifySignal.connect(self.verify_user)
    
      def verify_user(self):
        id_ = self._view.id_line.text()
        psd_ = self._view.psd_line.text()
    
        if id_ == self._model.name and psd_ == self._model.password:
          self._view.verify_ok()
        else:
          self._view.verify_no()
    
      def run(self):
        self._view.show()
        # 事件循环,直到应用退出
        return self._app.exec_()
    
    # main.py
    if __name__ == "__main__":
      login_control_ = LoginControll()
      # 退出主程序
      sys.exit(login_control_.run())

    在这个例子里需要注意的是,将model,view和controller分成了三个类。在view中定义信号以及信号何时发射,在controller中定义信号发射后连接的槽函数,即触发何种的响应。这样,通过信号的发射与连接,就将view和controller绑定在了一起。view负责页面展示与信号定义,controller负责信号的连接与功能的实现,完美。

    MVC实现

    单页面

    如果读懂以上内容,那么应该可以实战了。首先给出一个demo,就是将上面最简单的MVC的例子拆分为三个文件。这里不便代码展示,请移步到我的github进行观看,这是文件结构,这是主文件。

    多页面

    在实现个复杂点的逻辑,多个页面,多个controller,文件结构如下所示,一个主文件,配三个文件夹,完美。这里命名时尽量规范,文件名、类名、函数名,不然容易把自己搞晕了。python main.py执行。

    MVC-demo
    ├─ main.py
    ├─ UI
    │  ├─ leftbtn_ui.py
    │  ├─ login_ui.py
    │  ├─ main_window_ui.py
    │  └─ verify_ui.py
    ├─ control
    │  ├─ controller.py
    │  ├─ leftbtn_control.py
    │  ├─ login_control.py
    │  └─ verify_control.py
    └─ model
        └─ model.py

    调用关系如下:

    这里需要注意的是变量的生存周期,main调用controller,controller调用其它的子controller,很容易在声明一个类后局部变量消失,导致信号无法连接。如在controller.py中,典型错误的写法:

    class Controll(object):
    
      def __init__(self):
    
        self._app = QApplication([])
    
        self._stu = Student()
    
        self._view = MainWindow()
        self.init()
    
      def init(self):
        # 子 controller 作为局部变量,调用完后立刻消失,所以无法连接信号和槽
        # 这个问题困扰了我三天,可真是滑稽
        login_controller = login_control.Controller(self._view, self._stu.name, self._stu.password)
    

    因为代码文件实在太多且混乱,就不在这里展示了,不然读者会更容易感到乱。这里只展示一个效果,完整代码见我的github。其实看一个例子,就啥都懂了。

    以上就是PyQt5通过信号实现MVC的示例的详细内容,更多关于PyQt5通过信号实现MVC的资料请关注脚本之家其它相关文章!

    您可能感兴趣的文章:
    • PyQt5 matplotlib画图不刷新的解决方案
    • PyQt5 设置窗口全屏显示方式
    • Pyqt5 实现窗口缩放,控件在窗口内自动伸缩的操作
    • 解决PyQt5 无边框后窗口的移动问题
    • PyQt5 实现给无边框widget窗口添加背景图片
    • PyQt5中QSpinBox计数器的实现
    • pyqt5 设置窗体透明控件不透明的操作
    上一篇:python 利用matplotlib在3D空间绘制二次抛物面的案例
    下一篇:python 利用matplotlib在3D空间中绘制平面的案例
  • 相关文章
  • 

    关于我们 | 付款方式 | 荣誉资质 | 业务提交 | 代理合作


    © 2016-2020 巨人网络通讯

    时间:9:00-21:00 (节假日不休)

    地址:江苏信息产业基地11号楼四层

    《增值电信业务经营许可证》 苏B2-20120278

    X

    截屏,微信识别二维码

    微信号:veteran88

    (点击微信号复制,添加好友)

     打开微信