Qt Model/View/Delegate 架构详解

发布于:2025-09-07 ⋅ 阅读:(18) ⋅ 点赞:(0)

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:通用项目模型
自定义模型示例

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

// 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
// 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:树形视图
视图使用示例
// 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
// 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提供了默认的委托,但我们也可以创建自定义委托来实现特定的显示和编辑需求。

简单自定义委托示例
// 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
// 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);
}

使用自定义委托:

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

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

// 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)系统来区分不同类型的数据:

// 常见的角色
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开发的重要技能,它不仅适用于简单的数据展示,也能应对复杂的企业级应用需求。


网站公告

今日签到

点亮在社区的每一天
去签到