Files

20 KiB
Raw Permalink Blame History

Windows Qt / PySide / PyQt 适配指南

目录

  1. 确认绑定与 UI 技术
  2. 将 Windows 交互意图映射到 Qt
  3. 架构、对象生命周期与数据模型
  4. 布局、窗口化与高 DPI
  5. 主题、QSS、图标与 Windows 材质
  6. 命令、键盘与无障碍
  7. 线程、异步与响应速度
  8. Designer、资源、设置与本地化
  9. 部署与测试
  10. 常见反模式
  11. 交付检查清单
  12. 官方资料

1. 确认绑定与 UI 技术

先检查 pyproject.toml、锁文件、requirements、导入语句和实际安装版本,再生成代码。

选择绑定

  • 新项目优先选择 Qt 6 绑定,除非插件、操作系统或现有代码明确要求 Qt 5。
  • PySide6 是 Qt 官方 Python 绑定,采用 LGPLv3/GPLv3/商业许可组合;发布前确认应用对 Qt 许可义务的满足方式。
  • PyQt6 采用 GPLv3 或 Riverbank 商业许可,不提供 LGPL;闭源分发前必须确认许可方案。
  • 维护现有 PySide2/PyQt5 项目时遵循项目版本,不要在 UI 改造中顺带升级主版本。
  • 不要在一个进程或分发包中混用 PySide 与 PyQt。第三方组件的绑定必须与应用一致。

选择 UI 技术

场景 优先选择
高密度生产力工具、表格、菜单、工具栏、停靠面板 Qt Widgets
既有 .ui 文件或大量成熟 QWidget 控件 Qt Widgets
高度定制的视觉、连续动画、触控优先界面 Qt Quick/QML
Widgets 与 QML 混合 仅在价值明确时采用,并定义边界、线程、焦点和无障碍责任

不要把 Qt Quick 仅当作“更现代的 Widgets”,也不要为了原生外观强行用 Widgets 实现复杂动画。根据任务、团队能力、组件生态、性能、部署和无障碍要求选择。

保持绑定 API 一致

  • PySide 使用 Signal、Slot、Property;PyQt 使用 pyqtSignal、pyqtSlot、pyqtProperty。
  • PySide 项目仅在项目已统一启用时使用 snake_case 或 true_property 特性;不要与默认 camelCase 风格随机混用。
  • 生成示例时沿用仓库现有导入风格、枚举写法和 UI 构建方式。

2. 将 Windows 交互意图映射到 Qt

根据语义选择控件,不要逐字翻译 WinUI 控件名:

Windows/WinUI 意图 Qt Widgets 实现 说明
应用主窗口 QMainWindow + centralWidget 可组合菜单栏、工具栏、状态栏和停靠面板
顶级导航 带 Model 的 QListView + QStackedWidget 小型静态同级页也可用 QTabWidget;导航必须有文本标签
文档标签页 QTabWidget 或 QTabBar + 内容容器 定义关闭、重排、未保存和快捷键行为
辅助工作面板 QDockWidget 定义允许区域、浮动、关闭和状态恢复
分栏工作区 QSplitter 设置合理最小尺寸与折叠行为
页面级命令 QAction + QToolBar/按钮 同一 QAction 复用于菜单、工具栏和快捷键
应用菜单 QMenuBar、QMenu、QAction 使用标准菜单分组和角色
上下文命令 QMenu 或 QAction 集合 同时提供可见或键盘访问路径
列表 QListView + QAbstractListModel 动态或大型数据不要依赖 QListWidget
表格 QTableView + QAbstractTableModel 排序/筛选使用 QSortFilterProxyModel
树 QTreeView + QAbstractItemModel 保持展开、选择和键盘行为
简单表单 QFormLayout + 标准输入控件 设置标签 Buddy、验证和错误位置
模态任务 QDialog + QDialogButtonBox 仅用于真正需要阻塞的决策或短任务
简单系统消息 QMessageBox 不要用于频繁状态或长文本工作流
内联信息条 自定义语义化 Banner QWidget 包含图标、文本、操作和关闭入口,不要用状态栏替代关键错误
确定进度 QProgressBar 提供取消、失败和完成状态
阻塞进度 QProgressDialog 仅在确实需要窗口级阻塞时使用
持久设置 QSettings 先设置组织名和应用名,定义迁移与默认值

