Qt中MVC的M(Model)簡單介紹 Qt有自己的MVC框架,分別是model(模型)、view(視圖)、delegate(委托),這篇文章,簡單的介紹以下Qt中有關model(模型)的類以及一些基本的使用。 Qt官方的文檔已經很詳細了,如果想要詳細的去瞭解,建議花點精力去看官方文檔。 @ 類繼承 ...
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);
展現的效果:
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,來做到定製化不同的效果。