qt5focuspyside2qlabel

Why doesn't QLabel.setFocus() pass focus to the next widget?


When I TAB around a form with labels and line edits I don't see QLabels getting any focus; the focus rightly passes to the line edit next to it i.e. to the QWidget that is label.nextInFocusChain().

What I originally want: in a QLineEdit.editingFinished's slot I want to set focus to the next (in TAB order) line edit. I do self.sender().nextInFocusChain().setFocus(); instead of a line edit, its label, the one next in focus chain, gets focus.

When QLabel.setFocus() is called, focus is on the label itself; doing another TAB sets the focus to the expected line edit after it. Why is this the case? Why isn't the focus not set to the widget after it? Why is the focus not set like the TAB key does?

I checked the focus policy of the labels; they Qt.NoFocus (default). I also set the textInteractionFlags of the label to Qt.NoTextInteraction in vain.

Setting the line edit as the label's focus-proxy does what I want, but I'm not convinced this is the correct solution; or there's a gap in my understanding.

Minimal Example

#!/usr/bin/env python3

import sys
from PySide2.QtWidgets import QApplication, QLineEdit, QWidget, \
    QFormLayout, QPushButton, QMainWindow, QLabel
from PySide2.QtCore import Qt


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        txtName = QLineEdit('')
        txtAge = QLineEdit('')
        self.lblAge = QLabel('&Age')
        self.lblAge.setBuddy(txtAge)
        # This or (default) Qt.LinksAccessibleByMouse doesn't help.
        # self.lblAge.setTextInteractionFlags(Qt.NoTextInteraction)
        btnNext = QPushButton('N&ext')
        btnNext.clicked.connect(self.moveFocus)
        formLayout = QFormLayout()
        formLayout.addRow('&Name', txtName)
        formLayout.addRow(self.lblAge, txtAge)
        formLayout.addRow(btnNext)
        window = QWidget()
        window.setLayout(formLayout)
        self.setCentralWidget(window)

    def moveFocus(self):
        self.lblAge.setFocus()


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

I'm on ArchLinux with Xfce4 desktop environment using Qt5's official Python bindings i.e. PySide2.


Solution

  • The focus policy of widgets only tells whether the widget will accept focus after user interaction; in fact, the documentation says, for example, that "the widget accepts keyboard focus by tabbing" or "by clicking".

    When a label is set for a buddy it doesn't mean that it can get focus, nor that it's part of the focus chain, and neither it will set the focus on that buddy when it actually receives it (programmatically or not): the only purpose of the buddy feature is to set the focus on the buddy when its shortcut is triggered.

    Calling setFocus() explicitly, though, will always make the widget get focus, even if its policy is NoFocus, and that's because the call has been done programmatically, and it didn't get the focus from user interaction. Plus, the widget must be able to receive a focus in event, even if it ignores it, and that's exactly how focus proxies work.

    In fact, when focus is changed by user interaction (including window activation), Qt does not call setFocus() at all; instead, it sends a QFocusEvent to the target widget, which would eventually be propagated to the parent(s) in the object tree.

    It's unclear why you're trying to set the focus based on the label, but if you really need to do that, then you have the following options:

    With the subclass approach you can even override the mousePressEvent handler so that when the user clicks the label, it will automatically set the focus on the buddy.

    The same can be done by setting the widget as focus proxy of the label and then setting the focus policy of the label to ClickFocus.