-->

Fix tab order after inserting widgets in a layout

2019-02-23 09:00发布

问题:

I have a custom list implementation (a subclass of QWidget) in QT 5.5. The elements of the list are organized using a QVBoxLayout. At runtime, elements (which are also QWidgets) can be dynamically added to and removed from the list at any position in the layout. This is working fine, except for one detail: the tab order of inserted focusable elements is wrong. The last element inserted will always be the last in the tab order, even if inserted in between two other elements.

How can I fix the tab order to represent the layout order? I already tried iterating over the list elements and using setTabOrder() on each adjacent pair, without success.

A few more details on the implementation:

  • Widgets are not added directly to the list. Each time a widget should be added, a proxy widget is created and added instead, the 'real' widget will be reparented to the proxy (The proxy is doing some graphic stuff).
  • QVBoxLayout::insertWidget() is used to insert proxy widgets, followed by a call to QWidget::show()
  • when removing elements, the element will be hidden, removed from the proxy, the proxy is removed from the list layout and deallocated
  • focusable widgets can be anywhere in the object tree of elements which are added to the list, they are not necessarily the elements themselves

Update: Added a MCVE!

The following minified example demonstrates the problem. For completeness, I also included the headers, main function, and .pro file. You can safely skip those files if you don't want to reproduce the issue, TabOrderTestWindow.cpp is the important one.

TabOrderTestWindow.cpp:

#include "TabOrderTestWindow.h"

#include <QVBoxLayout>
#include <QPushButton>

// create a button inside a proxy widget
QWidget* createButtonProxy(const QString& caption, QWidget* parent) {
    QWidget* proxy = new QWidget(parent);
    QPushButton* button = new QPushButton(caption, proxy);
    proxy->setFocusProxy(button);
    return proxy;
}

TabOrderTestWindow::TabOrderTestWindow()
    : QWidget()
{
    setMinimumHeight(200);
    setMinimumWidth(350);

    QVBoxLayout* layout = new QVBoxLayout(this);

    // create and add 3 buttons in order
    QWidget* button1 = createButtonProxy("button 1", this);
    QWidget* button2 = createButtonProxy("button 2", this);
    QWidget* button3 = createButtonProxy("button 3", this);
    layout->addWidget(button1);
    layout->addWidget(button2);
    layout->addWidget(button3);

    // now insert a fourth button in between the others - incorrect tab order!
    QWidget* buttonInbetween = createButtonProxy("button in between", this);
    layout->insertWidget(1, buttonInbetween);

    // attempt to correct tab order - not working, even with focus proxy set...
    setTabOrder(button1, buttonInbetween);
    setTabOrder(buttonInbetween, button2);
}

TabOrderTestWindow.h:

#ifndef TABORDERTESTWINDOW_H
#define TABORDERTESTWINDOW_H

#include <QMainWindow>

class TabOrderTestWindow : public QWidget
{
    Q_OBJECT

public:
    TabOrderTestWindow();
};

#endif // TABORDERTESTWINDOW_H

main.cpp:

#include "TabOrderTestWindow.h"
#include <QApplication>

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

    return a.exec();
}

TabOrderTest.pro:

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = TabOrderTest
TEMPLATE = app


SOURCES += main.cpp\
        TabOrderTestWindow.cpp

HEADERS  += TabOrderTestWindow.h

回答1:

Here really seems to be a bug as the Doc state that focus proxies would be cared of.

But we can care of them ourself by using:

setTabOrder(button1->focusProxy(), buttonInbetween->focusProxy());
setTabOrder(buttonInbetween->focusProxy(), button2->focusProxy());

So it seems you need to do what Qt should have done for you.