Qt中MVC的M(Model)简单介绍
@
类继承的结构
Qt中的模型类,都继承自QAbstractItemModel,这个类定义了基本的必须的接口。
由于QAbstractItemModel这种带有abstract的类是抽象类,不建议直接使用,所以本文只介绍可直接使用的类的基本用法。
QStringListModel
根据Qt帮助文档中的解释,QStringListModel是一个可编辑的模型,可用于需要在视图小部件(如QListView或QComboBox)中显示许多字符串的简单情况。
下面是使用的代码以及效果展示:
QStringListModel *m_listModel_2 = new QStringListModel;
QStringList list_2 = {"111", "222", "333", "444", "555"};
m_listModel_2->setStringList(list_2);
ui->listView->setModel(m_listModel_2);
展现的效果:
QAbstractProxyModel
这里有一个Proxy(代理),这个要和Delegate(委托)区分开来。我的理解是,Proxy(代理)主要是应用在model上,用于对原数据进行处理,而Delegate(委托)主要是用来显示和编辑数据。
为什么要有这个代理呢?个人理解是,当Model关联了几个View时,如果你需要对某一个Model的数据进行排序,那如果不用代理,那么就意味着你原本的Model也会改变,那么所有的View都会改变。那么如果你仅仅只需要当前的view对这个数据进行改变,那么就需要用到代理,帮你把内容进行一个处理,然后发出来。
QSortFilterProxyModel
这个代理,提供了排序和过滤的接口,能够方便的调用,给数据提供一个排序过滤的功能;
根据Qt官方帮助文档对于QSortFilterProxyModel的介绍:
以下是基本的用法:
排序
QTableView* tableview = new QTableView(); QStandardItemModel *model = new QStandardItemModel(); model->setItem(0, 0, new QStandardItem("Aba")); model->setItem(1, 0, new QStandardItem("aba")); model->setItem(2, 0, new QStandardItem("ABc")); model->setItem(0, 1, new QStandardItem("C")); model->setItem(1, 1, new QStandardItem("A")); model->setItem(2, 1, new QStandardItem("c")); model->setItem(0, 2, new QStandardItem("c")); model->setItem(1, 2, new QStandardItem("b")); model->setItem(2, 2, new QStandardItem("C")); QSortFilterProxyModel* sortFilterModel = new QSortFilterProxyModel(); // 为代理设置源model sortFilterModel->setSourceModel(listModel); // 设置大小写敏感 sortFilterModel->setSortCaseSensitivity(); tableview->setModel(sortFilterModel); // 设置开启点击表头进行排序 tableview->setSortingEnable(true);
需注意的是,当你使用QTableView或者QTreeView时,调用setSortingEnable并设置为true,就可以设置点击表头进行排序。
当然,你可以手动进行排序// 对第二列进行升序排序 ui->tableview->sortByColumn(1, Qt::AscendingOrder);
但是这样排序有一个问题:表的序号没有进行改变。暂时没有找到方法来解决,有一个参考的解决方法可以看:QTableView自定义Model实现排序 。同样,如果你要自定义排序的规则的话,你可以继承QSortFilterProxyModel类,然后重写lessThan函数,重新写一下里面的排序规则。可以参考Qt官方的例子Custom Sort/Filter Model
过滤
过滤的规则你可以选择
- 正则表达式
- 通配符模式
- 固定字符串
在层级结构中,会递归的去过滤其子节点。同时,当父节点被过滤时,子节点也不会被显示。
基本用法如下:QStringListModel *m_listModel_2 = new QStringListModel; QStringList list_2 = {"111", "222", "333", "444", "555", "a.jpg", "b.jpg"}; QSortFilterProxyModel* listviewFilterModel = new QSortFilterProxyModel; // 设置源model listviewFilterModel->setSourceModel(m_listModel_2); m_listModel_2->setStringList(list_2); listviewFilterModel->setFilterRegExp(QRegExp(".jpg", Qt::CaseSensitive, QRegExp::FixedString)); ui->listView->setModel(listviewFilterModel);
其他的用法,请参考Qt官方例子Custom Sort/Filter Model
默认的情况下,在源model的数据发生改变时,会自动重新排序或者重新过滤信息。想要控制这个特性,设置dynamicSortFilter这个属性。
QTransposeProxyModel
这个类是一个用来行列交换的model。就是说如果源model中有一个索引index(0, 1),那么这个在代理model中就是index(1, 0)。
QStandardItemModel *model = new QStandardItemModel();
model->setItem(0, 0, new QStandardItem("2022-9-4 21:11:08"));
model->setItem(1, 0, new QStandardItem("2022-9-5 17:21:08"));
model->setItem(2, 0, new QStandardItem("2022-9-1 13:03:12"));
model->setItem(0, 1, new QStandardItem("C"));
model->setItem(1, 1, new QStandardItem("A"));
model->setItem(2, 1, new QStandardItem("c"));
model->setItem(0, 2, new QStandardItem("c"));
model->setItem(1, 2, new QStandardItem("b"));
model->setItem(2, 2, new QStandardItem("C"));
QTransposeProxyModel* transposeModel = new QTransposeProxyModel;
// 设置源model
transposeModel->setSourceModel(model);
ui->tableView->setModel(transposeModel);
QIdentityProxyModel
根据官方的文档:
也就是说,这个model不会对源model进行任何转换,只会简简单单的进行映射。主要是为了使用者可以通过重写data()函数,来定制每一个元素所显示的效果。同时这样也不会污染源model的数据,方便进行重用。也对应这上面的,proxy(代理)的作用就是一个源model可以在许多个view里设置,且基本互不影响。
以下代码源自Qt官方文档:
class DateFormatProxyModel : public QIdentityProxyModel
{
// ...
void setDateFormatString(const QString &formatString)
{
m_formatString = formatString;
}
QVariant data(const QModelIndex &index, int role) const override
{
if (role != Qt::DisplayRole)
return QIdentityProxyModel::data(index, role);
const QDateTime dateTime = sourceModel()->data(SourceClass::DateRole).toDateTime();
return dateTime.toString(m_formatString);
}
private:
QString m_formatString;
};
像上面这样,重载model类的data函数,输出定制化的日期格式。
至于为什么要引入这样一个类,我的理解是,你在进行继承的时候,你需要找一个和你要实现的功能类似的父类来继承,这样的话就方便一点。但是如果你要实现的子类,功能和前面两个代理类没有关系,那么你继承上面两个类会进行一些多余的操作,这个时候引入一个只做映射的类,不对源model进行任何的改变,这样定制化自己的代子类而不进行多余操作。
QSqlQueryModel
QSqlQueryModel提供了一个用于读取数据库数据的model,能够为像QTableView这样的view提供数据。
常用的函数有:
void setQuery(const QSqlQuery &query)
void setQuery(const QString &query, const QSqlDatabase &db = QSqlDatabase())
这个函数是用来设置查询的语句以及查询的数据库;QSqlRecord QSqlQueryModel::record(int row) const
查询指定第_row_行的数据;
QSqlRecord QSqlQueryModel::record( ) const
返回一个空的QSqlRecord,但是里面包含了字段的信息;
void fetchMore(const QModelIndex &parent = QModelIndex())
从数据库中取得更多的行数。这个只会对不返回QuerySize的数据库起作用。例如:oracle;可参看本人写的博客:QTableView实现在表格内直接对数据库内容进行修改、新增和删除等操作中,关于新增的部分。
其简单的用法是(代码源自Qt官方文档):
QSqlQueryModel *model = new QSqlQueryModel;
// 设置数据库查询语句,这里如果不指定QSqlDatabase的话,就会使用默认的数据库连接
model->setQuery("SELECT name, salary FROM employee");
// 设置表格的表头
model->setHeaderData(0, Qt::Horizontal, tr("Name"));
model->setHeaderData(1, Qt::Horizontal, tr("Salary"));
QTableView *view = new QTableView;
// 为view设置model
view->setModel(model);
view->show();
此处有一个重要的点,那就是你需要自己设置QSqlQueryModel所要访问的数据库。根据不同的数据库,创建不同的QSqlDatabase连接。
// 以Sqlite为例
QSqlDatabase m_db;
// 添加QSqlDatabase
// 此处addDatabase函数不指定connectName,就会添加一个defaultConnection
// 上面的setQuery的就可以访问到默认的数据库连接了。
m_db = QSqlDatabase::addDatabase("QSQLITE");
m_db.setDatabaseName("D:/database.db");
if(!m_db.open())
{
qDebug()<<"打开失败";
return;
}
同样,也可以只用model查询数据库数据,而不与view绑定中去。
QSqlQueryModel model;
model.setQuery("SELECT name, salary FROM employee");
// 获取第四行数据中字段salary的值
int salary = model.record(4).value("salary").toInt();
QSqlQueryModel是只读的,如果想要可读可写的话,你可以使用QSqlTableModel。
QSqlTableModel
QSqlTableModel继承自QSqlQueryModel类,是可读可写的。
常用的函数:
void setTable(const QString &tableName)
设置需要查询的数据库表名为tableName。
void setEditStrategy(QSqlTableModel::EditStrategy strategy)
设置数据编辑的策略,主要有三种策略,分别是有任何改变就提交、行改变提交、手动提交。
bool select()
根据生成的sql语句来查询。
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole)
设置指定角色的水平标题的标题值。如果orientation是Qt::Horizontal,并且_section_指的是一个有效的section,则返回true;否则返回假;
void setFilter(const QString &filter)
设置过滤规则。filter的内容为,一个没有where的关键字的where语句。比如:正常的语句"select * from table where name = 'ZhangSan' ",那么此时filter的内容就应该为" name = 'ZhangSan' "
void setSort(int column, Qt::SortOrder order)
设置指定列column排序,注意:调用这个函数设置新的排序之后,不会影响当前的数据,需要调用select函数来刷新数据。
void revert()
根据官方的文档中的解释,如果模型的策略设置为OnRowChange或OnFieldChange,则恢复更改。对OnManualSubmit策略不做任何操作。使用revertAll()恢复OnManualSubmit策略的所有挂起更改,或者使用revertRow()恢复特定行
bool submit()
如果模型的策略设置为OnRowChange或OnFieldChange,则提交当前编辑的行。对OnManualSubmit策略不做任何操作。使用submitAll()为OnManualSubmit策略提交所有挂起的更改
最基本的用法:
// 创建/打开数据库
QSqlDatabase db;
if (QSqlDatabase::contains("qt_sql_default_connection"))
{
// 获取默认的连接
db = QSqlDatabase::database("qt_sql_default_connection");
}
else
{
// 建立和SQlite数据库的连接
db = QSqlDatabase::addDatabase("QSQLITE");
// 设置数据库文件的名字
db.setDatabaseName("Database.db");
}
if (db.open()) {
// 创建表以及往表里插入数据
QSqlQuery query;
query.exec("create table person (Name varchar(20), Salary int)");
query.exec("insert into person values('ZhangSan', 1)");
query.exec("insert into person values('LiSi', 2)");
query.exec("insert into person values('WangWu', 3)");
query.exec("insert into person values('ZhaoLiu', 4)");
query.exec("insert into person values('QianQi', 5)");
}
QSqlTableModel* tableModel = new QSqlTableModel();
// 设置表名
tableModel->setTable("person");
// 设置编辑策略,设置为需手动提交
tableModel->setEditStrategy(QSqlTableModel::OnManualSubmit);
// 设置表头数据
tableModel->setHeaderData(0, Qt::Horizontal, "Name");
tableModel->setHeaderData(1, Qt::Horizontal, "Salary");
// 查询,必须要有,不然没有数据显示
tableModel->select();
ui->tableView->setModel(tableModel);
QTableView *view = new QTableView;
view->setModel(tableModel);
view->hideColumn(0); // don't show the ID
view->show();
通过setFilter函数来设置过滤规则。
tableModel->setFilter("name='ZhangSan' or name = 'WangWu'");
通过setSort函数来设置排序
tableModel->setSort(0, Qt::AscendingOrder);
tableModel->select();
其余的用法也可以参看之前写的博客:QTableView实现在表格内直接对数据库内容进行修改、新增和删除等操作
QConcatenateTablesProxyModel
这个也是一个代理,其作用是可以联立多个model,将数据放到一起显示。显示的列的数量为所有联立的model中,列数量最小的model决定。
简单的用法为:
QStringList list;
list << "5" << "2" << "1" << "4" << "3";
QStringListModel* listModel = new QStringListModel();
listModel->setStringList(list);
QSqlDatabase db;
if (QSqlDatabase::contains("qt_sql_default_connection"))
{
// 获取默认的连接
db = QSqlDatabase::database("qt_sql_default_connection");
}
else
{
// 建立和SQlite数据库的连接
db = QSqlDatabase::addDatabase("QSQLITE");
// 设置数据库文件的名字
db.setDatabaseName("Database.db");
}
if (db.open()) {
// 创建表以及往表里插入数据
QSqlQuery query;
query.exec("create table person (Name varchar(20), Salary int)");
query.exec("insert into person values('ZhangSan', 1)");
query.exec("insert into person values('LiSi', 2)");
query.exec("insert into person values('WangWu', 3)");
query.exec("insert into person values('ZhaoLiu', 4)");
query.exec("insert into person values('QianQi', 5)");
}
QSqlTableModel* tableModel = new QSqlTableModel();
tableModel->setTable("person");
tableModel->setEditStrategy(QSqlTableModel::OnManualSubmit);
tableModel->setHeaderData(0, Qt::Horizontal, "Name");
tableModel->setHeaderData(1, Qt::Horizontal, "Salary");
tableModel->setSort(0, Qt::AscendingOrder);
tableModel->select();
QConcatenateTablesProxyModel* concatenateModel = new QConcatenateTablesProxyModel;
// 添加源model
concatenateModel->addSourceModel(listModel);
concatenateModel->addSourceModel(tableModel);
ui->tableView->setModel(concatenateModel);
从上面就可以看出,tableModel中原应该有的第二列,被忽略掉了,因为listModel只有1列。
QDirModel、QFileSystemModel
根据Qt官方文档中的描述,已经不建议用QDirModel,建议使用QFileSystemModel,由于两个类很类似,所以本文只介绍QFileSystemModel。
QFileSystemModel是一个用于访问本地文件系统的类,提供了一些基本的读、写文件或目录以及创建新的目录的接口方便使用。常用的函数有:
获取文件的一些信息
目录操作
基本的用法:
QFileSystemModel* fileModel = new QFileSystemModel;
fileModel->setRootPath(QDir::currentPath());
ui->treeView->setModel(fileModel);
ui->treeView->setRootIndex(fileModel->index(QDir::currentPath()));
QStandardItemModel
根据Qt官方文档的描述:
QStandardItemModel实现了QAbstractItemModel接口,这意味着该模型可以用于在任何支持该接口的视图中提供数据(例如QListView, QTableView和QTreeView,以及您自己的自定义视图)。这是一个基于项的模型,像上面介绍的这些,基本都是特例化的一些子类,QStandardItemModel则可以自己创建一个整体的结构,Table、Tree或者List这些,都可以创建并填充数据。
以Table结构为例,简单的用法:
QStandardItemModel* model = new QStandardItemModel(4, 4);
for (int row = 0; row < model->rowCount(); ++row) {
for (int column = 0; column < model->columnCount(); ++column) {
QStandardItem *item = new QStandardItem(QString("row %0, column %1").arg(row).arg(column));
model->setItem(row, column, item);
}
}
model->setHorizontalHeaderLabels({"Column 1", "Column 2", "Column 3", "Column 4"});
ui->tableView->setModel(model);
自定义Model
有时候会需要对model中的数据进行一种修改, 然后反馈到View上,这个时候,你就需要子类化一个model,然后重写其data函数,来实现你想要的要求。
下面以Table的内容为例子:
mymodel.h
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QStandardItemModel>
class MyModel : public QStandardItemModel
{
public:
explicit MyModel();
protected:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
};
#endif
mymodel.cpp
#include "mymodel.h"
MyModel::MyModel()
{
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
// 背景色
if (index.column() == 1 && role == Qt::BackgroundRole) {
return QVariant(QColor(Qt::red));
}
// 前景色
if (index.column() == 2 && role == Qt::ForegroundRole) {
return QVariant(QColor(Qt::blue));
}
// 文字位置
if (index.column() == 3 && role == Qt::TextAlignmentRole) {
return QVariant(Qt::AlignBottom);
}
// 字体
if (index.column() == 0 && role == Qt::FontRole) {
return QVariant(QFont("MicroSoft YaHei", 18));
}
return QStandardItemModel::data(index, role);
}
使用代码:
MyModel* model = new MyModel;
for (int row = 0; row < 4; ++row) {
for (int column = 0; column < 4; ++column) {
QStandardItem *item = new QStandardItem(QString("row %0, column %1").arg(row).arg(column));
model->setItem(row, column, item);
}
}
model->setHorizontalHeaderLabels({"Column 1", "Column 2", "Column 3", "Column 4"});
ui->tableView->setModel(model);
最终呈现的效果为:
可以根据不同的role,来做到定制化不同的效果。