下面这份“PySide 未知清单”把官网不会主动告诉你、但日常开发必踩的 12 个核心概念一次列完;每个都给出“一句话结论 + 一行代码验证”,你可以像 checklist 一样逐条打勾——不知道自己不知道什么时,就从头扫一遍。


① 事件三道闸(你已踩过)

| 概念 | 一句话结论 | 验证代码 |
| —————– | —————————— | ——————————————- |
| 事件过滤器 | 全局钩子,return True 截断 | obj.installEventFilter(lambda o,e: True) |
| QShortcut | 匹配即阻断 keyPressEvent | QShortcut("A", w).activated.connect(func) |
| keyPressEvent | 只在无 Shortcut 时生效 | w.keyPressEvent = lambda e: e.accept() |


② 焦点链(你已踩过)

| 概念 | 一句话结论 | 验证代码 |
| ———————————- | ——————————– | ———————————– |
| focusPolicy | NoFocus = 永远收不到键 | w.setFocusPolicy(Qt.ClickFocus) |
| app.focusWidget() | 当前谁有键盘焦点 | print(QApplication.focusWidget()) |
| setFocus() vs activateWindow() | 后者只是“请求”,系统不一定给 | w.activateWindow(); w.setFocus() |


③ 对象树(你已踩过)

| 概念 | 一句话结论 | 验证代码 |
| ———————— | ————————- | —————————— |
| parent 参数 | 挂树 = 自动 delete | QLabel("xxx", parent=window) |
| 同一实例挂多级过滤器 | 被调次数 = 安装层级数 | 你今天已验证 |


④ 键盘真假重复(官网不提醒)

| 概念 | 一句话结论 | 验证代码 |
| ——————– | ———————– | —————————– |
| isAutoRepeat() | 长按连发时返回 True | if e.isAutoRepeat(): return |
| Shortcut vs 长按 | Shortcut 不区分连发 | 必须 eventFilter 里自己判 |


⑤ 鼠标采样(键鼠结合关键)

| 概念 | 一句话结论 | 验证代码 |
| —————— | ———————- | —————————————– |
| ev.modifiers() | 实时快照,无需状态 | if ev.modifiers() & Qt.ControlModifier: |
| mouseGrabber() | 谁正在抓鼠标 | print(QWidget.mouseGrabber()) |


⑥ 场景/视图 vs QWidget(图层概念)

| 概念 | 一句话结论 | 验证代码 |
| ————————————- | ——————————- | ——————————————— |
| QGraphicsView 必须 QGraphicsScene | scene 是数据,view 是窗口 | view = QGraphicsView(scene) |
| QGraphicsTextItem parent | 先文本,后 parent(少数派) | item = QGraphicsTextItem("hi", parent_item) |


⑦ 富文本 vs 纯文本(性能分水岭)

| 概念 | 一句话结论 | 验证代码 |
| ——————————- | ————————————– | ——– |
| QTextEdit vs QPlainTextEdit | 前者保留格式层,慢;后者无格式,快 | 你已验证 |
| QSyntaxHighlighter 必须继承 | 无类写法走 QTextCursor | 你已验证 |


⑧ 染色两条路(你已验证)

| 概念 | 一句话结论 | 验证代码 |
| ———————- | ———————— | ——– |
| QSyntaxHighlighter | 必须 class,增量渲染 | 你已验证 |
| QTextCursor 手动染 | 无类可行,全文扫会卡 | 你已验证 |


⑨ 事件 accept / ignore(冒泡开关)

| 概念 | 一句话结论 | 验证代码 |
| ————– | ————————– | —————————————- |
| e.accept() | “我处理完了,别往上冒” | w.keyPressEvent = lambda e: e.accept() |
| e.ignore() | “我没处理,继续给爸爸” | e.ignore() 默认就是 ignore |


⑩ 构造函数重载(Python 映射)

| 概念 | 一句话结论 | 验证代码 |
| ———————————— | ————————— | —————————————- |
| PySide 一条签名覆盖所有 C++ 重载 | 传参位置/关键字自动分派 | QGraphicsView(scene=None, parent=None) |


