Loading

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

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

Qt有自己的MVC框架,分别是model(模型)、view(视图)、delegate(委托),这篇文章,简单的介绍以下Qt中有关model(模型)的类以及一些基本的使用。
Qt官方的文档已经很详细了,如果想要详细的去了解,建议花点精力去看官方文档。

@

类继承的结构

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

展现的效果:
PluginForMyDemo_kYnZ78Hcsa.png

QAbstractProxyModel

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


QSortFilterProxyModel

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

QSortFilterProxyModel can be used for sorting items, filtering out items, or both. The model transforms the structure of a source model by mapping the model indexes it supplies to new indexes, corresponding to different locations, for views to use. This approach allows a given source model to be restructured as far as views are concerned without requiring any transformations on the underlying data, and without duplicating the data in memory.


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,就可以设置点击表头进行排序。
    image.png
    当然,你可以手动进行排序

    // 对第二列进行升序排序
    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);
    

    image.png
      其他的用法,请参考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);

image.png

QIdentityProxyModel

根据官方的文档:

QIdentityProxyModel can be used to forward the structure of a source model exactly, with no sorting, filtering or other transformation. This is similar in concept to an identity matrix where A.I = A.
Because it does no sorting or filtering, this class is most suitable to proxy models which transform the data() of the source model. For example, a proxy model could be created to define the font used, or the background colour, or the tooltip etc. This removes the need to implement all data handling in the same class that creates the structure of the model, and can also be used to create re-usable components.


QIdentityProxyModel可以用于准确地转发源模型的结构,不需要排序、过滤或其他转换。这在概念上类似于单位矩阵,其中A.I = A。
因为它不进行排序或过滤,所以这个类最适合于转换源模型的data()的代理模型。例如,可以创建一个代理模型来定义所使用的字体、背景颜色或工具提示等。这样就不需要在创建模型结构的同一个类中实现所有数据处理,并且还可以用于创建可重用的

  也就是说,这个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();

image.png
通过setFilter函数来设置过滤规则。

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

image.png
通过setSort函数来设置排序

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

image.png
其余的用法也可以参看之前写的博客: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);

image.png
从上面就可以看出,tableModel中原应该有的第二列,被忽略掉了,因为listModel只有1列。

QDirModel、QFileSystemModel

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

  1. 获取文件的一些信息

    函数原型 函数功能
    QIcon fileIcon(const QModelIndex &index) const 获取指定文件的图标
    QFileInfo fileInfo(const QModelIndex &index) const 获取指定文件的信息
    QString fileName(const QModelIndex &index) const 获取指定文件的名字
    QString filePath(const QModelIndex &index) const 获取指定文件的路径
  2. 目录操作

    函数原型 函数功能
    bool isDir(const QModelIndex &index) const 判断指定文件是否是目录
    QModelIndex mkdir(const QModelIndex &parent, const QString &name) 在指定的目录下,创建一个新的子目录
    bool rmdir(const QModelIndex &index) 删除指定目录

基本的用法:

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

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

image.png

QStandardItemModel

根据Qt官方文档的描述:

QStandardItemModel provides a classic item-based approach to working with the model. The items in a QStandardItemModel are provided by QStandardItem.


QStandardItemModel提供了一种经典的基于项的方法来处理模型。QStandardItemModel中的项由QStandardItem提供。

QStandardItemModel实现了QAbstractItemModel接口,这意味着该模型可以用于在任何支持该接口的视图中提供数据(例如QListView, QTableView和QTreeView,以及您自己的自定义视图)。这是一个基于项的模型,像上面介绍的这些,基本都是特例化的一些子类,QStandardItemModel则可以自己创建一个整体的结构,Table、Tree或者List这些,都可以创建并填充数据。

When you want a list or tree, you typically create an empty QStandardItemModel and use appendRow() to add items to the model, and item() to access an item. If your model represents a table, you typically pass the dimensions of the table to the QStandardItemModel constructor and use setItem() to position items into the table. You can also use setRowCount() and setColumnCount() to alter the dimensions of the model. To insert items, use insertRow() or insertColumn(), and to remove items, use removeRow() or removeColumn().
You can set the header labels of your model with setHorizontalHeaderLabels() and setVerticalHeaderLabels().
You can search for items in the model with findItems(), and sort the model by calling sort().
Call clear() to remove all items from the model


当您需要列表或树时,您通常创建一个空的QStandardItemModel,并使用appendRow()向模型添加项,并使用item()访问项。如果您的模型表示一个表,您通常将表的维度传递给QStandardItemModel构造函数,并使用setItem()将项目定位到表中。您还可以使用setRowCount()和setColumnCount()来更改模型的维度。要插入项,请使用insertRow()或insertColumn(),要删除项,请使用removeRow()或removeColumn()。您可以使用setHorizontalHeaderLabels()

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

image.png

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

最终呈现的效果为:
image.png
可以根据不同的role,来做到定制化不同的效果。
image.png

posted @ 2022-09-07 22:22  师从名剑山  阅读(990)  评论(0编辑  收藏  举报