Qt Quick/QML 项目使用 Qt Quick Controls 的等价语义控件,并为 Python 暴露的状态使用 Signal、Slot 和带通知信号的 Property。不要把 QWidget 嵌入 QML 作为常规布局方案。

核心交互模式映射

数据表格: 使用 QTableView、自定义 QAbstractTableModel 与 QSortFilterProxyModel,以稳定记录 ID 维护业务选择和勾选状态。currentIndex()、selectionModel() 与模型中的勾选角色分别对应当前单元格、选择集和勾选集,不能互相替代。通过视图的 clicked、doubleClicked、activated 与 customContextMenuRequested 明确区分单击、双击、键盘调用和右键;右键先解析目标索引,再按产品规则决定是否改变选择。复选框和行内按钮的委托事件必须停止继续触发行默认操作。

表单: 使用标准编辑控件、QFormLayout、标签 Buddy 和字段旁固定错误区。格式约束可用 QValidator 或输入掩码,领域规则仍由应用层验证。避免在每次按键后弹框;提交失败时保留输入,聚焦首个错误,并给出问题与修复方式。异步验证以请求 ID 或取消机制忽略过期结果。

导航与标签: 顶级导航可用 Model 驱动的 QListView 配合 QStackedWidget;文档工作区使用 QTabWidget,或以 QTabBar 加内容容器构建。页面与标签都用稳定 ID 管理,不以标题或当前索引作为业务身份。关闭、重排和切换前定义未保存检查、焦点返回、筛选/选择/滚动恢复及后台任务所有权。

对话框与任务: QDialog/QDialogButtonBox 只承载必须阻塞的决策;普通状态与可恢复错误使用页面内 Banner、状态区或非模态面板。长任务由 worker 或线程池执行,通过队列信号回传进度与结果,并支持取消、重复提交防护、关闭后忽略回调及成功/失败/重试状态。

多 Tab 数据工作台: 每个页面统一采用垂直布局:标题与说明、顶部 QToolBar/命令区、来源或校验状态区、可伸展的 QTableView 主区、底部选择摘要与完成操作区。将同一 QAction 复用于按钮、菜单和快捷键。普通成功不弹 QMessageBox;只有不可逆、高代价或缺少安全默认值的操作才在执行前确认。

3. 架构、对象生命周期与数据模型

对象与职责

  • 让 View 负责渲染和输入转发,让领域/应用服务负责业务规则、I/O 和持久化。
  • 使用 QObject 的父子所有权管理 Qt 对象生命周期;不要同时建立不必要的 Python 强引用环。
  • 对需要延迟销毁的 QObject 使用 deleteLater(),尤其是跨事件循环或线程退出时。
  • 将共享用户操作建模为 QAction,避免在多个按钮和菜单处理器中复制逻辑。
  • 在关闭窗口前处理未保存更改,但不要把业务保存逻辑塞入 closeEvent()。

信号与槽

  • PySide 类中将作为连接目标的方法标记为 @Slot(...);官方建议这样做以减少运行时开销,并正确表达线程亲和性。
  • 让信号载荷传递不可变数据、标识符或轻量结果,不要跨线程传递需要在工作线程访问的 GUI 对象。
  • 明确连接的所有者和断开时机,避免重复连接导致一次操作执行多次。
  • QML 可见属性必须提供通知信号,否则绑定不会可靠更新。

