M (Model) of MVC in Qt

A brief introduction to M (Model) of MVC in Qt

Qt has its own MVC framework, which are model (model), view (view), and delegate (delegate). This article briefly introduces the following classes of model (model) in Qt and some basic uses.
Qt's official documentation is already very detailed. If you want to understand it in detail, it is recommended to spend some effort to read the official documentation.

Structure of Class Inheritance

The model classes in Qt all inherit from QAbstractItemModel , which defines the basic necessary interfaces.


Since the class with abstract such as QAbstractItemModel is an abstract class, it is not recommended to use it directly , so this article only introduces the basic usage of the class that can be used directly.

QStringListModel

As explained in the Qt help documentation, QStringListModel is an editable model that can be used in simple cases where many strings need to be displayed in a view widget such as QListView or QComboBox.
The following is the code used and the effect display:

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

The effect shown:

QAbstractProxyModel

There is a Proxy here, which should be distinguished from Delegate. My understanding is that Proxy is mainly used on the model to process the original data, while Delegate is mainly used to display and edit data.
Why do we have this agent? My understanding is that when a Model is associated with several Views, if you need to sort the data of a certain Model, if you don’t use a proxy, it means that your original Model will also change, and all Views will change. So if you only need the current view to change this data, then you need to use a proxy to help you process the content and then send it out.
 

The benefits of this article, free to receive Qt development learning materials package, technical video, including (C++ language foundation, introduction to Qt programming, QT signal and slot mechanism, QT interface development-image drawing, QT network, QT database programming, QT project combat, QSS, OpenCV, Quick module, interview questions, etc.) ↓↓↓↓↓↓See below↓↓Click on the bottom of the article to receive the fee↓↓

QSortFilterProxyModel

This proxy provides a sorting and filtering interface, which can be easily called to provide a sorting and filtering function for data; according to the introduction of QSortFilterProxyModel
in the official Qt help document :

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 structured as far as views are concerned without requiring any transformations on the underlying data, and without duplicating the data in memory.


QSortFilterProxyModel can be used to sort items, filter items, or both. This model transforms the structure of the source model for use by the view by mapping the model index it provides to a new index (corresponding to a different position). This approach allows a given source model to be refactored in terms of views without any transformation of the underlying data and without copying the data in memory.

Here is the basic usage:

  1. to sort
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);

It should be noted that when you use QTableView or QTreeView , call setSortingEnable and set it to true, you can click the header to sort.

Of course, you can sort manually

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

But there is a problem with this sorting: the serial number of the table has not been changed. I haven't found a way to solve it yet. There is a reference solution to see: QTableView custom Model to implement sorting. Similarly, if you want to customize the sorting rules, you can inherit the QSortFilterProxyModel class, then rewrite the lessThan function, and rewrite the sorting rules inside. You can refer to the official Qt example Custom Sort/Filter Model

2. filter

filter rules you can choose

  • regular expression
  • wildcard pattern
  • fixed string


In the hierarchical structure, its child nodes are recursively filtered. At the same time, when the parent node is filtered, the child nodes will not be displayed.
The basic usage is as follows:

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

By default, when the data of the source model changes, the information will be automatically reordered or refiltered. To control this feature, set the dynamicSortFilter property.

QTransposeProxyModel

This class is a model for exchanging rows and columns. That is to say, if there is an index index(0, 1) in the source model, then this is index(1, 0) in the proxy model.

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

According to the official documentation:

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 AI = 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 can be used to exactly re-use the structure of the source model, without the need for sorting, filtering, or other transformations. This is conceptually similar to the identity matrix, where AI = A.
Because it does not sort or filter, this class is best suited for proxy models that transform the data() of the source model. For example, a proxy model can be created to define the font used, background color or tooltip, etc. This eliminates the need to implement all data processing in the same class that creates the model structure, and can also be used to create reusable

In other words, this model will not perform any transformation on the source model, but will simply map it. The main reason is that users can customize the effect displayed by each element by rewriting the data() function. At the same time, this will not pollute the data of the source model, which is convenient for reuse. Corresponding to the above, the role of the proxy (proxy) is that a source model can be set in many views, and basically does not affect each other.
The following code is from the official Qt documentation:

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

As above, overload the data function of the model class to output a customized date format.
As for why you want to introduce such a class, my understanding is that when you inherit, you need to find a parent class similar to the function you want to implement to inherit, which is more convenient. But if the function of the subclass you want to implement has nothing to do with the previous two proxy classes, then you will perform some redundant operations if you inherit the above two classes. At this time, introduce a class that only does mapping, without making any changes to the source model , so that you can customize your own subclasses without performing redundant operations.

QSqlQueryModel

QSqlQueryModel provides a model for reading database data, which can provide data for views like QTableView .
Commonly used functions are:

1.void setQuery(const QSqlQuery &query)

void setQuery(const QString &query, const QSqlDatabase &db = QSqlDatabase())
This function is used to set the query statement and the query database;

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

Query the data of the specified row _ row _;

3.QSqlRecord QSqlQueryModel::record( ) const

Return an empty QSqlRecord, but it contains field information;

4.void fetchMore(const QModelIndex &parent = QModelIndex())

Get more rows from the database. This will only work for databases that do not return QuerySize. For example: oracle; you can refer to the blog I wrote: QTableView implements operations such as modifying, adding, and deleting database content directly in the table, about the newly added part.

