文章目錄
- 1. QT中的MVD模式
- 2. View
- 3. Model
- 4. Delegate
- 5. 以TableView為例
1. QT中的MVD模式
模型視圖委托(MVD)是Qt中特有的設計模式,類似MVC設計模式,將MVC設計模式中的Controller當做MVD中的Delegate,兩者的概念基本相同。不同的是委托不是獨立存在,而是包含在視圖里面。 模型視圖委托設計模式中,模型負責存儲和管理數據;視圖負責顯示數據,其中界面的框架和基礎信息是視圖負責,具體數據的顯示是委托負責;委托不僅僅負責數據的顯示,還有一個重要的功能是負責數據的編輯,如在視圖中雙擊就可以編輯數據。
QT當中model-view-delegate(模型-視圖-代理),此結構實現數據和界面的分離。
Qt的模型-視圖結構分為三部分:模型(mode)-視圖(view)-代理(Delegate) ,其中模型與數據源通信,并為其它部件提供接口;視圖從模型中引用數據條的模型索引(Modellndex),在視圖當中,代理負責繪制數據條目,比如編輯條目,代理和模型進行直接通信。
1、模型 (model): 實現自定義模型可以通過QAbstractltemModel類繼承,也可以通過QAbstractListModel和QAbstractTableModel類繼承實現列表模型或者表格模型
2、視圖(view): 實現自定義的視圖View,可以繼承子QAbstractltemView類,對所需要的虛擬函數進行重定義。
3、代理 (delegate) :在表格當中嵌入各種不同的控件,通過表格中控件對編輯的內容進行操作。表格插入控件方式,控件始終顯示。
Model-View-Delegate機制可以簡單的理解為將本地的一些數據以特定的UI形式呈現出來。常見的數據結構包括列表數據(list)、表格數據(table)、樹狀數據(tree),分別對應著QT中的QListView、QTableView、QTreeView控件。本地數據和視圖代理之間的關系如下圖所示:
數據模型中的數據來源可以是本地的XML文件、JSON文件、二進制數據,也可以數據庫中的數據表。這些數據源中的數據按照一定的結構加載到對應的數據模型中,我們可以通過操作數據模型中的數據來間接的操作數據源中的數據。
有時候,我們需要對數據模型中的數據進行二次處理,包括數據篩選、數據排序、數據處理等等,這時候我們就得需要引入模型代理,負責對數據模型進行處理。當然模型代理不是必須的。QT中的模型代理有兩種都是QAbstractProxyModel的子類。分別是QIdentityProxyModel和QSortFilterProxyModel。
QIdentityProxyModel代理不會修改原有的數據模型,只是重寫了data()函數,對返回視圖的數據進行了重新組合和修改。
QSortFilterProxyModel代理會對模型的數據進行篩選和排序。
有了這兩個代理類,我們就可以對模型中的數據進行處理了。
數據模型加載完畢數據之后,View層就會對數據模型中的數據進行呈現了。由于數據模型中的數據都是以一個個數據單元存在的,我們可以為每個數據單元指定對應的UI。這就用到了委托代理Delegate,委托控件可以給數據模型中的每一個元素指定固定的UI。通過委托代理的機制,我們就可以以個性的圖形界面呈現本地數據了。
2. View
視圖組件(View)就是顯示數據模型的數據的界面組件,Qt 提供的視圖組件如下:
QListView:用于顯示單列的列表數據,適用于一維數據的操作。
QTreeView:用于顯示樹狀結構數據,適用于樹狀結構數據的操作。
QTableView:用于顯示表格狀數據,適用于二維表格型數據的操作。
QColumnView:用多個QListView顯示樹狀層次結構,樹狀結構的一層用一個QListView顯示。
QHeaderView:提供行表頭或列表頭的視圖組件,如QTableView的行表頭和列表頭。
視圖組件在顯示數據時,只需調用視圖類的 setModel() 函數,為視圖組件設置一個數據模型就可以實現視圖組件與數據模型之間的關聯,在視圖組件上的修改將自動保存到關聯的數據模型里,一個數據模型可以同時在多個視圖組件里顯示數據。
同時提供了 QListWidget、QTreeWidget 和 QtableWidget 3個可用于數據編輯的組件。這 3 個類稱為便利類(convenience classes),它們分別是 3 個視圖類的子類,其層次關系如圖 所示。
用于 Model/View 結構的幾個視圖類直接從 QAbstractItemView 繼承而來,而便利類則從相應的視圖類繼承而來。
視圖組件類的數據采用單獨的數據模型,視圖組件不存儲數據。便利類則為組件的每個節點或單元格創建一個項(item),用項存儲數據、格式設置等。所以,便利類沒有數據模型,它實際上是用項的方式集成了數據模型的功能,這樣就將界面與數據綁定了。
所以,便利類缺乏對大型數據源進行靈活處理的能力,適用于小型數據的顯示和編輯。
3. Model
QT提供了許多Model類
QStandardItemModel:可以作為QListView、QTableView、QTreeView的標準model。
QAbstractListModel:需要使用QListView顯示數據,并配合自定義model時,我們從此類繼承。
QAbstractTableModel:需要使用QTableView顯示數據時,并配合自定義model時,我們從此類繼承。
QAbstractItemModel:需要使用QTreeView顯示數據時,并配合自定義model時,我們從此類繼承。
如圖是數據模型的 3 種常見表現形式。不管數據模型的表現形式是怎么樣的,數據模型中存儲數據的基本單元都是項(item),每個項有一個行號、一個列號,還有一個父項。在列表和表格模式下,所有的項都有一個相同的頂層項;在樹狀結構中,行號、列號、父項稍微復雜一點,但是由這 3 個參數完全可以定義一個項的位置,從而存取項的數據。
為了保證數據的表示與數據存取方式隔離,數據模型中引入了模型索引的概念。通過數據模型存取的每個數據都有一個模型索引,視圖組件和代理都通過模型索引來獲取數據。
QModelIndex 表示模型索引的類。模型索引提供數據存取的一個臨時指針,用于通過數據模型提取或修改數據。因為模型內部組織數據的結構隨時可能改變,所以模型索引是臨時的。如果需要使用持久性的模型索引,則要使用 QPersistentModelIndex 類。
以QStandardItemModel為例
QTreeView* view = new QTreeView();
QStandardItemModel* model = new QStandardItemModel();
for (int row = 0; row < 4; ++row) {QStandardItem *item = new QStandardItem(QString("%1").arg(row) );model->appendRow( item );
}
view->setModel(model);
用法比較簡單,QStandardItemModel可以使用QStandardItem,通過不斷添加子節點,從而構建出list、table、tree結構的數據。
使用QStandardItemModel表示數據集具有以下優點:
- 實現代碼簡單
- 該類使用QStandardItem存放數據項,用戶不必定義任何數據結構來存放數據項;
- QStandardItem使用自關聯關系,能夠表達列表、表格、樹甚至更復雜的數據結構,能夠涵蓋各種各樣的數據集;
- QStandardItem本身存放著多個『角色,數據子項』,視圖類、委托類或者其他用戶定義的類能夠方便地依據角色訪問各個數據子項。
缺點: - 當數據集中的數據項很多時,施加在數據集上的某些操作的執行效率會很低。
- 數據太大時,占用內存巨大,性能低下
那么一般是使用抽象類來自定義實現一個Model來使用
這里以QAbstractTableModel為例
這三個純虛函數是必須實現的
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const = 0;
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const = 0;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0;
然后一般還需要顯示表頭,則還需要實現
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
如果使用通用的編輯功能(通過 view 的 editTriggers 設置觸發編輯狀態的方式,一般為雙擊),model 需要實現兩個接口:
//設置單元格數據
bool setData(const QModelIndex &index, const QVariant &value,int role = Qt::EditRole) override;
//單元格的可操作性標志位,如可編輯,可選中等
Qt::ItemFlags flags(const QModelIndex& index) const override;
要在單元格中啟用委托也是需要重寫這兩個函數
4. Delegate
首先查閱資料發現提到兩種委托類分別是QStyledItemDelegate
和QItemDelegate
QItemDelegate
作用
QItemDelegate
是Qt中較早期的委托類,用于處理視圖中項的顯示和編輯。它繼承自QAbstractItemDelegate
,并提供了默認的繪制和編輯功能。
特點
繪制功能:在繪制項目時,使用獨立的繪制方法,不依賴于當前樣式。。
編輯功能:它提供了默認的編輯器(如文本框、復選框等)和編輯行為。
在Qt的早期版本中,它是默認的委托,但后來被 QStyledItemDelegate
取代。
QStyledItemDelegate
作用
QStyledItemDelegate
是Qt 4.4引入的,旨在替代QItemDelegate
,提供更靈活和現代的項委托。它也是繼承自QAbstractItemDelegate
,并使用QStyle
進行繪制,但與QItemDelegate
相比,它在處理復雜和定制的用戶界面時更加高效和靈活。
特點
增強的繪制功能:QStyledItemDelegate
利用了QStyle
的高級功能,可以更好地支持復雜的UI元素和現代風格。
統一的風格:它能更好地與Qt的樣式系統集成,確保在不同平臺和風格下的外觀一致性。
簡化的自定義:提供了一些額外的虛函數(如initStyleOption
),使自定義項的顯示和編輯更加簡單和靈活。
區別
繪制機制:QStyledItemDelegate
利用了更高級的QStyle
功能,能夠更好地處理復雜的繪制需求,而QItemDelegate
使用的是較早期的繪制方法。
從Qt 4.4開始,QStyledItemDelegate
成為Qt視圖組件的默認委托(例如在 QTableView
、QListView
中)。
QItemDelegate是Qt早期的默認委托,現在主要用于向后兼容。
特性 | QStyledItemDelegate | QItemDelegate |
---|---|---|
繪制基礎 | 使用 Qt 樣式系統 (QStyle) 直接繪制 | (不依賴樣式系統) |
樣式支持 | 完全支持 Qt 樣式表 (QSS) | 不支持樣式表 |
外觀一致性 | 與應用程序主題保持一致 | 獨立繪制,可能不一致 |
方面 | QStyledItemDelegate | QItemDelegate |
---|---|---|
樣式表支持 | ? 完全支持 QSS | ? 不支持 |
高 DPI 縮放 | ? 自動處理 | ? 需手動實現 |
主題集成 | ? 完美匹配系統主題 | ? 可能不一致 |
自定義數據類型 | ? 通過 QVariant 支持 | ? 有限支持 |
推薦使用場景 | 所有新項目 | 維護遺留代碼 |
自定義簡便性:QStyledItemDelegate
提供了更多的虛函數和工具函數,使得自定義繪制和編輯行為更加簡便和靈活。
風格一致性:QStyledItemDelegate
能夠更好地與Qt的樣式系統集成,確保在不同平臺和風格下的外觀一致性。
官方建議:自 Qt 4.4
起,始終優先使用 QStyledItemDelegate
,它提供更好的樣式集成、更一致的渲染行為,并支持所有現代 Qt
特性。
結論
總的來說,QStyledItemDelegate
是對QItemDelegate
的改進,提供了更強大和靈活的功能。在大多數情況下,建議使用QStyledItemDelegate
來處理自定義項的顯示和編輯。QItemDelegate
雖然仍然可以使用,但在新項目中更推薦使用QStyledItemDelegate
。
然后還需要注意的是,在使用委托時,QT提供了四個方法需要重寫實現
// 創建編輯器
virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
// 設置編輯器數據
virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override;
// 更新編輯器集合屬性
virtual void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
// 設置模型數據
virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
5. 以TableView為例
實現效果如下:
表格中實現QSpinBox委托效果
注意:委托僅在模型項可編輯時才會觸發編輯器。
否則即使設置好了委托,也會發生點擊無反應的情況
在使用自定義的數據類型QAbstractTableModel
時要重寫flags()函數使模型可編輯
Qt::ItemFlags YourModel::flags(const QModelIndex &index) const {if (index.column() == 0) {return Qt::ItemIsEditable | QAbstractTableModel::flags(index);}return QAbstractTableModel::flags(index);
}
在使用QStandardItemModel
則需設置標志位
// 設置第0列的所有單元格可編輯
for (int row = 0; row < model->rowCount(); ++row) {QModelIndex index = model->index(row, 0);model->setData(index, Qt::ItemIsEditable, Qt::EditRole);
}
主要實現代碼如下:
SqlDialog.cpp
sqlTableModel = new SqlTableModel();ui.tableView->setModel(sqlTableModel);ui.tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); //表寬隨數據長度自適應SpinBoxDelegate* sboxDelegate = new SpinBoxDelegate(this);ComboBoxDelegate* cboxDelegate = new ComboBoxDelegate(this);sboxDelegate->setSboxMinValue(0);sboxDelegate->setSboxMaxValue(100);sboxDelegate->setSboxSingleStep(1);sboxDelegate->setSboxInitValue(10);ui.tableView->setItemDelegateForColumn(0, sboxDelegate);ui.tableView->setItemDelegateForColumn(1, cboxDelegate);
SqlTableModel.h
class SqlTableModel : public QAbstractTableModel
{public:SqlTableModel(QObject* parent = nullptr);~SqlTableModel();Qt::ItemFlags SqlTableModel::flags(const QModelIndex& index) const override;bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;int rowCount(const QModelIndex& parent = QModelIndex()) const override;int columnCount(const QModelIndex& parent = QModelIndex()) const override;QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;bool addSqlData(string lnum, string judge, string link);bool removeSqlData(int row);void removeAllSqlData();bool dataSaveAsFile();QList<QStringList> sqlList; //自定義item數據類型MyVsSqlTool* mySqlPtr = nullptr;
private:int maxStoreMessages;int maxDisMessages;int dataID;
};
SqlTableModel.cpp
Qt::ItemFlags SqlTableModel::flags(const QModelIndex& index) const {if (index.column() == 0) {return Qt::ItemIsEditable | QAbstractTableModel::flags(index);}else if (index.column() == 1) {return Qt::ItemIsEditable | QAbstractTableModel::flags(index);}return QAbstractTableModel::flags(index);
}bool SqlTableModel::setData(const QModelIndex& index, const QVariant& value, int role)
{int row = index.row();sqlList.operator [](row).replace(0, QString::number(value.toInt()).rightJustified(3, '0'));return true;
}
QVariant SqlTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{QStringList headers;headers << QString("消息ID")<< QString("時間")<< QString("車型號")<< QString("結果");if (orientation == Qt::Horizontal && role == Qt::DisplayRole){return headers.at(section);}return QVariant();
}int SqlTableModel::rowCount(const QModelIndex& parent) const //行總數查詢,我猜會一直中斷查詢
{if (sqlList.size() > maxDisMessages){return maxDisMessages;}elsereturn sqlList.size();}
int SqlTableModel::columnCount(const QModelIndex& parent) const //此為定義表的列總數,headerData遍歷的
{return 4;
}QVariant SqlTableModel::data(const QModelIndex& index, int role) const //data這里會依次查詢你寫上的每種role,遍歷其中的index(即表列數從0開始)
{QStringList stringList;if (!index.isValid())return QVariant();if (role == Qt::DisplayRole){int row = index.row();stringList = sqlList.at(row);if (stringList.count() >= 3){if (0 == index.column()) //返回值即是第一列填入return stringList.at(0);else if (1 == index.column()){QString strTime = stringList.at(1);if (!strTime.isEmpty()){strTime = strTime.replace('T', ' ');}return strTime;}else if (2 == index.column())return stringList.at(2);else if (3 == index.column())return stringList.at(3);}}elsereturn QVariant();
}
SimpleDelegate.h
class SpinBoxDelegate :public QStyledItemDelegate
{Q_OBJECTpublic:SpinBoxDelegate(QObject* parent = nullptr);
protected:QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;void setEditorData(QWidget* editor, const QModelIndex& index) const override;void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
private:void init();
public:/*QSpinBox設置相關參數函數*/void setSboxMaxValue(const int max);void setSboxMinValue(const int min);void setSboxPrefixStr(const QString& prefix);void setSboxSuffixStr(const QString& suffix);void setSboxSingleStep(const int SingleStep);void setSboxInitValue(const int initValue);void setSboxStepType(QAbstractSpinBox::StepType st);
private:/*QSpinBox相關參數*/int sboxMaxValue;/*微調框的最大值*/int sboxMinValue;/*微調框的最小值*/QString sboxPrefixStr;/*微調框前綴*/QString sboxSuffixStr;/*微調框后綴*/int sboxSingleStep;/*微調框步長*/int sboxInitValue;/*微調框初始值*/QAbstractSpinBox::StepType sboxStepType;/*微調框步長類型*/
};
SimpleDelegate.cpp
QWidget* SpinBoxDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem&/*option*/, const QModelIndex& index) const
{QSpinBox* sbox = new QSpinBox(parent);sbox->setRange(sboxMinValue, sboxMaxValue);sbox->setSuffix(sboxSuffixStr);sbox->setPrefix(sboxPrefixStr);sbox->setSingleStep(sboxSingleStep);sbox->setStepType(sboxStepType);sbox->setValue(sboxInitValue);return sbox;
}void SpinBoxDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{auto value = index.model()->data(index, Qt::EditRole);QSpinBox* spinBox = static_cast<QSpinBox*>(editor);spinBox->setValue(value.toInt());
}void SpinBoxDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,const QModelIndex& index) const
{QSpinBox* spinBox = static_cast<QSpinBox*>(editor);QVariant value = spinBox->value();model->setData(index, value, Qt::EditRole);}void SpinBoxDelegate::updateEditorGeometry(QWidget* editor,const QStyleOptionViewItem& option,const QModelIndex&/* index */) const
{editor->setGeometry(option.rect);
}