Qt中MVC的M(Model)简单介绍

@

类继承的结构

Qt中的模型类,都继承自QAbstractItemModel,这个类定义了基本的必须的接口。
[Qt基础内容-08] Qt中MVC的M(Model)-LMLPHP
由于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);

展现的效果:
[Qt基础内容-08] Qt中MVC的M(Model)-LMLPHP

QAbstractProxyModel

  这里有一个Proxy(代理),这个要和Delegate(委托)区分开来。我的理解是,Proxy(代理)主要是应用在model上,用于对原数据进行处理,而Delegate(委托)主要是用来显示和编辑数据。
  为什么要有这个代理呢?个人理解是,当Model关联了几个View时,如果你需要对某一个Model的数据进行排序,那如果不用代理,那么就意味着你原本的Model也会改变,那么所有的View都会改变。那么如果你仅仅只需要当前的view对这个数据进行改变,那么就需要用到代理,帮你把内容进行一个处理,然后发出来。


QSortFilterProxyModel

这个代理,提供了排序和过滤的接口,能够方便的调用,给数据提供一个排序过滤的功能;
根据Qt官方帮助文档对于QSortFilterProxyModel的介绍:

以下是基本的用法:

  1. 排序

    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,就可以设置点击表头进行排序。
    [Qt基础内容-08] Qt中MVC的M(Model)-LMLPHP
    当然,你可以手动进行排序

    // 对第二列进行升序排序
    ui->tableview->sortByColumn(1, Qt::AscendingOrder);
    

      但是这样排序有一个问题:表的序号没有进行改变。暂时没有找到方法来解决,有一个参考的解决方法可以看:QTableView自定义Model实现排序 。同样,如果你要自定义排序的规则的话,你可以继承QSortFilterProxyModel类,然后重写lessThan函数,重新写一下里面的排序规则。可以参考Qt官方的例子Custom Sort/Filter Model

  2. 过滤

    过滤的规则你可以选择

    • 正则表达式
    • 通配符模式
    • 固定字符串


      在层级结构中,会递归的去过滤其子节点。同时,当父节点被过滤时,子节点也不会被显示。
    基本用法如下:

    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基础内容-08] Qt中MVC的M(Model)-LMLPHP
      其他的用法,请参考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);

[Qt基础内容-08] Qt中MVC的M(Model)-LMLPHP

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提供数据。
常用的函数有:

  1. void setQuery(const QSqlQuery &query)

    void setQuery(const QString &query, const QSqlDatabase &db = QSqlDatabase())
    这个函数是用来设置查询的语句以及查询的数据库;

  2. QSqlRecord QSqlQueryModel::record(int row) const

    查询指定第_row_行的数据;

  3. QSqlRecord QSqlQueryModel::record( ) const

    返回一个空的QSqlRecord,但是里面包含了字段的信息;

  4. 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类,是可读可写的。
常用的函数:

  1. void setTable(const QString &tableName)

     设置需要查询的数据库表名为tableName。

  2. void setEditStrategy(QSqlTableModel::EditStrategy strategy)

     设置数据编辑的策略,主要有三种策略,分别是有任何改变就提交、行改变提交、手动提交。

  3. bool select()

     根据生成的sql语句来查询。

  4. bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole)

     设置指定角色的水平标题的标题值。如果orientationQt::Horizontal,并且_section_指的是一个有效的section,则返回true;否则返回假;

  5. void setFilter(const QString &filter)

     设置过滤规则。filter的内容为,一个没有where的关键字的where语句。比如:正常的语句"select * from table where name = 'ZhangSan' ",那么此时filter的内容就应该为" name = 'ZhangSan' "

  6. void setSort(int column, Qt::SortOrder order)

     设置指定列column排序,注意:调用这个函数设置新的排序之后,不会影响当前的数据,需要调用select函数来刷新数据。

  7. void revert()

     根据官方的文档中的解释,如果模型的策略设置为OnRowChangeOnFieldChange,则恢复更改。对OnManualSubmit策略不做任何操作。使用revertAll()恢复OnManualSubmit策略的所有挂起更改,或者使用revertRow()恢复特定行

  8. bool submit()

     如果模型的策略设置为OnRowChangeOnFieldChange,则提交当前编辑的行。对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();

[Qt基础内容-08] Qt中MVC的M(Model)-LMLPHP
通过setFilter函数来设置过滤规则。

tableModel->setFilter("name='ZhangSan' or name = 'WangWu'");

[Qt基础内容-08] Qt中MVC的M(Model)-LMLPHP
通过setSort函数来设置排序

tableModel->setSort(0, Qt::AscendingOrder);
tableModel->select();

[Qt基础内容-08] Qt中MVC的M(Model)-LMLPHP
其余的用法也可以参看之前写的博客: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);

[Qt基础内容-08] Qt中MVC的M(Model)-LMLPHP
从上面就可以看出,tableModel中原应该有的第二列,被忽略掉了,因为listModel只有1列。

QDirModel、QFileSystemModel

 根据Qt官方文档中的描述,已经不建议用QDirModel,建议使用QFileSystemModel,由于两个类很类似,所以本文只介绍QFileSystemModel。
 QFileSystemModel是一个用于访问本地文件系统的类,提供了一些基本的读、写文件或目录以及创建新的目录的接口方便使用。常用的函数有:

  1. 获取文件的一些信息

  2. 目录操作

基本的用法:

QFileSystemModel* fileModel = new QFileSystemModel;
fileModel->setRootPath(QDir::currentPath());

ui->treeView->setModel(fileModel);
ui->treeView->setRootIndex(fileModel->index(QDir::currentPath()));

[Qt基础内容-08] Qt中MVC的M(Model)-LMLPHP

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);

[Qt基础内容-08] Qt中MVC的M(Model)-LMLPHP

自定义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);

最终呈现的效果为:
[Qt基础内容-08] Qt中MVC的M(Model)-LMLPHP
可以根据不同的role,来做到定制化不同的效果。
[Qt基础内容-08] Qt中MVC的M(Model)-LMLPHP

09-07 23:05