- 在Qt中,鍵盤事件和QWidget的focus密不可分:一般來說,一個擁有焦點(focus)的QWidget或者grabKeyboard()的QWidget才可以接受鍵盤事件。
鍵盤事件派發給誰?
如何確定誰來接收鍵盤事件,不妨看一點點QApplication的源碼:
X11下
QETWidget *keywidget=0;
bool grabbed=false;
if (event->type==XKeyPress || event->type==XKeyRelease) {
keywidget = (QETWidget*)QWidget::keyboardGrabber();
if (keywidget) {
grabbed = true;
} else if (!keywidget) {
if (d->inPopupMode()) // no focus widget, see if we have a popup
keywidget = (QETWidget*) (activePopupWidget()->focusWidget() ? activePopupWidget()->focusWidget() : activePopupWidget());
else if (QApplicationPrivate::focus_widget)
keywidget = (QETWidget*)QApplicationPrivate::focus_widget;
else if (widget)
keywidget = (QETWidget*)widget->window();
}
}
Windows下
QWidget *g = QWidget::keyboardGrabber();
if (g && qt_get_tablet_widget() && hwnd == qt_get_tablet_widget()->winId()) {
// if we get an event for the internal tablet widget,
// then don't send it to the keyboard grabber, but
// send it to the widget itself (we don't use it right
// now, just in case).
g = 0;
}
if (g)
widget = (QETWidget*)g;
else if (QApplication::activePopupWidget())
widget = (QETWidget*)QApplication::activePopupWidget()->focusWidget()
? (QETWidget*)QApplication::activePopupWidget()->focusWidget()
: (QETWidget*)QApplication::activePopupWidget();
else if (QApplication::focusWidget())
widget = (QETWidget*)QApplication::focusWidget();
else if (!widget || widget->internalWinId() == GetFocus()) // We faked the message to go to exactly that widget.
widget = (QETWidget*)widget->window();
大致順序:
- QWidget::keyboardGrabber()
- QApplication::activePopupWidget()
- QApplication::focusWidget()
- QWidget::window() [注:對於native的接收到鍵盤事件的widget,此時Qt將派發給其所屬窗口]
在QWidget間切換焦點
在Qt鍵盤事件一文中我們提到這個和focusPolicy相關。我們可以通過Tab鍵或者鼠標單擊來使得某個QWidget獲得焦點。
問題:當我們按下Tab鍵(或者上下左右箭頭鍵)時,下一個或獲取焦點的QWidget是如何被確定的?
我們重新貼出上文最后貼出過的QWidget::event()的源碼:
case QEvent::KeyPress: {
QKeyEvent *k = (QKeyEvent *)event;
bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier?
if (k->key() == Qt::Key_Backtab
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
else if (k->key() == Qt::Key_Tab)
res = focusNextPrevChild(true);
if (res)
break;
}
keyPressEvent(k);
老是覺得 QWidget::focusNextPrevChild() 這個函數有點名不符實(或者有點別扭),因為:
bool QWidget::focusNextPrevChild(bool next)
{
Q_D(QWidget);
QWidget* p = parentWidget();
bool isSubWindow = (windowType() == Qt::SubWindow);
if (!isWindow() && !isSubWindow && p)
return p->focusNextPrevChild(next);
...
}
當我們調用一個Widget該成員時,最終將遞歸調用到其所在窗口的focusNextPrevChild成員。(不過這是一個protected的虛函數,在派生類中可以覆蓋它,從而控制派生類實例中的焦點移動。)
prev/next
QWidgetPrivate內有3個成員變量:
class Q_GUI_EXPORT QWidgetPrivate : public QObjectPrivate
{
...
QWidget *focus_next;
QWidget *focus_prev;
QWidget *focus_child;
這3個變量可以分別用:
- QWidget::nextInFocusChain()
- QWidget::previousInFocusChain()
- QWidget::focusWidget() [注意區分:QApplication::focusWidget()]
進行獲取。
前兩個可以用來構成一個focus鏈表。
void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
{
Q_Q(QWidget);
focus_next = focus_prev = q;
...
void QWidgetPrivate::reparentFocusWidgets(QWidget * oldtlw)
{
...
通過QWidget::setTabOrder()可以調整Widgets在focus鏈表中的順序
Widget上放置大量按鈕怎么樣?
比如一個類似軟鍵盤的東西,在一個QWidget上面放置了大量的QPushButton。此時,除了Tab/Shift+Tab外,上下左右箭頭也都可以用來移動焦點。
這是因為:
void QAbstractButton::keyPressEvent(QKeyEvent *e)
{
bool next = true;
switch (e->key()) {
case Qt::Key_Up:
case Qt::Key_Left:
next = false;
// fall through
case Qt::Key_Right:
case Qt::Key_Down:
...
focusNextPrevChild(next);
}
focus proxy
- QWidget::setFocusProxy()
- QWidget::focusProxy()
Manual中說的比較清楚:
- If there is a focus proxy, setFocus() and hasFocus() operate on the focus proxy.
簡單列出源碼:
void QWidget::setFocus(Qt::FocusReason reason)
{
QWidget *f = this;
while (f->d_func()->extra && f->d_func()->extra->focus_proxy)
f = f->d_func()->extra->focus_proxy;
bool QWidget::hasFocus() const
{
const QWidget* w = this;
while (w->d_func()->extra && w->d_func()->extra->focus_proxy)
w = w->d_func()->extra->focus_proxy;
其他
- 對於 Qt for Embedded Linux、Symbian 和 Windows CE,還有一個
QApplication::setNavigationMode()
設置可通過方向鍵來控制焦點進行上下左右的移動。
》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》
setfocus() 是讓某個窗體獲得焦點
setfocusPolicy 是設置窗體怎么獲得焦點
he focus policy is Qt::TabFocus if the widget accepts keyboard focus by tabbing, Qt::ClickFocus if the widget accepts focus by clicking, Qt::StrongFocus if it accepts both, and Qt::NoFocus (the default) if it does not accept focus at all.
void QWidget::setFocusProxy ( QWidget * w ) [virtual] 就是把窗體獲得焦點時的處理,委托給窗體w處理
Sets this widget's focus proxy to w. If w is 0, this function resets this widget to not have any focus proxy.
Some widgets, such as QComboBox, can "have focus," but create a child widget to actually handle the focus. QComboBox, for example, creates a QLineEdit.
setFocusProxy() sets the widget which will actually get focus when "this widget" gets it. If there is a focus proxy, focusPolicy(), setFocusPolicy(), setFocus() and hasFocus() all operate on the focus proxy.
See also focusProxy().
將該widget的focus proxy設置給w。如果w為0,該函數將此widget設為沒有任何focus proxy。
有些widget,比如QComboBox,可以“擁有focus”,但是它們會創建一個子的widget來實際地處理焦點。比如QComboBox創建的叫做QLineEdit。
setFocusProxy()用來指定當該widget獲得焦點時實際上由誰來處理這個焦點。如果某個widget擁有focus proxy,focusPolicy(),setFocusPolicy(),setFocus()和hasFocus()都是對focus proxy進行操作。
http://blog.sina.com.cn/s/blog_a401a1ea0101ec9z.html
