QCheckBox state change PyQt4

Dipanjan Patra picture Dipanjan Patra · Jul 18, 2016 · Viewed 7.4k times · Source

I'm trying to implement a system in PyQt4 where unchecking a checkbox would call function disable_mod and checking it would call enable_mod. But even though state is changing the checkboxes call the initial function they started with. For this case if an already checked box was clicked it'd always keep calling the disable_mod function! I don't understand why is this happening? Can you guys help me out here a little bit? Here's my code:

from PyQt4 import QtCore, QtGui
from os import walk
from os.path import join
import sys

def list_files_regex(dir):
    l = []
    for (root, dirnames, filenames) in walk(dir):
        for d in dirnames:
            list_files_regex(join(root, d))
        l.extend(filenames)
    return l

directory = "J:/test_pack"
directory = join(directory, "/mods")

count = 0
for y in list_files_regex(directory):
    print y
    count += 1
print count

class ModEdit(QtGui.QMainWindow):
    def __init__(self, title, icon, x, y, w, h):
        super(ModEdit, self).__init__()
        self.setWindowTitle(title)
        self.setWindowIcon(QtGui.QIcon(icon))
        self.setGeometry(x, y, w, h)
        self.choices = []
        self.init()

    def init(self):
        scroll_widget = QtGui.QScrollArea()
        sub_widget = QtGui.QWidget()
        v_layout = QtGui.QVBoxLayout()

        for y in list_files_regex(directory):
            tmp = QtGui.QCheckBox(y, self)
            tmp.resize(tmp.sizeHint())
            if tmp.text()[len(tmp.text()) - 8: len(tmp.text())] != 'disabled':
                tmp.setChecked(True)
            # if tmp.isChecked() == 0:
            #     tmp.stateChanged.connect(self.enable_mod)
            # if tmp.isChecked():
            #     tmp.stateChanged.connect(self.disable_mod)
            # v_layout.addWidget(tmp)
            self.choices.append(tmp)
        print self.choices
        for choice in self.choices:
            v_layout.addWidget(choice)
            if choice.isChecked():
                choice.stateChanged.connect(self.disable_mod)
            else:
                choice.stateChanged.connect(self.enable_mod)
        sub_widget.setLayout(v_layout)
        scroll_widget.setWidget(sub_widget)
        self.setCentralWidget(scroll_widget)
        self.show()

    def enable_mod(self):
        print "ENABLE_TEST"
        print self.choices[1].isChecked()
    def disable_mod(self):
        print "DISABLE_TEST"
        print self.choices[1].isChecked()

    def test(self):
        print 'test'
        for ch in self.choices:
            if ch.isChecked():
                ch.stateChanged.connect(self.disable_mod)
            else:
                ch.stateChanged.connect(self.enable_mod)

class Rename(QtCore.QObject):
    enable = QtCore.pyqtSignal
    disable = QtCore.pyqtSignal

app = QtGui.QApplication(sys.argv)
ex = ModEdit("Minecraft ModEdit", "ModEdit.png", 64, 64, 640, 480)
sys.exit(app.exec_())

Answer

Brendan Abel picture Brendan Abel · Jul 18, 2016

The problem is that you're only connecting the checkbox stateChanged signal once during initialization. After the state of the checkbox changes, you're not disconnecting the signal and reconnecting it to the correct slot.

You'll need to connect the stateChanged signal to an intermediary slot that will decide which function to call based on the checkbox state. Since you're using the same slot for multiple checkboxes, it's probably best to also pass the checkbox to the slot as well.

from functools import partial

def init(self):
    ...
    for tmp in list_of_checkboxes:
        enable_slot = partial(self.enable_mod, tmp)
        disable_slot = partial(self.disable_mod, tmp)
        tmp.stateChanged.connect(lambda x: enable_slot() if x else disable_slot())

def enable_mod(self, checkbox):
    print "ENABLE_TEST"
    print checkbox.isChecked()

def disable_mod(self, checkbox):
    print "DISABLE_TEST"
    print checkbox.isChecked()

Alternatively, since we are now passing the checkbox to the slots, you could just use a single slot and check the checkbox state inside the slot

def init(self):
    ...
    for tmp in list_of_checkboxes:
        slot = partial(self.enable_disable_mod, tmp)
        tmp.stateChanged.connect(lambda x: slot())

def enable_disable_mod(self, checkbox):
    if checkbox.isChecked():
        ...
    else:
        ...