PyQt与MVC模式

36 投票
4 回答
30721 浏览
提问于 2025-04-15 15:35

我正在尝试用PyQt设计一个MVC模式的程序。我的想法是把所有的程序分成三个部分:

  1. 从所有Qt类中抽象出来的类(模型)
  2. 将模型中的数据提供给Qt应用的类(控制器)
  3. Qt应用本身,里面有一个定义好的方法SignalsToSlots,用来连接信号和控制器。

这样设计是否最优?在PyQt开发中,推荐使用什么样的方案?

4 个回答

7

这里有一个链接,里面详细介绍了Qt架构是如何为应用程序提供模型-视图设计的。

http://doc.qt.io/qt-5/model-view-programming.html

在Qt中,视图和控制器是结合在一起的,因此可以使用模型-视图框架来设计应用程序。

模型负责和数据源进行沟通,为架构中的其他组件提供一个接口。沟通的方式取决于数据源的类型以及模型的实现方式。视图从模型中获取模型索引;这些索引是数据项的引用。通过将模型索引提供给模型,视图可以从数据源中获取数据项。在标准视图中,代理负责渲染数据项。当某个数据项被编辑时,代理会直接使用模型索引与模型进行沟通。

...

模型、视图和代理之间通过信号和槽进行沟通。

9

没错,PyQt使用的是模型/视图的概念(官方上没有“控制器”这一部分),但你可能对这个在PyQt中的意思有些误解。

它主要分为两个部分:

  1. 模型,这些模型是从PyQt的基础抽象模型类(比如 QAbstractItemModelQAbstractTableModelQAbstractListModel 等)派生出来的。这些模型可以直接和你的数据源(比如文件、数据库)进行沟通,或者代理你之前写的与PyQt无关的模型。
  2. 视图,这些视图是由Qt库实现的,通常不需要任何修改(例如:QTreeViewQTableView等)。甚至一些简单的控件,比如 QComboBox 也可以作为PyQt模型的视图。

你应用程序中的其他部分,比如响应信号等,可以被视为“控制器”。

PyQt还提供了一些预定义的“通用”模型,如果你只需要简单的功能,可以直接使用或继承这些模型,比如 QStringListModelQStandardItemModel 等。此外,还有一些模型可以直接与数据库沟通,比如 QSqlTableModel

47

首先,你应该使用Qt4设计工具来设计你的界面,然后用pyuic4来生成Python的图形用户界面(GUI)。这个生成的文件就是你的视图,记住,绝对不要手动编辑这些Python文件。所有的修改都要通过设计工具来完成,这样可以确保你的视图和模型、控制器是分开的。

对于控制部分,创建一个中心类,这个类要继承自你的基本GUI组件,比如QMainWindow。这个对象会包含一个成员ui,这个ui就是你刚刚生成的视图对象。

这里有一个来自教程的例子。

更新于2013年:这里有一些关于PyQt和MVC模型的最新教程 PyQt MVC教程系列

import sys
from PyQt4 import QtCore, QtGui
from edytor import Ui_notepad

class StartQT4(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.ui = Ui_notepad()
        self.ui.setupUi(self)


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    myapp = StartQT4()
    myapp.show()
    sys.exit(app.exec_())

上面例子中的关键点是控制器包含了ui,但并没有直接继承它。控制器负责管理你的GUI的信号和槽的连接,并为你的数据模型提供接口。

为了描述模型部分,我们需要一个例子,假设你的项目是创建一个电影收藏数据库。模型会包含表示单个电影的内部对象,以及表示电影列表的对象。控制器会从视图中获取输入的数据,并捕捉信号,然后在请求模型更新之前验证这些数据。这一点非常重要,控制器尽量不要直接访问模型,而是应该请求模型自己去访问。

这里有一个小例子来展示这种交互(未经测试,可能有一些拼写错误):

class Movie():
    def __init__(self,title=None,year=None,genre=None):
        self.title=title
        self.year=year
        self.genre=genre
    def update(self,title=None,year=None,genre=None):
        self.title=title
        self.year=year
        self.genre=genre
    def to_xml(self,title=None,date=None,genre=None):
        pass #not implementing this for an example!

#when the controller tries to update it should use update function
movie1.update("Manos Hands Of Fate",1966,"Awesome")
#don't set by direct access, your controller shouldn't get that deep
movie1.title="Bad Idea" #do not want!

在MVC中,集中访问也很重要,比如用户可以通过双击屏幕上的标题来更改标题,或者通过点击标题字段旁边的编辑按钮来更改,这两种方式都应该使用相同的方法来进行更改。我并不是说每一种方式都调用movie.update_title(title),而是说这两种信号应该在控制器中使用同一个方法。

尽量让视图和控制器之间的关系是多对一的。也就是说,如果你有5种方式在GUI中更改某个东西,就应该在控制器中有一个方法来处理这个。如果这些槽不兼容,那么就为每种方法创建一个方法,然后调用同一个方法。如果你为5种视图样式解决了5次问题,那就没有理由把视图和控制分开。而且现在你在控制器中只有一种方式来做事情,这样控制器和模型之间就形成了良好的1对1关系。

至于让你的模型完全与Qt分开,其实并不是必要的,反而可能会让你更麻烦。在模型中使用QString等东西是很方便的,如果在另一个应用中你不想要GUI的开销,但又想要模型,只需导入QtCore即可。希望这些能对你有所帮助!

撰写回答