Its simple usage is (the code is derived from the official Qt documentation):

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

There is an important point here, that is, you need to set the database to be accessed by QSqlQueryModel yourself. According to different databases, create different QSqlDatabase connections .

// 以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;
}

Similarly, you can only use the model to query database data without binding it to the view.

QSqlQueryModel model;
model.setQuery("SELECT name, salary FROM employee");
// 获取第四行数据中字段salary的值
int salary = model.record(4).value("salary").toInt();

QSqlQueryModel is read-only, if you want to read and write, you can use QSqlTableModel .

QSqlTableModel

QSqlTableModel inherits from the QSqlQueryModel class and is readable and writable.
Commonly used functions:

1.void setTable(const QString &tableName)

Set the name of the database table to be queried to tableName.

2.void setEditStrategy(QSqlTableModel::EditStrategy strategy)

There are mainly three strategies for setting data editing strategies, namely submitting any changes, submitting row changes, and submitting manually.

3.bool select()

Query based on the generated sql statement.

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

Sets the title value for the specified role's horizontal title. Returns true if orientation is Qt::Horizontal and _section_ refers to a valid section; otherwise returns false;

5.void setFilter(const QString &filter)

Set filtering rules. The content of the filter is a where statement without the where keyword. For example: the normal statement "select * from table where name = 'ZhangSan' ", then the content of the filter should be "name = 'ZhangSan' "

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

Set the sorting of the specified column . Note: After calling this function to set a new sorting, the current data will not be affected. You need to call the select function to refresh the data.

7.void revert()

According to the explanation in the official documentation, if the model's strategy is set to OnRowChange or OnFieldChange , the changes are reverted. Do nothing for the OnManualSubmit policy. Use revertAll() to revert all pending changes of the OnManualSubmit policy, or use revertRow() to revert a specific row

8.bool submit()

Commits the currently edited row if the model's policy is set to OnRowChange or OnFieldChange . Do nothing for the OnManualSubmit policy. Submit all pending changes for the OnManualSubmit strategy using submitAll()

The most basic usage:

// 创建/打开数据库
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();


Set filter rules through the setFilter function.

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


Set sorting through the setSort function

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

QConcatenateTablesProxyModel

This is also an agent, its role is to connect multiple models and display the data together. The number of displayed columns is determined by the model with the smallest number of columns among all simultaneous models.
Simple usage is:

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


It can be seen from the above that the second column that should have been in the tableModel is ignored, because the listModel has only one column.

QDirModel、QFileSystemModel

According to the description in the official Qt documentation, it is no longer recommended to use QDirModel, and it is recommended to use QFileSystemModel. Since the two classes are very similar, this article only introduces QFileSystemModel.
QFileSystemModel is a class used to access the local file system. It provides some basic interfaces for reading and writing files or directories and creating new directories for easy use. Commonly used functions are:

  1. Get some information about the file
function prototype function function
QIcon fileIcon(const QModelIndex &index) const Get the icon of the specified file
QFileInfo fileInfo(const QModelIndex &index) const Get information about the specified file
QString fileName(const QModelIndex &index) const Get the name of the specified file
QString filePath(const QModelIndex &index) const Get the path of the specified file
  1. directory operations
function prototype function function
bool isDir(const QModelIndex &index) const Determine whether the specified file is a directory
QModelIndex mkdir(const QModelIndex &parent, const QString &name) Create a new subdirectory under the specified directory
bool rmdir(const QModelIndex &index) delete specified directory

Basic usage:

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

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

QStandardItemModel

According to the description of Qt official documentation:

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


. Items in QStandardItemModel are provided by QStandardItem.

QStandardItemModel implements the QAbstractItemModel interface, which means that the model can be used to provide data in any view that supports this interface (such as QListView, QTableView and QTreeView, as well as your own custom views). This is an item-based model. The ones described above are basically subclasses of specialization. QStandardItemModel can create an overall structure by itself. Table, Tree or List can all be created and filled with data.

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

When you need a list or tree, you usually create an empty QStandardItemModel and use appendRow() to add items to the model and use item() to access the items. If your model represents a table, you typically pass the table's dimensions to the QStandardItemModel constructor and use setItem() to position items into the table. You can also use setRowCount() and setColumnCount() to change the dimensions of the model. To insert an item, use insertRow() or insertColumn(), and to remove an item, use removeRow() or removeColumn(). You can use setHorizontalHeaderLabels()

Take the Table structure as an example, simple usage:

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

Custom Model

Sometimes you need to make a modification to the data in the model, and then feed it back to the View. At this time, you need to subclass a model, and then rewrite its data function to achieve your desired requirements.

Let's take the content of Table as an example:
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);
}

Use the code:

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

The final rendering effect is:


Different effects can be customized according to different roles .

The article was transferred from Blog Garden (under the tutelage of Ming Jianshan): [Qt Basic Content-08] M (Model) of MVC in Qt

The benefits of this article, free to receive Qt development learning materials package, technical video, including (C++ language foundation, introduction to Qt programming, QT signal and slot mechanism, QT interface development-image drawing, QT network, QT database programming, QT project combat, QSS, OpenCV, Quick module, interview questions, etc.) ↓↓↓↓↓↓See below↓↓Click on the bottom of the article to receive the fee↓↓

Guess you like

Origin blog.csdn.net/QtCompany/article/details/132091051