Qt有自己的MVC框架,分別是model(模型)、view(檢視)、delegate(委託),這篇文章,簡單的介紹以下Qt中有關model(模型)的類以及一些基本的使用。
Qt官方的檔案已經很詳細了,如果想要詳細的去了解,建議花點精力去看官方檔案。
@
Qt中的模型類,都繼承自QAbstractItemModel,這個類定義了基本的必須的介面。
由於QAbstractItemModel這種帶有abstract的類是抽象類,不建議直接使用,所以本文只介紹可直接使用的類的基本用法。
根據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);
展現的效果:
這裡有一個Proxy(代理),這個要和Delegate(委託)區分開來。我的理解是,Proxy(代理)主要是應用在model上,用於對原資料進行處理,而Delegate(委託)主要是用來顯示和編輯資料。
為什麼要有這個代理呢?個人理解是,當Model關聯了幾個View時,如果你需要對某一個Model的資料進行排序,那如果不用代理,那麼就意味著你原本的Model也會改變,那麼所有的View都會改變。那麼如果你僅僅只需要當前的view對這個資料進行改變,那麼就需要用到代理,幫你把內容進行一個處理,然後發出來。
這個代理,提供了排序和過濾的介面,能夠方便的呼叫,給資料提供一個排序過濾的功能;
根據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這個屬性。
這個類是一個用來行列交換的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 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提供了一個用於讀取資料庫資料的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繼承自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實現在表格內直接對資料庫內容進行修改、新增和刪除等操作
這個也是一個代理,其作用是可以聯立多個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列。
根據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()));
根據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中的資料進行一種修改, 然後反饋到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,來做到客製化化不同的效果。