Fix tab order after inserting widgets in a layout in QT
Asked Answered
C

1

8

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
Centro answered 17/11, 2015 at 15:38 Comment(6)
You could try setting the focus proxy on the proxy widgets that are actually the parents of the widgets you're adding, then use setTabOrder.Betroth
I already tried that, not success eitherCentro
Adding an mcve to the question would likely improve your it and probably better help find a solution to your problem.Betroth
Thanks, I added one.Centro
Have you tried calling setTabOrder for all the buttons, after adding the new one, as in the Qt documentation?Betroth
Yes, that doesn't work either. But just the two setTabOrder()s are sufficient if called with the QPushButtons directly (when the buttons are not inside a proxy widget). Also, that might be feasable for such a simple example, in a real-world program, this little list implementation will NEVER have access to all focusable objects outside of its scope, making this impossible.Centro
T
3

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.

Tindall answered 2/12, 2015 at 19:44 Comment(5)
This is possible for this simple example, but imagine a more complex object tree - you have to find ALL focusable widgets and link them in the correct order. I already implemented methods for this, but still failed.Centro
And what if I want to insert an element at the beginning of the list? How to find the appropriate previous focusable widget outside of the list's scope? I experimented with QWidget::previousInFocusChain() to no avail.Centro
Ok, but i think the docs don't promise any more than what my code above does. Let my think about the object tree ...Tindall
Yes, you're right about that. Ideally, there would be one simple method to rebuild the whole tab order (including all children) of a widget. Well, one can dream ;)Centro
Had a look at the designer .. you can assign the tab order as you like across nested QGroupBoxes and everything. Even jumping between different items from different group boxes. In the resulting ui_xyz.h is a block of QWidget::setTabOrder('from', 'to'); calls. But pointers of every individual widget are at hand .. than it's easy. I tried it with nested classes (setting order of button relative to widgets containing other buttons). Was not possible. So it seems you are right. It's something to dream of ; ).Tindall

© 2022 - 2024 — McMap. All rights reserved.