Qt Model/View/Delegate 架构详解

Qt Model/View/Delegate 架构详解

Qt的Model/View/Delegate架构是Qt框架中一个重要的设计模式,它实现了数据存储、数据显示和数据编辑的分离。这种架构不仅提高了代码的可维护性和可重用性,还提供了极大的灵活性。

1. 架构概述

Model/View/Delegate架构将应用程序分为三个主要部分:

  • Model(模型):负责数据的存储和管理
  • View(视图):负责数据的显示
  • Delegate(委托):负责数据的编辑和渲染

这种分离使得我们可以独立地修改数据的存储方式、显示方式和编辑方式,而不会影响其他部分。

2. 核心组件详解

2.1 Model(模型)

模型是数据的容器,负责管理数据并提供标准接口供视图和委托访问。Qt提供了多个预定义的模型类:

  • QAbstractItemModel:所有模型的基类
  • QAbstractListModel:列表模型的基类
  • QAbstractTableModel:表格模型的基类
  • QStringListModel:字符串列表模型
  • QStandardItemModel:通用项目模型
自定义模型示例

让我们创建一个自定义的员工信息模型:

cpp 复制代码
// employee_model.h
#ifndef EMPLOYEEMODEL_H
#define EMPLOYEEMODEL_H

#include <QAbstractTableModel>
#include <QList>
#include <QString>

struct Employee {
    QString name;
    int age;
    QString department;
    double salary;
};

class EmployeeModel : public QAbstractTableModel {
    Q_OBJECT

public:
    enum Column {
        NameColumn = 0,
        AgeColumn,
        DepartmentColumn,
        SalaryColumn,
        ColumnCount
    };

    explicit EmployeeModel(QObject *parent = nullptr);

    // 必须实现的基本函数
    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;
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

    // 可选:支持编辑功能
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;

    // 自定义功能
    void addEmployee(const Employee &employee);
    void removeEmployee(int row);

private:
    QList<Employee> m_employees;
};

#endif // EMPLOYEEMODEL_H
cpp 复制代码
// employee_model.cpp
#include "employee_model.h"
#include <QColor>

EmployeeModel::EmployeeModel(QObject *parent)
    : QAbstractTableModel(parent)
{
    // 添加示例数据
    m_employees.append({"张三", 28, "开发部", 12000.0});
    m_employees.append({"李四", 32, "测试部", 10000.0});
    m_employees.append({"王五", 25, "设计部", 9000.0});
}

int EmployeeModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;
    return m_employees.size();
}

int EmployeeModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;
    return ColumnCount;
}

QVariant EmployeeModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || index.row() >= m_employees.size())
        return QVariant();

    const Employee &employee = m_employees.at(index.row());

    switch (role) {
    case Qt::DisplayRole:
    case Qt::EditRole:
        switch (index.column()) {
        case NameColumn:
            return employee.name;
        case AgeColumn:
            return employee.age;
        case DepartmentColumn:
            return employee.department;
        case SalaryColumn:
            return employee.salary;
        default:
            break;
        }
        break;
    case Qt::TextAlignmentRole:
        if (index.column() == AgeColumn || index.column() == SalaryColumn)
            return Qt::AlignRight;
        break;
    case Qt::BackgroundRole:
        if (employee.salary > 11000)
            return QColor(Qt::green).lighter(180);
        break;
    }

    return QVariant();
}

QVariant EmployeeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation == Qt::Horizontal) {
        switch (section) {
        case NameColumn:
            return tr("姓名");
        case AgeColumn:
            return tr("年龄");
        case DepartmentColumn:
            return tr("部门");
        case SalaryColumn:
            return tr("薪资");
        default:
            break;
        }
    }

    return QVariant();
}

bool EmployeeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid() || role != Qt::EditRole || index.row() >= m_employees.size())
        return false;

    Employee &employee = m_employees[index.row()];

    switch (index.column()) {
    case NameColumn:
        employee.name = value.toString();
        break;
    case AgeColumn:
        employee.age = value.toInt();
        break;
    case DepartmentColumn:
        employee.department = value.toString();
        break;
    case SalaryColumn:
        employee.salary = value.toDouble();
        break;
    default:
        return false;
    }

    emit dataChanged(index, index);
    return true;
}

Qt::ItemFlags EmployeeModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::NoItemFlags;

    return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

