0

I am beginning to write a GUI using PyQt4. This is my first experience with GUIs (and also oo-programming is somewhat new to me). Part of that GUI will be like 4 to 5 instances of QComboBox. As many choices are to be made, I want the user to be able to lock a choice, such that is not being changed unintenionally later. For one QComboBox I can solve the problem with this code that I wrote:

import sys
from PyQt4 import QtGui, QtCore

class MyGui(QtGui.QWidget):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.resize(250, 50)

        # vertical layout for widgets
        self.vbox = QtGui.QVBoxLayout()
        self.setLayout(self.vbox)

        # Create a combo box with some choices
        self.combo_color = QtGui.QComboBox()
        self.vbox.addWidget(self.combo_color)
        items = 'Red Yellow Purple'.split()
        self.combo_color.addItems(items)
        self.connect(self.combo_color, QtCore.SIGNAL('activated(QString)'), self.use_choice)

        # add a checkbox next to the combobox which (un-)locks the the combo-choice
        self.checkbox_color = QtGui.QCheckBox('Lock Choice', self)
        self.vbox.addWidget(self.checkbox_color)
        self.connect(self.checkbox_color, QtCore.SIGNAL('stateChanged(int)'), self.lock_choice)

    def use_choice(self, text):
        # do something very useful with the choice
        print 'The current choice is: {choice}'.format(choice=text)

    def lock_choice(self):
        if self.checkbox_color.isChecked():
            self.combo_color.setEnabled(False)
            print 'Choice {choice} locked'.format(choice=self.combo_color.currentText())
        else:
            self.combo_color.setEnabled(True)
            print 'Choice unlocked'


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    mygui = MyGui()
    mygui.show()
    app.exec_()

This code does what it should, but I am very unhappy with its design, because the method lock_choice is hard-coded to only lock the choice of the QComboBox combo_color. What if I now want to do the same thing for another QComboBox (say combo_name) and a second QCheckBox (say checkbox_name) which could be realized by appending the following code to the classes __init__(self) code block:

    # create second combo box with some other choices
    self.combo_name = QtGui.QComboBox()
    self.vbox.addWidget(self.combo_name)
    items = 'Bob Peter'.split()
    self.combo_name.addItems(items)
    self.connect(self.combo_name, QtCore.SIGNAL('activated(QString)'), self.use_choice)

    # add a checkbox next to the combobox which (un-)locks the the combo-choice
    self.checkbox_name = QtGui.QCheckBox('Lock Choice', self)
    self.vbox.addWidget(self.checkbox_name)
    self.connect(self.checkbox_name, QtCore.SIGNAL('stateChanged(int)'), self.lock_choice) # <-- obviously wrong, as it (un-)locks color choice at the moment

Both QComboBoxes can share the method use_choice() right now, but they cannot share the method lock_choice(), as both checkboxes lock the color-choice. I want the checkbox checkbox_name to lock the name-choice, without copy and pasting the current lock_choice()-method and switching the hardcoded combobox. I am sure there is an easy way, like passing the target-combobox to the method, which i just don't know yet. Help would be appreciated!

Nras
  • 4,251
  • 3
  • 25
  • 37

2 Answers2

1

Try partial functions-

from functools import partial

with the connect signal, pass the QWidget name:

self.connect(self.checkbox_color, QtCore.SIGNAL('stateChanged(int)'), partial(self.lock_choice, self.combo_color))

And in your method, you may add an argument.

We'll add an argument in the method like below, to handle the QWidget(QComboBox) that we'll pass through the partial function above.

def lock_choice(self, combos):
    if combos.isEnabled(True):
        combos.setEnabled(False)
        print 'Choice {choice} locked'.format(choice=combos.currentText())
Ejaz
  • 1,504
  • 3
  • 25
  • 51
  • Can please you expand your answer by explaining the "you may add an argument"-part? This seems to be what i am looking for. – Nras Oct 17 '14 at 15:21
  • Tnanks, I see what you mean. The current version, however, has a bug as the line ``if combos.isChecked():`` raises the ``AttributeError: 'QComboBox' object has no attribute 'isChecked'``, which makes sense since it should be the checkbox, which is checked for ``isChecked()``. Would you suggest passing the correct checkbox as argument as well or is there another way? – Nras Oct 17 '14 at 15:32
  • for QComboBox, you may use if combos.isEnabled(True). – Ejaz Oct 17 '14 at 15:35
  • for QCheckBox also you may pass it using the partial functions, the same way we did for QComboBox. – Ejaz Oct 17 '14 at 15:38
  • Both ways are fine. It works with: ``self.connect(self.checkbox_name, QtCore.SIGNAL('stateChanged(int)'), partial(self.lock_choice, self.combo_name, self.checkbox_name))`` and in the lock_choice-method: ``def lock_choice(self, combos, checkbox):`` followed by: ``if checkbox.isChecked():``. Learned something here. Thanks, +1. – Nras Oct 17 '14 at 15:39
  • Last question: is ``partial()`` still the correct way to go? All arguments are passed over now. Couldn't something with ``map()`` also do the job? – Nras Oct 17 '14 at 15:43
  • There is another way, you can try QSignalMapper, but it gets complicated for me...partial() is easy and ofcourse correct...you may explore [QSignalMapper Class](http://qt-project.org/doc/qt-5/qsignalmapper.html) – Ejaz Oct 17 '14 at 15:48
1

The simplest solution would be to use the toggled signal with the setDisabled slot:

    self.checkbox_color.toggled.connect(self.combo_color.setDisabled)

(And note how much cleaner the new-style sytax is when making signal connections).

It's also worth pointing out that you can also use a lambda for making inline signal connections, like this:

    self.checkbox_color.toggled.connect(
        lambda checked: self.combo_color.setDisabled(checked))

This is probably the most idiomatic solution when there are no convenient signal/slot pairings (and of course partial functions can achieve more or less the same thing in different way).

ekhumoro
  • 115,249
  • 20
  • 229
  • 336