QWidget 鍵盤事件 焦點(QApplication源碼)


  • 在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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM