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编写一个国际象棋AI程序
Nov 28 Python
详解Python当中的字符串和编码
Apr 25 Python
Python简单实现的代理服务器端口映射功能示例
Apr 08 Python
Python实现的根据IP地址计算子网掩码位数功能示例
May 23 Python
详解TensorFlow查看ckpt中变量的几种方法
Jun 19 Python
python实现扫描ip地址的小程序
Apr 16 Python
Python Pickle 实现在同一个文件中序列化多个对象
Dec 30 Python
python 计算方位角实例(根据两点的坐标计算)
Jan 17 Python
tensorflow安装成功import tensorflow 出现问题
Apr 16 Python
PySide2出现“ImportError: DLL load failed: 找不到指定的模块”的问题及解决方法
Jun 10 Python
Python 制作自动化翻译工具
Apr 25 Python
python基础之模块的导入
Oct 24 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正则的Unknown Modifier错误解决方法
2010/03/02 PHP
通过curl模拟post和get方式提交的表单类
2014/04/23 PHP
PHP CodeIgniter框架的工作原理研究
2015/03/30 PHP
PHP实现微信申请退款功能
2018/10/01 PHP
JavaScript 字符串乘法
2009/08/20 Javascript
一些相见恨晚的 JavaScript 技巧
2010/04/25 Javascript
javascript常用方法、属性集合及NodeList 和 HTMLCollection 的浏览器差异
2010/12/25 Javascript
html中的input标签的checked属性jquery判断代码
2012/09/19 Javascript
javascript中setTimeout和setInterval的unref()和ref()用法示例
2014/11/26 Javascript
javascript 判断两个日期之差的示例代码
2015/09/05 Javascript
Node.js中常规的文件操作总结
2016/10/13 Javascript
Vue.js 和 MVVM 的注意事项
2016/11/07 Javascript
vue2+el-menu实现路由跳转及当前项的设置方法实例
2017/11/07 Javascript
微信小程序动画(Animation)的实现及执行步骤
2018/10/28 Javascript
vue Treeselect 树形下拉框:获取选中节点的ids和lables操作
2020/08/15 Javascript
python之wxPython菜单使用详解
2014/09/28 Python
Python导入oracle数据的方法
2015/07/10 Python
Python基于动态规划算法计算单词距离
2015/07/25 Python
python去除空格和换行符的实现方法(推荐)
2017/01/04 Python
使用Python进行AES加密和解密的示例代码
2018/02/02 Python
零基础使用Python读写处理Excel表格的方法
2019/05/02 Python
python配置文件写入过程详解
2019/10/19 Python
python 函数嵌套及多函数共同运行知识点讲解
2020/03/03 Python
使用已经得到的keras模型识别自己手写的数字方式
2020/06/29 Python
Python xlrd/xlwt 创建excel文件及常用操作
2020/09/24 Python
Python爬虫分析微博热搜关键词的实现代码
2021/02/22 Python
CSS3 :default伪类选择器使用简介
2018/03/15 HTML / CSS
意大利和国际最佳时尚品牌:Drestige
2019/12/28 全球购物
名词解释型面试题(主要是网络)
2013/12/27 面试题
考试不及格的检讨书
2014/01/22 职场文书
银行批评与自我批评
2014/02/10 职场文书
财务会计专业求职信
2014/06/09 职场文书
入学证明
2015/06/23 职场文书
《槐乡的孩子》教学反思
2016/02/20 职场文书
Python+Appium实现自动抢微信红包
2021/05/21 Python
Python面向对象之成员相关知识总结
2021/06/24 Python