Luis E.
Luis E.

Reputation: 851

How to set a "read-only checkbox" in PySide/PyQt

I am trying to display the value of a Boolean variable using a QCheckBox widget, and render the user unable to change the displayed value. I don't want to disable it, as the resulting graying doesn't look good. I have tried to approximate the effect by changing the new value back to its previous value when the user clicks the QCheckBox. However, the problem is compounded by the fact that the state of the widget is described by the "checked" properties of the QAbstractButton parent class, and the "state" properties of the QCheckBox class itself. This gives rise to a combinatorial exercise of signals and slots, of which I have been unable to obtain any good result.

var_ctrl = QtGui.QCheckBox( 'some name' )

def rdslot1(state): 
    if state == QtCore.Qt.Checked:
        var_ctrl.setCheckState( QtCore.Qt.Unchecked )
    else:                               
        var_ctrl.setCheckState( QtCore.Qt.Checked )

def rdslot2(state): 
    if var_ctrl.isChecked():
        var_ctrl.setChecked(False)
    else:
        var_ctrl.setChecked(True)

# Signal/Slot combinations (only one should be active)
var_ctrl.stateChanged.connect( rdslot1 )
var_ctrl.toggled.connect( rdslot2 )
var_ctrl.stateChanged.connect( rdslot2 )
var_ctrl.toggled.connect( rdslot1 )

Upvotes: 4

Views: 9088

Answers (4)

Zeeshan Qureshi
Zeeshan Qureshi

Reputation: 105

Make Another QLable on top of QCheckBox based on condition, it will stop checbox under it to be checked on unchecked.

Upvotes: 0

Eric Hulser
Eric Hulser

Reputation: 4022

I'm late to the party - it seems like you got a solution that works. For future reference tho, another way you can do it would be to consume the mouse events - which keeps all of your signals working the way they should:

from PyQt4 import QtGui, QtCore

class MyCheckBox(QtGui.QCheckBox):
    def __init__( self, *args ):
        super(MyCheckBox, self).__init__(*args) # will fail if passing **kwargs
        self._readOnly = False

    def isReadOnly( self ):
        return self._readOnly

    def mousePressEvent( self, event ):
        if ( self.isReadOnly() ):
            event.accept()
        else:
            super(MyCheckBox, self).mousePressEvent(event)

    def mouseMoveEvent( self, event ):
        if ( self.isReadOnly() ):
            event.accept()
        else:
            super(MyCheckBox, self).mouseMoveEvent(event)

    def mouseReleaseEvent( self, event ):
        if ( self.isReadOnly() ):
            event.accept()
        else:
            super(MyCheckBox, self).mouseReleaseEvent(event)

    # Handle event in which the widget has focus and the spacebar is pressed.
    def keyPressEvent( self, event ):
        if ( self.isReadOnly() ):
            event.accept()
        else:
            super(MyCheckBox, self).keyPressEvent(event)

    @QtCore.pyqtSlot(bool)
    def setReadOnly( self, state ):
        self._readOnly = state

    readOnly = QtCore.pyqtProperty(bool, isReadOnly, setReadOnly)

Setting the code up this way gets you a few things (which you may or may not care about) but can be useful when developing custom Qt widgets:

  1. Consuming the event blocks the signal emission, so you can still connect other slots to things like clicked & toggled. If you're looking for those signals and then just switching the value on/off - then other widgets listening for those signals will be triggered incorrectly
  2. Using isReadOnly/setReadOnly keeps the class following the Qt coding style
  3. Creating pyqtSignals & pyqtSlots will help if you expose the plugin to Qt's Designer

Upvotes: 4

warvariuc
warvariuc

Reputation: 59664

Try to disable the checkbox widget, but override its look using widget palette or style

Upvotes: 1

Luis E.
Luis E.

Reputation: 851

Well later on I came up with a shortcut, which simply catches the clicks of the user and handles them according to a specifiable 'Modifiable' property. I have made this class:

class MyQCheckBox(QtGui.QCheckBox):

    def __init__(self, *args, **kwargs):
        QtGui.QCheckBox.__init__(self, *args, **kwargs)        
        self.is_modifiable = True
        self.clicked.connect( self.value_change_slot )

    def value_change_slot(self): 
        if self.isChecked():
            self.setChecked(self.is_modifiable)
        else:
            self.setChecked(not self.is_modifiable)            

    def setModifiable(self, flag):
        self.is_modifiable = flag            

    def isModifiable(self):
        return self.is_modifiable

It behaves just like a normal QCheckBox, being modifiable by default. However, when you call setModifiable(False), everytime you click it, it keeps the current state of the widget. The trick was to catch the clicked signal, not toggled neither stateChanged.

Upvotes: 1

Related Questions