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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/95689.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/95689.shtml
英文地址,请注明出处:http://en.pswp.cn/web/95689.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

光谱相机在手机行业的应用

在手机行业&#xff0c;光谱相机技术通过提升拍照色彩表现和扩展健康监测等功能&#xff0c;正推动摄像头产业链升级&#xff0c;并有望在AR/VR、生物医疗等领域实现更广泛应用。以下为具体应用场景及技术突破的详细说明&#xff1a;‌一、光谱相机在手机行业的应用场景‌‌拍照…

FASTMCP中的Resources和Templates

Resources 给 MCP 客户端/LLM 读取的数据端点&#xff08;只读、按 URI 索引、像“虚拟文件系统”或“HTTP GET”&#xff09;&#xff1b; Templates 可带参数的资源路由&#xff08;URI 里占位符 → 运行函数动态生成内容&#xff09;。 快速要点 • 用途&#xff1a;把文件…

OpenBMC之编译加速篇

加快 OpenBMC 的编译速度是一个非常重要的话题,因为完整的构建通常非常耗时(在高性能机器上也需要数十分钟,普通电脑上可能长达数小时)。以下是从不同层面优化编译速度的详细策略,您可以根据自身情况组合使用。 一、核心方法:利用 BitBake 的缓存和共享机制(效果最显著…

Kafka面试精讲 Day 8:日志清理与数据保留策略

【Kafka面试精讲 Day 8】日志清理与数据保留策略 在Kafka的高吞吐、持久化消息系统中&#xff0c;日志清理与数据保留策略是决定系统资源利用效率、数据可用性与合规性的关键机制。作为“Kafka面试精讲”系列的第8天&#xff0c;本文聚焦于日志清理机制&#xff08;Log Cleani…

基于Hadoop的网约车公司数据分析系统设计(代码+数据库+LW)

摘 要 本系统基于Hadoop平台&#xff0c;旨在为网约车公司提供一个高效的数据分析解决方案。随着网约车行业的快速发展&#xff0c;平台上产生的数据量日益增加&#xff0c;传统的数据处理方式已无法满足需求。因此&#xff0c;设计了一种基于Hadoop的大规模数据处理和分析方…

Python反向迭代完全指南:从基础到高性能系统设计

引言&#xff1a;反向迭代的核心价值在数据处理和算法实现中&#xff0c;反向迭代是解决复杂问题的关键技术。根据2024年Python开发者调查报告&#xff1a;85%的链表操作需要反向迭代78%的时间序列分析依赖反向处理92%的树结构遍历需要后序/逆序访问65%的加密算法使用反向计算P…

ClickHouse使用Docker部署

OLTP和OLAP介绍基本业务量到达分库分表量级&#xff0c;则离不开数据大屏、推荐系统、画像系统等搭建&#xff0c;需要搭建以上系统&#xff0c;则离不开海量数据进行存储-分析-统计。 而海量数据下 TB、PB级别数据存储&#xff0c;靠Mysql进行存储-分析-统计无疑是灾难。所以就…

Python 算数运算练习题

计算数字特征值题目描述 编写一个程序&#xff0c;接收用户输入的两个整数 a 和 b&#xff08;a > b > 0&#xff09;&#xff0c;计算并输出以下结果&#xff1a;a 与 b 的和的平方a 除以 b 的商和余数a 与 b 的平均数&#xff08;保留 2 位小数&#xff09;示例请输入整…

【物种分布模型】R语言物种气候生态位动态量化与分布特征模拟——气候生态位动态检验、质心转移可视化、适生区预测等

R语言是一种广泛用于统计分析和图形表示的编程语言&#xff0c;强大之处在于可以进行多元数据统计分析&#xff0c;以及丰富的生态环境数据分析的方法&#xff0c;在生态学领域得到广泛应用。本次教程将通过R语言多个程序包与GIS融合应用&#xff0c;提升物种气候生态位动态量化…

【算法速成课2 | 题单】背包问题

专栏指路&#xff1a;《算法速成课》 前导&#xff1a; 动态规划问题中最入门、也最多变的&#xff0c;当属背包问题。 简单来说&#xff0c;就是在有限的空间&#xff0c;&#xff08;花费最小的代价&#xff09;达成最大的收益。 本文会讲一些常见的背包问题&#xff08;可…

计算机视觉与深度学习 | 深度学习图像匹配算法在不同纹理复杂度场景下的鲁棒性和计算效率评估方法

如何评估深度学习图像匹配算法在不同纹理复杂度场景下的鲁棒性和计算效率? 文章目录 如何评估深度学习图像匹配算法在不同纹理复杂度场景下的鲁棒性和计算效率? 一、评估框架概述 1.1 核心评估维度 1.2 评估流程 二、纹理复杂度场景分类方法 2.1 纹理特征量化指标 2.2 场景分…

AI 提示词工程与上下文工程:从入门到深入的系统实践指南

前言近年来&#xff0c;随着大语言模型&#xff08;LLM&#xff0c;Large Language Model&#xff09;的快速发展&#xff0c;提示词工程&#xff08;Prompt Engineering&#xff09;与上下文工程&#xff08;Context Engineering&#xff09;逐渐成为 AI 应用开发中至关重要的…

救火!Linux服务器慢如蜗牛:一套从根源到应用的性能问题诊断全攻略

前言&#xff1a;从“玄学”到“科学” “服务又卡了&#xff01;” 这是我们每个Linux运维/SRE工程师最不想听到&#xff0c;却又最常听到的一句话。随之而来的&#xff0c;往往是开发、产品、甚至老板的连环追问。此时&#xff0c;一个经验不足的工程师可能会立刻登录服务器&…

BYOFF (Bring Your Own Formatting Function)解析(80)

BYOFF (Bring Your Own Formatting Function)解析(80) 看起来不错!要注意的是,我们并没有真正使用任何自定义的特殊标记。其中 “Question”(问题)、“Answer”(答案)、井号(#)以及 EOS 标记,都是分词器词汇表中常见的条目。在本节后续内容中,我们将探讨自定义特…

秋招|MCU+RTOS技术栈——面试八股文整理3:STM32

目录 1.单片机启动流程 2.看门狗 3.最小系统 4.ROM、RAM、Flash 5.EPROM、EEPROM 6.Bootloader与OTA 1.单片机启动流程 单片机的启动流程是指从上电或复位开始到应用用户主程序执行的一系列自动操作过程&#xff0c;不同架构的单片机流程略有差异&#xff0c;但核心逻辑…

在 CentOS 9 上安装 Docker 的完整指南

1.准备安装环境&#xff08;1&#xff09;禁用防火墙与SELinux[rootlocalhost ~]# systemctl disable --now firewalld.service Removed "/etc/systemd/system/multi-user.target.wants/firewalld.service". Removed "/etc/systemd/system/dbus-org.fedoraproj…

如何实现外语播客的中文同传?

Bayt播客可以将任何语言的外语播客&#xff08;英文播客、日文播客、韩文播客等&#xff09;转换成中文音频收听&#xff0c;实现同声传译。并且还提供中文和原文的双语字幕。帮助你跨越语言障碍&#xff0c;收听高质量外语内容 核心功能&#xff1a; 1、所有语言的播客均可转…

Spring Cloud ------ Gateway

一、什么是网关 经常面试的人肯定知道&#xff0c;在去公司面试时&#xff0c;通常不会直接去面试官那里面试&#xff0c;而是先去前台进行询问面试官的所在地&#xff0c;并进行一些相关登记。而网关对于一个微服务项目来说&#xff0c;就类似于一个前台&#xff0c;打到微服…

Go初级之九:Select 与并发控制

在Go语言中&#xff0c;select语句是处理并发编程的核心工具之一。它让我们能够优雅地管理多个通道操作&#xff0c;实现高效的并发控制。 1. Select 语句基础 1.1 Select 的基本语法 package mainimport ("fmt""time" )func main() {ch1 : make(chan stri…

使用 Acme.sh 获取和管理免费 SSL 证书

Acme.sh 是一个开源的 Shell 脚本工具&#xff0c;支持从 Let’s Encrypt 等证书颁发机构获取免费的 SSL/TLS 证书。它支持多种验证方式&#xff0c;并能自动续期证书&#xff0c;适合个人网站或企业使用。 目标 同时支持&#xff0c;主域名和泛域名 安装 Acme.sh获取源码 git …