# qtHostComputerCodeLib **Repository Path**: fengdays/qtHostComputerCodeLib ## Basic Information - **Project Name**: qtHostComputerCodeLib - **Description**: qt上位机中代码库整理 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-22 - **Last Updated**: 2026-10-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # qtHostComputerCodeLib Qt 上位机代码库,包含各模块的学习与测试程序。基于 Qt 5.12,兼容 Qt 6。 ## 模块列表 | 模块 | 说明 | 依赖模块 | |------|------|----------| | [study1](#study1--qt-基础功能综合测试) | Qt Core 常用类综合测试 | Core | | [study2](#study2--modbus-tcp-客户端-demo) | Modbus TCP 客户端封装与 UI Demo | Core, Widgets, SerialBus | | [study3](#study3--qtablewidget-表格控件-demo) | QTableWidget 表格控件 Demo,支持增删改查与选项控制 | Core, Widgets | | [modbus](#modbus--modbus-tcpserial-双协议客户端升级) | Modbus TCP/Serial + QTableView/QCustomPlot + LED委托,工业级实时监控 | Core, Widgets, Network, SerialBus, SerialPort, Sql, PrintSupport | | [modbus-hub](#modbus-hub--modbus-rtu-多通道采集系统) | Modbus RTU 多通道并行采集系统,支持 Float CDAB | Core, Widgets, SerialBus, SerialPort | | [modbus-studio](#modbus-studio--modbus-现代化调试工具) | Modbus 工业级调试平台,看板/趋势图/全量日志/工程文件 | Core, Widgets, Network, SerialBus, SerialPort | | [modbus-qslave](#modbus-qslave--modbus-rtu-从站网络模拟器) | Modbus RTU 从站网络模拟器,多设备虚拟/XML 配置/串口通信 | Core, Widgets, Xml, SerialPort | | [InstrumentBoard](#instrumentboard--工业级仪表盘控件) | 工业级仪表盘控件,QPainter 自绘引擎,3 种风格,色带区间 + 指针动画 | Core, Widgets | | [radar](#radar--工业级雷达扫描控件) | 工业级雷达扫描控件,QPainter PPI 分层绘制 + QGeoCoordinate 地理引擎,方位刻度、扫描波束、虚线航迹、目标状态 | Core, Widgets, Positioning | | [qpainter](#qpainter--qpainter-进阶绘制技术演示) | QPainter 进阶技术演示:双缓冲、合成模式(遮罩/橡皮擦)、抗锯齿 | Core, Widgets | | [qcoro](#qcoro--qt-协程异步编程范式演示) | Qt 协程异步编程范式,Task 封装 + 回调地狱 vs 协程对比 + 并行执行 + 后台计算 | Core, Widgets, Concurrent | | [treeview](#treeview--qpainter-自定义树形控件连线绘制) | QTreeView 自定义委托绘制节点连线,Model/View 架构实践 | Core, Widgets | | [undoredo](#undoredo--qt-撤销框架-demo) | Qt 撤销框架 Demo,QUndoStack + 命令模式,支持撤销/重做/命令合并 | Core, Widgets | | [dataformat](#dataformat--结构化数据格式化工具) | JSON/XML/CSV 格式化、校验、转换工具,Qt6→Qt5 转译 | Core, Widgets, Xml | | [LogViewer](#logviewer--日志查看器) | 高性能日志查看器,编码识别/级别过滤/关键词高亮/实时跟随,Qt6→Qt5 转译 | Core, Widgets | | [EncodingTool](#encodingtool--编码转换与文本处理工具) | 编码转换/Base64/URL/Unicode/Hex/哈希/批量转码,Qt6→Qt5 转译 | Core, Widgets | | [opencv](#opencv--qpainter-交互式图像查看器) | QGraphicsView 交互式图像查看器,滚轮缩放/拖拽平移/ROI 框选/信息面板 | Core, Widgets | | [mvvm](#mvvm--qt-mvvm-架构-demo) | Qt MVVM 架构 Demo,ViewModel + DataBinding + Command 三层分离,登录页实战 | Core, Widgets | | [movetothread](#movetothread--qthread-movetothread-线程模型-demo) | QThread moveToThread 线程模型 Demo,演示线程亲和性、信号槽跨线程通信 | Core | --- ### study1 — Qt 基础功能综合测试 综合测试程序,覆盖 Qt Core 常用类与中文/UTF-8 支持。 #### 技术点 | 技术点 | 说明 | |--------|------| | **QObject / 信号与槽** | 自定义 QObject 子类,3 个信号 + 3 个槽,`QObject::connect` 连接,Lambda 槽 | | **QString** | 中文/英文/日文/俄文/法文多语言字符串,拼接、查找、`arg` 格式化,UTF-8 字节转换 | | **QByteArray** | UTF-8 编解码、Hex 表示、`append`/`prepend`/`replace`、Base64 编解码 | | **QVector** | int / QString / 自定义结构体存储,排序、查找、`std::accumulate` 求和 | | **INI 文件读写** | `QSettings` + `setIniCodec("UTF-8")`,写入含中文的分组/键值并读回验证 | #### 文件结构 ``` study1/ ├── study1.pro # qmake 项目文件 (Qt5::Core) ├── testobject.h # QObject 子类定义 (信号与槽) └── main.cpp # 主测试程序 ``` #### 编译运行 ```bash cd study1 qmake make ./study1 ``` --- ### study2 — Modbus TCP 客户端 Demo 基于 `QModbusTcpClient` 的 Modbus TCP 客户端封装与 UI Demo,用于连接 PLC 进行寄存器读写。 #### 技术点 | 技术点 | 说明 | |--------|------| | **连接管理** | 输入 IP/端口/从站号连接 PLC,显示连接状态(连接中/已连接/已断开/错误) | | **自动重连** | 断线后定时器自动重试,可配置重连间隔 | | **异步读写** | `readRegister` / `readRegisters` / `writeRegister` / `writeRegisters`,回调方式接收结果,禁止阻塞等待 | | **批量读取** | 多寄存器批量读取,减少通信轮询压力 | | **QModbusDataUnit** | 存放点位数据,支持批量结果拆解 | | **错误处理** | 通信超时、PLC 异常码、网络断开,统一回调 + 信号通知 | | **UI Demo** | 输入 PLC IP/端口/从站号,按钮读写保持寄存器,读取数值显示在表格 | #### 文件结构 ``` study2/ ├── study2.pro # qmake 项目文件 (Qt5::Core + Widgets + SerialBus) ├── modbustcpclient.h # Modbus TCP 客户端封装类 ├── modbustcpclient.cpp # 封装类实现 ├── mainwindow.h # 主窗口 UI ├── mainwindow.cpp # 主窗口实现 └── main.cpp # 程序入口 ``` #### 编译运行 ```bash cd study2 qmake make ./study2 ``` --- ### study3 — QTableWidget 表格控件 Demo 基于 `QTableWidget` 的表格控件 Demo,演示表格的常用操作与选项控制。 #### 技术点 | 技术点 | 说明 | |--------|------| | **表格初始化** | 设置列数、表头标签,预置示例数据(姓名/性别/年龄/国籍) | | **行表头/列表头控制** | 通过 `QCheckBox` 切换垂直/水平表头的显示与隐藏 | | **交替背景色** | `setAlternatingRowColors()` 实现隔行换色效果 | | **单元格编辑控制** | `setEditTriggers()` 动态切换可编辑/只读模式 | | **选择模式切换** | `QRadioButton` 切换 `SelectItems`(选择单元格)与 `SelectRows`(选择行) | | **添加行** | 读取输入框内容,在表格末尾追加一行数据 | | **修改行** | 将输入框内容写入当前选中行 | | **插入行** | 在当前选中行位置插入一行新数据 | | **删除行** | 移除当前选中行 | #### 文件结构 ``` study3/ ├── study3.pro # qmake 项目文件 (Qt5::Core + Widgets) ├── mainwindow.h # 主窗口定义 ├── mainwindow.cpp # 主窗口实现(UI 布局 + 表格操作 + 槽函数) └── main.cpp # 程序入口 ``` #### 编译运行 ```bash cd study3 qmake make ./study3 ``` --- ### modbus — Modbus TCP/Serial 双协议客户端(升级) 支持 TCP 和 RTU 串口两种通信方式的 Modbus 客户端,集成 QCustomPlot 实时曲线、QTableView + Model/View 表格、LED 状态指示灯委托,配置持久化到 INI 文件,支持 QTimer 定时轮询。 #### 技术点 | 技术点 | 说明 | |--------|------| | **TCP / Serial 双协议** | 通过基类 `QModbusClient*` 指针,工厂方法 `createModbusClient()` 动态创建 `QModbusTcpClient` 或 `QModbusRtuSerialClient`,UI 下拉框一键切换 | | **Qt5/Qt6 跨版本兼容** | `#if QT_VERSION` 条件编译,Qt5 的 `QModbusRtuSerialMaster` 通过 `using` 别名映射为 `QModbusRtuSerialClient`;串口参数枚举适配 `SerialBaudRateParameter` / `SerialParityParameter` | | **QTimer 轮询机制** | 连接成功后自动启动定时器,按可配置间隔发送读请求;`m_pollPending` 标志防止请求堆积 | | **INI 配置持久化** | `QSettings` + `IniFormat` 读写 `conf/conf.ini`,保存 TCP(IP/端口)、Serial(串口/波特率/校验位)、Common(从站ID/轮询间隔)三组配置 | | **菜单栏配置管理** | "配置"菜单提供"读取配置"和"保存配置"两个 Action,运行时可热加载/保存参数 | | **UI 动态显隐** | 切换 TCP/Serial 时,`onComboConnTypeChanged()` 自动控制对应配置区域的 `show()`/`hide()` | | **日志输出到界面** | `appendLog()` 方法将所有运行日志写入 `textLog` 控件,替代 `qDebug()` 输出 | | **异步读写 + Lambda 回调** | `QModbusReply::finished` 信号通过 Lambda 捕获 reply 指针传递到槽函数,避免信号/槽签名不匹配 | | **QAbstractTableModel** | 工业级 Model/View 架构,`PointTableModel` 管理寄存器点位数据,替代 QTableWidget | | **增量更新(高性能)** | `updatePoints()` 仅对值变化的行 emit `dataChanged`,未变化的行不刷新,大幅降低 CPU 占用 | | **QStyledItemDelegate** | `LEDIndicatorDelegate` 在单元格直接绘制红绿 LED 圆点(值≠0 绿灯,值=0 红灯),径向渐变模拟立体感,性能远高于放置 QLabel | | **QCustomPlot 实时曲线** | 每个寄存器一条曲线,不同颜色区分,自动缩放 Y 轴,支持拖拽/缩放,X 轴保留最近 100 个采样点 | | **SQLite 历史存储** | `DatabaseManager` 将每次轮询数据写入 SQLite,支持历史数据查询与曲线回放 | | **断线自动停轮询** | `slotStateChanged()` 检测连接状态,Connected 自动启动轮询,Unconnected 自动停止 | #### 文件结构 ``` modbus/ ├── modbus.pro # qmake 项目文件 ├── mainwindow.h # 主窗口定义(含 Qt5/Qt6 兼容宏) ├── mainwindow.cpp # 主窗口实现(连接/读写/轮询/配置/图表/表格) ├── mainwindow.ui # 界面布局(连接方式选择 + TCP/Serial 配置 + 操作按钮 + 菜单) ├── main.cpp # 程序入口 ├── pointtablemodel.h # QAbstractTableModel 子类,管理寄存器点位数据 ├── pointtablemodel.cpp # 增量更新逻辑,仅变化行 emit dataChanged ├── ledindicatordelegate.h # QStyledItemDelegate 子类,绘制 LED 指示灯 ├── ledindicatordelegate.cpp# 红绿圆点绘制(径向渐变 + 高光反射) ├── databasemanager.h # SQLite 数据库管理 ├── databasemanager.cpp # 历史数据读写 ├── historydialog.h # 历史数据查询对话框 ├── historydialog.cpp # 历史数据查询 UI ├── qcustomplot.h # QCustomPlot 图表库 ├── qcustomplot.cpp # QCustomPlot 实现 ├── conf/ │ ── conf.ini # 配置文件(TCP/Serial/Common 三组参数) └── data/ └── modbus_data.db # SQLite 历史数据库 ``` #### 编译运行 ```bash cd modbus qmake make ./modbus ``` > 需要 Qt 5.12 安装了 `Qt SerialBus`、`Qt SerialPort` 和 `Qt Sql` 模块。 --- ### modbus-hub — Modbus RTU 多通道采集系统 基于 Qt 的 SCADA 上位机软件,支持多串口通道并行通信,遵循 Modbus RTU 协议。参考 [agentthink/modbus-hub](https://github.com/agentthink/modbus-hub) 用 Qt 实现。 > 界面采用 SCADA 深色主题,配色方案源自原项目的 `ScadaTheme.xaml`。 #### 功能特性 | 功能 | 说明 | |------|------| | **多通道通信** | 每个通道独立对应一个串口(COM),可并行采集 | | **Modbus RTU 功能码** | 03 读保持寄存器、06 写单个寄存器、16 写多个寄存器 | | **数据类型** | Float CDAB(32 位浮点,字序 CDAB)、UInt16 | | **实时数据采集** | 可配置轮询间隔,显示数据质量与时间戳 | | **读写控制** | 功能码 06/16 的点可写入 | | **连接状态** | 串口打开成功后界面持续显示「已连接」,直到手动停止 | | **配置持久化** | 与程序同目录的 channels.json | | **通信日志** | 连接、读写、异常记录,支持清空 | #### 技术点 | 技术点 | 说明 | |--------|------| | **多通道并行** | 每个 `ChannelWorker` 独立管理一个串口连接和标签轮询,互不干扰 | | **轮询状态机** | FC03 标签逐个轮询,`m_pollPending` 防止请求堆积,完成一个再发下一个 | | **Float CDAB 字序** | 32 位浮点占 2 个保持寄存器,第一寄存器为 CD(低字),第二寄存器为 AB(高字),通过 `memcpy` 实现 IEEE 754 转换 | | **JSON 配置持久化** | `QJsonDocument` 读写 `channels.json`,保存通道列表、标签定义、串口参数 | | **Qt5/Qt6 兼容** | `QModbusRtuSerialMaster` / `QModbusRtuSerialClient` 条件编译适配 | | **异步读写** | 所有 Modbus 读写操作通过 `QModbusReply::finished` 信号异步回调,禁止阻塞 | | **缩放/偏移** | 标签支持 `scale` 和 `offset` 参数,显示值 = 原始值 × scale + offset | #### 文件结构 ``` modbus-hub/ ├── modbus_hub.pro # qmake 项目文件 (Qt5::Core + Widgets + SerialBus + SerialPort) ├── tagitem.h # 数据模型(TagItem 标签定义 + ChannelConfig 通道配置) ├── channelworker.h # 通道工作类(串口通信 + 轮询 + 写入) ├── channelworker.cpp # 通道工作类实现(含 Float CDAB 转换) ├── mainwindow.h # 主窗口定义 ├── mainwindow.cpp # 主窗口实现(多通道 UI + 标签管理 + 配置持久化) ├── main.cpp # 程序入口 ├── channels.json # 配置文件(首次启动自动生成) └── resources/ ├── scada_theme.qss # SCADA 深色主题样式表 └── app.png # 应用图标 ``` #### 编译运行 ```bash cd modbus-hub qmake make ./modbus_hub ``` #### 使用说明 1. **添加通道**:点击「+ 添加通道」,配置串口路径、波特率、轮询间隔 2. **配置标签**:选中通道后点击「+ 添加标签」,功能码选 03/06/16,Float 使用 CDAB 字序 3. **启动采集**:点击「启动通道」或「启动全部」;连接成功后通道状态显示「已连接」 4. **写入数据**:选中功能码 06/16 的标签,点击「写入选中」输入数值 5. **清空日志**:通信日志区点击「清空日志」 > 需要 Qt 5.12 安装了 `Qt SerialBus` 和 `Qt SerialPort` 模块。 --- ### modbus-studio — Modbus 现代化调试工具 基于 Qt 的工业级 Modbus 调试与监控平台,参考 [agentthink/modbus-studio](https://github.com/agentthink/modbus-studio) 用 Qt 实现。支持 TCP/RTU 双协议、可视化看板、实时趋势图、仪表盘、全量日志、工程文件管理。 > 界面采用 SCADA 深色主题,配色方案:`#1A2530` / `#243448` / `#2D3E55` / `#2EE6C0` / `#4EC9F5` / `#FF6B6B` #### 功能特性 | 功能 | 说明 | |------|------| | **TCP + RTU 双协议** | 支持 Modbus TCP 和 Modbus RTU 串口,下拉框一键切换 | | **虚拟设备** | 内置虚拟 Modbus 设备,无需硬件即可调试 | | **多数据类型** | UInt16、Int16、UInt32、Int32、Float CDAB、Float ABCD、Bool | | **三种视图** | 表格视图 / 卡片视图 / 可视化看板,一键切换 | | **可视化看板** | 拖拽式 SCADA 看板,支持数显、开关、指示灯、输入控制、进度条、仪表盘 6 种控件 | | **实时趋势图** | 示波器级实时曲线,支持多 Y 轴、多曲线、时间窗口缩放 | | **报文监控** | 实时显示 TX/RX 原始报文,自动解析功能码与地址,统计 TX/RX/错误计数 | | **十六进制日志** | 高性能日志记录器,自动解析 18 种功能码 + 11 种异常码,支持文件轮转 | | **自动写入** | 定时/单次自动写入任务,支持间隔执行与计数 | | **批量变量生成** | 输入起始地址和数量,一键生成连续寄存器变量 | | **CSV 导入/导出** | 智能列识别 CSV 导入,标准化点表导出(含元数据区 + 汇总统计) | | **.mpilot 工程文件** | 一键保存全部配置 + UI 布局,3 秒恢复完整工作环境 | | **快照管理** | 命名快照保存/恢复/删除,完整捕获项目全部状态 | | **标准导出** | 导出标准 JSON 配置 / 配置模板 / CSV 点表 | | **线性变换** | 支持 Scale/Offset,将原始值转换为物理量(温度、压力等) | #### 技术点 | 技术点 | 说明 | |--------|------| | **TCP/RTU/虚拟三模** | `QModbusTcpClient`、`QModbusRtuSerialClient` 动态创建 + 虚拟设备模式,统一接口 | | **多数据类型解析** | `convertRegistersToValue()` 支持 7 种数据类型,含 Float CDAB/ABCD 双字序 | | **看板画布引擎** | `DashboardCanvas` 自绘看板,鼠标拖拽/缩放/双击编辑,右键菜单创建控件 | | **仪表盘渲染** | 看板内嵌仪表盘 QPainter 自绘:圆弧刻度、色带区间、指针渐变、数字显示 | | **趋势图** | `TrendChartWidget` 实时曲线,多 Y 轴独立缩放,10s~300s 时间窗口,鼠标缩放/平移 | | **报文解析引擎** | `HexLogger` 智能帧解析:TCP MBAP 报头 + RTU 帧,18 种功能码 + 11 种异常码 | | **全量序列化** | `buildProjectJson()` / `applyProjectJson()` v2.0 格式,覆盖连接/变量/看板/趋势/自动写入/日志/UI布局 | | **UI 布局持久化** | 分割器位置、视图模式、当前 Tab、窗口几何全部保存到 .mpilot 工程文件 | | **CSV 智能导入** | 支持中英文表头自动识别(Name/FC/Address/DataType 等),跳过注释行 | | **点表标准化导出** | 元数据区(`#` 注释)+ 数据区 + 汇总统计,Excel/PLC 工程师可直接复用 | | **轮询状态机** | 逐个轮询可读标签,`m_pollPending` 防止请求堆积 | | **Qt5/Qt6 兼容** | `QModbusRtuSerialMaster` / `QModbusRtuSerialClient` 条件编译 | #### 文件结构 ``` modbus-studio/ ├── modbus_studio.pro # qmake 项目文件 ├── datamodels.h # 数据模型(TagItem、ConnectionConfig、PacketLogEntry) ├── modbusworker.h/cpp # Modbus 工作类(TCP+RTU+虚拟 连接、轮询、报文日志) ├── mainwindow.h/cpp # 主窗口实现(~1900行,全量序列化 + 所有功能集成) ├── tagcardwidget.h/cpp # 变量卡片控件 ├── cardviewwidget.h/cpp # 卡片视图容器(流式布局) ├── dashboard.h/cpp # 可视化看板(6种控件 + 拖拽 + 属性编辑 + 右键菜单) ├── trendchart.h/cpp # 实时趋势图(多Y轴 + 时间窗口 + 缩放/平移) ├── hexlogger.h/cpp # 高性能十六进制日志记录器(帧解析 + 文件轮转) ├── snapshotdialog.h/cpp # 快照管理对话框 ├── main.cpp # 程序入口 ├── test_tags.csv # 测试 CSV(50变量,中文表头) ├── test_tags_en.csv # 测试 CSV(15变量,英文表头) ├── test_tags_multi_slave.csv # 测试 CSV(26变量,多从站) └── resources/ ├── scada_theme.qss # SCADA 深色主题样式表 └── app.png # 应用图标 ``` #### 编译运行 ```bash cd modbus-studio qmake make ./modbus_studio ``` #### 使用说明 1. **选择连接类型**:下拉框切换 Modbus TCP / Modbus RTU / 虚拟设备,配置对应参数 2. **添加变量**:点击「+ 添加」手动添加,「批量生成」一键创建,「导入 CSV」批量导入 3. **切换视图**:表格 / 卡片 / 看板 三种视图一键切换 4. **看板操作**:看板视图右键空白区域添加控件(数显/开关/指示灯/输入/进度条/仪表盘),双击编辑属性 5. **趋势图**:切换到趋势图 Tab,拖拽变量添加曲线,支持多 Y 轴和时间窗口调整 6. **连接设备**:点击「连接」,状态栏显示连接状态 7. **报文监控**:切换到「报文监控」标签页查看 TX/RX 原始报文与统计 8. **保存工程**:菜单「工程 → 保存工程」,保存为 `.mpilot` 工程文件,下次一键恢复 9. **导出点表**:菜单「工程 → 导出 → 导出点表 CSV/Excel」,标准化格式交付上位机/PLC 工程师 > 需要 Qt 5.12 安装了 `Qt SerialBus` 和 `Qt SerialPort` 模块。 ### modbus-qslave — Modbus RTU 从站网络模拟器 基于 Qt 的 Modbus RTU 从站网络模拟器,参考 [maisvendoo/qslave](https://github.com/maisvendoo/qslave) 转译。模拟多个从站设备通过虚拟串口网络连接,支持 XML 配置文件定义设备寄存器。 #### 功能特性 | 功能 | 说明 | |------|------| | 多从站模拟 | 同时模拟多个 Modbus 从站设备,独立 ID 和寄存器空间 | | XML 配置 | 通过 XML 文件定义从站 ID、线圈、离散输入、保持寄存器、输入寄存器 | | 串口通信 | 支持 RS232/RS485 串口连接,可配置波特率、数据位、停止位、校验 | | 实时内存表 | 四象限表格显示 DI/Coils/IR/HR,支持手动编辑值 | | 系统日志 | 实时显示通信日志、错误信息 | | 设备列表 | 左侧列表展示所有虚拟从站,点击切换查看寄存器 | #### 支持的 Modbus 功能码 | 功能码 | 名称 | 说明 | |--------|------|------| | 0x01 | Read Coils | 读取线圈 | | 0x02 | Read Discrete Inputs | 读取离散输入 | | 0x03 | Read Holding Registers | 读取保持寄存器 | | 0x04 | Read Input Registers | 读取输入寄存器 | | 0x05 | Write Single Coil | 写入单个线圈 | | 0x06 | Write Single Register | 写入单个寄存器 | | 0x0F | Write Multiple Coils | 写入多个线圈 | | 0x10 | Write Multiple Registers | 写入多个寄存器 | #### 架构设计 ``` ┌─────────────────────────────────────────┐ │ MainWindow (GUI) │ │ ┌──────────┐ ┌──────────────────────┐ │ │ │ Slave │ │ Memory Map (4 tables) │ │ │ │ List │ │ DI | Coils | IR | HR │ │ │ └──────────┘ └──────────────────────┘ │ │ ┌──────────────────────────────────────┐│ │ │ System Log ││ │ └──────────────────────────────────────┘│ ├─────────────────────────────────────────┤ │ ModbusNetwork │ │ QSerialPort ←→ Slave[] ←→ CRC16 │ ├─────────────────────────────────────────┤ │ CfgReader │ │ QDomDocument → XML config parse │ └─────────────────────────────────────────┘ ``` #### 文件结构 ``` modbus-qslave/ ├── modbus-qslave.pro # 合并工程文件 ├── include/ │ ├── CfgReader.h # XML 配置读取 │ ├── convert.h # 字符串转换工具 │ ├── modbus.h # Modbus 网络类 │ ├── request.h # 请求结构体 + CRC16 │ ├── serial-config.h # 串口配置结构 │ ├── slave-data.h # 从站数据结构 │ ├── slave.h # 从站设备类 │ └── mainwindow.h # 主窗口 ├── src/ # 对应实现文件 ├── forms/mainwindow.ui # 布局化界面 ├── resources/ # 图标资源 └── cfg/example/ # 示例 XML 配置 ``` #### 编译运行 ```bash cd modbus-qslave qmake make ./release/modbus-qslave ``` #### XML 配置示例 ```xml Traffic light 1 traffic-light
16
Red signal0
0
Ready1
5
Timer15
2
Count3
``` > 原始项目使用 subdirs 多库结构(CfgReader + modbus + qslave-gui),已合并为单工程。UI 从绝对定位改为布局管理。 --- ### InstrumentBoard — 工业级仪表盘控件 独立的工业级仪表盘控件模块,基于 QPainter 自绘引擎,可嵌入任意 Qt 项目。 #### 功能特性 | 功能 | 说明 | |------|------| | **3 种风格** | Classic(经典圆形)、Modern(现代弧形)、Industrial(工业深色 + 发光指针) | | **圆弧刻度** | 主刻度 + 小刻度 + 刻度数值,自动适配尺寸 | | **色带区间** | `addColorBand()` 标注正常/警告/危险区域,支持任意颜色 | | **指针动画** | `QPropertyAnimation` 驱动,OutCubic 缓动,平滑过渡 | | **数字显示** | 等宽字体 + 半透明背景框,自动精度适配 | | **全量主题** | 指针/表盘/文字/边框/背景色均可自定义 | | **独立模块** | 仅依赖 Qt Widgets,无外部依赖,可直接嵌入项目 | #### 技术点 | 技术点 | 说明 | |--------|------| | **QPainter 自绘** | 全量自绘引擎:径向渐变背景、圆弧刻度、指针渐变、发光效果、数字显示 | | **QPropertyAnimation** | 属性动画驱动指针转动,`QEasingCurve::OutCubic` 缓动曲线 | | **角度映射** | -135° 到 +135° 弧形范围,值→角度线性映射 | | **色带绘制** | `QPainter::drawArc()` 绘制彩色弧形色带,支持 FlatCap 端点 | | **自适应尺寸** | 根据 widget 尺寸自动计算所有元素大小,最小 150×150 | #### 文件结构 ``` InstrumentBoard/ ├── instrumentboard.pro # qmake 项目文件 (Qt5::Core + Widgets) ├── instrumentgauge.h # 仪表盘控件声明(3种风格 + 色带 + 动画 + 主题色) ├── instrumentgauge.cpp # QPainter 自绘引擎(465行) └── main.cpp # 演示程序(3个仪表盘 + 正弦动画) ``` #### 编译运行 ```bash cd InstrumentBoard qmake make ./instrumentboard ``` #### 使用示例 ```cpp #include "instrumentgauge.h" // 创建温度仪表 InstrumentGauge *gauge = new InstrumentGauge(parent); gauge->setTitle("温度"); gauge->setUnit("°C"); gauge->setRange(0, 200); gauge->setGaugeStyle(InstrumentGauge::StyleIndustrial); gauge->setNeedleColor("#FF6B6B"); gauge->addColorBand(0, 120, QColor("#2EE6C0")); // 绿色正常区 gauge->addColorBand(120, 160, QColor("#F0C040")); // 黄色警告区 gauge->addColorBand(160, 200, QColor("#FF6B6B")); // 红色危险区 gauge->setValue(85.5); // 设置当前值,自动动画 ``` --- ### radar — 工业级雷达扫描控件 独立的工业级雷达扫描控件模块,参考《C++ Qt6 雷达模拟器实战》系列文章,基于 QPainter PPI 分层绘制引擎 + `QGeoCoordinate` 地理坐标计算(`distanceTo` / `azimuthTo` / `atDistanceAndAzimuth`),支持方位刻度、扫描波束、虚线航迹、目标状态管理、鼠标悬停反算经纬度、自动移动模拟。可直接在 Qt Designer 中提升 QWidget 为 RadarWidget。 #### 功能特性 | 功能 | 说明 | |------|------| | **PPI 分层绘制** | 背景 → 距离环 → 十字线 → 方位刻度 → 扫描波束 → 航迹 → 目标 → 文字,7 层叠加 | | **本机经纬度** | 可配置雷达站位置的纬度和经度 | | **目标经纬度** | 目标以 `QGeoCoordinate` 定位,支持输入速度(节)、航向(度)、颜色 | | **添加/更新目标** | 添加新目标,或更新已有目标参数 | | **目标丢失状态** | `setTargetLost()` 标记目标为 LOST,PPI 上显示 "LOST" 标签 | | **同名追加轨迹** | 同名目标更新时自动追加历史轨迹点(含时间戳),默认保留 20 点 | | **扫描高亮** | 扫描线经过目标时高亮显示,之后逐渐衰减(目标始终可见,最低亮度 12%) | | **比例尺设置** | 最大范围、每圈距离 (`setRingDistanceKm`)、同心圆数量均可配置 | | **距离单位** | 支持公里 (km)、海里 (nm)、米 (m) 三种单位 | | **顶部方位角** | 可设置雷达顶部对应的方位角(默认 0°=北) | | **超距标记** | 超过显示范围的目标贴最外圈,标记“[超距]” | | **超距隐藏** | 超过 1000km 的目标不再显示 | | **轨迹时间标签** | 最后一个轨迹点标注 `HH:mm:ss` 时间戳,避免多目标文字堆叠 | | **鼠标悬停提示** | 鼠标在雷达盘内移动时,QToolTip 显示估算经纬度、距离中心(km)、方位角(°) | | **屏幕→地理反算** | `coordinateForPoint()` 屏幕像素 → `atDistanceAndAzimuth()` 反算地理坐标 | | **目标列表** | 右侧显示所有目标名称、坐标、速度、状态 | | **点击查看详情** | 点击雷达上的目标或列表项,显示完整详情 | | **选中高亮** | 选中目标显示白色圆环标识 | | **自动移动** | 随机方位 + 随机 80~260m 位移,模拟目标缓慢移动(测试用) | | **Widget 提升** | 可直接在 Qt Designer 中提升 QWidget 为 RadarWidget | #### 技术点 | 技术点 | 说明 | |--------|------| | **QGeoCoordinate** | Qt Positioning 模块,`distanceTo()` 距离 / `azimuthTo()` 方位 / `atDistanceAndAzimuth()` 位移反算 | | **方位→屏幕坐标** | 顶部方位角旋转 + `sin/cos` 变换,compass→screen 映射(Y 轴向下) | | **屏幕→方位反算** | `atan2(dx, -dy)` 屏幕角度 + topAzimuth 偏移 → 方位角,像素距离 → 实际距离 | | **QConicalGradient** | 锥形渐变实现扫描尾迹,5 个颜色节点 `(200→120→60→15→0)` 平滑衰减 | | **PPI 分层绘制** | 7 层绘制顺序:背景(径向渐变+网格) → 距离环 → 十字线 → 方位刻度 → 扫描波束 → 航迹/目标 → 文字 | | **方位刻度系统** | 每 2° 小刻度 + 每 30° 大刻度 + 角度数字,N/E/S/W 用扫描色高亮 | | **虚线航迹** | `Qt::DashLine` 虚线连接历史位置,轨迹点为 2px 小圆点 | | **目标渲染** | 实心圆 + 中心白点增强可见性,光晕效果,选中白色圆环 | | **QPainter 自绘** | 全量自绘引擎:径向渐变背景 + 网格纹理、同心圆、十字线、扫描线、目标光晕、轨迹点、时间标签 | | **QTimer 动画** | 30ms 定时器驱动扫描旋转 + 随机移动 + 亮度衰减(~33 FPS) | | **QToolTip 悬停** | `setMouseTracking(true)` + `mouseMoveEvent()` + `QToolTip::showText()` | | **Qt5/Qt6 兼容** | `event->localPos()` (Qt5) / `event->position()` (Qt6) 条件编译 | | **超距处理** | 距离 > maxRange 时钳位到外圈,距离 > 1000km 时隐藏 | | **命中测试** | 鼠标点击遍历所有目标,15px 半径内匹配 | #### 文件结构 ``` radar/ ├── radar.pro # qmake 项目文件 (Qt5::Core + Widgets + Positioning) ├── radarwidget.h # 雷达控件声明(QGeoCoordinate + Target/TrajectoryPoint 结构体 + 目标状态) ── radarwidget.cpp # QPainter PPI 分层绘制引擎 + QGeoCoordinate 地理计算(~630行) └── main.cpp # 演示程序(雷达 + 目标列表 + 编辑面板 + 自动移动 + LOST 状态) ``` #### 编译运行 ```bash cd radar qmake make ./radar ``` > 需要 Qt 5.12 安装了 `Qt Positioning` 模块。 #### 使用示例 ```cpp #include "radarwidget.h" // 创建雷达控件(可在 Qt Designer 中提升 QWidget) RadarWidget *radar = new RadarWidget(parent); radar->setObserverCoord(31.2304, 121.4737); // 本机位置(上海) radar->setMaxRange(120); // 最大范围 120km radar->setRingDistanceKm(20); // 每圈 20km radar->setRingCount(6); // 6 个同心圆 radar->setTopAzimuth(0); // 顶部对应正北 radar->setSweepSpeed(1.5); // 每帧旋转 1.5° radar->setMaxTrajectoryPoints(20); // 轨迹保留 20 点 // 添加目标(经纬度 + 速度 + 航向) radar->addTarget(RadarWidget::Target("T-01", 31.50, 121.80, 12, 45, QColor("#2EE6C0"))); // 同名目标更新 → 自动追加历史轨迹(含时间戳) radar->addTarget(RadarWidget::Target("T-01", 31.52, 121.82, 12, 45, QColor("#2EE6C0"))); // 标记目标为丢失状态(PPI 上显示 "LOST" 标签) radar->setTargetLost("T-01", true); // 开启自动移动(随机位移模拟) radar->setAutoMove(true); ``` --- ### qpainter — QPainter 进阶绘制技术演示 独立的 QPainter 进阶技术演示模块,参考《C++ Qt6 雷达模拟器实战》系列文章中的绘制技术总结,通过 3 个交互式 Tab 页直观展示双缓冲、合成模式、抗锯齿三项核心进阶技术的原理与效果。 #### 功能特性 | 功能 | 说明 | |------|------| | **双缓冲对比** | 2000+ 几何体动画,可切换离屏 QPixmap 缓冲 / 直接绘制,实时 FPS 对比 | | **图形数量调节** | 滑块控制 100~5000 个图形元素,观察性能变化 | | **圆形遮罩** | `CompositionMode_SourceIn` 将方形图片裁剪为圆形头像 | | **橡皮擦效果** | `CompositionMode_Clear` 鼠标拖拽擦除灰色涂层,露出底层图片 | | **橡皮擦尺寸** | 鼠标滚轮调节橡皮擦大小(10~60px) | | **抗锯齿对比** | 同一画面上下分屏,上半关闭 / 下半开启抗锯齿,斜线/圆/多边形/文字直观对比 | #### 技术点 | 技术点 | 说明 | |--------|------| | **双缓冲机制** | 离屏 `QPixmap` 缓冲区 + `QPainter` 绑定到缓冲区绘制 + `drawPixmap()` 一次性搬运到屏幕,消除闪烁 | | **QPixmap vs 直接绘制** | 双缓冲模式在内存中完成全部绘制再一次性输出,适合大量复杂图形的场景 | | **FPS 实时计算** | `QElapsedTimer` 计时 + 帧计数器,每秒刷新一次 FPS 显示 | | **CompositionMode_SourceIn** | 仅保留源与目标重叠部分的像素,实现圆形遮罩裁剪效果 | | **CompositionMode_Clear** | 将绘制区域擦除为纯透明(Alpha=0),配合 `RoundCap` 画笔实现平滑橡皮擦 | | **QImage::Format_ARGB32_Premultiplied** | 预乘 Alpha 格式,支持透明度混合,遮罩和橡皮擦的核心格式 | | **Antialiasing** | `QPainter::Antialiasing` 几何图形抗锯齿 + `TextAntialiasing` 文本抗锯齿 | | **性能权衡** | 关闭抗锯齿渲染快 1.5~3 倍但边缘粗糙,开启后边缘平滑适合 UI 控件和动画 | | **QLinearGradient** | 线性渐变生成测试底图背景 | #### 文件结构 ``` qpainter/ ├── qpainter.pro # qmake 项目文件 (Qt5::Core + Widgets) └── main.cpp # 3 个 Tab 页完整演示(~450行) ``` #### 编译运行 ```bash cd qpainter qmake make ./qpainter ``` #### Tab 页说明 | Tab | 演示内容 | 交互操作 | |-----|---------|----------| | **双缓冲** | 2000+ 彩色几何体(圆/方/三角)螺旋动画 | 勾选/取消双缓冲开关,滑块调节图形数量,观察 FPS 变化 | | **合成模式** | 左侧 `SourceIn` 圆形遮罩 + 右侧 `Clear` 橡皮擦 | 鼠标在灰色涂层上拖拽擦除,滚轮调节橡皮擦大小 | | **抗锯齿** | 斜线/圆/六边形/文字 上下分屏对比 | 直接观察上半(关闭)与下半(开启)的边缘差异 | --- ### qcoro — Qt 协程异步编程范式演示 基于 `QEventLoop` + `QtConcurrent` 实现的轻量级协程封装模块,通过 3 个交互式 Tab 页直观展示协程相比传统回调方式在异步编程中的巨大优势:消除回调地狱、线性书写异步流程、后台计算不阻塞 UI。 #### 核心设计 | 组件 | 说明 | |------|------| | **Task** | 轻量级协程封装类,模拟 C++20 `co_await` 的 suspend/resume 语义 | | **await()** | 阻塞等待异步结果(内部使用 `QEventLoop`),代码线性书写 | | **then()** | 链式回调,任务完成后执行 continuation 函数 | | **delayed()** | 静态工厂,模拟异步 IO 延迟操作 | | **run()** | 静态工厂,在线程池执行 CPU 密集计算,完成后回到主线程 | #### 功能特性 | 功能 | 说明 | |------|------| | **回调 vs 协程对比** | 同样的异步流程(获取用户→订单→金额→保存),左右分屏对比代码复杂度和执行效果 | | **并行执行** | 5 个异步任务同时发起,对比串行(~2.5s)vs 并行(~0.5s)耗时 | | **后台计算** | CPU 密集任务放线程池,UI 动画帧率指示器证明主线程不被阻塞 | | **实时日志** | 带时间戳的彩色日志,精确显示每步耗时 | | **深色主题** | GitHub 风格深色主题,代码对比区使用等宽字体 | #### 技术点 | 技术点 | 说明 | |--------|------| | **QEventLoop 嵌套** | `await()` 内部创建局部事件循环,阻塞当前"协程帧"直到结果就绪 | | **QtConcurrent::run** | 将 CPU 密集计算提交到线程池,`QFutureWatcher` 监听完成信号 | | **shared_ptr 生命周期** | Task 对象通过 `shared_ptr` 在 Lambda 捕获链中保持存活,防止提前析构 | | **QTimer::singleShot** | 模拟异步 IO 延迟,同时作为"协程上下文"的启动入口 | | **回调地狱消除** | 传统 4 层嵌套回调 → 协程 4 行线性代码,可读性提升 10 倍 | | **并行任务** | 同时创建多个 Task 对象即并行执行,逐个 `await()` 收集结果 | | **UI 流畅度验证** | 定时器驱动动画 + FPS 计数器,直观验证后台计算不阻塞主线程 | #### 文件结构 ``` qcoro/ ├── qcoro.pro # qmake 项目文件 (Qt5::Core + Widgets + Concurrent) └── main.cpp # Task 封装 + 3 个对比 Demo(~545行) ``` #### 编译运行 ```bash cd qcoro qmake make ./qcoro ``` #### Tab 页说明 | Tab | 演示内容 | 核心对比 | |-----|---------|----------| | **回调 vs 协程** | 获取用户→订单→金额→保存报告的完整流程 | 左栏 4 层嵌套回调 vs 右栏 4 行 `.await()` 线性代码 | | **并行执行** | 5 个异步任务同时发起 | 串行总耗时 ~2.5s vs 并行总耗时 ~0.5s | | **后台计算** | 大整数求和放线程池 | UI 动画帧率始终 >15 FPS,证明主线程流畅 | --- ### mvvm — Qt MVVM 架构 Demo 基于 Qt QWidget 的 MVVM 架构演示程序,参考《别再往QWidget里塞业务逻辑了!3个理由改用MVVM》(Aether项目精讲系列第5篇)实现。通过登录页示例展示 ViewModel + DataBinding + Command 三层解耦方案。 > 核心理念:**数据驱动 UI,而不是 UI 驱动数据**。View 只做声明式绑定,ViewModel 纯 C++ 对象零 UI 依赖。 #### 技术点 | 技术点 | 说明 | |--------|------| | **ViewModel 基类** | `QHash` 存储属性,O(1) 查找,`propertyChanged` 信号驱动 View 自动刷新 | | **VIEWMODEL_PROPERTY 宏** | 自动生成 getter / setter / Changed 信号,比 Q_PROPERTY 更轻量 | | **批量更新** | `beginBatchUpdate()` / `endBatchUpdate()` 合并多次属性变更为一次通知 | | **DataBinding 单向绑定** | `bindOneWay(vm, "prop", label)` —— ViewModel 变 → QLabel 自动刷新 | | **DataBinding 双向绑定** | `bind(vm, "prop", lineEdit)` —— ViewModel ↔ QLineEdit 自动同步,`QSignalBlocker` 防死循环 | | **DataBinding 命令绑定** | `bindCommand(cmd, button)` —— canExecute → 按钮 enabled,clicked → run() | | **DataBinding 状态绑定** | `bindEnabled()` / `bindVisible()` —— bool 属性驱动控件状态 | | **Command 模式** | `FunctionalCommand` 封装 Lambda 动作 + 可执行条件,支持中间件扩展 | | **接口隔离** | `ILoginService` 接口注入,ViewModel 不依赖具体实现,可替换 Mock / 真实 API | | **可测试性** | ViewModel 是纯 C++ 对象,不需要 QApplication / QWidget 即可单元测试 | #### 文件结构 ``` mvvm/ ├── mvvm.pro # qmake 项目文件 (Qt5::Core + Widgets) ├── viewmodel.h # ViewModel 基类(QHash 属性存储 + propertyChanged 信号) ├── viewmodel.cpp # ViewModel 实现(含批量更新) ├── command.h # Command 基类 + FunctionalCommand(Lambda 命令) ├── databinding.h # DataBinding 工具类(单向/双向/命令/状态绑定) ├── loginservice.h # ILoginService 接口 + MockLoginService 模拟实现 ├── loginviewmodel.h # LoginViewModel(纯业务状态,零 UI 依赖) ├── loginviewmodel.cpp # LoginViewModel 实现(登录逻辑 + 命令绑定) ├── loginview.h # LoginView(纯 UI 组装) ├── loginview.cpp # LoginView 实现(声明式 DataBinding) └── main.cpp # 程序入口 ``` #### 编译运行 ```bash cd mvvm qmake make ./mvvm ``` #### 使用说明 1. 运行后显示登录界面 2. 输入用户名 `admin`,密码 `123456` 3. 登录按钮在用户名非空且密码≥6位时自动启用(Command canExecute 驱动) 4. 点击登录,显示加载动画(isLoading 属性驱动 visible) 5. 800ms 后模拟返回结果:成功显示欢迎信息,失败显示红色错误提示 > 参考文章:[别再往QWidget里塞业务逻辑了!3个理由改用MVVM](https://mp.weixin.qq.com/s/6VXZN6CUDQb1h4Y1f-gmwQ) --- ### treeview — QTreeView 自定义节点连线绘制 + 通用搜索过滤 基于 QTreeView + QStyledItemDelegate + QSortFilterProxyModel 的 Model/View 架构实践,通过自定义委托在树形控件中绘制父子节点之间的连接线,并实现**通用树形搜索过滤**功能。SearchFilterProxyModel 仅依赖 QAbstractItemModel 接口,可配合任意树形模型使用。参考 [treeview_nodeline](https://github.com/bikong7/treeview_nodeline) 和 [qt_example](https://github.com/bikong7/qt_example) 实现。 #### 功能特性 | 功能 | 说明 | |------|------| | **节点连线** | 自定义委托绘制父子节点之间的垂直/水平连接线 | | **树形搜索** | 输入关键字实时过滤,命中的节点连同父级路径一起保留,不会变成孤儿 | | **递归过滤** | 后代命中时自动保留祖先路径(Qt `setRecursiveFilteringEnabled`) | | **祖先匹配** | 祖先命中时保留所有后代(自定义 `hasMatchingAncestor`) | | **自动展开** | 搜索后自动展开所有匹配节点,清空时折叠还原 | | **悬停高亮** | 鼠标悬停节点背景色变化(浅蓝) | | **选中状态** | 选中节点背景色加深,支持键盘导航 | | **展开/折叠** | 支持动画展开/折叠,连线随节点状态动态更新 | #### 技术点 | 技术点 | 说明 | |--------|------| | **通用搜索代理** | `SearchFilterProxyModel` 仅依赖 `QAbstractItemModel` 接口,可配合任意树形模型 | | **QStyledItemDelegate** | 自定义委托重写 `paint()` 方法,完全控制节点绘制 | | **QSortFilterProxyModel** | 自定义代理模型实现树形搜索过滤 | | **递归过滤** | `setRecursiveFilteringEnabled(true)` 后代命中时保留祖先路径 | | **祖先匹配** | `hasMatchingAncestor()` 遍历 `parent()` 链,祖先命中时保留所有后代 | | **三向过滤** | 自己命中 + 后代命中 + 祖先命中,三个条件取或,确保树结构完整 | | **自动展开** | 搜索后调用 `expandAll()` 展开所有匹配节点,清空时 `collapseAll()` | | **paintBranches()** | 核心绘制逻辑:计算节点层级、绘制祖先垂直线、当前节点水平分支线 | | **层级计算** | `getIndexLevel()` 通过遍历 `parent()` 链计算节点深度 | | **末端子节点判断** | 最后一个子节点的垂直线只画到中间(`midY`),形成「L」形收尾 | | **QPainter 抗锯齿** | `setRenderHint(Antialiasing)` 确保连线平滑 | #### 文件结构 ``` treeview/ ├── treeview.pro # qmake 项目文件 (Qt5::Core + Widgets) ├── treeitem.h/cpp # 树节点数据类(父子关系、数据存取) ├── treemodel.h/cpp # QAbstractItemModel 实现(93 个节点的 5 层树) ├── treedelegate.h/cpp # QStyledItemDelegate 实现(节点连线绘制核心) ├── searchfilterproxymodel.h/cpp # 自定义代理模型(树形搜索过滤) ├── mainwindow.h/cpp # 主窗口(搜索框 + QTreeView 配置) └── main.cpp # 程序入口 ``` #### 编译运行 ```bash cd treeview qmake make ./treeview ``` #### 核心绘制逻辑 ```cpp void TreeDelegate::paintBranches(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { int level = getIndexLevel(index); // 计算节点深度 int midY = rect.top() + rect.height() / 2; // 1. 绘制祖先节点的垂直连线 for (int i = level - 1; i >= 1; --i) { // 检查祖先是否有更多兄弟 → 决定是否画垂直线 if (ancestorHasMoreSiblingsBelow) painter->drawLine(parentLineX, rect.top(), parentLineX, rect.bottom()); } // 2. 绘制当前节点的父级连线 if (isLastChild) painter->drawLine(arrowX, rect.top(), arrowX, midY); // L 形收尾 else painter->drawLine(arrowX, rect.top(), arrowX, rect.bottom()); // 3. 绘制水平分支线 painter->drawLine(arrowX, midY, lineX, midY); } ``` #### 搜索过滤原理 树形搜索需要凑齐三个"或"条件,确保树结构完整: | 条件 | 说明 | 实现方式 | |------|------|----------| | **自己命中** | 当前节点文本包含关键字 | 默认 `filterAcceptsRow` | | **后代命中** | 子树中有节点命中 → 保留祖先路径 | Qt `setRecursiveFilteringEnabled(true)` | | **祖先命中** | 祖先节点命中 → 保留所有后代 | 自定义 `hasMatchingAncestor()` | **核心代码:** ```cpp bool SearchFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QModelIndex index = sourceModel()->index(sourceRow, filterKeyColumn(), sourceParent); if (!index.isValid()) return false; // 1. 自己命中:使用 filterRegExp() 匹配 QString data = index.data(filterRole()).toString(); if (data.contains(filterRegExp())) return true; // 2. 祖先命中(子因父留):顺着 parent() 往上爬 return hasMatchingAncestor(index); } bool SearchFilterProxyModel::hasMatchingAncestor(const QModelIndex &index) const { QModelIndex parent = index.parent(); while (parent.isValid()) { QString data = parent.data(filterRole()).toString(); if (data.contains(filterRegExp())) return true; parent = parent.parent(); } return false; } ``` **通用性设计:** - 不依赖具体模型实现,仅使用 `sourceModel()`, `index()`, `data()`, `parent()` 等基类接口 - 使用 `filterRegExp()` 和 `filterRole()` 进行匹配,符合 Qt 标准做法 - 可配合 `QStandardItemModel`、自定义 `TreeModel`、`QFileSystemModel` 等任意树形模型 **分工说明:** - Qt 的 `filterRecursiveAcceptsRow` 管"后代命中→本行留"(向下递归) - 自定义的 `hasMatchingAncestor` 管"祖先命中→本行留"(向上遍历) - 一个向下一个向上,正好凑齐"后代""祖先"两个方向,缺一不可 > 参考项目: > - [bikong7/treeview_nodeline](https://github.com/bikong7/treeview_nodeline) — 节点连线绘制 > - [bikong7/qt_example](https://github.com/bikong7/qt_example) — 通用搜索过滤实现 --- ### undoredo — Qt 撤销框架 Demo 基于 Qt Undo Framework 的撤销/重做功能演示程序,参考 Qt 5.15 官方 undoframework 示例实现。通过 QGraphicsScene 画布上的图元操作(添加、删除、移动)展示命令模式(Command Pattern)在撤销框架中的应用。 > 核心理念:**每个用户操作封装成一个 QUndoCommand 子类**,`redo()` 执行、`undo()` 回退;`push()` 进栈时立刻调 `redo()` 执行;`Ctrl+Z`/`Ctrl+Y` 触发的 QAction 由 `createUndoAction`/`createRedoAction` 生成。 #### 功能特性 | 功能 | 说明 | |------|------| | **添加图元** | 菜单或快捷键添加方框/三角形,支持撤销/重做 | | **删除图元** | 删除选中图元,支持撤销恢复 | | **移动图元** | 鼠标拖拽移动图元,支持撤销/重做 | | **命令合并** | 同一图元连续移动合并为一条记录,避免历史刷屏 | | **命令历史** | QUndoView 显示命令历史列表 | | **快捷键** | Ctrl+Z 撤销,Ctrl+Y 重做,Ctrl+B 添加方框,Ctrl+T 添加三角形,Delete 删除 | #### 技术点 | 技术点 | 说明 | |--------|------| | **QUndoStack** | 撤销栈,管理所有命令对象 | | **QUndoCommand** | 命令基类,子类实现 `redo()` 和 `undo()` | | **命令模式** | 每个操作封装为命令对象,执行和回退逻辑对称 | | **push 即执行** | `push()` 内部立刻调用 `redo()`,不是仅存储 | | **createUndoAction** | QUndoStack 工厂方法,生成 Undo/Redo 的 QAction | | **QUndoView** | 命令历史视图,实时显示命令栈内容 | | **mergeWith** | 合并同类命令,连续移动只保留一条记录 | | **id()** | 命令标识,相同 id 的命令可尝试合并 | | **QGraphicsScene** | 图形场景,承载图元 | | **QGraphicsView** | 图形视图,显示场景内容 | #### 文件结构 ``` undoredo/ ├── undoredo.pro # qmake 项目文件 (Qt5::Core + Widgets) ├── diagramitem.h/cpp # 图元类(方框/三角形) ├── diagramscene.h/cpp # 画布类(捕获鼠标事件) ├── commands.h/cpp # 命令类(AddCommand/DeleteCommand/MoveCommand) ├── mainwindow.h/cpp # 主窗口(菜单、撤销栈、视图) └── main.cpp # 程序入口 ``` #### 编译运行 ```bash cd undoredo qmake make ./undoredo ``` #### 撤销框架原理 **1. 命令模式核心** 每个用户操作封装成一个 `QUndoCommand` 子类: ```cpp class AddCommand : public QUndoCommand { public: void redo() override { scene->addItem(item); } // 执行 void undo() override { scene->removeItem(item); } // 回退 }; ``` **2. push 即执行** ```cpp undoStack->push(new AddCommand(type, scene)); // push 内部立刻调 redo() ``` **3. Undo/Redo 触发** ```cpp // QUndoStack 生成 QAction undoAction = undoStack->createUndoAction(this, "&Undo"); undoAction->setShortcuts(QKeySequence::Undo); // Ctrl+Z redoAction = undoStack->createRedoAction(this, "&Redo"); redoAction->setShortcuts(QKeySequence::Redo); // Ctrl+Y ``` QAction 的 `triggered` 信号直接连到 `QUndoStack::undo()`/`redo()` 槽。 **4. 命令合并(mergeWith)** 连续移动同一图元时,合并为一条记录: ```cpp class MoveCommand : public QUndoCommand { public: enum { Id = 1234 }; int id() const override { return Id; } bool mergeWith(const QUndoCommand *command) override { const MoveCommand *move = static_cast(command); if (m_item != move->m_item) return false; // 不同图元不合并 m_newPos = move->m_item->pos(); // 只保留终点 return true; } }; ``` **5. 鼠标移动触发命令** ```cpp // DiagramScene 捕获鼠标事件 void DiagramScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (m_movingItem && oldPos != m_movingItem->pos()) { emit itemMoved(item, oldPos); // 发送信号 } } // MainWindow 接收信号,push 命令 void MainWindow::itemMoved(DiagramItem *item, const QPointF &oldPos) { undoStack->push(new MoveCommand(item, oldPos)); } ``` > 参考示例:Qt 5.15 `widgets/tools/undoframework` --- ### dataformat — 结构化数据格式化工具 基于 Qt 的结构化数据格式化工具,支持 JSON/XML/CSV 的格式化、压缩、校验、互转和表格预览。原项目基于 Qt 6,本模块转译至 Qt 5.12 兼容版本。 > 核心理念:**DataFormatter 静态工具类** 封装所有格式处理逻辑,MainWindow 通过 UI 调用,职责清晰。 #### 功能特性 | 功能 | 说明 | |------|------| | **格式化** | JSON/XML/CSV 美化输出,自动缩进 | | **压缩** | JSON/XML 紧凑输出,去除空白 | | **校验** | JSON/XML 语法校验,CSV 列数一致性检查,错误定位到行列 | | **JSON ↔ CSV** | JSON 对象数组与 CSV 表格互转 | | **JSON ↔ XML** | JSON 与 XML 互转,支持属性/文本节点映射 | | **XML ↔ CSV** | XML 与 CSV 互转,自动展开嵌套字段 | | **表格预览** | 自动识别数据表格结构,实时预览 | | **文件操作** | 打开/保存文件,自动识别格式 | | **编辑器交换** | 输入与结果一键交换 | | **统计信息** | 字符数、行数、格式识别实时显示 | #### Qt6 → Qt5 转译要点 | Qt6 API | Qt5 替代方案 | |---------|----------| | `qsizetype` | `int` | | `QTextStream::setEncoding(QStringConverter::Utf8)` | `QTextStream::setCodec("UTF-8")` | | `#include ` | 移除(Qt5 中不存在) | | `qBound(...)` | `qBound(0, offset, text.size())` | #### 文件结构 ``` dataformat/ ├── dataformat.pro # qmake 项目文件 (Qt5::Core + Widgets + Xml) ├── dataformatter.h/cpp # 核心格式处理类(静态方法) ├── mainwindow.h/cpp # 主窗口 UI 逻辑 ├── mainwindow.ui # Qt Designer 界面文件 ├── main.cpp # 程序入口 └── examples/ # 示例文件 ├── sample.json ├── sample.xml └── sample.csv ``` #### 编译运行 ```bash cd dataformat qmake make ./dataformat ``` #### 架构设计 ``` DataFormatter (静态工具类) ├── detectFormat() # 自动识别格式(文件后缀 + 内容特征) ├── validate() # 语法校验,返回错误位置 ├── formatText() # 格式化输出 ├── compactText() # 压缩输出 ├── jsonToCsv() # 格式转换 ├── csvToJson() ├── jsonToXml() ├── xmlToJson() ├── xmlToCsv() ├── csvToXml() └── previewTable() # 表格预览 MainWindow ├── 输入/输出编辑器 (QPlainTextEdit) ├── 格式选择 (QComboBox) ├── 操作按钮(格式化/压缩/校验/转换) ├── 表格预览 (QTableWidget) └── 状态栏(统计信息) ``` > 原项目:[DataFormatter](https://github.com/GeekDawnTools/DataFormatter) by GeekDawn --- ### LogViewer — 日志查看器 基于 Qt 的高性能日志查看器,支持自动编码识别(UTF-8/GBK)、日志级别过滤、关键词/正则高亮、时间范围筛选、上下文显示、实时跟随(tail -f)。原项目基于 Qt 6,本模块转译至 Qt 5.12 兼容版本。 > 核心理念:**LogDocument** 负责文件加载/编码检测/解析/过滤,**LogTableModel** 提供 Model/View 数据接口,**MainWindow** 负责 UI 交互,三层职责分离。 #### 功能特性 | 功能 | 说明 | |------|------| | **编码自动识别** | 检测 BOM 头 + UTF-8 合法性分析,自动选择 UTF-8/GBK 编码 | | **日志级别检测** | 自动识别 FATAL/ERROR/WARN/INFO/DEBUG 等级别,颜色区分 | | **时间戳提取** | 自动检测 `yyyy-MM-dd HH:mm:ss.zzz` 格式时间戳 | | **关键词高亮** | 普通关键词或正则表达式,HTML 富文本高亮显示 | | **级别过滤** | 按日志级别勾选显示/隐藏 | | **时间范围过滤** | 起止时间筛选日志条目 | | **上下文显示** | 匹配条目前后 N 行上下文 | | **实时跟随** | tail -f 模式,定时追加新内容并滚动到底部 | | **导出结果** | 将当前筛选结果导出为文本文件 | | **最近文件** | 记录最近打开的 10 个日志文件 | | **状态栏** | 实时显示总行数、可见行数、编码、文件大小、跟随状态 | #### Qt6 → Qt5 转译要点 | Qt6 API | Qt5 替代方案 | |---------|----------| | `qsizetype` | `int` | | `QStringDecoder` | `QTextCodec`(`codecForName` + `toUnicode`) | | `out.setEncoding(QStringConverter::Utf8)` | `out.setCodec("UTF-8")` | | `std::as_const` | `qAsConst` | | `QString::indexOf(QStringView, int)` | `QString::indexOf(const QString &, int)` | #### 文件结构 ``` LogViewer/ ├── LogViewer.pro # qmake 项目文件 (Qt5::Core + Widgets) ├── logtypes.h # 枚举与结构体(LogEncoding/LogLevel/LogEntry/LogFilterOptions) ├── logdocument.h/cpp # 文档类(文件加载/编码检测/解析/过滤,~450行) ├── logmodel.h/cpp # 表格模型(QAbstractTableModel,4列:行号/时间/级别/内容) ├── mainwindow.h/cpp # 主窗口(UI 逻辑 + 高亮委托,~490行) ├── mainwindow.ui # Qt Designer 界面文件 ├── main.cpp # 程序入口 └── examples/ └── sample-logviewer.log # 示例日志文件 ``` #### 编译运行 ```bash cd LogViewer qmake make ./LogViewer ``` #### 架构设计 ``` LogDocument (文档层) ├── loadFile() # 加载文件,自动检测编码 ├── decodeBytes() # QTextCodec 解码(UTF-8/GBK) ├── detectLevel() # 日志级别识别(单词边界匹配) ├── detectTimestamp() # 时间戳提取(固定格式解析) ├── applyFilter() # 过滤逻辑(级别+时间+关键词+上下文) └── appendNewData() # 增量追加(tail -f 模式) LogTableModel (模型层) ├── rowCount/columnCount # Model/View 接口 ├── data() # 显示数据 + 背景色/前景色 + ToolTip └── refresh() # 过滤后刷新(beginResetModel) MainWindow (视图层) ├── 文件操作(打开/重载/导出/最近文件) ├── 筛选控制(关键词/正则/级别/时间/上下文) ├── LogHighlightDelegate(关键词 HTML 富文本高亮) └── 状态栏(行数/编码/大小/跟随状态) ``` > 原项目:[LogViewer](https://github.com/GeekDawnTools/LogViewer) by GeekDawn --- ### EncodingTool — 编码转换与文本处理工具 基于 Qt 的编码转换与文本处理工具,集成文本编码转换、Base64/URL/Unicode 编解码、Hex 查看、哈希校验、批量转码等功能。原项目基于 Qt 6,本模块转译至 Qt 5.12 兼容版本。 > 核心理念:**EncodingTool 静态工具类** 封装所有编解码/转换/哈希逻辑,MainWindow 通过 UI 调用,职责清晰。 #### 功能特性 | 功能 | 说明 | |------|------| | **文本编码转换** | UTF-8/GBK/UTF-16LE/UTF-16BE/本地编码互转,自动识别源编码 | | **BOM 检测** | 自动检测 UTF-8 BOM / UTF-16 LE/BE BOM | | **换行符处理** | 自动识别 CRLF/LF/CR,支持转换为目标换行格式 | | **Base64 编解码** | 文本与 Base64 互转 | | **URL 编解码** | 百分号编码/解码 | | **Unicode 转义** | `\uXXXX` 转义与还原 | | **Hex 查看** | 文本转十六进制 / 十六进制转文本 | | **哈希校验** | MD5 / SHA1 / SHA256 计算,支持文本和文件 | | **批量转码** | 递归扫描目录,批量转换文件编码,支持 CSV 预览导出 | | **示例文件** | 内置示例文本,一键载入 | #### Qt6 → Qt5 转译要点 | Qt6 API | Qt5 替代方案 | |---------|----------| | `#include ` | `#include ` | | `QStringDecoder` / `QStringEncoder` | `QTextCodec::toUnicode()` / `fromUnicode()` | | `QStringConverter::encodingForName()` | `QTextCodec::codecForName()` | | `qsizetype` | `int` | | `stream.setEncoding(QStringConverter::Utf8)` | `stream.setCodec("UTF-8")` | | `QByteArray::fromBase64Encoding()` | `QByteArray::fromBase64()` + 空检查 | #### 文件结构 ``` EncodingTool/ ├── EncodingTool.pro # qmake 项目文件 (Qt5::Core + Widgets) ├── encodingtool.h/cpp # 核心工具类(编解码/转换/哈希/批量处理,~680行) ├── mainwindow.h/cpp # 主窗口 UI 逻辑(~615行) ├── mainwindow.ui # Qt Designer 界面文件(4 Tab 页) ├── main.cpp # 程序入口 └── examples/text/ # 示例文件 ├── data.csv ├── snippets.log └── utf8-sample.txt ``` #### 编译运行 ```bash cd EncodingTool qmake make ./EncodingTool ``` #### 架构设计 ``` EncodingTool (静态工具类) ├── detectEncoding() # 自动编码识别(BOM + UTF-8 合法性 + GBK 回退) ├── decodeBytes() # QTextCodec 解码 ├── encodeText() # QTextCodec 编码 + BOM 前缀 ├── normalizeNewlines() # 换行符转换 ├── base64/url/unicode # 编解码方法 ├── textToHex/hexToText # Hex 转换 ├── hashesForText/Bytes # MD5/SHA1/SHA256 ├── scanTextFiles() # 递归扫描文本文件 ├── buildBatchPreview() # 批量预览 ├── executeBatch() # 批量执行转码 └── batchPreviewToCsv() # CSV 导出 MainWindow (视图层) ├── Tab 1: 文本编码(输入/输出 + 编码选择 + 转换) ├── Tab 2: 编解码(Base64/URL/Unicode/Hex) ├── Tab 3: 哈希校验(文本/文件 MD5/SHA1/SHA256) └── Tab 4: 批量转码(目录扫描 + 预览表格 + 执行 + CSV 导出) ``` > 原项目:[EncodingTool](https://github.com/GeekDawnTools/EncodingTool) by GeekDawn --- ### opencv — QGraphicsView 交互式图像查看器 基于 QGraphicsView Scene-View-Item 三层架构的专业图像查看器,支持滚轮缩放、鼠标拖拽平移、右键 ROI 框选、实时坐标显示、信息面板。参考《Qt 5 + OpenCV 交互式图像处理开发实战》实现,使用纯 Qt 替代 OpenCV 部分(图片加载用 QPixmap 代替 cv::Mat)。 > 核心理念:**场景坐标 = 像素坐标**。将 QGraphicsPixmapItem 放在 (0,0) 且场景矩形等于图片尺寸,ROI 坐标直接对应像素坐标,无需缩放换算。 #### 功能特性 | 功能 | 说明 | |------|------| | **滚轮缩放** | 以鼠标位置为锚点缩放,factor=1.15 | | **左键拖拽平移** | 滚动条驱动,ClosedHandCursor 反馈 | | **右键拖拽 ROI** | 绿色边框 + 白色角控制点,实时显示坐标/尺寸/面积 | | **双击适应窗口** | fitInView 自动缩放 | | **R 键清除 ROI** | 快捷键操作 | | **前景缩放信息** | drawForeground 绘制缩放百分比(不重写 paintEvent) | | **左侧信息面板** | 图像信息 + ROI 信息 + 操作说明 | | **状态栏** | 缩放比例 + 鼠标场景坐标实时更新 | | **工具栏** | 打开图片/适应窗口/原始大小/放大/缩小/清除 ROI/清除图片 | #### 技术点 | 技术点 | 说明 | |--------|------| | **Scene-View-Item 架构** | QGraphicsView + QGraphicsScene + QGraphicsPixmapItem 三层分离 | | **AnchorUnderMouse** | 缩放锚点设为鼠标位置,缩放中心跟随光标 | | **滚动条平移** | 通过修改 hidden scrollbar 的 value 实现视图平移 | | **QGraphicsRectItem 子类** | ROISelectionRect 自定义绘制:绿色边框 + 半透明填充 + 白色控制点 | | **drawForeground** | 在视图前景层绘制缩放信息,避免 paintEvent 冲突 | | **SmartViewportUpdate** | 静态图片 + ROI 动态变化的最优更新策略 | | **坐标一致性** | 图片放 (0,0) + sceneRect=图片尺寸 → 场景坐标=像素坐标 | | **DontSavePainterState** | 减少 QPainter 状态保存/恢复开销 | #### 文件结构 ``` opencv/ ├── opencv.pro # qmake 项目文件 (Qt5::Core + Widgets) ├── imageviewer.h/cpp # 核心图像视图(滚轮缩放/拖拽平移/ROI/键盘事件,~285行) ├── roiselectionrect.h/cpp # ROI 选择框(绿色边框 + 控制点绘制) ├── mainwindow.h/cpp # 主窗口(工具栏 + 信息面板 + 状态栏,~186行) └── main.cpp # 程序入口 ``` #### 编译运行 ```bash cd opencv qmake make ./opencv ``` #### 架构设计 ``` ImageViewer (视图层) ├── setupView() # 渲染优化 + 鼠标追踪 + 隐藏滚动条 ├── setPixmap() # 图片加载 + 场景坐标对齐 ├── wheelEvent() # 滚轮缩放(AnchorUnderMouse) ├── mousePressEvent() # 左键平移 / 右键 ROI ├── mouseMoveEvent() # 平移 + ROI 更新 + 坐标信号 ├── drawForeground() # 缩放百分比叠加显示 └── keyPressEvent() # R 键清除 ROI MainWindow (组装层) ├── 工具栏(打开/缩放/ROI/清除) ├── 左侧面板(图像信息 + ROI 信息 + 操作说明) └── 状态栏(就绪状态 + 缩放 + 坐标) ``` > 参考文章:《Qt 5 + OpenCV 交互式图像处理开发实战》 --- ### movetothread — QThread moveToThread 线程模型 Demo 基于 `QThread::moveToThread()` 的线程模型演示程序,通过累加计算任务展示 Qt 多线程的核心概念:线程亲和性、事件循环、跨线程信号槽通信。 > 核心理念:**对象归属决定槽函数执行线程**。`moveToThread()` 改变对象的线程亲和性,信号槽通过事件队列实现跨线程调用。 #### 技术点 | 技术点 | 说明 | |--------|------| | **moveToThread()** | 将 Worker 对象的线程亲和性从主线程 A 转移到工作线程 B | | **线程亲和性** | `QObject::thread()` 返回对象归属线程,决定槽函数在哪个线程执行 | | **QThread::currentThread()** | 获取当前代码执行所在的线程,用于观察线程分工 | | **Qt::QueuedConnection** | 跨线程信号槽连接方式,槽函数在目标线程的事件循环中执行 | | **事件循环** | 工作线程 `exec()` 启动事件循环,处理排队的信号调用 | | **进度回调** | Worker 发 `progressChanged` 信号,主线程接收并更新进度 | | **结果返回** | Worker 完成计算后发 `resultReady` 信号,主线程接收结果 | | **优雅退出** | `thread.quit()` + `thread.wait()` 等待工作函数完成后退出 | | **deleteLater** | 线程结束时延迟删除 Worker 对象,确保安全释放 | | **心跳定时器** | 主线程定时器证明主事件循环未被阻塞 | #### 文件结构 ``` movetothread/ ├── movetothread.pro # qmake 项目文件 (Qt5::Core) └── main.cpp # 完整 Demo(Controller + Worker + 线程管理) ``` #### 编译运行 ```bash cd movetothread qmake make ./movetothread ``` #### 运行输出示例 ``` [probe] before move current=A affinity=A [probe] direct call after move current=A affinity=B [heartbeat] current=A [work] current=B affinity=B [progress] 5% receiver=A [progress] 10% receiver=A ... [progress] 100% receiver=A [result] 210 receiver=A [cleanup] Worker current=B [exit] clean ``` #### 关键概念解析 | 概念 | 说明 | |------|------| | **线程 A** | 主线程,`QCoreApplication` 归属线程,运行 UI 事件循环 | | **线程 B** | 工作线程,`QThread` 对象管理的线程,运行 Worker 的槽函数 | | **Controller** | 请求发送对象,归属 A,通过信号请求 Worker 执行任务 | | **Worker** | 工作对象,`moveToThread()` 后归属 B,槽函数在 B 执行 | | **直接调用 vs 信号调用** | 直接调用 `worker->sumTo()` 在当前线程执行;信号调用按连接类型决定 | > 注意:`moveToThread()` 只改变对象的线程亲和性,不会启动新线程。必须调用 `thread.start()` 启动工作线程的事件循环。