2

I'm using some QRadioButtons inside a QButtonGroup in pyQt5. I would like the user to be able to select one of the exclusive options or none, so if he accidentally clicks a radio button, he should be able to click it again to uncheck it.

My current approach is to connect the clicked method to a custom function that checks the button status, but I couldn't figure how to do it in a simple way, without using shady click counters.

from PyQt5.QtWidgets import QApplication, QWidget, QRadioButton, QHBoxLayout, QButtonGroup
import sys


class MainWindow(QWidget):

    def __init__(self):

        super().__init__()

        # Radio buttons
        self.group = QButtonGroup()

        self.b1 = QRadioButton()
        self.group.addButton(self.b1)
        self.b1.clicked.connect(lambda: self.radioButtonClicked())

        self.b2 = QRadioButton()
        self.group.addButton(self.b2)
        self.b2.clicked.connect(lambda: self.radioButtonClicked())

        # Layout
        self.layout = QHBoxLayout()
        self.layout.addWidget(self.b1)
        self.layout.addWidget(self.b2)
        self.setLayout(self.layout)


    def radioButtonClicked(self):
        if self.sender().isChecked():
            self.sender().setAutoExclusive(False)
            self.sender().setChecked(False) # This is not working, as it fires on the first click
            self.sender().setAutoExclusive(True)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()
dvilela
  • 1,200
  • 12
  • 29
  • 1
    Sorry, why aren't you using QCheckBox? – S. Nick Jun 15 '20 at 20:18
  • 1
    @S.Nick The user should be able to select only one option (or none). I don't know if this can be achieved with QCheckBox (I guess so), but even if it's the case, I think users tend to associate check-exclusivity with round radio buttons better than squared check boxes. It's only an opinion, of course, and I'm open to discussion. – dvilela Jun 15 '20 at 20:31

3 Answers3

3

Finally, I've come with a solution in two steps: first, make the button group not exclusive, so buttons can be unchecked when clicked again. And second, when a radio is selected, uncheck every other button.

from PyQt5.QtWidgets import (QApplication, QWidget, QRadioButton,QHBoxLayout, QButtonGroup)
import sys

class MainWindow(QWidget):

    def __init__(self):

        super().__init__()

        # Radio buttons
        self.group = QButtonGroup()
        self.group.setExclusive(False)  # Radio buttons are not exclusive
        self.group.buttonClicked.connect(self.check_buttons)

        self.b1 = QRadioButton()
        self.group.addButton(self.b1)

        self.b2 = QRadioButton()
        self.group.addButton(self.b2)

        # Layout
        self.layout = QHBoxLayout()
        self.layout.addWidget(self.b1)
        self.layout.addWidget(self.b2)
        self.setLayout(self.layout)


    def check_buttons(self, radioButton):
        # Uncheck every other button in this group
        for button in self.group.buttons():
            if button is not radioButton:
                button.setChecked(False)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()
dvilela
  • 1,200
  • 12
  • 29
  • I think there's a slightly better approach. you can also pass the group in `check_buttons(self, radioButton, radioGroup)` so that this function becomes reusable for the upcoming QButtonGroup, by entirely not depend on any class variable – greendino Oct 28 '20 at 14:22
2

Try it:

from PyQt5.QtWidgets import QApplication, QWidget, QRadioButton, QHBoxLayout, QButtonGroup
import sys


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

        self._dictRB = {                                                   # +++                                         
            'rb1': False,
            'rb2': False,
            'rb3': False,
        }


        # Radio buttons
        self.group = QButtonGroup()

        self.b1 = QRadioButton('rb1')                                       # + 'rb1'  
        self.group.addButton(self.b1)
#        self.b1.clicked.connect(lambda: self.radioButtonClicked())

        self.b2 = QRadioButton('rb2')                                       # + 'rb2'  
        self.group.addButton(self.b2)
#        self.b2.clicked.connect(lambda: self.radioButtonClicked())

        self.b3 = QRadioButton('rb3')                                       # +++
        self.group.addButton(self.b3)

        # Layout
        self.layout = QHBoxLayout()
        self.layout.addWidget(self.b1)
        self.layout.addWidget(self.b2)
        self.setLayout(self.layout)

        self.group.buttonClicked.connect(self.check_button)                 # +++

    def check_button(self, radioButton):                                    # +++
        if self._dictRB[radioButton.text()]:
            self._dictRB[radioButton.text()] = False
            self._dictRB['rb3'] = True
            self.b3.setChecked(True)           
        else:
            for b in self._dictRB:
                self._dictRB[b] = False
            self._dictRB[radioButton.text()] = True
        print("clickеd button -> `{} - {}`".format(radioButton.text(), radioButton.isChecked()))        

    '''
    def radioButtonClicked(self):
        if self.sender().isChecked():
            self.sender().setAutoExclusive(False)
            self.sender().setChecked(False)         # This is not working, as it fires on the first click
            self.sender().setAutoExclusive(True)
    '''


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()

enter image description here

S. Nick
  • 12,879
  • 8
  • 25
  • 33
  • Sorry, I think we may live in different time zones and yesterday I had to go to sleep. I was just trying your code, but despite it works, there is a problem with it: I cannot use text in the radiobuttons, and I will have lots of radios, so keeping that dict seems a little bit too much to me. But thanks for your solution anyway. But I've found another solution that I just posted. – dvilela Jun 16 '20 at 17:59
-2

def delectRadioButtons(self):

    if self.radioButton_female.isCheckable() or self.radioButton_male.isCheckable():
        self.radioButton_male.setChecked(True)
        self.radioButton_female.setChecked(True)
        self.radioButton_male.setCheckable(False)
        self.radioButton_female.setCheckable(False)



    self.radioButton_female.setCheckable(True)
    self.radioButton_male.setCheckable(True)