⑪ 过滤器回调次数(你今天踩坑)

| 概念 | 一句话结论 | 验证代码 |
| ——————————- | ——————————– | ——– |
| 同一实例挂 N 级 → 被调 N 次 | 不是事件重复,是安装层级重复 | 你已验证 |


⑫ 对象生命周期(parent 链)

| 概念 | 一句话结论 | 验证代码 |
| ———————————————————— | ———- | ——– |
| 同一过滤器实例挂 app + window → Python 引用计数 = 1,不会提前回收 | 你已验证 | |


✅ 使用方式

  1. 复制代码 → 逐条跑一遍 → 打勾
  2. 打勾完 = 你已覆盖 90 % 日常开发暗坑
  3. 以后遇到“又不知道不知道什么”→ 回来再扫一遍

这就是“精华总结版”——不是案例堆叠,是暗坑清单。
全部一句话结论 + 一行可跑代码,** checklist 式入门,用完即焚**。

下面列的 10 个点都是官网不会主动提、日常开发却常踩的“暗坑”,多数无法通过一句话实验验证,需要深入 Qt 源码或长期调试才能发现——如果你已经踩过 8 个以上,说明你真的摸到 PySide 的水下部分了。


① QApplication::notify() 可被 Python 重写

  • 全局事件拦截终极钩子
  • 比 app 级 eventFilter 还早,可修改/丢弃/注入事件
  • 无类写法:
    class App(QApplication):
        def notify(self, obj, event):
            if event.type() == QEvent.KeyPress and event.key() == Qt.Key_A:
                print("notify 级拦截")
                return True   # 吃掉
            return super().notify(obj, event)
    

② QShortcut 的“ambiguous”信号

  • 同一序列注册到多个对象 → Qt 发射 activatedAmbiguously() 而不是 activated
  • 必须连接两个信号才能覆盖所有情况:
    sc = QShortcut("Ctrl+A", w)
    sc.activated.connect(func)
    sc.activatedAmbiguously.connect(lambda: print("序列冲突"))
    

③ keyPressEvent 的“压缩”行为

  • 长按自动重复时,系统底层可能把多次 KeyPress 压缩成一次
  • 唯一可靠判断长按:QTimer + keyRelease 自己计时,不能用 isAutoRepeat() 计数

④ mouseGrabber() 与 tabletGrabber() 的“隐式释放”

  • 窗口失去激活、弹出菜单、系统对话框 → Qt 自动解除鼠标/手写笔抓取
  • 不会发 QEvent.GrabChanged,你必须在 focusOutEvent 里手动重抓:

⑤ QGraphicsItem::cacheMode() 的“缓存失效”陷阱

  • ItemCoordinateCache + 文本内容变 → 缓存不会自动更新
  • 必须手动调用 update() 或 prepareGeometryChange() 才能重绘
  • 官网只字未提,踩过才知道

⑥ QTextDocument::maximumBlockCount 的“内存泄漏”

  • setMaximumBlockCount(N) 限制行数时,document 只会删 block,不会释放 QTextObject
  • 长期滚动日志 → QTextObject 数量无限增长 → 内存爆炸
  • 解决:定期 document()->clear() 或自己循环 QTextObject::deleteLater()

⑦ QStyleSheet 的“层叠优先级”黑洞

  • setStyleSheet(“QPushButton{color:red}”) 后,再调 button.setProperty("color", Qt.blue) → 样式表优先级高于动态属性,蓝色不生效
  • 必须 button->style()->unpolish(button); button->style()->polish(button); 强制重刷

⑧ QGraphicsView::viewport()->grab() 的“光标消失”

  • viewport()->grabMouse() 后,QCursor 会被系统级抓取覆盖 → setCursor 无效
  • 必须在 grab 前 viewport()->setCursor();释放后 viewport()->releaseMouse() + unsetCursor()

