pyside有3种键盘处理方式

全局快捷键的最佳实践

  • 定义在menu上, 参见 10-08-pyside菜单

事件处理

  • 事件一旦被截断处理,后续节点就再也收不到。
    • 任意节点event处理 accept()
    • filter过滤器 return True
    • shortcut会阻断event传递
  • app.filter->shortcut->widget.filter->widget.event-> 爸爸widget.event ….直到顶层widget.event -> 如果没有accept -> scene -> item.event 是这样吗?

纯键盘


  1. 重写 keyPressEvent, 简单, 开销小, 猴子补丁, 历史最久, 首选方案
import sys
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication, QWidget

app = QApplication(sys.argv)
w = QWidget()
w.keyPressEvent = lambda e: print('key:', e.key()) if e.key() == Qt.Key_B else None
w.show()
sys.exit(app.exec())
  • 只对当前 widget 生效
  • 简单直接,但只能改“自己”

  1. 事件过滤器, 通用钩子, 跨对象, 跨事件类型(键盘+鼠标), 次选方案
import sys
from PySide6.QtCore import Qt, QEvent
from PySide6.QtWidgets import QApplication, QWidget

app = QApplication(sys.argv)
w = QWidget()

def keyFilter(obj, ev):
    if ev.type() == QEvent.KeyPress and ev.key() == Qt.Key_A:
        print('按了 A')
        return True          # 吃掉事件
    return False

w.installEventFilter(keyFilter)   # 纯函数当过滤器
w.show()
sys.exit(app.exec())
  • 能拦截任何对象、任何事件
  • 不破坏原有事件流(返回 False 就继续传)

  1. QShortcut, 全局/局部热键,无需焦点, 精确控制组合键, 全局热键方案
    ```python
    import sys
    from PySide6.QtGui import QKeySequence
    from PySide6.QtWidgets import QApplication, QWidget

app = QApplication(sys.argv)
w = QWidget()

from PySide6.QtWidgets import QShortcut
QShortcut(QKeySequence(“Ctrl+C”), w, activated=lambda: print(“Ctrl+C 触发”))
w.show()
sys.exit(app.exec())

- 只要窗口在,**不管谁有焦点**都能响应  
- 只能做“快捷键”,拿不到原始 `QKeyEvent`(不知道组合键细节)
- 就算注册在btn上, 他的触发依旧是全局的, parent只是和他的生命周期有关

```py
btn = QPushButton("Owner", main_window)
sc = QShortcut(QKeySequence("A"), btn)   # parent 是按钮
# context 默认 WindowShortcut → 整个窗口有效
sc.activated.connect(lambda: print("快捷键A"))
# 再做一个新的widget, 证明和焦点无关
btn2=QPushButton("        No Owner", main_window)

一句话对比
| 方式 | 作用域 | 能否拿原始事件 | 适合场景 |
| ———————— | ———– | ——————- | ——————— |
| 重写虚函数 keyPressEvent | 单个 widget | ✅ | 快速测试、简单 widget |
| 事件过滤器 | 任意对象 | ✅ | 全局拦截 |
| QShortcut | 整个窗口 | ❌(只有 activated) | 热键、菜单加速键 |

键鼠组合


  1. 在鼠标事件里当场查键盘修饰符(最常用)
    ```python
    from PySide6.QtCore import Qt
    from PySide6.QtWidgets import QApplication, QWidget

app = QApplication([])
w = QWidget()

w.mousePressEvent = lambda e: (
print(‘Ctrl+左键’ if e.modifiers() & Qt.ControlModifier and e.button() == Qt.LeftButton else ‘普通左键’)
)

w.show()
app.exec()

- 任何鼠标事件(`mousePress/Move/Release`)都带 `QMouseEvent.modifiers()`  
- 现场判断 Ctrl / Shift / Alt / Meta 即可,**一行代码搞定**

------------------------------------------------
2. 用事件过滤器(跨对象或需要预处理)
```python
from PySide6.QtCore import QEvent, QObject, Qt
class FilterShell(QObject):
  def eventFilter(self, obj, ev):
    if ev.type() == QEvent.MouseButtonPress and ev.modifiers() & Qt.ControlModifier:
      print('过滤器截到 Ctrl+点击')
      return False  # true = 吃掉
    return False
shell = FilterShell()
w.installEventFilter(shell)
  • 适合“父窗口统一拦截子控件”
  • 同样靠 ev.modifiers(),只是换了个拦截点

tab切换焦点 - 全局废弃此能力


# 方案一: 只要这一条就完全屏蔽了
QShortcut(QKeySequence("Tab"), tab).activated.connect(new_son) 

# 方案二: 
def tab_eater(obj, ev):
    if ev.type() == ev.KeyPress and ev.key() == Qt.Key_Tab:
        # 这里调用你的逻辑
        new_son()
        return True          # 吃掉,不再分发
    return False
app.installEventFilter(tab_eater)

总结

  • shortcut是优先级最高的, 只要有他别的都没有了, 他是一定会截断的.

  • 过滤器有下沉机制, 会从最外层一路执行到最内层, 除非return true.

  • event有冒泡机制, 除非accept —-这个我没验证, 上面2个都验证过了.