Qt中MVC的M(Model)簡單介紹
Qt有自己的MVC框架,分別是model(模型)、view(視圖)、delegate(委托),這篇文章,簡單的介紹以下Qt中有關model(模型)的類以及一些基本的使用,
Qt官方的檔案已經很詳細了,如果想要詳細的去了解,建議花點精力去看官方檔案,
@
目錄- Qt中MVC的M(Model)簡單介紹
- 類繼承的結構
- QStringListModel
- QAbstractProxyModel
- QSortFilterProxyModel
- QTransposeProxyModel
- QIdentityProxyModel
- QSqlQueryModel
- QSqlTableModel
- QConcatenateTablesProxyModel
- QDirModel、QFileSystemModel
- QStandardItemModel
- 自定義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的介紹:
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 可用于排序專案、過濾專案或兩者兼而有之, 該模型通過將其提供的模型索引映射到新索引(對應于不同位置)來轉換源模型的結構,以供視圖使用, 這種方法允許就視圖而言對給定的源模型進行重構,而無需對基礎資料進行任何轉換,也無需復制記憶體中的資料,
以下是基本的用法:
-
排序
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
根據官方的檔案:
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提供資料,
常用的函式有:
-
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是一個用于訪問本地檔案系統的類,提供了一些基本的讀、寫檔案或目錄以及創建新的目錄的介面方便使用,常用的函式有:
-
獲取檔案的一些資訊
函式原型 函式功能 QIcon fileIcon(const QModelIndex &index) const 獲取指定檔案的圖示 QFileInfo fileInfo(const QModelIndex &index) const 獲取指定檔案的資訊 QString fileName(const QModelIndex &index) const 獲取指定檔案的名字 QString filePath(const QModelIndex &index) const 獲取指定檔案的路徑 -
目錄操作
函式原型 函式功能 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()));

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

自定義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,來做到定制化不同的效果,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/505486.html
標籤:其他
上一篇:Qt5.14.2使用虛擬鍵盤
