Change checkbox state to not checked when other checkbox is checked pyqt
Asked Answered
C

3

13

I'm using Qt Designer and pyqt code to write an app.

I have a very simple question: in my UI I have 2 checkboxes and what I need is to set the first checkbox as always unchecked when the second checkbox is unchecked.

In other words the first checkbox can be checked only when the second checkbox is checked, but the second checkbox can be checked also if the first one is not checked.

In Qt Designer I have not find an easy way to do that with the Signals/Slots function.

I had a look in the Qt API and I tried to write some code:

class CreateRIVLayerDialog(QDialog, FORM_CLASS):
    def __init__(self, iface)

    # some more code here...

    if self.addCheckDB.isChecked():
        self.addCheck.setChecked(False)

but without results.

Does anybody have some hints?

Thanks

Clausewitz answered 29/3, 2016 at 10:1 Comment(0)
P
15

The simplest way is with two signal connections, one of which can be done in Qt Designer.

Your design requires that "the first checkbox can be checked only when the second checkbox is checked". This is equivalent to saying the first checkbox should be disabled when the second checkbox is unchecked. Disabling the checkbox makes it clear to the user that the option is unavailable.

So in Qt Designer, you should connect the toggled signal of checkbox2 to the setEnabled slot of checkbox1. (It may also be necessary to set the initial enabled state of checkbox1 in Qt Designer as well).

Then, in your code, you should make a second connection, like this:

    self.checkbox2.toggled.connect(
        lambda checked: not checked and self.checkbox1.setChecked(False))

This will clear the checked state of checkbox1 whenever checkbox2 is unchecked.

If you wanted to do the whole thing in code, it would look like this:

    self.checkbox1.setEnabled(False)
    self.checkbox2.toggled.connect(self.checkbox1.setEnabled)
    self.checkbox2.toggled.connect(
        lambda checked: not checked and self.checkbox1.setChecked(False))
Popovich answered 29/3, 2016 at 16:19 Comment(3)
@ekhmoro. thanks for the quick method! It's quite strange that you cannot change easily the checkbox state (cheked or not) with the signal/slot system. But with your code it worksClausewitz
@matteo. It does not seem at all strange to me. You're asking for the state of one widget to depend on another in a quite specific way. I think the two answers you got show that pyqt makes doing that about as easy as it reasonably could be.Popovich
setChecked(...) is incorrect call from the types point of view... You should be careful in using types. There is no compiler in Python. So if you're using PyQt there are two ways: look into the original signature of API (doc.qt.io/qt-5/qcheckbox.html#setCheckState) or use partially ready documentation for PyQt for version that you're using: riverbankcomputing.com/static/Docs/PyQt5/api/qtwidgets/… p.s. Just be careful in big projects absence of types and compilers is only headaque.Rewire
B
5

Just use signals. You are correct when saying that you cannot directly do that via the designer since you have to invert the checked property. The easiest and most readable way that comes to my mind is using a common slot plus an internal member variable that holds the checked state for both:

  • Add self._toggle = INITIAL_VALUE to your class - the INITIAL_VALUE holds a boolean value, which you use to check/uncheck your checkboxes

      self._toggle = True
      self.check1 = QCheckBox('Check 1', self)
      self.check1.setChecked(self._toggle)
      self.check2 = QCheckBox('Check 2', self)
      self.check2.setChecked(not self._toggle)
    
  • Add a slot:

      @pyqtSlot()
      def toggle(self):
        self._toggle = not self._toggle
        self.check1.setChecked(self._toggle)
        self.check2.setChecked(not self._toggle)
    
  • Connect the clicked signal of both checkboxes to this slot.

    Warning! Do not use the stateChanged signal here or you will start an infinite recursion. :3

      self.check1.clicked.connect(self.toggle)
      self.check2.clicked.connect(self.toggle)
    

What I'm doing here is basically taking over the change of the state of both checkboxes and do it manually using the value of self._toggle for the first checkbox and the inverted value of self._toggle for the second checkbox.

If you want less inverting inside the slot the following also works though it is less readable omho:

@pyqtSlot()
def toggle(self):
  self.check2.setChecked(self._toggle) # Old value of our check1 becomes the value of check2
  self._toggle = not self._toggle # Invert
  self.check1.setChecked(self._toggle) # New value is assigned to check1

Note: You can also use isChecked() inside the slot and do all of the above without the extra variable however I find this more readable and with much less function calls (every isChecked() and setChecked() is a function call)

Blarney answered 29/3, 2016 at 10:41 Comment(3)
If with "a lot" you mean 3-4 lines...Sure. LOL You can't omit the middle-man (the slot) here since you want some custom behaviour. If it was "I want to (un)check both checkboxes at the same time whenever I (un)check on one of them" you would have had the simpler solution of directly connected their slots and signals. To my knowledge inverting a signal's value without an additional place (slot) where you handle it is not possible. Maybe with a lambda function...Blarney
yep, if the check-uncheck states should be synchronized I think you can simply use Qt Designer.. I will try your code, thanks again!Clausewitz
Np, if you have more questions/suggestions/etc. feel free to post a comment here. :)Blarney
D
1

I know I am late to this party but there is a way to accomplish this easier and I think how the original person wanted. Here you go. If you have a checkbox_1 and you check/uncheck it, it connects to the following function.

def Change_the_Checkbox_Function(self):
    if self.checkbox_1.isChecked(): 
        if self.checkbox_2.isChecked():
            pass
        else:
            self.checkbox_2.setChecked(True)
    else:
        self.checkbox_2.setChecked(False)

If you want checkbox_1 to check or uncheck many other checkboxes (like a select all), then instead of checkbox_1 directly calling the above function, you have another function that calls other functions to check/uncheck the boxes, as follows:

def Select_ALL_checkboxes(self):
    self.Change_the_Checkbox_1_Function()
    self.Change_the_Checkbox_2_Function()
    self.Change_the_Checkbox_3_Function()

    ....etc

To me, this follows the most logical way without getting too technical. It works well and is fast. May not be the most efficient or prettiest, but I found this to be the best way to check/uncheck many checkboxes at once.

Deerhound answered 16/6, 2018 at 22:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.