案例:全局快捷键处理
一、 前言
在一个复杂的大型QT项目中,会存在很多QAction,并为之设置快捷键,若要自定义和QAction相同的快捷键、或者全局的快捷键(类似于Ctrl+A、Ctrl+S之类),就可能被冲突掉
二、快捷键响应过程
1)、去官网下载相应版本的PDB文件,链接https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/, 找到相应的版本号qt5_597/ (代表Qt 5.9.7)
2、导入PDB后,随便在界面上按一个键,在QApplication中会看到如下堆栈消息
(1)、在QT开启了事件循环(QEventLoop),当我们按键就会发送sendPostedEvents (注意此函数也可以手动调用)
(2)、QT会把所有的事件解析一次,解析成相应的类型,不同的消息,调用不同的process函数
(3)、注意按键事件,会优先处理一次handleShortcutEvent
(4)、在handleShortcutEvent中,先传递ShortcutOverride事件(这个事件会比Shortcut事件优先相应)
三、实现
1、重新一个Application :public QApplication, 处理QEvent::ShortcutOverride
bool Application::notify(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::ShortcutOverride)
{
QKeyEvent *keyEvent = (QKeyEvent *)event;
if (qApp->modalWindow() == nullptr)
{
QObject *receiver = QWidget::keyboardGrabber();
if (!receiver)
{
receiver = qApp->focusObject();
}
// 注:编辑框按键是不需要响应快捷键的
QLineEdit *lineEdit = qobject_cast<QLineEdit *>(receiver);
if (lineEdit == nullptr)
{
// 处理快捷键事件
if (event->isAccepted())
{
return true;
}
}
}
}
return Application::notify(obj, event);
}
2·、为什么不在QMainWindow里面重载event(QEvent *event) 再截取QEvent::ShortcutOverride事件
Qt信号机制,在Notify中分发,然后在QObject处理,然后一层一层的向上分发
1)、无法避免在分发过程中被截断 (一般不会)
2)、部分窗口不存在parent的话,传递不到QMainWindow(例如:悬浮状态的QDockwidget)
四、优化
1、在使用过程中,非模态弹窗、QWindow也响应快捷键(其实不应该响应)
2、在notify中打断点,对于不同的对象,观察obj会出现4种情况
A:QObject:是一个QWidgetWindow, m_wiget是QMainWindw (在QMainWindow按键)
B:QObject:是一个QWidgetWindow, m_wiget是QDialog(弹框)
C: QObject: 是一个QWidgetWindow, m_wiget是QDockWidget(悬浮状态的QDock,停靠状态响应A)
D: QObject: 是一个QWindow, 没有m_wiget (在QWindow中按键)
3、如果能吧QObject转化成QWidgetWindow,并判断获取到m_widget就能解决,我们自定义面板是否能响应快捷键了
4、qwidgetwindow_p.h 是一个受保护的类,但是QT还是支持引用的,博主的做法是,直接拷贝过来引用(QT dll里面支持,只要不修改源码,就不影响使用)
注:这边吧响应的头文件引用方法修改了
4、只处理QDockWidget、和QMainWindow的按键事件--可以根据自己自定义修改
bool Application::notify(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::ShortcutOverride)
{
// 只处理Dock, MainWindow
QWidgetWindow *window = dynamic_cast<QWidgetWindow *>(obj);
if (!window)
{
// 这里再次进入消息循环,不能return
return QApplication::notify(obj, event);
}
QDockWidget *dockWidget = dynamic_cast<QDockWidget*>(window->widget());
if (!dockWidget)
{
QMainWindow *pWindow = dynamic_cast<QMainWindow*>(window->widget());
if (!pWindow)
{
// 这里再次进入消息循环,不能return
return QApplication::notify(obj, event);
}
}
QKeyEvent *keyEvent = (QKeyEvent *)event;
if (qApp->modalWindow() == nullptr)
{
QObject *receiver = QWidget::keyboardGrabber();
if (!receiver)
{
receiver = qApp->focusObject();
}
// 注:编辑框按键是不需要响应快捷键的
QLineEdit *lineEdit = qobject_cast<QLineEdit *>(receiver);
if (lineEdit == nullptr)
{
// 处理快捷键事件
if (event->isAccepted())
{
return true;
}
}
}
}
return Application::notify(obj, event);
}