Pyside的键盘处理
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 是这样吗?
纯键盘
- 重写
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 生效
- 简单直接,但只能改“自己”
- 事件过滤器, 通用钩子, 跨对象, 跨事件类型(键盘+鼠标), 次选方案
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 就继续传)
- 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) | 热键、菜单加速键 |
键鼠组合
- 在鼠标事件里当场查键盘修饰符(最常用)
```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个都验证过了.