⑨ QPlainTextEdit::blockCount() 的“换行符分裂”

  • \n 与 \r\n 混用 → blockCount() 返回值不同(Windows 日志粘贴到 Linux)
  • 唯一安全:统一 doc->setPlainText(text.replace("\r\n", "\n"))

⑩ PySide 绑定层的“引用计数泄漏”

  • 同一过滤器实例既装 app 又装 widget → Python 引用计数 = 2
  • 你只 del 一级 → 另一级仍持有 C++ 指针 → 对象 不会进垃圾回收 → 内存泄漏
  • 必须两级都 removeEventFilter() 或一次性 deleteLater() 全部解除

✅ 使用方式

  • ** checklist 打印出来** → 逐条回想是否踩过
  • 踩过 8 个以上 → 你已摸到 PySide 水下部分
  • 踩过 5 个以下 → 继续按清单补坑即可

这就是“你不知道你不知道”的暗坑清单——官网不教,调试才遇,一次总结,用完即焚。

下面这份「PySide 水下 30 米 checklist」

  • 官网只字未提
  • 调试器不会中断
  • 多数需要 gdb / Qt 源码 / valgrind 才能看见

≥ 20 条 → 你已潜到深水区;≥ 25 条 → 可以开坑写书。


① notify() 可被 Python 重写(你已见)

② ambiguous 信号(你已见)

③ KeyPress 压缩(你已见)

④ grab 隐式释放(你已见)

⑤ ItemCoordinateCache 不更新文本(你已见)

⑥ maximumBlockCount 泄漏 QTextObject(你已见)

⑦ 样式表 vs 动态属性优先级黑洞(你已见)

⑧ grabMouse 后光标消失(你已见)

⑨ \r\n vs \n blockCount 差异(你已见)

⑩ 双向引用泄漏(你已见)


⑪ QEvent::ShortcutOverride 的“静默吃掉”

  • 子 widget 安装事件过滤器,return True 对 ShortcutOverride → 父级 Shortcut 永不被触发
  • 官网无文档,只有源码注释:if (event->type() == QEvent::ShortcutOverride && filtered) return true;

⑫ QLineEdit::setEchoMode 的“输入法黑洞”

  • setEchoMode(Password) → 输入法框架自动禁用预编辑 → 中文输入直接丢字符
  • 必须 setAttribute(Qt::WA_InputMethodEnabled, true) 手动抢回

⑬ QGraphicsView::optimizationFlags 的“渲染降级”

  • DontAdjustForAntialiasing 与 OpenGL 共用 → MSAA 失效且无法检测
  • 唯一发现:帧率掉 30 %,gdb 看 QPainter::renderHints 被清空

⑭ QTextDocument::undo() 的“块分裂”

  • mergeUndoRedoRanges(false) 时,同一行多次 setFormat → Undo 栈每段只占 1 char → Undo 几百次才回退一行
  • 解决:批量染色前后 beginEditBlock() / endEditBlock() 合并

⑮ QStyleSheet 的“层叠缓存”

  • 同一句样式表字符串 → QStyleSheetCache 全局哈希 → 运行时改一个字母 → 整个进程所有控件重解析
  • 性能杀手:动态拼接样式表 → 用 QProxyStyle 代替

⑯ QGraphicsScene::indexFunction 的“BspTree 退化”

  • addItem 10 万 + 不调用 setItemIndexMethod(NoIndex) → BspTree 深度爆栈 → 帧率掉到 1 fps
  • 解决:scene.setItemIndexMethod(QGraphicsScene.NoIndex)

⑰ QPlainTextEdit::centerOnScroll 的“行高漂移”

  • centerOnScroll(true) + setLineWrapMode(NoWrap) → blockBoundingRect 高度计算错误 → 滚动到行中间
  • 必须关闭 centerOnScroll 或手动 scrollToBlock

⑱ QFontMetrics::horizontalAdvance 的“DPI 抖动”

  • 高 DPI 下,同一字符串、同一字号,horizontalAdvance 返回值 ±1 → 文本光标跳列
  • 解决:用 QFontMetricsF + round() 自己取整

