PyQt5通过信号实现MVC的示例


Posted in Python onFebruary 06, 2021

众所周知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

调用关系如下:

PyQt5通过信号实现MVC的示例

这里需要注意的是变量的生存周期,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通过信号实现MVC的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python实现的二维码生成小软件
Jul 11 Python
Python多进程同步Lock、Semaphore、Event实例
Nov 21 Python
python提取内容关键词的方法
Mar 16 Python
安装ElasticSearch搜索工具并配置Python驱动的方法
Dec 22 Python
Python连接DB2数据库
Aug 27 Python
Python使用wxPython实现计算器
Jan 30 Python
Python快速查找list中相同部分的方法
Jun 27 Python
基于python的Paxos算法实现
Jul 03 Python
python连接PostgreSQL过程解析
Feb 09 Python
详解python内置常用高阶函数(列出了5个常用的)
Feb 21 Python
python统计文章中单词出现次数实例
Feb 27 Python
Django:使用filter的pk进行多值查询操作
Jul 15 Python
python 利用matplotlib在3D空间绘制二次抛物面的案例
Feb 06 #Python
python 利用panda 实现列联表(交叉表)
Feb 06 #Python
jupyter 添加不同内核的操作
Feb 06 #Python
解决import tensorflow导致jupyter内核死亡的问题
Feb 06 #Python
PyCharm常用配置和常用插件(小结)
Feb 06 #Python
完美解决torch.cuda.is_available()一直返回False的玄学方法
Feb 06 #Python
python反扒机制的5种解决方法
Feb 06 #Python
You might like
安装PHP可能遇到的问题“无法载入mysql扩展” 的解决方法
2007/04/16 PHP
php 记录进行累加并显示总时长为秒的结果
2011/11/04 PHP
PHP简单检测网址是否能够正常打开的方法
2016/09/04 PHP
解决PHP上传非标准格式的图片pjpeg失败的方法
2017/03/12 PHP
php制作圆形用户头像的实例_自定义封装类源代码
2017/09/18 PHP
php-msf源码详解
2017/12/25 PHP
IE iframe的onload方法分析小结
2010/01/07 Javascript
基于Jquery的$.cookie()实现跨越页面tabs导航实现代码
2011/03/03 Javascript
『JavaScript』限制Input只能输入数字实现思路及代码
2013/04/22 Javascript
JQUERY实现网页右下角固定位置展开关闭特效的方法
2015/07/27 Javascript
PHP+jQuery+Ajax+Mysql如何实现发表心情功能
2015/08/06 Javascript
Bootstrap项目实战之子栏目资讯内容
2016/04/25 Javascript
AngularJS下对数组的对比分析
2016/08/24 Javascript
在线引用最新jquery文件的实现方法
2016/08/26 Javascript
Vue.js学习之计算属性
2017/01/22 Javascript
JS实现商品筛选功能
2020/08/19 Javascript
JS添加或删除HTML dom元素的方法实例分析
2019/03/05 Javascript
微信小程序实现星星评分效果
2020/11/01 Javascript
python实现百度关键词排名查询
2014/03/30 Python
解析Python的缩进规则的使用
2019/01/16 Python
完美解决python针对hdfs上传和下载的问题
2020/06/05 Python
Python3.7安装pyaudio教程解析
2020/07/24 Python
浅谈Selenium+Webdriver 常用的元素定位方式
2021/01/13 Python
魅力惠奢品线上平台:MEI.COM
2016/11/29 全球购物
迪奥官网:Dior.com
2018/12/04 全球购物
洛杉矶时尚女装系列:J.ING US
2019/03/17 全球购物
Silk Therapeutics官网:清洁、抗衰老护肤品
2020/08/12 全球购物
软件售后服务方案
2014/05/29 职场文书
普通党员个人剖析材料
2014/10/08 职场文书
民主评议政风行风活动心得体会
2014/10/29 职场文书
个人事迹材料范文
2014/12/29 职场文书
2015年小学数学教师个人工作总结
2015/05/25 职场文书
硕士毕业答辩开场白
2015/05/27 职场文书
赵氏孤儿观后感
2015/06/09 职场文书
详解OpenCV曝光融合
2022/04/29 Python
JavaScript实现九宫格拖拽效果
2022/06/28 Javascript