Model/View

  • 动态、共享、可排序筛选或可能变大的数据使用 QListView、QTableView、QTreeView 与 QAbstractItemModel 派生模型。
  • 一维和二维数据分别优先从 QAbstractListModel、QAbstractTableModel 开始,不要无必要直接实现完整 QAbstractItemModel。
  • 使用 QSortFilterProxyModel 处理排序和筛选,不要为每次查询复制整个源数据集合。
  • 插入、删除、移动和重置模型时使用正确的 begin/end 通知;不得只修改 Python 列表后强制刷新视图。
  • 自定义绘制和编辑使用 QStyledItemDelegate;不要为每个单元格创建常驻 QWidget。
  • QListWidget、QTableWidget、QTreeWidget 仅适合小型、简单、不会共享模型的数据。

4. 布局、窗口化与高 DPI

布局

  • 使用 QLayout 派生布局、QSizePolicy、stretch、sizeHint() 和最小尺寸,不要用 setGeometry() 手工排列常规界面。
  • 需要随宽度换行的自定义控件实现合理的 heightForWidth();内容变化时调用 updateGeometry()。
  • 为窗口定义可用的最小尺寸,而不是固定尺寸。长文本、翻译和字体放大后仍应可操作。
  • 在少量语义断点下重排或显示/隐藏次要面板;不要在每次 resizeEvent() 中重建整棵 Widget 树。
  • 响应式切换时保留焦点、选择、滚动、编辑和展开状态。

主窗口与窗口状态

  • QMainWindow 只把一个控件设置为 centralWidget,再通过 Qt 提供的 API 管理菜单栏、工具栏、状态栏和 QDockWidget。
  • 使用 saveGeometry()/restoreGeometry() 和 QMainWindow.saveState()/restoreState() 持久化窗口与停靠布局。
  • 恢复窗口时检查当前屏幕可用区域,防止窗口因显示器移除或 DPI 变化而出现在屏幕外。
  • 为辅助窗口定义所有者、模态性、任务栏显示、激活、关闭和共享数据生命周期。
  • 优先保留原生窗口框架。无边框窗口需要自行正确实现缩放命中测试、拖动、系统菜单、标题按钮、Snap Layouts、高 DPI 和无障碍,除非产品价值足以承担成本,否则不要使用。

高 DPI 与多显示器

  • Qt 6 以设备无关像素描述大多数 Widget、Quick、事件和窗口几何;不要再次手工乘系统缩放比例。
  • 自定义像素绘制或图像几何时读取 devicePixelRatio();不要把物理像素当作布局单位。
  • 使用 SVG、QIcon 多尺寸资源或 @2x 高分辨率资源。验证 100%、125%、150%、200% 以及跨显示器移动。
  • 屏幕几何使用 QScreen 的设备无关坐标;不要假设多个屏幕组成无间隙的物理像素网格。

5. 主题、QSS、图标与 Windows 材质

主题优先级

  1. 优先让 QStyle 和系统 QPalette 提供平台行为与控件状态。
  2. 用语义 Token 生成有限的自定义 QPalette 或组件级 QSS。
  3. 只有品牌需要时才替换标准控件绘制或实现自定义 QStyle。

QPalette 为活动、非活动和禁用状态提供颜色组。覆盖颜色时同时定义这些状态,并独立验证浅色、深色和高对比度。

Qt 6.5 或更高版本可通过 QGuiApplication.styleHints().colorScheme() 读取系统配色方案,并监听 colorSchemeChanged。处理主题变化时也要响应 QEvent.PaletteChange 或 QEvent.ApplicationPaletteChange。使用前检查项目 Qt 版本;较旧版本不得生成不存在的 API。

Qt 6.10 或更高版本提供更多平台无障碍提示;低版本仍必须通过实际 Windows 高对比度测试验证,而不是自行猜测系统颜色。

QSS

  • QSS 不是完整 CSS:不要期待 Web 布局、CSS 变量、继承和选择器行为完全一致。
  • 避免 QWidget { ... } 等大范围规则覆盖所有标准状态。
  • 为 hover、pressed、checked、selected、disabled、focus、active/inactive 和高对比度定义可辨识行为。
  • 使用动态属性选择器时,属性变化后按项目需要重新 polish;不要依赖偶然刷新。
  • 样式表可能改变原生绘制和尺寸提示。修改后重新测试菜单、滚动条、输入法、焦点框、停靠窗口和高 DPI。