void EmployeeModel::addEmployee(const Employee &employee)
{
    beginInsertRows(QModelIndex(), m_employees.size(), m_employees.size());
    m_employees.append(employee);
    endInsertRows();
}

void EmployeeModel::removeEmployee(int row)
{
    if (row < 0 || row >= m_employees.size())
        return;

    beginRemoveRows(QModelIndex(), row, row);
    m_employees.removeAt(row);
    endRemoveRows();
}

2.2 View(视图)

视图负责显示模型中的数据。Qt提供了多种视图类:

  • QListView:列表视图
  • QTableView:表格视图
  • QTreeView:树形视图
视图使用示例
cpp 复制代码
// main_window.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTableView>
#include "employee_model.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);

private slots:
    void addEmployee();
    void removeEmployee();

private:
    QTableView *m_view;
    EmployeeModel *m_model;
};

#endif // MAINWINDOW_H
cpp 复制代码
// main_window.cpp
#include "main_window.h"
#include <QVBoxLayout>
#include <QWidget>
#include <QPushButton>
#include <QInputDialog>
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , m_view(new QTableView)
    , m_model(new EmployeeModel(this))
{
    setWindowTitle("员工管理系统");
    
    // 设置模型
    m_view->setModel(m_model);
    
    // 创建按钮
    QPushButton *addButton = new QPushButton("添加员工");
    QPushButton *removeButton = new QPushButton("删除员工");
    
    connect(addButton, &QPushButton::clicked, this, &MainWindow::addEmployee);
    connect(removeButton, &QPushButton::clicked, this, &MainWindow::removeEmployee);
    
    // 布局
    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(m_view);
    layout->addWidget(addButton);
    layout->addWidget(removeButton);
    
    QWidget *centralWidget = new QWidget;
    centralWidget->setLayout(layout);
    setCentralWidget(centralWidget);
    
    // 调整列宽
    m_view->resizeColumnsToContents();
}

void MainWindow::addEmployee()
{
    Employee employee;
    employee.name = QInputDialog::getText(this, "添加员工", "姓名:");
    if (employee.name.isEmpty())
        return;
        
    employee.age = QInputDialog::getInt(this, "添加员工", "年龄:", 25, 18, 100);
    employee.department = QInputDialog::getText(this, "添加员工", "部门:");
    employee.salary = QInputDialog::getDouble(this, "添加员工", "薪资:", 8000, 0, 1000000, 2);
    
    m_model->addEmployee(employee);
}

void MainWindow::removeEmployee()
{
    QModelIndex index = m_view->currentIndex();
    if (!index.isValid()) {
        QMessageBox::information(this, "提示", "请先选择要删除的员工");
        return;
    }
    
    m_model->removeEmployee(index.row());
}

2.3 Delegate(委托)

委托负责控制视图中项目的显示和编辑方式。Qt提供了默认的委托,但我们也可以创建自定义委托来实现特定的显示和编辑需求。

简单自定义委托示例
cpp 复制代码
// salary_delegate.h
#ifndef SALARYDELEGATE_H
#define SALARYDELEGATE_H

#include <QStyledItemDelegate>
#include <QDoubleSpinBox>

class SalaryDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
    explicit SalaryDelegate(QObject *parent = nullptr);

    // 创建编辑器
    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;
};

#endif // SALARYDELEGATE_H
cpp 复制代码
// salary_delegate.cpp
#include "salary_delegate.h"
#include <QDoubleSpinBox>

SalaryDelegate::SalaryDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
{
}

QWidget *SalaryDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                                      const QModelIndex &index) const
{
    QDoubleSpinBox *editor = new QDoubleSpinBox(parent);
    editor->setMinimum(0);
    editor->setMaximum(1000000);
    editor->setDecimals(2);
    editor->setSuffix(" 元");
    return editor;
}

void SalaryDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    double value = index.model()->data(index, Qt::EditRole).toDouble();
    QDoubleSpinBox *spinBox = static_cast<QDoubleSpinBox*>(editor);
    spinBox->setValue(value);
}

void SalaryDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                  const QModelIndex &index) const
{
    QDoubleSpinBox *spinBox = static_cast<QDoubleSpinBox*>(editor);
    spinBox->interpretText();
    double value = spinBox->value();
    model->setData(index, value, Qt::EditRole);
}

