版权声明
无需授权即可转载

Model和View

界面的事就写到这,后续需要什么跟界面相关的技术再临时补,毕竟简单些,现在得写点技术性的东西了
MVC是一种设计模式,说简单点就是数据和视图要分离,数据是数据,界面是界面,互相不掺和,中间设计一个东西来实现交互,这样谁出问题动谁就行,在Qt中就存在这样的Model,负责将数据呈现在界面中,针对不同的数据结构,使用不同的Model,list有list的Model,table自然就要用到table, 树状结构数据也有自己的Model,这三个Model能解决大多数数据在界面呈现的问题
说到底Model处理的数据,Viwe处理的是界面
说人话的PYQT5『3』 - 图1
更详细的可以看这个链接model view

通过一个TODO的例子来说下这事,红色的字事每个控件的名字,这是在QtDesigner中画的,大家可以自己试着弄下,QtDesigner的下载链接是这个QtDesigner
image.png
基本功能就是在todoEdit上写字,然后按下Add Todo按钮,就在todoView中看到你写的这几个字了,写的字就是数据,todoView就是界面,这就是MV的结构,这里面我加了一个todoCombo的控件,可以看到同一个数据源会在不同的界面元素上呈现

QtDesigner

插一句,如何把QtDesigner设计的界面导入到Python中
第一步 在终端,就是windows上的cmd,输入

  1. pyuic5 '你自己界面文件'.ui -o '要转换的python文件名'.py

第二步 在程序里面调用,像这样
image.png

第三步 运行,真是所见即所得
image.png

接着

Qt提供了几个标准Model,常用的是QAbstractTableModel和QAbstractListModel,看名字就知道一个对应表格,一个对应列表,这个TODO的例子明显要用List的Model,因为数据是线性的,一维的,不是二维的
开始之前第一步需要先设计一下数据的结构,既然是Todo,那么就应该有要做的事,和这个事做没做完,要做的事可以用str数据类型,做没做完可以用bool类型,而且Todo的事也不可能只有一件,那么列表结构肯定是首选,然后每一件事可以用一个元组,最后的数据结构可以是这个样

  1. TODos = [(True, 'task1'), (False, 'task2'), (True, 'task3'),..............]

image.png

TodoModel是我们自己写的类,要继承自QAbstractListModel,因为这个是Qt已经写好的东西,这个类里面有两个方法,一个data,一个rowCount
先来看rowCount,这个简单点,rowCount说通俗点就是返回的数就是View视图中要显示的行数,比如你可以把data函数注释掉,然后在rowCount函数里直接返回一个5,像这样
image.png
然后运行下
001.gif
可以看到rowCount就是占位子,把位置先占好,但是数据得data函数来整,所以rowCount这个函数

  1. return len(self.todos)

就能理解了,todos列表中有多少东西,就给你占多少位置,说的学术点就是从数据中获取列表长度返回给View
接着再来看data函数,data函数有两个参数,一个index,这是个类,一个是role,这是个int类型,这些都不重要,重要的是index就是数据的位置,它有两个方法,一个是.row(),一个是.column(),针对Table,这两个方法都得用到,因为数据是二维的,而针对列表,column方法就可以不用了,因为数据是一维的
所以要这么写,等号左边是两个值,是因为列表里面存的是元组

  1. status, value = self.todos[index.row()]

接下来再来看看role,有些时候我真觉得代码这个事有点像玄学,云里雾里的,为什么要有role这个参数,因为数据不一定总是呈现在表格里或者列表框里,有些时候还会呈现出别的样子,比如我想让数据以icon来呈现,或者我想让数据显示成一个Tooltip,role可以理解为数据呈现的样子或者地方或者形式或者你想到的任何样子
DisplayRole就是最常见的一种,就是呈现原本的样子,在控件中显示一条数据
可以先在todos这个列表里加点内容
image.png
然后运行下可以看到数据显示出来了
image.png
到目前为止,数据和界面已经分离了,自始至终都没在界面上写什么代码,只是在处理跟数据相关的代码,接下来要实现按钮的功能了,其实到目前再想想也简单,就是怎么样把东西添加到todos的列表中。到这个节点,完整的代码如下
image.png

处理按钮操作

第一个按钮是Add Todo,按下后就将文本框里的条目添进去
image.png
第6行这段代码是一个signal,它的作用可以简单理解成把上面的窗口刷新一下让填进去的条目显示出来,你也可以去掉这段看看效果是什么
002.gif

接下来是删除条目,操作步骤是选中一条,然后点Delete按钮删掉
image.png
你可以通过运行

  1. print(self.model.todos[indexes[0].row()])

实际看看输出的内容,最后效果是这样的
003.gif
最后来看看Complete这个按钮,操作肯定是选择条目,按下按钮,然后要么是这个条目前面显示个对勾,要么是删除,要么是划道删除线,这里实现一下前面加个对勾的操作吧,对勾肯定是一个图形,然后通过数据里面那个True和Fasle来决定到底加不加,点下Complete按钮其实就是把选中条目里面的Fasle改成True,之后数据端看到这个变化,加一个对勾的icon在上面,所有首先要改一下Model端的代码
image.png
data函数里面需要增加一个Role,这个role就是来让数据以图像的形式来显示,关于role前面说过了,可以翻翻,status如果是True,则加一个tick的图片,tick就是对勾图片。
之后是View端的代码
image.png
这里面第8行用了一个dataChianged的signal,为什么没用layoutChanged,因为这次修改,界面没有什么变化,只是数据变化了,所以这个Signal会传递到Model告诉他数据改变了,然后Model层加个对勾到条目前面
004.gif
有些时候条目添加错了,要修改,因为操作的是数据,所以在Model层可以这样做
image.png这两个方法都是Model提供的方法,效果是这样的
005.gif
每次窗口一打开,又全是空白一片,我想要显示之前的内容,可以通过将内容写出到文件进行保存,然后每次运行时再打开之前保存的文件即可
需要写两个方法,一个load,一个Save
image.png
load方法要在程序运行的时候调用,save方法要在每一次鼠标操作后进行调用,还有一个地要加这个保存的方法,你们自己想想吧,完整的代码是这样的
image.png