QMenuBar焦点问题

来源:互联网 发布:java中的random类 编辑:程序博客网 时间:2024/05/05 10:57

QTBUG18896问题

  • http://bugreports.qt.nokia.com/browse/QTBUG-18896

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();}
原创粉丝点击