Qt 串口调试入门-6:ui美化
Qt 串口调试入门-6:ui美化

Qt 串口调试助手的 UI 工程化(布局 / DPI / 浮层抽屉 / 对齐日志)

主题不再是单点功能堆叠,而是把工具往“长期可用、可维护、跨环境稳定”的方向再推一把:界面结构更清晰、高 DPI 下尺寸更可控、接收区更像日志、低频功能被收纳到更顺手的位置。

1. 布局:从“能摆上去”到“可维护”的结构化拆分

做桌面工具时,最容易掉进去的坑是:靠绝对坐标和“硬 resize”把控件摆出来,短期看很快,长期会被三件事反噬:

  • 窗口尺寸变化(用户拖拽、不同分辨率、最大化/还原)

  • 字体变化(系统字体、DPI 缩放导致的字号变化)

  • 需求变化(多加一个选项,原来的坐标全都得改)

这一轮的学习重点是:把界面当成“页面”,按区域拆分、交给布局管理器,让“可伸缩”成为默认能力。

1.1 先确定“三段式骨架”:顶部 / 中部 / 低频入口

整体用一个垂直布局作为根(root),再把界面拆成:

1) 顶部:左侧控制区 + 右侧接收显示区 2) 中部:发送区(含文件/轮发/属性等) 3) 低频入口:个性化抽屉(把“偶尔用”的功能收起来)

这样做的收益很直接:任何区域内部调整,不会把整个窗口拖崩;也方便后续做“只改局部”的迭代。

1.2 左侧控制区“固定宽”,右侧接收区“尽量大”

串口工具的核心信息密度在接收区:越大越好、越稳定越好。实践上可以把左侧控制区设成固定宽度,而让右侧接收区用 Expanding 策略吃满剩余空间。

这背后是一个通用经验:高频阅读区(日志/图表/表格)应该优先占空间;低频配置区(参数/开关)应该稳定、紧凑、对齐。

1.3 用 GridLayout 解决“对齐感”:标签列固定、控件列限宽、空白列伸展

在“串口设置”这种表单区域,观感很大程度来自对齐。学习到的做法是:

  • 标签列固定宽度,保证每行起点一致;

  • 控件列给合理的最大宽度,避免无限拉伸导致“空、散”;

  • 余下空间交给 Stretch 列,形成自然留白。

这比“手动挪控件”稳定得多:字体变大、翻译变长、控件换成更宽的版本,都不会造成整体错位。


2. 高 DPI:弄清“逻辑像素”和“物理像素”,才能让尺寸真的可控

在 Windows 上开了 125%/150% 缩放后,Qt6 的窗口几何尺寸使用的是 DIP(设备无关像素,逻辑像素)。用户感知到的“窗口外框物理像素”会变大:你以为设置了 1600×900,实际外框可能变成 2000×1125。

这轮的关键学习点是两句话:

1) 物理像素 = 逻辑像素 × devicePixelRatio 2) resize() 影响的是客户区(client area),不是“窗口外框”(含标题栏/边框)

2.1 为什么要在 showEvent 之后再做一次校准?

窗口真正显示出来后,边框厚度、标题栏高度、所属屏幕等信息才稳定。 因此一个更稳的策略是:

  • 构造函数里先给一个“逻辑尺寸”的初始值(保证布局能跑起来)

  • showEvent() 里用 singleShot 延迟到事件循环,再把目标“外框物理像素”反算为“客户区逻辑尺寸”,最后 resize() 一次

2.2 反推公式:外框目标 → 外框逻辑 → 客户区逻辑

核心思路:

1) 取 dpr = windowHandle()->devicePixelRatio()(或从 screen 兜底) 2) 目标外框逻辑尺寸:desiredOuterLogical = targetOuterPx / dpr 3) 估算边框开销:frameDelta = frameGeometry - geometry 4) 目标客户区逻辑尺寸:desiredClient = desiredOuterLogical - frameDelta 5) resize(desiredClient) 一次到位

这个方法的价值不在“追求完全精确”,而在“足够接近且可解释”。一旦可解释,就能用同样套路做更多窗口/对话框的尺寸一致性。

2.3 同样思路迁移到子窗口:Modbus 工具对话框

对话框也遇到同样问题:希望外框固定为某个物理像素尺寸,但 Qt 的 setFixedSize() 仍然是 client area 的 DIP。 解决办法一样:在 showEvent() 延迟到窗口创建完成后,拿到 DPR + 边框开销,再用 setFixedSize(targetClientLogical) 定住。


3. 接收区“对齐日志模式”:让串口数据像日志一样可读

串口数据本质上是“流”,但人读信息需要“结构”。这一轮把接收区做成两种模式:

  • 普通模式:尽量保持原样(文本/HEX)

  • 日志模式:统一前缀 + 对齐列 + 可预测的换行规则

3.1 两个前提:等宽字体 + 禁用自动换行

只要涉及“列对齐”,等宽字体就是必要条件;否则同样字符数量会显示出不同宽度。 此外必须禁用自动换行,否则 Qt 会根据窗口宽度随时改变折行点,你算出来的列宽就失效了。

