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

讓我們創建一個自定義的員工信息模型:

// 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 …