简体   繁体   中英

PyQt5 Radio Button Tab Stop Dysfunction

I have some programs that use dialog boxes with groups of radio buttons. The expected behavior when initially designating a button with setChecked(True) is that it will be assigned the tab stop for its group. That's how it works in the Windows API. But it appears that the tab stop always goes to the first button in the group (even though it's not checked) until you manually re-select the intended button with a mouse click or arrow key. You can use setFocus(True) on a designated button, but this only can be used for one group of buttons. This happens with Windows 10 and Ubuntu, using PyQt5 versions 5.12 and 5.15. I've tried the various radio button focus-related functions listed in the Qt documentation without success.

This question was asked 2 years ago (53645767/radio-button-tab-sequencing), without an answer that explains definitively either how to set the tab stops, or that it's not an option.

I adapted this script from a Web tutorial. For both groups, I initialize the second button in the group with setChecked(True), and use setFocus(True) on the button in the first group. Cycling with "Tab" shows the tab stop at the second button of the first group as intended, but it stays with the first (unchecked) button of the second group until you click the second button or use the arrow key to re-select it. Am I missing something here or it this an intentional "feature"?

def init_ui(self):
    self.label = QLabel('What is your favorite color?')
    self.rbtn1 = QRadioButton('blue')
    self.rbtn2 = QRadioButton('red')
    self.label2 = QLabel("")
    self.label3 = QLabel('What is your favorite element?')
    self.rbtn3 = QRadioButton('bolognium')
    self.rbtn4 = QRadioButton('unobtainium')
    self.label4 = QLabel("")
    
    self.btngroup1 = QButtonGroup()
    self.btngroup2 = QButtonGroup()
    self.btngroup1.addButton(self.rbtn1)
    self.btngroup1.addButton(self.rbtn2)
    self.btngroup2.addButton(self.rbtn3)
    self.btngroup2.addButton(self.rbtn4)
    
    self.rbtn1.toggled.connect(self.onClickedColor)
    self.rbtn2.toggled.connect(self.onClickedColor)
    self.rbtn3.toggled.connect(self.onClickedElement)
    self.rbtn4.toggled.connect(self.onClickedElement)        
    
    layout = QVBoxLayout()
    layout.addWidget(self.label)
    layout.addWidget(self.rbtn1)
    layout.addWidget(self.rbtn2)
    layout.addWidget(self.label2)
    layout.addWidget(self.label3)
    layout.addWidget(self.rbtn3)
    layout.addWidget(self.rbtn4)
    layout.addWidget(self.label4)
    
    self.setGeometry(200, 200, 300, 300)
    self.setLayout(layout)

    self.rbtn2.setChecked(True)
    self.rbtn2.setFocus(True)
    self.rbtn4.setChecked(True)
    
    self.show()

def onClickedColor(self):
    radioBtn = self.sender()
    if radioBtn.isChecked():
        self.label2.setText("Your favorite color is " + radioBtn.text())
        
def onClickedElement(self):
    radioBtn = self.sender()
    if radioBtn.isChecked():
        self.label4.setText("Your favorite element is " + radioBtn.text())

That seems like a bug (btw, the question you linked only shows a similar behavior, but considering it's about HTML I believe it's unrelated).

Unfortunately, "auto-focus" management is pretty complex and is based on various aspects: order of creation, parenthood, focus policies, position of widgets and system behavior.

A possible (and "hacky") solution could be to override focusNextPrevChild() .

The trick is to get the current focused widget and cycle through its own nextFocusInChain and previousInFocusChain (by checking its focus policy and recursively until a valid policy is found), and then ensure that the widget is contained in the button group.

Note that the following is a very raw implementation, and you'll probably need further tests in order to get it correctly working and ensure that no bug/recursion issue happens.

    def focusNextPrevChild(self, isNext):
        if isNext:
            func = 'nextInFocusChain'
            reason = Qt.TabFocusReason
        else:
            func = 'previousInFocusChain'
            reason = Qt.BacktabFocusReason
        current = self.focusWidget()
        other = getattr(current, func)()
        while True:
            while not other.focusPolicy():
                other = getattr(other, func)()
            if isinstance(other, QRadioButton):
                for group in self.btngroup1, self.btngroup2:
                    if other in group.buttons():
                        checked = group.checkedButton()
                        if checked == current and not isNext:
                            continue
                        if checked:
                            checked.setFocus(reason)
                        else:
                            other.setFocus(reason)
                        return True
            break
        return super().focusNextPrevChild(isNext)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM