QMenuBar焦点问题

QTBUG18896问题

Sometimes it occurs that keypresses act like the Alt-key has been pressed also, which means that the key event is only sent to the menubar and not to the application. I encountered this in our own application, but I could reproduce this with Qt Designer. Please follow the next recipe exactly and come back to me if you can't reproduce.

简单地说:菜单栏本来需要ALT+'M'(或其他字符)来激活并弹出某个菜单,现在直接按'M'就可以激活了。

初次看到感觉很有意思,后来发现问题还算简单。我们可以用下面的程序来重现这个问题:

#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>

class MainWindow : public QMainWindow
{
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow(){}
};

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    QMenu * menu = menuBar()->addMenu("&Menu");
    menu->addAction("&Item");
    menu->addAction("I&tem");
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}
  • 一旦菜单栏获得焦点,我们就可以通过"m"键来弹出菜单(而不需要"Alt+M")。这是正常行为
  • 在这个例子中,一旦工具栏获得焦点,它不会自动失去焦点(没有其他控件接受焦点)。这是原因

根源

  • 工具栏开始工作时,
    • setKeyboardMode(true)
    • 会先设置自己获得焦点(如果此次其他widget拥有焦点,则先保存下来该指针(使用QPointer,作用你应该懂的))。
  • 工作完毕
    • setKeyboardMode(false)
    • 尝试恢复原来的焦点
    • 如果一开始没有其他widget拥有焦点,或者拥有焦点的widget在这期间被销毁了怎么办??
      • Qt 目前的做法是:让工具栏继续持有焦点
      • 我们期待的应:让工具栏失去焦点

源码

  • QToolBar 初始化会给它的parent和它所在的顶级窗口安装事件过滤器:
void QMenuBarPrivate::handleReparent()
{
    Q_Q(QMenuBar);
    QWidget *newParent = q->parentWidget();
    //Note: if parent is reparented, then window may change even if parent doesn't

    // we need to install an event filter on parent, and remove the old one

    if (oldParent != newParent) {
        if (oldParent)
            oldParent->removeEventFilter(q);
        if (newParent)
            newParent->installEventFilter(q);
    }

    //we also need event filter on top-level (for shortcuts)
    QWidget *newWindow = newParent ? newParent->window() : 0;

    if (oldWindow != newWindow) {
        if (oldParent && oldParent != oldWindow)
            oldWindow->removeEventFilter(q);

        if (newParent && newParent != newWindow)
            newWindow->installEventFilter(q);
    }

    oldParent = newParent;
    oldWindow = newWindow;
  • 在显示之前,它还会将菜单栏各菜单的加速键注册成快捷键:
    • 通过QKeySequence的mnemonic()成员
void QMenuBarPrivate::updateGeometries()
{
...
        for(int i = 0; i < actions.count(); i++)
            shortcutIndexMap.append(q->grabShortcut(QKeySequence::mnemonic(actions.at(i)->text())));
  • 事件过滤器所做工作:
    • 主要为了处理Alt键?(菜单导航)
    • 注意:一旦遇到包含Alt键值的QEvent::ShortcutOverride事件,它会将自己安装成QApplication的事件过滤器

bool QMenuBar::eventFilter(QObject *object, QEvent *event)
{
    if (style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, this)) {
        if (d->altPressed) {
            switch (event->type()) {
            case QEvent::KeyPress:
            case QEvent::KeyRelease:
            {
                QKeyEvent *kev = static_cast<QKeyEvent*>(event);
                if (kev->key() == Qt::Key_Alt || kev->key() == Qt::Key_Meta) {
                    if (event->type() == QEvent::KeyPress) // Alt-press does not interest us, we have the shortcut-override event
                        break;
                    d->setKeyboardMode(!d->keyboardState);
                }
            }
            // fall through
            case QEvent::MouseButtonPress:
            case QEvent::MouseButtonRelease:
            case QEvent::MouseMove:
            case QEvent::FocusIn:
            case QEvent::FocusOut:
            case QEvent::ActivationChange:
                d->altPressed = false;
                qApp->removeEventFilter(this);
                break;
            default:
                break;
            }
        } else if (isVisible()) {
            if (event->type() == QEvent::ShortcutOverride) {
                QKeyEvent *kev = static_cast<QKeyEvent*>(event);
                if ((kev->key() == Qt::Key_Alt || kev->key() == Qt::Key_Meta)
                    && kev->modifiers() == Qt::AltModifier) {
                    d->altPressed = true;
                    qApp->installEventFilter(this);
                }
            }
        }
    }

    return false;
  • QShortcutEvent 事件的接收
    • 其中 _q_internalShortcutActivated 用来激活(弹出)相应的菜单。
bool QMenuBar::event(QEvent *e)
{
    Q_D(QMenuBar);
    switch (e->type()) {
    case QEvent::Shortcut: {
        QShortcutEvent *se = static_cast<QShortcutEvent *>(e);
        int shortcutId = se->shortcutId();
        for(int j = 0; j < d->shortcutIndexMap.size(); ++j) {
            if (shortcutId == d->shortcutIndexMap.value(j))
                d->_q_internalShortcutActivated(j);
        }
    } break;
  • keypress的处理
    • 菜单的导航控制
    • 菜单的弹出控制
    • 对加速键字符的处理
void QMenuBar::keyPressEvent(QKeyEvent *e)
{
    Q_D(QMenuBar);
    d->updateGeometries();
    int key = e->key();
    if(isRightToLeft()) {  // in reverse mode open/close key for submenues are reversed
        if(key == Qt::Key_Left)
            key = Qt::Key_Right;
        else if(key == Qt::Key_Right)
            key = Qt::Key_Left;
    }
    if(key == Qt::Key_Tab) //means right
        key = Qt::Key_Right;
    else if(key == Qt::Key_Backtab) //means left
        key = Qt::Key_Left;

    bool key_consumed = false;
    switch(key) {
    case Qt::Key_Up:
    case Qt::Key_Down:
    case Qt::Key_Enter:
    case Qt::Key_Space:
    case Qt::Key_Return: {
        if(!style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, this) || !d->currentAction)
           break;
        if(d->currentAction->menu()) {
            d->popupAction(d->currentAction, true);
        } else if(key == Qt::Key_Enter || key == Qt::Key_Return || key == Qt::Key_Space) {
            d->activateAction(d->currentAction, QAction::Trigger);
            d->setCurrentAction(d->currentAction, false);
            d->setKeyboardMode(false);
        }
        key_consumed = true;
        break; }

    case Qt::Key_Right:
    case Qt::Key_Left: {
        if(d->currentAction) {
            int index = d->actions.indexOf(d->currentAction);
            if (QAction *nextAction = d->getNextAction(index, key == Qt::Key_Left ? -1 : +1)) {
                d->setCurrentAction(nextAction, d->popupState, true);
                key_consumed = true;
            }
        }
        break; }

    case Qt::Key_Escape:
        d->setCurrentAction(0);
        d->setKeyboardMode(false);
        key_consumed = true;
        break;

    default:
        key_consumed = false;
    }

    if(!key_consumed &&
       (!e->modifiers() ||
        (e->modifiers()&(Qt::MetaModifier|Qt::AltModifier))) && e->text().length()==1 && !d->popupState) {
        int clashCount = 0;
        QAction *first = 0, *currentSelected = 0, *firstAfterCurrent = 0;
        {
            QChar c = e->text()[0].toUpper();
            for(int i = 0; i < d->actions.size(); ++i) {
                if (d->actionRects.at(i).isNull())
                    continue;
                QAction *act = d->actions.at(i);
                QString s = act->text();
                if(!s.isEmpty()) {
                    int ampersand = s.indexOf(QLatin1Char('&'));
                    if(ampersand >= 0) {
                        if(s[ampersand+1].toUpper() == c) {
                            clashCount++;
                            if(!first)
                                first = act;
                            if(act == d->currentAction)
                                currentSelected = act;
                            else if (!firstAfterCurrent && currentSelected)
                                firstAfterCurrent = act;
                        }
                    }
                }
            }
        }
        QAction *next_action = 0;
        if(clashCount >= 1) {
            if(clashCount == 1 || !d->currentAction || (currentSelected && !firstAfterCurrent))
                next_action = first;
            else
                next_action = firstAfterCurrent;
        }
        if(next_action) {
            key_consumed = true;
            d->setCurrentAction(next_action, true, true);
        }
    }
    if(key_consumed)
        e->accept();
    else
        e->ignore();
}
  • 这儿还有一个setKeyboardMode,做什么工作呢?
    • 控制焦点转移的
      • true:设置自己为焦点控件,将先保存原来拥有焦点的控件
      • false:恢复原来拥有焦点的控件
void QMenuBarPrivate::setKeyboardMode(bool b)
{
    Q_Q(QMenuBar);
    if (b && !q->style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, q)) {
        setCurrentAction(0);
        return;
    }
    keyboardState = b;
    if(b) {
        QWidget *fw = QApplication::focusWidget();
        if (fw != q)
            keyboardFocusWidget = fw;
        focusFirstAction();
        q->setFocus(Qt::MenuBarFocusReason);
    } else {
        if(!popupState)
            setCurrentAction(0);
        if(keyboardFocusWidget) {
            if (QApplication::focusWidget() == q)
                keyboardFocusWidget->setFocus(Qt::MenuBarFocusReason);
            keyboardFocusWidget = 0;
        }
    }
    q->update();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值