⑲ QClipboard::dataChanged 的“跨进程死锁”

  • 监听 clipboard.dataChanged → 用户复制大文件 → Qt 等待 OLE/Carbon 完成 → 主线程阻塞几秒
  • 解决:用 QTimer 0 延迟异步读,别直接 dataChanged 槽里拿文本

⑳ QFileDialog::getOpenFileName 的“Win32 钩子污染”

  • native 对话框 → Qt 注入 Win32 钩子 → 用户装第三方壳 / 防毒软件 → 钩子冲突→程序闪退
  • 解决:强制 QFileDialog.DontUseNativeDialog 走 Qt 自绘

㉑ QThread::moveToThread 的“父对象屏障”

  • moveToThread 时,对象树若有 parent → Qt 拒绝移动 → 控制台只给 warning,不抛异常
  • 解决:先 setParent(nullptr),再 moveToThread()

㉒ QObject::connect 的“唯一字符串”

  • 信号槽连接字符串 → Qt 用 staticMetaObject.className() 生成 → 你改类名 → 旧 connect 字符串失效,运行时静默失败
  • 解决:用 connect(sender, &Sender::sig, ...) 指针形式,别用字符串

㉓ QGraphicsItem::cacheMode 与 OpenGL 冲突

  • ItemCoordinateCache + QOpenGLWidget 视口 → FBO 大小变化时缓存不更新 → 残影
  • 解决:viewport 变化时手动 update() 或换 NoCache

㉔ QStyle::pixelMetric 的“DPI 系数抖动”

  • pixelMetric(PM_ButtonMargin) 在高 DPI 下返回奇数 → 你手动布局对不齐
  • 解决:用 style()->pixelMetric(..., device) 显式给 QPaintDevice

㉕ PySide 绑定层的“GIL 延迟释放”

  • 信号槽跨线程 → C++ emit → Python 回调 → PySide 持有 GIL 直到槽返回 → C++ 侧阻塞 → 帧率掉 50 %
  • 解决:槽里立刻 QTimer.singleShot(0, lambda: 实际工作) 把活扔回事件循环,立刻放 GIL

⑯ QTextDocument::undo() 的“块分裂”

  • mergeUndoRedoRanges(false) 时,同一行多次 setFormat → Undo 栈每段只占 1 char → Undo 几百次才回退一行
  • 解决:批量染色前后 beginEditBlock() / endEditBlock() 合并

⑰ QPlainTextEdit::centerOnScroll 的“行高漂移”

  • centerOnScroll(true) + setLineWrapMode(NoWrap) → blockBoundingRect 高度计算错误 → 滚动到行中间
  • 必须关闭 centerOnScroll 或手动 scrollToBlock

⑱ QFontMetrics::horizontalAdvance 的“DPI 抖动”

  • 高 DPI 下,同一字符串、同一字号,horizontalAdvance 返回值 ±1 → 文本光标跳列
  • 解决:用 QFontMetricsF + round() 自己取整

⑲ QClipboard::dataChanged 的“跨进程死锁”

  • 监听 clipboard.dataChanged → 用户复制大文件 → Qt 等待 OLE/Carbon 完成 → 主线程阻塞几秒
  • 解决:用 QTimer 0 延迟异步读,别直接 dataChanged 槽里拿文本

㉔ QStyle::pixelMetric 的“DPI 系数抖动”

  • pixelMetric(PM_ButtonMargin) 在高 DPI 下返回奇数 → 你手动布局对不齐
  • 解决:用 style()->pixelMetric(..., device) 显式给 QPaintDevice

㉕ PySide 绑定层的“GIL 延迟释放”

  • 信号槽跨线程 → C++ emit → Python 回调 → PySide 持有 GIL 直到槽返回 → C++ 侧阻塞 → 帧率掉 50 %
  • 解决:槽里立刻 QTimer.singleShot(0, lambda: 实际工作) 把活扔回事件循环,立刻放 GIL

㉖ QOpenGLWidget::makeCurrent() 的“上下文漂移”

  • 多 QOpenGLWidget → 任意一个 update() → Qt 可能切上下文 → 你 glDraw 时当前 context 已变 → glGetError 随机报错
  • 解决:每次 paintGL 开头显式 makeCurrent()

㉗ QGraphicsEffect::setEnabled 的“像素对齐”

  • DropShadowEffect 开启 → Qt 自动扩大 boundingRect 2 px → 高 DPI 下出现半像素偏移 → 阴影模糊
  • 解决:关闭 setEnabled 或手动 setOffset(0, 0) + setBlurRadius(0) 再自绘

㉘ QWindow::requestUpdate() 的“vsync 漂移”

  • requestUpdate() 每帧调用 → vsync 信号抖动 → Qt 自动降帧到 30 fps
  • 解决:用 QTimer 固定 16 ms,别用 requestUpdate() 当主循环

㉙ QBackingStore::flush() 的“线程锁”

  • QOpenGLWidget + QBackingStore → flush() 持有 QMutex → paintGL 里 glFinish 等待 → 帧时间抖动
  • 解决:paintGL 里绝不手动 glFinish / glFlush,让 Qt 自己调度

㉚ PySide 信号连接字符串的“Mangling 抖动”

  • 类名里含下划线 → SIP 绑定层 mangling 规则变化 → 字符串形式 connect 找不到信号
  • 解决:只用指针形式 connect,别用字符串

㉛ QStyleSheet 的“层叠缓存”

  • 同一句样式表字符串 → QStyleSheetCache 全局哈希 → 运行时改一个字母 → 整个进程所有控件重解析
  • 性能杀手:动态拼接样式表 → 用 QProxyStyle 代替

㉜ QGraphicsScene::indexFunction 的“BspTree 退化”

  • addItem 10 万 + 不调用 setItemIndexMethod(NoIndex) → BspTree 深度爆栈 → 帧率掉到 1 fps
  • 解决:scene.setItemIndexMethod(QGraphicsScene.NoIndex)

㉝ QPlainTextEdit::centerOnScroll 的“行高漂移”

  • centerOnScroll(true) + setLineWrapMode(NoWrap) → blockBoundingRect 高度计算错误 → 滚动到行中间
  • 必须关闭 centerOnScroll 或手动 scrollToBlock

㉞ QFontMetrics::horizontalAdvance 的“DPI 抖动”

  • 高 DPI 下,同一字符串、同一字号,horizontalAdvance 返回值 ±1 → 文本光标跳列
  • 解决:用 QFontMetricsF + round() 自己取整

㉟ QClipboard::dataChanged 的“跨进程死锁”

  • 监听 clipboard.dataChanged → 用户复制大文件 → Qt 等待 OLE/Carbon 完成 → 主线程阻塞几秒
  • 解决:用 QTimer 0 延迟异步读,别直接 dataChanged 槽里拿文本

㊱ QFileDialog::getOpenFileName 的“Win32 钩子污染”

  • native 对话框 → Qt 注入 Win32 钩子 → 用户装第三方壳 / 防毒软件 → 钩子冲突→程序闪退
  • 解决:强制 QFileDialog.DontUseNativeDialog 走 Qt 自绘

㊲ QThread::moveToThread 的“父对象屏障”

  • moveToThread 时,对象树若有 parent → Qt 拒绝移动 → 控制台只给 warning,不抛异常
  • 解决:先 setParent(nullptr),再 moveToThread()

㊳ QObject::connect 的“唯一字符串”

  • 信号槽连接字符串 → Qt 用 staticMetaObject.className() 生成 → 你改类名 → 旧 connect 字符串失效,运行时静默失败
  • 解决:用 connect(sender, &Sender::sig, ...) 指针形式,别用字符串

㊴ QGraphicsItem::cacheMode 与 OpenGL 冲突

  • ItemCoordinateCache + QOpenGLWidget 视口 → FBO 大小变化时缓存不更新 → 残影
  • 解决:viewport 变化时手动 update() 或换 NoCache

