MySQL on Qt (3): SQL的MVC结构基础知识总结

写在前面:

大一下学期的 CPP 大作业“自主订餐系统”可真是颇费了一些力气,每天晚睡早起撸代码,课都没听,很多知识都是现学现用。

为了避免当时学到的东西给全部忘光光(`_>`,我这脑子啊。。。),所以我把当时大作业的一些核心部分(一些零碎的小细节,能记就记)给记录一下,一方面是复习,一方面也方便以后用到的时候查阅。

这篇文章是大作业 MySQL + SQLite 操作相关部分的第三章,主要讲一下 Qt 中 SQL 的 MVC 结构的基础知识,包括一部分的 MVC 原理介绍和基础使用。

什么是 MVC 结构

关于 MVC 结构的具体介绍,可以看一个名为《Qt中文文档》的 Github 项目中 Qt 关于 MVC 的官方文档的中文翻译版(这里打个广告=。=):

模型/视图 编程

https://www.cryfeifei.cn/2020/08/08/qt-zhong-wen-wen-dang-mo-xing-shi-tu-jian-jie/

也可以看上一篇我转载自网络的文章:

Qt Model/View(模型/视图)结构(无师自通)

http://www.skykeyjoker.com/2020/07/23/model-view/

以上两篇网络文章我都转载到了我的博客中。

这里我就不再讲太多太细的知识和原理,只是简单概括一下。

GUI 应用程序的一个很重要的功能是由用户在界面上编辑和修改数据,典型的如数据库应用程序。数据库应用程序中,用户在界面上执行各种操作,实际上是修改了界面组件所关联的数据库内的数据。

Model/View(模型/视图)结构是 Qt 中用界面组件显示与编辑数据的一种结构,视图(View)是显示和编辑数据的界面组件,模型(Model)是视图与原始数据之间的接口。

将界面组件与所编辑的数据分离开来,又通过数据源的方式连接起来,是处理界面与数据的一种较好的方式。Qt 使用 Model/View 结构来处理这种关系,Model/View 的基本结构如图 1 所示。

Model/View基本结构

其中各部分的功能如下:

  • 数据(Data)是实际的数据,如数据库的一个数据表或SQL查询结果,内存中的一个 StringList,或磁盘文件结构等。
  • 视图或视图组件(View)是屏幕上的界面组件,视图从数据模型获得每个数据项的模型索引(model index),通过模型索引获取数据,然后为界面组件提供显示数据。Qt 提供一些现成的数据视图组件,如 QListView、QTreeView 和 QTableView 等。
  • 模型或数据模型(Model)与实际数据通信,并为视图组件提供数据接口。它从原始数据提取需要的内容,用于视图组件进行显示和编辑。Qt 中有一些预定义的数据模型,如 QStringListModel 可作为 StringList 的数据模型,QSqlTableModel 可以作为数据库中一个数据表的数据模型。

开启 MVC 之旅

讲完又臭又长的理论,我们就要进入令人激动的实践啦。

外甥打灯笼,这次我也是写了一个 Demo 来演示具体的应用。

demo

与以往不同,这次会先把全部工程代码贴出来再分块讲。

sqlmvcdemo.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#ifndef SQLMVCDEMO_H
#define SQLMVCDEMO_H

#include <QWidget>
#include <QDebug>

#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLineEdit>
#include <QLabel>

#include <QTableView>
#include <QHeaderView>
#include <QSqlTableModel>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlRecord>


QT_BEGIN_NAMESPACE
namespace Ui { class SQLMVCDemo; }
QT_END_NAMESPACE

class SQLMVCDemo : public QWidget
{
Q_OBJECT

public:
SQLMVCDemo(QWidget *parent = nullptr);
~SQLMVCDemo();

QVBoxLayout *mainLay;

// Sql model/view
QSqlTableModel *_tableModel;
QTableView *_tableView;

bool connectToDb();

private:
Ui::SQLMVCDemo *ui;

QSqlDatabase _db = QSqlDatabase::addDatabase("QMYSQL");
};
#endif // SQLMVCDEMO_H

sqlmvcdemo.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include "sqlmvcdemo.h"
#include "ui_sqlmvcdemo.h"

SQLMVCDemo::SQLMVCDemo(QWidget *parent)
: QWidget(parent)
, ui(new Ui::SQLMVCDemo)
{
ui->setupUi(this);

// Instance
mainLay = new QVBoxLayout(this);
_tableView = new QTableView;
_tableModel = new QSqlTableModel(nullptr,_db); // Construct from allocated Database

// UI
mainLay->addWidget(_tableView);

// Connect to Db
if(connectToDb())
qDebug()<<"Connected sucessfully.";
else
{
QSqlError error = _db.lastError();

qDebug()<<"Connected failed.";
qDebug()<<error.text();
}

//Model
_tableModel->setTable("user"); // Set table
_tableModel->select(); // Must ** SELECT ** first!
//_tableModel->setEditStrategy(QSqlTableModel::OnManualSubmit); // Manual submit

//View
_tableView->setModel(_tableModel);

/* Init Hearders
_tableView->setColumnCount(5);
_tableView->setHorizontalHeader(QStringList()<<"ID"<<"Name");
*/

// Set width of cols
_tableView->setColumnWidth(0,100);
_tableView->setColumnWidth(1,180);
_tableView->setColumnWidth(2,100);
_tableView->setColumnWidth(3,220);
_tableView->setColumnWidth(4,100);

// Resize with stretch
_tableView->horizontalHeader()->setSectionResizeMode(3,QHeaderView::Stretch);

// Color varies in rows
_tableView->setAlternatingRowColors(true);

/* Some propertis of view
_tableView->setSelectionBehavior(QAbstractItemView::SelectRows); // SelectRows
_tableView->setSelectionMode(QAbstractItemView::SingleSelection); // SingleSelection
_tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); // NoEditTriggers
*/
}

SQLMVCDemo::~SQLMVCDemo()
{
delete ui;
}

bool SQLMVCDemo::connectToDb()
{
_db.setHostName("");
_db.setPort(3306);
_db.setDatabaseName("");
_db.setUserName("");
_db.setPassword("");

bool ret = _db.open();

return ret;
}

Model/View 相关的类

demo 头文件sqlmvcdemo.h比较重要的部分是头文件include部分,这涉及了 Qt 中 MVC 相关的类。

Qt 中 MVC 结构相关类列表如下:

Qt中模型类的层次结构

视图相关类的层次结构图

从图里列出的类名基本上就能推测出我们要用到哪些类:

1
2
3
#include <QTableView>
#include <QHeaderView>
#include <QSqlTableModel>

其中 QHeaderView 类比较特殊,看起来与我们要使用的 SQL 毫不相关对不对。但其实它作用很大,到后面用到的时候再来说一说。


SQL Model/View 绑定

正如前面所说,Model 作为数据模型为操作数据提供了相关接口,因而在使相关 Model 时我们要绑定到具体的数据上。在我们这次使用的 SQL MVC 模型中,就是要把 QSqlTableModelQSqlDatabase 对象绑定。所以便有了如下实例化代码:

1
_tableModel = new QSqlTableModel(nullptr,_db); // Construct from allocated Database

这样待绑定的数据库连接后,QSqlTableModel对象就可以对数据库进行相关操作了。

这里要注意一下QSqlTableModel模型指定一个表后,还要执行一下QSqlTableModel::select()函数才能选中表中的数据。

1
2
3
//Model
_tableModel->setTable("user"); // Set table
_tableModel->select(); // Must ** SELECT ** first!

关于数据模型我们还要考虑一个小细节:用户提交策略

一般情况下,用户完成数据编辑后,会有两种提交策略:

  • 自动提交(默认),即编辑完数据( dataChanged() )后,数据模型自动提交。
  • 手动提交,即编辑完数据后,需手动调用提交函数方能提交用户数据修改。

若要设置为手动模式,我们需要对 Model 进行如下设置:

1
_tableModel->setEditStrategy(QSqlTableModel::OnManualSubmit); // Manual submit

并在相应位置手动调用该函数提交:

1
_tableModel->submitAll()

前面也说过,View 是视图结构,与数据模型绑定,为用户操作提供相应的接口,并将用户操作具体应用到数据模型,数据模型再将操作实际应用到数据中。在我们这次使用的 SQL MVC 模型中,就是要把 QTableViewQSqlTableModel绑定。所以便有以下代码:

1
_tableView->setModel(_tableModel);

对 View 控件的设置,主要包括以下方面:

  • 表头设置
  • 编辑策略
  • 代理

View 表头设置

先来讲一下 View 控件表头的设置。

还记得前面提到的QHeaderView类嘛?这里对表头的各种设置,就必须引用该类。

首先是设置表头,这里以水平表头为例:

1
2
3
// Init Hearders
_tableView->setColumnCount(5);
_tableView->setHorizontalHeader(QStringList()<<"ID"<<"Name");

然后我们肯定会遇到要设置某一列宽度的情况:

1
2
3
4
5
6
// Set width of cols
_tableView->setColumnWidth(0,100);
_tableView->setColumnWidth(1,180);
_tableView->setColumnWidth(2,100);
_tableView->setColumnWidth(3,220);
_tableView->setColumnWidth(4,100);

当然,我们还能设置某一列的宽度随着界面缩放比例而改变(建议开启该选项,这样才能让表格占满 View 控件,美观度++):

1
2
// Resize with stretch
_tableView->horizontalHeader()->setSectionResizeMode(3,QHeaderView::Stretch);

再来设置一下让数据按行交替现实不同的颜色:

1
2
// Color varies in rows
_tableView->setAlternatingRowColors(true);

编辑策略

按行选中而不是按单元格选中:

1
_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);  // SelectRows

单选:

1
_tableView->setSelectionMode(QAbstractItemView::SingleSelection); // SingleSelection

只读:

1
_tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);  // NoEditTriggers

代理

例如本 Demo 中的 Gender 项,我想编辑的时候出现一个“男生|女生”的下拉框而不是仅仅出现一个0或1的编辑框,这个时候我们就需要使用代理。

代理这里先卖一个关子。

好吧其实不是卖关子,而是太长了实在是不想在这一章讲了。。。

代理是一个很庞大的部分,我打算在下一章讲一些皮毛的东西。


到这里本章的内容基本上就结束了,下一章我会讲一下代理最为皮毛的一些原理和应用。

Qt Model/View(模型/视图)结构(无师自通)

http://www.skykeyjoker.com/2020/07/23/model-view/