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();
}
参考

6069

被折叠的 条评论
为什么被折叠?