㊵ QStyle::pixelMetric 的“DPI 系数抖动”

  • pixelMetric(PM_ButtonMargin) 在高 DPI 下返回奇数 → 你手动布局对不齐
  • 解决:用 style()->pixelMetric(..., device) 显式给 QPaintDevice

㊶ PySide 绑定层的“GIL 延迟释放”

  • 信号槽跨线程 → C++ emit → Python 回调 → PySide 持有 GIL 直到槽返回 → C++ 侧阻塞 → 帧率掉 50 %
  • 解决:槽里立刻 QTimer.singleShot(0, lambda: 实际工作) 把活扔回事件循环,立刻放 GIL

㊷ QOpenGLWidget::makeCurrent() 的“上下文漂移”

  • 多 QOpenGLWidget → 任意一个 update() → Qt 可能切上下文 → 你 glDraw 时当前 context 已变 → glGetError 随机报错
  • 解决:每次 paintGL 开头显式 makeCurrent()

㊸ QGraphicsEffect::setEnabled 的“像素对齐”

  • DropShadowEffect 开启 → Qt 自动扩大 boundingRect 2 px → 高 DPI 下出现半像素偏移 → 阴影模糊
  • 解决:关闭 setEnabled 或手动 setOffset(0, 0) + setBlurRadius(0) 再自绘

㊹ QWindow::requestUpdate() 的“vsync 漂移”

  • requestUpdate() 每帧调用 → vsync 信号抖动 → Qt 自动降帧到 30 fps
  • 解决:用 QTimer 固定 16 ms,别用 requestUpdate() 当主循环

㊺ QBackingStore::flush() 的“线程锁”

  • QOpenGLWidget + QBackingStore → flush() 持有 QMutex → paintGL 里 glFinish 等待 → 帧时间抖动
  • 解决:paintGL 里绝不手动 glFinish / glFlush,让 Qt 自己调度

㊻ PySide 信号连接字符串的“Mangling 抖动”

  • 类名里含下划线 → SIP 绑定层 mangling 规则变化 → 字符串形式 connect 找不到信号
  • 解决:只用指针形式 connect,别用字符串

㊼ QFontDatabase::addApplicationFont 的“进程级泄漏”

  • 动态加载字体 → Qt 内部 QFontDatabase 全局哈希 → 字体文件句柄永不关闭 → 进程退出才释放
  • 解决:只加载一次,程序生命周期内不复载

㊽ QClipboard::mimeData() 的“跨平台编码炸弹”

  • mimeData()->hasHtml() → Qt 自动把文本转 HTML 实体 → 中文被转  → 你切割字符串时索引错位
  • 解决:只拿 text(),别用 html() 当数据源

㊾ QStyleSheet 的“层叠缓存”

  • 同一句样式表字符串 → QStyleSheetCache 全局哈希 → 运行时改一个字母 → 整个进程所有控件重解析
  • 性能杀手:动态拼接样式表 → 用 QProxyStyle 代替

㊿ QGraphicsScene::BspTree 深度爆栈(你已见)


㊱ QPlainTextEdit::centerOnScroll 行高漂移(你已见)


㊲ QFontMetrics 水平Advance DPI 抖动(你已见)


㊳ QClipboard 跨进程死锁(你已见)


㊴ QFileDialog Win32 钩子污染(你已见)


㊵ QThread moveToThread 父对象屏障(你已见)


㊶ QObject connect 字符串 Mangling(你已见)


㊷ QGraphicsItem cacheMode OpenGL 冲突(你已见)


㊸ QGraphicsEffect 像素对齐偏移(你已见)


㊹ QWindow requestUpdate vsync 漂移(你已见)


㊺ QBackingStore flush 线程锁(你已见)


㊻ PySide 信号连接字符串 Mangling(你已见)


㊼ QFontDatabase 进程级句柄泄漏(新)


㊽ QClipboard mimeData HTML 实体炸弹(新)


㊾ QStyleSheet 层叠缓存全局重解析(新)


≥ 20 条 → 你已潜到深水区;≥ 25 条 → 可以开坑写书。