Qt的模型/视图(Model/View)框架提供了一种将数据展示与数据处理逻辑分离的方法,使得开发者可以更方便地管理和展示数据。该框架主要由三部分组成:模型(Model)、视图(View)和代理(Delegate)。
核心组件
模型(Model)
模型负责存储数据,并向视图提供访问这些数据的接口。Qt提供了几种现成的模型类,包括:
- QStringListModel:用于存储简单的字符串列表。
- QStandardItemModel:支持更复杂的树形结构数据,每个项都可以有多个角色(Role),允许存储不同类型的数据显示在同一模型中。
- QFileSystemModel:为文件系统提供了一个现成的模型实现,可用于显示目录和文件信息。
- 自定义模型:如果内置模型不能满足需求,还可以继承
QAbstractItemModel
或其子类如QAbstractTableModel
、QAbstractListModel
来创建自己的模型。 QSqlQueryModel
/QSqlTableModel
:数据库模型
视图(View)
视图负责呈现模型中的数据。Qt提供了多种视图组件:
- QListView:以列表形式展示数据。
- QTableView:以表格形式展示数据,适合二维数据集。
- QTreeView:支持层次化数据展示,适用于树形结构的数据。
- QColumnView:提供一个列式的导航视图,通常用于浏览层次化的数据结构。
代理(Delegate)
代理用于控制如何渲染视图中的项目以及如何编辑它们。默认情况下,视图使用QStyledItemDelegate
进行绘制和编辑。但可以通过继承QAbstractItemDelegate
来创建自定义代理,以实现特定的外观或行为。
数据角色
在模型/视图框架中,数据角色(Role)用来区分不同类型的数据。例如,Qt::DisplayRole
用于获取要在视图中显示的文本,而Qt::EditRole
则用于编辑操作。此外还有许多其他预定义的角色,同时也支持自定义角色。
使用示例
简单例子,演示了如何使用QStringListModel
与QListView
一起工作:
#include <QApplication>
#include <QStringListModel>
#include <QListView>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QStringListModel *model = new QStringListModel();
QStringList list;
list << "Item 1" << "Item 2" << "Item 3";
model->setStringList(list);
QListView *view = new QListView();
view->setModel(model);
view->show();
return app.exec();
}
这个例子展示了如何创建一个简单的模型并将其连接到视图上,最终在应用程序窗口中显示出来。
自定义模型
创建自定义模型需要继承QAbstractItemModel
并实现必要方法:
class 自定义模型 : public QAbstractTableModel {
public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
return 数据列表.size();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override {
return 2; // 两列
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
if (!index.isValid() || role != Qt::DisplayRole)
return QVariant();
return 数据列表[index.row()][index.column()];
}
QVariant headerData(int section, Qt::Orientation orientation,
int role) const override {
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
return section == 0 ? "名称" : "值";
}
return QVariant();
}
private:
QVector<QVector<QString>> 数据列表;
};
自定义代理
代理(Delegate)用于控制如何渲染视图中的项目以及如何编辑它们。通过自定义代理,你可以完全控制项目的外观和编辑行为
1. 继承 QAbstractItemDelegate 或其子类
通常情况下,可能会选择继承QStyledItemDelegate
而不是直接从QAbstractItemDelegate
开始,因为QStyledItemDelegate
提供了更多的默认实现,特别是与样式相关的功能。
class CustomDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit CustomDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}
// 绘制函数
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
// 返回编辑器的大小建议
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
};
2. 实现 paint()
函数
paint()
函数负责绘制视图中的每个项。可以根据需要使用QPainter
来绘制文本、图像或其他图形元素。
void CustomDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
// 调用基类方法以获取默认样式选项
QStyledItemDelegate::paint(painter, option, index);
// 自定义绘制逻辑
if (index.data().canConvert<QString>()) {
QString text = index.data().toString();
painter->drawText(option.rect, Qt::AlignCenter, text);
}
}
3. 实现 sizeHint()
函数
sizeHint()
提供了关于项目大小的信息,这对于确保视图正确布局非常重要。
QSize CustomDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
// 可以返回一个固定的大小或基于数据计算大小
return QSize(50, 50); // 示例:固定大小
}
4. 编辑支持(可选)
如果希望允许用户编辑模型中的数据,还需要重写以下两个函数:
createEditor()
:创建一个新的编辑器控件。setEditorData()
:将模型中的数据加载到编辑器中。setModelData()
:将编辑器中的数据保存回模型。updateEditorGeometry()
:更新编辑器控件的几何形状。
QWidget* CustomDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
QLineEdit *editor = new QLineEdit(parent);
return editor;
}
void CustomDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const override {
QString value = index.model()->data(index, Qt::EditRole).toString();
QLineEdit *lineEdit = static_cast<QLineEdit*>(editor);
lineEdit->setText(value);
}
void CustomDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override {
QLineEdit *lineEdit = static_cast<QLineEdit*>(editor);
QString value = lineEdit->text();
model->setData(index, value, Qt::EditRole);
}
void CustomDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
editor->setGeometry(option.rect);
}
5. 将自定义代理设置给视图
最后一步是将自定义代理应用到视图上:
QListView *view = new QListView(this);
CustomDelegate *delegate = new CustomDelegate(this);
view->setItemDelegate(delegate);
通过以上步骤,就可以成功地创建并使用自定义的模型/视图框架代理了。能够精确控制每个项目的显示方式,并提供高级编辑功能。
模型-视图通信机制
-
信号槽机制:
-
模型数据变化时发射信号(
dataChanged()
,rowsInserted()
等) -
视图自动连接这些信号并更新显示
-
-
数据角色:
-
DisplayRole
:显示文本 -
EditRole
:编辑内容 -
DecorationRole
:装饰图标 -
其他自定义角色
-
使用 dataChanged()
信号
当模型中的数据发生变化时,应该调用QAbstractItemModel
类(或其子类)提供的dataChanged()
信号,告知视图哪些数据已经改变。这个信号需要两个参数:topLeft
和bottomRight
,它们定义了已更改数据区域的范围。如果仅更改了一个项目的数据,则这两个参数应指向同一个模型索引。
示例代码
假设有一个自定义模型继承自QAbstractTableModel
,并且想更新一个特定项的数据:
bool MyCustomModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (index.isValid() && role == Qt::EditRole) {
// 更新底层数据结构
internalData[index.row()][index.column()] = value;
// 发出dataChanged信号通知视图更新
emit dataChanged(index, index, {role});
return true;
}
return false;
}
在这个例子中,internalData
是存储实际数据的容器(如二维数组),当某个项目的值被修改时,首先更新该容器中的相应位置,然后使用emit dataChanged(...)
通知视图有数据发生了变化。
传递角色信息
从Qt 5.0开始,dataChanged()
信号接受第三个参数,这是一个包含所有受影响的角色的列表。这意味着你可以更精确地指出哪些角色的数据发生了变化,从而让视图只重新获取必要的数据,而不是全部重绘。如果不提供这个参数,默认情况下会认为所有角色都可能受到影响。
视图自动响应
一旦发出了dataChanged()
信号,连接到该模型的所有视图都会自动调用相应的槽函数来更新显示。这是因为视图默认已经连接到了模型的信号上,所以不需要额外的工作就可以使视图响应模型的变化。
注意事项
- 确保索引有效:在调用
dataChanged()
之前,确保所提供的索引是有效的。- 角色管理:只有当相关角色的数据发生改变时才发送对应的角色信息给
dataChanged()
信号,这样可以优化性能,避免不必要的重绘。- 批量更新:如果有大量连续的数据项需要更新,考虑合并为一次较大的
dataChanged()
调用来提高效率。
通过正确地使用dataChanged()
信号,可以确保你的模型与视图保持同步,使得用户界面能够及时反映出任何后台数据的变化。这对于维护良好的用户体验至关重要。
总结
优势特点
-
关注点分离:数据管理与显示逻辑解耦
-
高效处理:只加载和渲染可见数据
-
灵活扩展:
-
同一模型可支持多个不同视图
-
可通过委托完全自定义显示和编辑方式
-
-
标准化接口:统一的数据访问方式
典型应用场景
-
数据库记录展示与编辑
-
文件资源管理器
-
配置参数表格
-
树形结构数据展示
-
任何需要处理结构化数据的界面
实用技巧
-
大数据优化:
-
实现
canFetchMore
/fetchMore
进行分批加载 -
使用
QIdentityProxyModel
等代理模型进行数据转换
-
-
自定义显示:
-
继承
QStyledItemDelegate
重写paint()
和createEditor()
-
使用
QDataWidgetMapper
将模型映射到独立控件
-
-
性能提升:
-
对于静态数据,实现
flags()
返回Qt::NoItemFlags
-
批量修改时使用
beginResetModel()
/endResetModel()
-
Qt的模型/视图架构是其数据处理的核心机制,熟练掌握它不仅简化了数据展示的设计,而且增强了灵活性和可扩展性,非常适合构建复杂的数据驱动应用。