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的Tornado框架异步编程入门实例
Apr 24 Python
Python中字典映射类型的学习教程
Aug 20 Python
python kmeans聚类简单介绍和实现代码
Feb 23 Python
用Python分析3天破10亿的《我不是药神》到底神在哪?
Jul 12 Python
在Python中分别打印列表中的每一个元素方法
Nov 07 Python
pycham查看程序执行的时间方法
Nov 29 Python
给我一面国旗 python帮你实现
Sep 30 Python
python利用dlib获取人脸的68个landmark
Nov 27 Python
Python中Selenium库使用教程详解
Jul 23 Python
Python 操作 MySQL数据库
Sep 18 Python
Python操作dict时避免出现KeyError的几种解决方法
Sep 20 Python
使用gunicorn部署django项目的问题
Dec 30 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
20个PHP常用类库小结
2011/09/11 PHP
php合并js请求的例子
2013/11/01 PHP
php获取数组元素中头一个数组元素值的实现方法
2014/12/20 PHP
PHP SPL标准库之接口(Interface)详解
2015/05/11 PHP
分享PHP函数实现数字与文字分页代码
2015/07/28 PHP
ThinkPHP整合datatables实现服务端分页的示例代码
2018/02/10 PHP
Referer原理与图片防盗链实现方法详解
2019/07/03 PHP
js滚动条多种样式,推荐
2007/02/05 Javascript
基于JQUERY的两个ListBox子项互相调整的实现代码
2011/05/07 Javascript
Fastest way to build an HTML string(拼装html字符串的最快方法)
2011/08/20 Javascript
Javascript 闭包引起的IE内存泄露分析
2012/05/23 Javascript
javascript实现动态侧边栏代码
2014/02/19 Javascript
JS去掉第一个字符和最后一个字符的实现代码
2014/02/20 Javascript
node.js中的path.join方法使用说明
2014/12/08 Javascript
window.onload与$(document).ready()的区别分析
2015/05/30 Javascript
Javascript实现div的toggle效果实例分析
2015/06/09 Javascript
JS原型链 详解及示例代码
2016/09/06 Javascript
xmlplus组件设计系列之分隔框(DividedBox)(8)
2017/05/02 Javascript
利用JavaScript如何查询某个值是否数组内
2017/07/30 Javascript
原生javascript实现文件异步上传的实例讲解
2017/10/26 Javascript
js实现上传按钮并显示缩略图小轮子
2020/05/04 Javascript
解决vue 退出动画无效的问题
2020/08/09 Javascript
python将html转成PDF的实现代码(包含中文)
2013/03/04 Python
pyside写ui界面入门示例
2014/01/22 Python
python简单程序读取串口信息的方法
2015/03/13 Python
Python多线程编程(二):启动线程的两种方法
2015/04/05 Python
python requests模拟登陆github的实现方法
2019/12/26 Python
python 爬取百度文库并下载(免费文章限定)
2020/12/04 Python
解决html5中video标签无法播放mp4问题的办法
2017/05/07 HTML / CSS
阿拉伯世界最大的电子卖场:Souq埃及
2016/08/01 全球购物
迪士尼法国在线商店:shopDisney FR
2020/12/03 全球购物
实现strstr功能,即在父串中寻找子串首次出现的位置
2016/08/05 面试题
会计师职业生涯规划范文
2014/02/18 职场文书
四风问题自查自纠工作情况报告
2014/10/28 职场文书
面试被问select......for update会锁表还是锁行
2021/11/11 MySQL
css弧边选项卡的项目实践
2023/05/07 HTML / CSS