图标与 Windows 材质

  • 使用一致的 SVG/QIcon 资源,为 normal、disabled、active、selected 等模式提供可读变体。
  • 不要用 Emoji 作为结构图标,也不要依赖某台开发机才存在的字体图标。
  • Qt Widgets/PySide 没有与 WinUI MicaBackdrop 完全等价的跨版本 API。需要 Mica/Acrylic 时,把 Windows 原生集成隔离为可选适配层,按 Windows/Qt 版本检查,并始终提供纯色 QPalette 回退。
  • 不要为装饰效果移除原生标题栏、破坏高对比度或引入不可维护的全局事件过滤器。

6. 命令、键盘与无障碍

命令与键盘

  • 用 QAction 集中管理文本、图标、启用/选中状态和快捷键,并复用于菜单、工具栏、按钮或上下文菜单。
  • 标准操作优先使用 QKeySequence.StandardKey,避免硬编码与平台约定冲突的组合键。
  • 使用 QShortcut 时设置合适的 Qt.ShortcutContext;不要让页面快捷键意外覆盖整个应用。
  • 使用标签中的 & 和 QLabel.setBuddy() 提供访问键。处理中文界面时仍要检查访问键是否重复和是否可发现。
  • 优先使用自然 Tab 顺序,仅在布局顺序无法表达正确流程时使用 QWidget.setTabOrder()。
  • 所有悬停、右键和拖拽加速操作都必须有可见或键盘等价路径。

无障碍

  • 标准 Widgets 与 Qt Quick Controls 已提供基础无障碍接口,优先复用它们。
  • 仅在可见文本无法形成正确名称时设置 accessibleName;把补充说明放入 accessibleDescription。
  • 为表单建立标签 Buddy 和合理焦点顺序;不要只通过占位符标识输入字段。
  • 自定义 QWidget 需要公开正确角色、名称、状态、值、操作和事件;复杂控件根据需要实现 QAccessibleInterface/QAccessibleWidget。
  • QML 自定义项使用 Accessible 附加属性公开角色、名称、描述和操作。
  • 用 Narrator、Accessibility Insights/Inspect、键盘、高对比度和文本/显示缩放验证真实体验。

7. 线程、异步与响应速度

GUI 线程规则

  • QWidget 及其子类不是可重入对象,只能在 GUI 主线程创建和访问。Qt Quick 场景对象也不得从工作线程直接修改。
  • 后台工作使用 worker QObject + moveToThread(),或 QThreadPool + QRunnable;根据项目复杂度选择一种模式并统一。
  • 通过默认 AutoConnection 或显式 QueuedConnection 的信号把结果送回 GUI 线程。
  • 对具有事件循环的接收者使用绑定对应的 Slot 装饰器,确保代码在接收对象所属线程执行。
  • 在线程退出时使用 quit()、wait() 和 deleteLater() 建立明确的清理链;关闭窗口后不得让回调访问已销毁 UI。
  • 不要对同一线程使用 BlockingQueuedConnection,这会导致死锁。

任务设计

  • 长任务提供进度、取消和最终状态;取消必须检查任务是否仍可安全提交结果。
  • 搜索、预览和重新加载使用请求 ID 或取消令牌忽略过期结果。
  • 网络请求优先使用事件驱动的 QNetworkAccessManager,或在工作线程使用不会触碰 GUI 的客户端。
  • 不要在槽函数中执行阻塞 I/O,也不要用频繁 processEvents() 掩盖冻结。
  • 需要 asyncio 时沿用项目已有事件循环集成。采用 PySide6.QtAsyncio 前核对精确版本和支持范围,不要同时运行两个互相竞争的主事件循环。

8. Designer、资源、设置与本地化

  • Qt Widgets Designer 生成的 .ui 文件应通过 pyside6-uic/pyuic6 生成代码,或由项目统一使用 QUiLoader 运行时加载。
  • 不要手工编辑生成的 ui_*.py;在继承/组合的业务类中扩展界面,重新生成时避免丢失修改。
  • PySide 官方建议优先使用与绑定版本匹配的 pyside6-uic,避免直接调用不匹配的独立 uic。
  • 使用 .qrc 与 pyside6-rcc/对应 PyQt 工具管理图标和资源,或采用项目已经验证的包资源方案。
  • 使用 QSettings 前设置组织名、域和应用名;为设置键、默认值、版本迁移和损坏恢复建立规范。
  • 使用 tr()/翻译上下文和 QTranslator 进行本地化。不要拼接句子;测试复数、快捷键、长文本和从右到左布局。

9. 部署与测试

部署

  • 固定 Python、绑定、Qt 和打包工具版本,并在干净虚拟环境中构建。
  • PySide6 优先评估官方 pyside6-deploy;它使用 pysidedeploy.spec 保存配置,并支持 --dry-run 查看实际命令。
  • PyQt 使用项目许可和工具链已验证的打包方案;不要把 PySide 部署命令用于 PyQt。
  • 检查 Qt 平台插件、图像格式插件、QML imports、翻译、OpenSSL/多媒体依赖、MSVC Runtime、应用图标和资源是否完整。
  • 在干净 Windows 虚拟机上测试首次启动、无开发环境运行、非 ASCII 路径、只读目录、多用户配置、升级与卸载。
  • 发布前确认代码签名、安装器、文件关联、协议处理、自动更新和许可文件要求。

测试

  • 使用 Qt Test/QTest 或项目现有测试栈覆盖关键 Widget、快捷键、模型通知和信号行为。
  • 对 Model/View 测试插入、删除、移动、排序、筛选、编辑、空数据和超大数据。
  • 对线程测试取消、窗口提前关闭、过期结果、异常和应用退出。
  • 执行 Windows 测试矩阵:最低支持版本与当前 Windows 11;100%–200% 缩放;跨显示器;浅色、深色、高对比度;键盘、鼠标和声明支持的触控;Narrator。
  • 测试打包产物,而不只测试源码环境。

10. 常见反模式

  • 在同一项目中混用 PySide6 与 PyQt6 导入。
  • 从工作线程调用 Widget 方法或把 Widget 传给 worker。
  • 用 time.sleep()、同步网络或大循环阻塞 GUI 线程。
  • 为动态表格使用 QTableWidget 并为每个单元格嵌入 Widget。
  • 在每次窗口调整大小时销毁并重建全部页面。
  • 用固定坐标、固定字体像素或手动 DPI 倍增代替 Qt 布局系统。
  • 用全局 QSS 抹掉原生焦点、禁用、选择、高对比度和非活动窗口状态。
  • 手工编辑 pyside6-uic/pyuic6 生成文件。
  • 无版本检查地复制 Qt C++、旧 PySide2、PyQt5 或另一个绑定的示例。
  • 为获得 Mica/Acrylic 无条件启用无边框窗口或私有 Windows API。

11. 交付检查清单

  • 已确认绑定、精确版本、Qt Widgets/QML 路线和许可证。
  • 未混用 PySide/PyQt 或绑定特有 API。
  • 标准控件、Model/View、QAction 和布局系统得到优先使用。
  • GUI 对象只在主线程访问,后台结果通过队列信号返回。
  • 主题跟随系统,QSS 未破坏高对比度、焦点或禁用状态。
  • 窗口在调整大小、贴靠、跨显示器和 100%–200% 缩放时可用。
  • 键盘、上下文菜单、拖拽替代方式和无障碍语义完整。
  • 加载、空、错误、取消、超时、过期结果和撤销状态已处理。
  • Designer 生成代码、资源和设置具有可重复的生成/迁移流程。
  • 已在干净 Windows 环境测试最终打包产物。

12. 官方资料