文章目录
- [1. QT中的MVD模式](#1. QT中的MVD模式)
- [2. View](#2. View)
- [3. Model](#3. Model)
- [4. Delegate](#4. Delegate)
- [5. 以TableView为例](#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为例
cpp
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为例
这三个纯虚函数是必须实现的
cpp
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;
然后一般还需要显示表头,则还需要实现
cpp
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
如果使用通用的编辑功能(通过 view 的 editTriggers 设置触发编辑状态的方式,一般为双击),model 需要实现两个接口:
cpp
//设置单元格数据
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提供了四个方法需要重写实现
cpp
// 创建编辑器
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()函数使模型可编辑
cpp
Qt::ItemFlags YourModel::flags(const QModelIndex &index) const {
if (index.column() == 0) {
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}
return QAbstractTableModel::flags(index);
}
在使用QStandardItemModel
则需设置标志位
cpp
// 设置第0列的所有单元格可编辑
for (int row = 0; row < model->rowCount(); ++row) {
QModelIndex index = model->index(row, 0);
model->setData(index, Qt::ItemIsEditable, Qt::EditRole);
}
主要实现代码如下:
SqlDialog.cpp
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
cpp
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
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;
}
else
return 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);
}
}
else
return QVariant();
}
SimpleDelegate.h
cpp
class SpinBoxDelegate :
public QStyledItemDelegate
{
Q_OBJECT
public:
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
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);
}