Reputation: 59
[ ]All1 [ ]All2
[ ]checkbox1A [ ]checkbox1B
[ ]checkbox2A [ ]checkbox2B
Based on the chart above, a few things need to happen:
So really this is more like a chain reaction setup. If checkbox All1 is on, then chieckbox1A and 2A will be on, and because they are on, checkbox1B and 2B are also on, but checkbox All2 remains off. I tried hooking up the signals based on this logic, but only the paired logic works 100%. The All checkbox logic only works 50% of the time, and not accurately, and there's no way for me to turn off the All checkbox without turning all already checked checkboxes off.
Really really need help ... T-T
Sample code:
cbPairKeys = cbPairs.keys()
for key in cbPairKeys:
cbOne = cbPairs[key][0][0]
cbTwo = cbPairs[key][1][0]
cbOne.stateChanged.connect(self.syncCB)
cbTwo.stateChanged.connect(self.syncCB)
def syncCB(self):
pairKeys = cbPairs.keys()
for keys in pairKeys:
cbOne = cbPairs[keys][0][0]
cbOneAllCB = cbPairs[keys][0][4]
cbTwo = cbPairs[keys][1][0]
cbTwoAllCB = cbPairs[keys][1][4]
if self.sender() == cbOne:
if cbOne.isChecked() or cbTwoAllCB.isChecked():
cbTwo.setChecked(True)
else:
cbTwo.setChecked(False)
else:
if cbTwo.isChecked() or cbOneAllCB.isChecked():
cbOne.setChecked(True)
else:
cbOne.setChecked(False)
EDIT
Thanks to user Avaris's help and patience, I was able to reduce the code down to something much cleaner and works 100% of the time on the 1st and 2nd desired behavior:
#Connect checkbox pairs
cbPairKeys = cbPairs.keys()
for key in cbPairKeys:
cbOne = cbPairs[key][0][0]
cbTwo = cbPairs[key][1][0]
cbOne.toggled.connect(cbTwo.setChecked)
cbTwo.toggled.connect(cbOne.setChecked)
#Connect allCB and allRO signals
cbsKeys = allCBList.keys()
for keys in cbsKeys:
for checkbox in allCBList[keys]:
keys.toggled.connect(checkbox.setChecked)
Only need help on turning off the All checkbox when the user selectively turns off the modular checkboxes now
Upvotes: 1
Views: 3787
Reputation: 36715
If I'm understanding your data structure, I have a solution. Correct me if I'm wrong: allCBList
is a dict
(confusing name! :) ). Its keys are the all*
checkboxes. And a value allCBList[key]
is a list
with checkboxes associated with that all
checkbox. For your example structure it'll be something like this:
{ All1 : [checkbox1A, checkbox1B],
All2 : [checkbox2A, checkbox2B]}
Then what you need to is this: when a checkbox is toggled and it is in checked
state, then you need to check the All*
checkbox if all the other checkboxes are in checked
state. Otherwise it will be unchecked.
for key, checkboxes in allCBList.iteritems():
for checkbox in checkboxes:
checkbox.toggled.connect(lambda checked, checkboxes=checkboxes, key=key: key.setChecked(checked and all(checkbox.isChecked() for checkbox in checkboxes))
I guess, this statement requires a bit of explanation:
lambda checked, checkboxes=checkboxes, key=key:
lambda
creates the callable that is connected to the signal. toggled
passes checkbox status, and it will be passed to checked
variable. checkboxes=checkboxes
and key=key
parts pass the current values to checkboxes
and key
parameters of the lambda. (You need this because of the closure in lambda
s)
Next comes:
key.setChecked(...)
We are setting the checked
state of key
which is the appropriate All*
checkbox. And inside this:
checked and all(checkbox.isChecked() for checkbox in checkboxes)
all
is True
if everything inside is True
, where we check every checkbox
s state. And this will return True
if all are checked
(i.e. isChecked()
returns True
).
checked and ...
part is there to short-circuit the all
. If the current checkbox turns unchecked
, then we don't need to check others. All*
would be unchecked
.
(PS: By the way, you don't need to get .keys()
of a dict
to iterate over keys. You can just iterate over the dict
and it will iterate over its keys
.)
Edit: Just to avoid chain reaction with All*
checkboxes toggled by clicking any sub-checkboxes, it's necessary to change the signal for All*
checkboxes to clicked
, instead of toggled
. So, the All*
check boxes will affect other below them only in the case of user interaction.
In the end, your modified code will be:
# Connect checkbox pairs
# you just use the values
# change 'itervalues' to 'values' if you are on Python 3.x
for cbPair in cbPairs.itervalues():
cbOne = cbPair[0][0]
cbTwo = cbPair[1][0]
cbOne.toggled.connect(cbTwo.setChecked)
cbTwo.toggled.connect(cbOne.setChecked)
# Connect allCB and allRO signals
# change 'iteritems' to 'items' if you are on Python 3.x
for key, checkboxes in allCBList.iteritems():
for checkbox in checkboxes:
key.clicked.connect(checkbox.setChecked)
checkbox.toggled.connect(lambda checked, checkboxes=checkboxes, key=key: key.setChecked(checked and all(checkbox.isChecked() for checkbox in checkboxes))
Upvotes: 3
Reputation: 5452
Your problem is that your checkboxes are connecting the toggled
signal and toggling their state in your connected slots so the signal is emitted again (so the slots are executed again...) and you get unpredictable results. Obviously that is not your wanted behavior. You can fix it in several ways:
clicked
signal because it is not re-emitted when the checkbox state changesWhich approach you follow is up to you. The following code uses the third approach:
self.cbPair = {}
self.cbPair['0'] = (QtGui.QCheckBox('all1', parent),
QtGui.QCheckBox('all2', parent))
self.cbPair['1'] = (QtGui.QCheckBox('1a', parent),
QtGui.QCheckBox('1b', parent))
self.cbPair['2'] = (QtGui.QCheckBox('2a', parent),
QtGui.QCheckBox('2b', parent))
for v in self.cbPair.values():
for cb in v:
cb.clicked.connect(self.updateCB)
def updateCB(self):
cb = self.sender()
is_checked = cb.isChecked()
id = str(cb.text())
try:
# Update a whole column
column = int(id[-1]) - 1
rows = ('1', '2')
except ValueError:
# Update a row and the headers row
rows = (id[0], )
column = {'a': 1, 'b': 0}.get(id[-1])
if not is_checked:
for c in (0, 1):
self.cbPair['0'][c].setChecked(is_checked)
for r in rows:
self.cbPair[r][column].setChecked(is_checked)
Note that I'm using the checkboxes text as a UID from wich row and colum values are calculated. If you want to use different text labels for your checkboxes you may need to set the UIDs as attributes to every checkbox.
Upvotes: 0