3.2 自动计算“每行多少列”:用 FontMetrics 做近似估算

固定列数不是写死 16,更合理的做法是:根据当前接收区 viewport 宽度动态计算一次,然后在“本次显示周期”内固定下来。

计算时用到的信息:

  • charW:一个字符大概多宽(用 QFontMetrics'0' 的宽度)

  • viewportPx:可视区域像素宽度

  • availChars = viewportPx / charW:大概能放多少字符

  • fixedChars:时间字段 + T/R 字段 + 分隔空格的固定开销

  • cellChars:每个字节单元格占的字符数(含空格),需要能放下列号的位数

为了让列号(0..N-1)始终对齐,这里还做了一个小迭代:先估算 cols,再根据 cols 的位数回推 cellChars,直到稳定。

3.3 输出策略:表头只打一次,内容按固定列换行

日志模式的输出原则是:

1) 首次输出前打印表头:TIME | T/R | 00 01 02 ... 2) 每帧输出时,第一行带时间和 TX/RX;如果一帧被拆成多行,后续行用空白前缀占位,保证“数据从同一列开始” 3) HEX 模式按固定列数分块;文本模式只保证起始对齐,不强制拆成列

这种显示方式在“看协议帧、对比多次收发”时特别省眼睛:定位字节位置、对齐字段、回看上下文都更快。

3.4 模式切换要重置:避免“表头”和“内容”不一致

日志模式依赖“固定列数”。当用户切换“显示时间/日志模式”时,最安全的做法是:

  • 清空接收区

  • 重置列数/表头状态

  • 让下一次输出重新计算列数并打印新表头

这看似“粗暴”,但比“继续沿用旧配置导致错位”更可控。


4. 个性化组件抽屉:用浮层收纳低频功能,让主界面更清爽

工具做大之后,一个典型矛盾是:功能越多越强,但主界面越挤越乱。 解决思路是把功能分成两类:

  • 高频:必须“一眼能看到、一步能点到”

  • 低频/高级:可以“收纳”,但要“随时可达”

4.1 为什么选“覆盖式浮层”,而不是新开一个 Tab?

Tab 适合“并列的大功能模块”,但这里更多是“工具入口 + 高级参数卡片”。 浮层抽屉更像“系统菜单/工具箱”:点一下展开,选完就收起,不打断主流程。

4.2 实现关键:不参与布局 + 位置重算 + 几何动画

抽屉面板使用一个独立 QWidget,父对象挂在 centralwidget 上,但不放进任何 layout,这样:

  • 展开/收起不会挤压主布局,也就不会改变窗口整体尺寸

  • 可以用 setGeometry() 做精确定位,并在窗口 resize 时重算位置

动画使用 QPropertyAnimation 作用在 geometry 上,配合 OutCubic 让展开更自然。 收起时在动画结束回调里 hide(),避免隐藏控件继续吃鼠标事件。

4.3 内容组织:卡片化 + 可滚动 + 高度自适应

抽屉内部用 ScrollArea 包裹多个“卡片”(例如快捷工具、轮发高级、文件发送等)。 一个小但很实用的点是:让抽屉高度跟内容自适应,避免固定高度导致出现大片“空白区域”,观感会更像成熟软件。


5. 细节打磨:可用性往往就藏在“小功能”里

5.1 波特率支持“自定义…”

真实现场常见的波特率并不总在下拉框里。把波特率下拉框改成:

  • editable(可直接输入)

  • validator(限制成正整数范围)

  • 加一个“自定义…”入口(用对话框输入并回填)

这类交互属于“做完就回不去”的提升:用户不需要为了一个特殊波特率改代码或改配置。

5.2 端口列表“点开即刷新”:用 eventFilter 做更顺手的刷新策略

端口会热插拔,虚拟串口也可能运行时出现。把刷新动作绑定到“用户展开下拉框”的时刻,比提供一个单独按钮更自然。

做法是给端口下拉框装 eventFilter,在鼠标按下、或键盘触发下拉(F4/Alt+Down)时刷新一次列表。 这样用户的心理预期是:我只要点开,就一定是最新的端口


结语:这次学到的“工程化”不是炫技,而是把不确定性变小

第六次迭代里,我最大的收获是:桌面工具的体验好坏,很少来自“某个大功能”,更多来自对不确定性的处理:

  • 不确定的窗口尺寸(DPI / 边框 / 屏幕)→ 用 DPR + frameDelta 让尺寸可控

  • 不确定的布局变化(控件增减 / 字体变化)→ 用布局管理器把变化局部化

  • 不确定的数据形态(文本/二进制/长帧/多行)→ 用对齐日志模式把流结构化

  • 不确定的功能膨胀(越做越多)→ 用抽屉把低频功能收纳但保持可达

接下来如果继续迭代,我会更关注两点: 1) 抽屉内容的“插件化/模块化”组织(更像一个工具箱); 2) 接收区在超长日志下的性能策略(增量渲染、日志滚动策略、数据导出)。

如果对您有帮助的话,能否支持一下博主?
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