简 述: 全局热键召唤的新窗口后,🖱不点击此程序的托盘图标和相关窗口(但是可以移动,开启光标跟踪),只点击键盘按键却无响应。但🖱点击过后,却可以响应⌨按键了。解决此怪异问题。

[TOC]


本文初发于 “偕臧的小站“,同步转载于此。


  💻 win10 21H2 📎 Qt 5.12.11


背景

​ 写截图时,当源码在 IDE 中,通过编译和运行后,右下加出现一个托盘图标,表示程序在运行中,此时通过快捷键 F6 可顺利召唤出截图窗口(无标题栏 + 最大化 + 置顶)。


操作如下: 此时鼠标故意不点击此截图窗口托盘图标,仅移动,可以看到实时显示其中光标的绝对坐标的变化。此时按下快捷键 Esc截图窗口 并不会消失。甚是奇怪??? 但倘若是通过右键点击托盘菜单,召唤的 截图窗口,按下 Esc 则有关闭响应。后续再快捷键召唤 截图窗口Esc 则总是正常的。


分析

​ 一开始是认为程序缺少焦点导致,尝试 【QT】新弹窗默认无焦点 中两种设置焦点方法,均无效。

索性写了一个 TestHotKey 来验证这个全局热键功能,原因为何?

托盘相关代码如下

// -------------------- tray.h --------------------
class Tray : public QObject
{
    Q_OBJECT
public:
    explicit Tray(QObject *parent = nullptr);

public slots:
    void onScreenShot();

private:
    QAction* m_screenShot;
    QAction* m_quit;
    QMenu* m_menuTary;
    QSystemTrayIcon* m_sysTary;

    QHotkey* m_hkScrnShot;  // 热键
};

// -------------------- tray.cpp --------------------

Tray::Tray(QObject *parent)
    : QObject(parent)
    , m_screenShot(nullptr)
    , m_quit(nullptr)
    , m_menuTary(nullptr)
    , m_sysTary(nullptr)
    , m_hkScrnShot(new QHotkey(QKeySequence("f2"), true, qApp))
{
    m_screenShot = new QAction(tr("ScreenShot"), this);
    m_quit = new QAction(tr("Quit"), this);

    m_menuTary = new QMenu();
    m_menuTary->addAction(m_screenShot);
    m_menuTary->addSeparator();
    m_menuTary->addAction(m_quit);

    m_sysTary = new QSystemTrayIcon(this);
    m_sysTary->setIcon(QIcon(":/resources/PicShot_32.svg"));
    m_sysTary->setToolTip(tr("PicShot Test"));
    m_sysTary->setContextMenu(m_menuTary);
    m_sysTary->setVisible(true);

    // 快捷键响应
    connect(m_hkScrnShot, &QHotkey::activated, this, &Tray::onScreenShot);
    // 右键菜单触发
    connect(m_screenShot, &QAction::triggered, this, &Tray::onScreenShot);
        
    connect(m_quit, &QAction::triggered, []() {qApp->quit();});
}

void Tray::onScreenShot()
{
    auto& ins = Widget::instance();
    ins.show();
}

截图窗口代码如下:

// -------------------- tray.cpp --------------------
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    setFocusPolicy(Qt::StrongFocus);

    QDesktopWidget *desktop = QApplication::desktop();  // 获取桌面的窗体对象
    const QRect geom = desktop->geometry();             // 多屏的矩形取并集
    setWindowFlags(Qt::FramelessWindowHint /*| Qt::WindowStaysOnTopHint */| windowFlags()); // 去掉标题栏 + 置顶

//    setAttribute(Qt::WA_ShowWithoutActivating,true);
//    setFocusPolicy(Qt::StrongFocus);
//    setFixedSize(QSize(geom.size().width() / 4, geom.size().height()));
    setFixedSize(QSize(512, geom.size().height()));
    setMouseTracking(true);
}

void Widget::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Escape) {
        qDebug() << "Key_Escape";
        hide();
    } else if (event->key() == Qt::Key_A) {
        qDebug() << "Key_A";
    }
}

void Widget::paintEvent(QPaintEvent *event)
{
    QPainter pa(this);
    pa.drawText(100, 200, QString("m_pos(%1, %2)").arg(m_pos.x()).arg(m_pos.y()));
}

突破点在 Widget::keyPressEvent(),想知道到底对应状态是否会响应键盘按键,确认此便可以找到原因。


根因解决

​ 此次思路感觉有点欧亨利式:

知乎此篇 —> tab 切换窗口 —> 联想到 激活窗口 —> google key: focus 变成 窗口激活, —> 去掉置顶后尝试(去掉干扰因素) —> ok


使用全局热键召唤出来的截图窗口,此时不属于激活窗口。 解决方法,再 show() 后,将此窗口设置为激活窗口即可。

void Tray::onScreenShot() {
    auto& ins = Widget::instance();
    ins.show();

    // 解决方案: show() 之后,设置为激活窗口即可
    if(!ins.isActiveWindow())
        ins.activateWindow();
}

系列地址

QtExamples 『TestHotKey』

欢迎 star ⭐ 和 fork 🍴 这个系列的 C++ / QT / DTK 学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”


参考

  1. Qt绘图:Keyboard Focus in Widgets 给予启发,灵光乍现
  2. Qt界面focus焦点设置的一些体会 虽然不是原因不同,其中参考文章却有关于焦点理解的价值
  3. QT 激活窗口 未尝试,若是本文未解决,可以试试这种方法
  4. 附: 考虑跨平台或需参考 Qt激活窗口