Qt开发:模型/视图(Model/View)框架

Qt的模型/视图(Model/View)框架提供了一种将数据展示与数据处理逻辑分离的方法,使得开发者可以更方便地管理和展示数据。该框架主要由三部分组成:模型(Model)、视图(View)和代理(Delegate)。

核心组件

模型(Model)

模型负责存储数据,并向视图提供访问这些数据的接口。Qt提供了几种现成的模型类,包括:

  • QStringListModel:用于存储简单的字符串列表。
  • QStandardItemModel:支持更复杂的树形结构数据,每个项都可以有多个角色(Role),允许存储不同类型的数据显示在同一模型中。
  • QFileSystemModel:为文件系统提供了一个现成的模型实现,可用于显示目录和文件信息。
  • 自定义模型:如果内置模型不能满足需求,还可以继承QAbstractItemModel或其子类如QAbstractTableModelQAbstractListModel来创建自己的模型。
  • QSqlQueryModel/QSqlTableModel:数据库模型

视图(View)

视图负责呈现模型中的数据。Qt提供了多种视图组件:

  • QListView:以列表形式展示数据。
  • QTableView:以表格形式展示数据,适合二维数据集。
  • QTreeView:支持层次化数据展示,适用于树形结构的数据。
  • QColumnView:提供一个列式的导航视图,通常用于浏览层次化的数据结构。

代理(Delegate)

代理用于控制如何渲染视图中的项目以及如何编辑它们。默认情况下,视图使用QStyledItemDelegate进行绘制和编辑。但可以通过继承QAbstractItemDelegate来创建自定义代理,以实现特定的外观或行为。

数据角色

在模型/视图框架中,数据角色(Role)用来区分不同类型的数据。例如,Qt::DisplayRole用于获取要在视图中显示的文本,而Qt::EditRole则用于编辑操作。此外还有许多其他预定义的角色,同时也支持自定义角色。

使用示例

简单例子,演示了如何使用QStringListModelQListView一起工作:

#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()信号,告知视图哪些数据已经改变。这个信号需要两个参数:topLeftbottomRight,它们定义了已更改数据区域的范围。如果仅更改了一个项目的数据,则这两个参数应指向同一个模型索引。

示例代码

假设有一个自定义模型继承自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()信号,可以确保你的模型与视图保持同步,使得用户界面能够及时反映出任何后台数据的变化。这对于维护良好的用户体验至关重要。

总结

优势特点

  1. 关注点分离:数据管理与显示逻辑解耦

  2. 高效处理:只加载和渲染可见数据

  3. 灵活扩展

    • 同一模型可支持多个不同视图

    • 可通过委托完全自定义显示和编辑方式

  4. 标准化接口:统一的数据访问方式

典型应用场景

  • 数据库记录展示与编辑

  • 文件资源管理器

  • 配置参数表格

  • 树形结构数据展示

  • 任何需要处理结构化数据的界面

实用技巧

  1. 大数据优化

    • 实现canFetchMore/fetchMore进行分批加载

    • 使用QIdentityProxyModel等代理模型进行数据转换

  2. 自定义显示

    • 继承QStyledItemDelegate重写paint()createEditor()

    • 使用QDataWidgetMapper将模型映射到独立控件

  3. 性能提升

    • 对于静态数据,实现flags()返回Qt::NoItemFlags

    • 批量修改时使用beginResetModel()/endResetModel()

Qt的模型/视图架构是其数据处理的核心机制,熟练掌握它不仅简化了数据展示的设计,而且增强了灵活性和可扩展性,非常适合构建复杂的数据驱动应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值