void SalaryDelegate::updateEditorGeometry(QWidget *editor,
                                          const QStyleOptionViewItem &option,
                                          const QModelIndex &index) const
{
    editor->setGeometry(option.rect);
}

使用自定义委托:

cpp 复制代码
// 在MainWindow构造函数中添加
m_view->setItemDelegateForColumn(EmployeeModel::SalaryColumn, new SalaryDelegate(this));
复杂自定义委托示例

以下是一个更复杂的委托示例,用于显示星级评分:

cpp 复制代码
// star_delegate.h
#ifndef STARDELEGATE_H
#define STARDELEGATE_H

#include <QStyledItemDelegate>
#include <QPolygonF>

class StarRating
{
public:
    enum EditMode { Editable, ReadOnly };

    explicit StarRating(int starCount = 1, int maxStarCount = 5);

    void paint(QPainter *painter, const QRect &rect,
               const QPalette &palette, EditMode mode) const;
    QSize sizeHint() const;
    int starCount() const { return m_myStarCount; }
    int maxStarCount() const { return m_myMaxStarCount; }
    void setStarCount(int starCount) { m_myStarCount = starCount; }
    void setMaxStarCount(int maxStarCount) { m_myMaxStarCount = maxStarCount; }

private:
    QPolygonF m_starPolygon;
    QPolygonF m_diamondPolygon;
    int m_myStarCount;
    int m_myMaxStarCount;
};

class StarDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
    explicit StarDelegate(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;
    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;
};

#endif // STARDELEGATE_H

3. 工作原理

3.1 数据流

  1. 数据获取:视图通过模型的接口获取数据
  2. 数据显示:视图使用委托来渲染数据项
  3. 数据编辑:用户交互触发委托创建编辑器
  4. 数据更新:编辑完成后,委托将数据写回模型

3.2 角色系统

Qt使用角色(Role)系统来区分不同类型的数据:

cpp 复制代码
// 常见的角色
Qt::DisplayRole      // 显示文本
Qt::EditRole         // 编辑数据
Qt::DecorationRole   // 装饰(图标等)
Qt::ToolTipRole      // 工具提示
Qt::StatusTipRole    // 状态栏提示
Qt::WhatsThisRole    // "这是什么"帮助
Qt::SizeHintRole     // 尺寸提示
Qt::FontRole         // 字体
Qt::TextAlignmentRole // 文本对齐
Qt::BackgroundRole   // 背景
Qt::ForegroundRole   // 前景(文本颜色)

4. 最佳实践

4.1 性能优化

  1. 使用begin/end函数 :在修改模型数据时使用beginInsertRows/endInsertRows等函数
  2. 批量更新 :对于大量数据更新,考虑使用layoutAboutToBeChanged/layoutChanged
  3. 懒加载:对于大数据集,实现懒加载机制

4.2 代码组织

  1. 分离关注点:将模型、视图、委托分别实现
  2. 信号与槽:使用Qt的信号与槽机制进行组件间通信
  3. 可重用性:设计通用的模型和委托,提高代码复用性

4.3 错误处理

  1. 边界检查:始终检查索引的有效性
  2. 异常安全:确保在异常情况下模型状态的一致性
  3. 资源管理:正确管理内存和资源

5. 总结

Qt的Model/View/Delegate架构提供了一种强大而灵活的方式来处理数据的显示和编辑。通过将数据管理、显示和编辑分离,我们可以:

  1. 提高代码的可维护性:各组件职责明确,易于维护
  2. 增强代码的可重用性:模型可以在不同视图中重用
  3. 提升用户体验:通过自定义委托实现丰富的交互效果
  4. 优化性能:通过合理的模型设计提高大数据集的处理效率

掌握Model/View/Delegate架构是Qt开发的重要技能,它不仅适用于简单的数据展示,也能应对复杂的企业级应用需求。

相关推荐
isyangli_blog7 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
_codemonster7 小时前
30分钟快速搭建 Spring Cloud Alibaba 微服务实战(一)
微服务·架构·毕业设计·课程设计
vb2008117 小时前
FastAPI APIRouter
开发语言·python
Benszen7 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆7 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木7 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
Cosolar7 小时前
从零写一个 Attention Is All You Need
人工智能·面试·架构
杨充8 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~8 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言
basketball6168 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang