Qt Model/View 架构详解 — Item Model 体系

一、架构总览 Qt 的 Model/View 架构是对经典 MVC 模式的实现与改造: 层级 职责 对应类 Model(数据源) 存储数据、提供数据访问接口 QAbstractItemModel 系列 View(视图) 显示数据、处理用户交互 QTableView, QTreeView, QListView Delegate(委托) 自定义渲染和编辑 QStyledItemDelegate 核心思想:数据(Model)与展示(View)完全解耦,同一个 Model 可以同时被多个 View 显示。 二、类继承体系 QObject └── QAbstractItemModel ← 所有 Item Model 的根基类 ├── QAbstractListModel ← 简单一维列表的抽象基类 │ └── QStringListModel ├── QAbstractTableModel ← 二维表格的抽象基类(自己写表模型时继承它) ├── QStandardItemModel ← 开箱即用的通用模型(树/表/列表通吃) └── SQL 模型是三级单链,不是从 QAbstractTableModel 派生: QAbstractItemModel → QSqlQueryModel → QSqlTableModel → QSqlRelationalTableModel 其他直接从 QAbstractItemModel 派生的专用模型: QFileSystemModel — 文件系统树 三、核心索引系统:QModelIndex Model/View 的通信枢纽是 QModelIndex(模型索引)。 ...

2026 年 10 月 13 日

Qt MVC 与达梦数据库集成

电力设备运维场景:变电站设备经 IEC 104 规约报文上送遥测/遥信数据,Qt 后台接收解析后写入达梦数据库,界面用 Qt Model/View(MVC)框架展示。 数据库部分讲解详细,全程与 MySQL 对比。 一、场景背景 业务链路: 变电站设备 --(IEC 104 报文)--> Qt 后台 TCP 服务端 --> 报文解析 --> 达梦数据库(DM8) --> QSqlTableModel --绑定--> QTableView 界面 遥测(YC):电流、电压、功率等模拟量,周期上送。 遥信(YX):开关位置、刀闸状态等开关量,变位上送。 本篇定位:MVC 基础原理(QModelIndex/Role/Delegate 机制)已在 ../Qt/Qt_ModelView_MVC架构详解.md 详细讲解,本篇不重复, 重点放在数据库侧:达梦连接、三种数据库 Model 的用法、报文落库与界面刷新的完整链路。 二、达梦连接配置(详细) 2.1 为什么 Qt 用 ODBC 连接达梦 对比项 达梦 DM8 MySQL Qt 驱动 无官方原生驱动,走 ODBC(QODBC) 官方 QMYSQL 驱动 端口 5236 3306 驱动来源 达梦安装目录自带 ODBC 驱动(drivers/odbc/ 下) Qt 编译时编译 qsqlmysql 插件 MySQL 有 QMYSQL 驱动,直接 QSqlDatabase::addDatabase("QMYSQL") 即可。 达梦没有对应的 Qt 原生驱动,只能通过微软 ODBC 或 unixODBC 桥接,Qt 侧统一使用 QODBC。 ...

2026 年 10 月 13 日

Qt 架构阅读方法:从构造函数入手

看 Qt 桌面程序,不要从头文件按顺序"从小往大"看——Qt 没有顺序这一说,要从主页面构造函数去总结架构。 一、核心观点 Qt 程序(尤其带界面的桌面应用)里,构造函数是"所有功能的展示": 成员初始化 资源/数据加载函数 功能函数(布局、控件创建、业务初始化) 信号与槽连接(最重要) 想看懂某一个页面的功能大局,步骤是: 看构造函数 → 看构造函数里的注释 → 找被调用的实现方法 → 看"哪些被使用了、有什么用" → 再去对应头文件/源文件看定义 → 看懂 而不是:打开一个头文件,从头到尾按声明顺序啃。 二、为什么 Qt 不能"从头文件按顺序看" 误区 原因 按头文件声明顺序读 头文件只是声明集合,不含调用关系;顺序 ≠ 执行顺序 从最小的类看起 小类之间没有依赖线索,看完全部也不懂"为什么存在" 先读业务函数 不知道它被谁调用、在什么时机触发(信号槽是异步/解耦的) 想找"程序入口→顺序执行" Qt 是事件驱动的,没有线性执行流 Qt 程序的"执行顺序"不是代码顺序,而是: main() 里创建主窗口 → 执行构造函数 构造函数一次性搭好:界面、数据、连接 之后程序进入事件循环,靠信号槽响应——调用关系藏在构造函数里 所以构造函数 = 架构入口,是唯一能"一览全局"的地方。 三、构造函数里到底有什么 以常见主窗口为例: MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // 1. 窗口属性初始化 setWindowTitle("示例程序"); resize(800, 600); // 2. 加载函数:资源、配置、数据 loadConfig(); // 读配置文件 initDatabase(); // 连接数据库 setupToolbar(); // 加载图标/工具栏 // 3. 功能函数:创建控件与布局 createCentralWidget(); createStatusBar(); createMenu(); // 4. ⭐ 信号与槽连接(核心:把"谁"和"谁"绑起来) connect(ui->startBtn, &QPushButton::clicked, this, &MainWindow::onStart); connect(this, &MainWindow::dataReady, ui->table, &TableWidget::refresh); connect(timer, &QTimer::timeout, this, &MainWindow::updateStatus); } 构造函数 = 组装说明书: ...

2026 年 10 月 13 日

Qt 网络编程 01:TCP 客户端与服务端(第一天)

适用版本:Qt 6.9 / MinGW / C++17 更新日期:2026-08-19 源码位置:<你的工程目录>\net(TcpSocket / Tcpserver / TCP_Client / TCP_Server 四个项目) 目录 一、今天做了什么 二、项目清单与角色分工 三、准备工作:.pro 加 network 模块 四、控制台版:客户端 TcpSocket 五、控制台版:服务器 Tcpserver(多客户端广播) 六、GUI 版:TCP_Client 七、GUI 版:TCP_Server 八、核心知识点总结 九、今天踩的坑 / 值得注意的点 十、进阶写法:多客户端封装 ClientConnection 模式 十一、明天可以做什么 一、今天做了什么 用 Qt 实现了 TCP 客户端 / 服务器 双向通信,共 4 个项目: 项目 类型 角色 特点 TcpSocket 控制台(QCoreApplication) 客户端 连接即自动发一条 “Hello Server!” Tcpserver 控制台(QCoreApplication) 服务器 多客户端 + 广播 TCP_Client GUI(QWidget) 客户端 界面输入 IP/端口,收发消息 TCP_Server GUI(QWidget) 服务器 界面选择端口,收发消息 我已有 Qt 基础,但网络编程是零基础。今天的关键收获是建立了"客户端/服务器"模型和 Qt 异步收发的心智模型。 ...

2026 年 10 月 13 日

Qt 网络编程学习路线(网络编程零基础视角)

适用人群:已掌握 Qt 基础(信号槽、事件循环、QWidget),网络编程零基础 适用版本:Qt 6.x(Qt 5 大部分通用) 更新日期:2026-08-19 目录 1. 学习定位与目标 2. 网络编程零基础必须先懂的 6 个概念 3. 阶段一:TCP 编程入门(第 1~2 周) 4. 阶段二:TCP 深入与实战化(第 3~4 周) 5. 阶段三:UDP 编程(第 5 周) 6. 阶段四:HTTP 与高层网络(第 6~7 周) 7. 阶段五:进阶与安全(第 8 周起) 8. 阶段六:实战项目 9. 推荐资源 10. 总结 1. 学习定位与目标 核心要点:你的 Qt 基础(信号槽、事件循环、对象树)是最大的优势——Qt 把网络编程封装成了"发信号 + 收信号",你要补的不是 C++ 网络 API,而是网络协议的基本概念。 1.1 为什么用 Qt 学网络编程 优势 说明 异步事件驱动 收发数据都靠信号槽触发,不用自己写多线程阻塞 IO 跨平台 Windows / Linux / Android 同一套代码 高层封装 QTcpSocket / QUdpSocket / QNetworkAccessManager 把 socket API 包装成 Qt 风格 与 GUI 天然结合 收发数据直接刷新界面,客户端/服务器程序最容易做出可视化效果 1.2 和 C++ 原生 socket 的对比 原生 C++(Winsock / POSIX) Qt 封装 socket() / bind() / listen() / accept() QTcpServer::listen() / nextPendingConnection() connect() / send() / recv() QTcpSocket::connectToHost() / write() / readAll() 阻塞线程等待,需多线程处理 信号槽异步通知,事件循环驱动 手动管理缓冲区、错误码 readyRead 信号 + readAll(),errorString() 结论:先学概念,再学 Qt API。概念(TCP、端口、粘包)是通用的,Qt 只是更友好的实现工具。 ...

2026 年 10 月 13 日

qt-doc · Qt 智能文档 Skill

不要死记硬背 Qt 类名,告诉它你想干什么。 一个为 opencode 设计的 Qt 领域智能体 Skill:你只需要用中文描述需求 (“查一下线程怎么用”),它会自动映射为 QThread,抓取官方文档,并返回小白能看懂的核心 API 总结。 这不是搜索引擎,这是一个懂 Qt 的领域智能体。 ✨ 它能做什么 能力 说明 示例 意图感知 中文概念自动映射为 Qt 类名 “线程” → QThread,“画形状” → QPainterPath 继承链展开 自动递归抓取父类文档 查 QTcpSocket 返回整个 QAbstractSocket→QIODevice→QObject 族,2 个 API → 216 个 来源标注 每个 API 标注来自哪个类 connectToHost(来自 QAbstractSocket) 小白化总结 官方文档降维成通俗解释 构造函数 / 最常用函数 / 关键信号 / 示例 / 注意事项 三级数据源 在线 → 本地离线文档 → JSON 缓存 断网可用,二次查询秒回 会话隔离 显式开启专家模式,不误触发 未开启时零劫持 Web/Java 等非 Qt 问题 🚀 快速开始 环境要求 Python 3.10+ 依赖:requests、beautifulsoup4 opencode(可选用任意支持 Skill 的 Agent 运行器) 安装 git clone <your-repo-url> qt-doc cd qt-doc pip install -r requirements.txt # 安装 skill 到 opencode 全局(可选) cp -r qt-doc ~/.config/opencode/skills/ 使用 重启 opencode 后,在会话中: ...

2026 年 10 月 13 日

qt-doc(Qt 智能文档)

仅当用户显式要求开启 Qt 专家模式时触发,如说"开启Qt专家模式"、“进入Qt专家模式”、 “Qt专家模式"等。 一旦触发,本会话进入 Qt 专家模式,直到会话结束: 之后该会话内的所有 Qt/C++ 问题都用本 skill 流程回答(映射类名→抓文档→小白化总结)。 会话保持生效:通过 /sessions 恢复该对话,专家模式依然生效。 新开对话默认不加载本 skill,需再次显式开启——实现会话级隔离。 在专家模式下,非 Qt 的通用编程问题(如 Java/Web)正常回答,不套用 Qt 文档流程。 流程 把用户描述映射为 Qt 类名(Q 开头)。 先查本 skill 目录下的 mapping.json 精确匹配。 未命中则凭 Qt 领域知识推断(如"画形状”→QPainterPath)。 无法确定时询问用户。 调用抓取脚本(可传多个类名,一次抓取): 优先使用环境变量 QT_FETCH_CMD 指向脚本(如 python /path/to/qt_fetch.py)。 未设置时,用当前仓库内脚本:python <repo>/qt_fetch.py <类名1> <类名2>...。 示例:python ../qt_fetch.py QThread QTimer 读取 stdout JSON。若 errors 非空,说明该类未找到,提示用户检查拼写或模块。 基于 JSON 的 title/brief/apis 做小白化总结。 输出规范(中文,开头带 [qt-doc]) 这是什么:一句话说明该类用途。 构造函数:核心 1-2 个。 最常用函数:数量视类而定——核心常用类 5-8 个,简单类 3-4 个。 关键信号:1 个 + 触发时机(有信号时)。 示例代码:简短 5-8 行。 ⚠️ 注意:继承或使用要点。 继承链 查询结果含 inheritance_chain(从派生到基类)与每个 API 的 inheritsFrom 来源。 若某类 API 很少(如 QTcpSocket),说明它继承自父类,常用函数多在父类文档中—— 总结时应说明函数来自哪个父类(如 connectToHost 来自 QAbstractSocket)。 ...

2026 年 10 月 13 日

Qt5 信号槽重载与 explicit 关键字详解

适用版本:Qt 5.x / C++14+ 更新日期:2026-07-29 目录 一、信号与槽函数重载(Overload) 场景假设 方案一:QOverload / qOverload(最推荐) 方案二:static_cast 显式转型 方案三:Lambda 表达式(最灵活) 信号和槽同时重载的混搭处理 总结建议 二、explicit 关键字详解 1. 不加 explicit(允许隐式转换) 2. 加上 explicit(禁止隐式转换) 3. Qt 中的经典实战场景 总结 一、信号与槽函数重载(Overload) 在 Qt5 中,信号和槽函数发生重载是非常常见的场景,也是最容易引起编译错误或运行时行为不符合预期的地方。 由于 Qt5 使用 &ClassName::functionName 的新式语法,当遇到重载时,编译器无法自动推断你要使用哪一个具体的函数指针,因此必须进行强制类型转换或使用 Lambda 表达式来消除歧义。 场景假设 假设有一个类,里面定义了两个重载的普通成员函数(在 Qt5 中都可以当槽): class Receiver : public QObject { Q_OBJECT public: // 槽函数 1:无参 void handleUpdate() { qDebug() << "无参槽被触发"; } // 槽函数 2:带一个 int 参数 void handleUpdate(int value) { qDebug() << "带参槽被触发,值为" << value; } }; 现在有一个信号想要连接到这个槽,有以下几种方案: ...

2026 年 10 月 13 日

QTabWidget 与 QStackedWidget 的区别及常用方式

一、概述 QTabWidget 和 QStackedWidget 都是 Qt 中用于管理多页面切换的控件,但它们的设计定位和使用场景有显著区别。 特性 QTabWidget QStackedWidget 内置导航 自带标签栏(Tab Bar) 无内置导航 页面切换方式 点击标签切换 需通过代码 setCurrentIndex() 切换 独立性 独立控件,开箱即用 需配合其他控件(如 QListWidget)使用 灵活性 固定标签栏样式 可自定义任意切换方式 适用场景 设置对话框、分类浏览 向导流程、无标签界面 二、QTabWidget — 自带标签栏的多页容器 2.1 特点 顶部(或底部/左侧/右侧)自带 QTabBar 每个 Tab 对应一个页面(QWidget 子类) 点击 Tab 自动切换页面,无需额外代码 2.2 常用方式 // 创建 TabWidget QTabWidget *tabWidget = new QTabWidget(this); // 创建页面 QWidget *page1 = new QWidget(); QWidget *page2 = new QWidget(); // 添加页面(传入 widget 和标题) tabWidget->addTab(page1, "基本设置"); tabWidget->addTab(page2, "高级设置"); // 设置当前页 tabWidget->setCurrentIndex(0); // 常用设置 tabWidget->setTabPosition(QTabWidget::North); // 标签位置 tabWidget->setTabShape(QTabWidget::Rounded); // 标签形状 tabWidget->setMovable(true); // 信号与槽:标签切换 connect(tabWidget, &QTabWidget::currentChanged, [](int index) { qDebug() << "切换到第" << index << "页"; }); 三、QStackedWidget — 无内置导航的页面堆栈 3.1 特点 不提供任何导航控件,只负责管理页面 一次只显示一个页面(当前页) 必须通过代码或其他控件控制切换 3.2 常用方式 // 创建 StackedWidget QStackedWidget *stackedWidget = new QStackedWidget(this); // 创建页面 QWidget *page1 = new QWidget(); QWidget *page2 = new QWidget(); QWidget *page3 = new QWidget(); // 添加页面(返回 index) int idx1 = stackedWidget->addWidget(page1); int idx2 = stackedWidget->addWidget(page2); int idx3 = stackedWidget->addWidget(page3); // 切换页面 stackedWidget->setCurrentIndex(1); // 按索引切换 stackedWidget->setCurrentWidget(page3); // 按指针切换 // 获取当前页面 int currentIdx = stackedWidget->currentIndex(); 3.3 配合 QListWidget 实现导航 // 左侧列表 + 右侧堆栈的经典布局 QListWidget *list = new QListWidget(); list->addItem("基本设置"); list->addItem("高级设置"); list->addItem("关于"); QStackedWidget *stack = new QStackedWidget(); stack->addWidget(new BasicSettingsPage()); stack->addWidget(new AdvancedSettingsPage()); stack->addWidget(new AboutPage()); // 连接信号 connect(list, &QListWidget::currentRowChanged, stack, &QStackedWidget::setCurrentIndex); 3.4 配合 QPushButton 实现向导流程 // 上一步 / 下一步 向导 connect(btnNext, &QPushButton::clicked, [=]() { int next = stack->currentIndex() + 1; if (next < stack->count()) { stack->setCurrentIndex(next); } }); connect(btnPrev, &QPushButton::clicked, [=]() { int prev = stack->currentIndex() - 1; if (prev >= 0) { stack->setCurrentIndex(prev); } }); 四、核心区别总结 对比维度 QTabWidget QStackedWidget 内置 Tab 栏 有 无 实现代码量 少(开箱即用) 多(需自行实现导航) 切换方式 点击 Tab setCurrentIndex() / setCurrentWidget() 自定义导航 受限(可隐藏 Tab 栏) 完全灵活 典型场景 偏好设置、分类浏览 安装向导、多步表单 灵活性 低(样式固定) 高(完全可控) 如何选择? 需要标准标签切换 → QTabWidget(简单快速) 需要自定义切换方式 → QStackedWidget(如列表导航、按钮翻页) 需要向导/步骤流程 → QStackedWidget(搭配按钮更自然) 既有标签又想自定义 → 用 QTabWidget 隐藏 TabBar + 代码控制切换 五、完整示例:左侧列表 + 右侧堆栈 #include <QApplication> #include <QHBoxLayout> #include <QListWidget> #include <QStackedWidget> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; QHBoxLayout *layout = new QHBoxLayout(&window); // 左侧导航 QListWidget *list = new QListWidget(); list->addItem("页面一"); list->addItem("页面二"); list->addItem("页面三"); list->setFixedWidth(120); // 右侧页面 QStackedWidget *stack = new QStackedWidget(); stack->addWidget(new QLabel("这是页面一的内容")); stack->addWidget(new QLabel("这是页面二的内容")); stack->addWidget(new QLabel("这是页面三的内容")); layout->addWidget(list); layout->addWidget(stack, 1); connect(list, &QListWidget::currentRowChanged, stack, &QStackedWidget::setCurrentIndex); window.resize(600, 400); window.show(); return app.exec(); }

2026 年 10 月 13 日

QWidget 深入详解

目录 1. 什么是 QWidget 2. QWidget 的继承体系 3. 创建与基本使用 4. 窗口属性 5. 几何布局与坐标系统 6. 事件系统 7. 绘制系统 8. 样式表 (QSS) 9. 布局管理 10. 子控件与层级管理 11. 自定义控件实战 12. 性能优化 13. 常见陷阱与最佳实践 14. 总结 1. 什么是 QWidget QWidget 是 Qt 框架中所有用户界面对象的基类。它既是"控件"(widget),也是"窗口"(window)。在 Qt 的世界里,几乎所有可见元素都是 QWidget——按钮、标签、输入框、对话框,乃至整个应用程序的主窗口。 核心要点:QWidget 是一个"空白的矩形区域",它知道如何绘制自己、如何处理鼠标键盘事件、如何管理子控件。你可以在它之上构建任意复杂的 UI。 核心职责 职责 说明 窗口管理 最小化、最大化、关闭、标题栏 绘制 通过 paintEvent 自绘外观 事件处理 鼠标、键盘、触控、拖放等 子控件管理 添加、删除、查找子控件 布局管理 与 QLayout 协作自动排列子控件 样式设置 通过 QSS 或 QPalette 定制外观 2. QWidget 的继承体系 QObject └── QPaintDevice └── QWidget ← 本文主角 ├── QFrame │ ├── QLabel │ ├── QAbstractScrollArea │ └── ... ├── QAbstractButton │ ├── QPushButton │ ├── QCheckBox │ ├── QRadioButton │ └── ... ├── QComboBox ├── QLineEdit ├── QSlider ├── QSpinBox ├── QDialog ├── QMainWindow └── ... QObject:提供了信号槽机制、对象树管理、事件过滤器等基础设施。 QPaintDevice:声明了"可以被绘制"的能力,提供了绘制设备的抽象接口。 QWidget:将两者结合,成为一个可交互、可绘制、可包含子对象的矩形区域。 3. 创建与基本使用 3.1 最简单的 QWidget #include <QApplication> #include <QWidget> int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget w; w.setWindowTitle("Hello QWidget"); w.resize(400, 300); w.show(); return app.exec(); } 3.2 创建自定义 QWidget 子类 // MyWidget.h #pragma once #include <QWidget> #include <QPainter> class MyWidget : public QWidget { Q_OBJECT // 启用信号槽、元对象特性 public: explicit MyWidget(QWidget *parent = nullptr); protected: void paintEvent(QPaintEvent *event) override; void mousePressEvent(QMouseEvent *event) override; }; // MyWidget.cpp #include "MyWidget.h" #include <QPainter> MyWidget::MyWidget(QWidget *parent) : QWidget(parent) { setMinimumSize(200, 150); } void MyWidget::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setBrush(QColor(100, 150, 255)); painter.drawRoundedRect(rect().adjusted(5, 5, -5, -5), 10, 10); } void MyWidget::mousePressEvent(QMouseEvent *event) { // 点击时改变颜色 Q_UNUSED(event); update(); } 3.3 对象树与父子关系 QWidget 的构造函数接受一个 parent 参数: ...

2026 年 10 月 13 日