pythonpyqtpyqt6qcheckbox

How to fully implement custom right mousebutton functionality for QCheckBox


When a normal QCheckbox is in PartiallyChecked state and you left-click on it, it will change to Checked state. Right-clicking on a checkbox does nothing.

I want to modify a checkbox so that right-clicking does the same as left-clicking except when clicking in PartiallyChecked state the state will change to Unchecked instead of Checked.

Here's my attempt to achieve that:

class MyCheckBox(QCheckBox):
    def __init__(self):
        super().__init__()
        self.clicked.connect(lambda: self.setTristate(False))

    def mousePressEvent(self, event: QMouseEvent):
        if event.button() == Qt.MouseButton.RightButton:
            event = QMouseEvent(event.type(), event.position(), Qt.MouseButton.LeftButton, event.buttons(), event.modifiers())
        super().mousePressEvent(event)

    def mouseReleaseEvent(self, event: QMouseEvent):
        if event.button() == Qt.MouseButton.RightButton:
            event = QMouseEvent(event.type(), event.position(), Qt.MouseButton.LeftButton, event.buttons(), event.modifiers())
            if self.checkState() == Qt.CheckState.PartiallyChecked:
                self.setCheckState(Qt.CheckState.Checked)
        super().mouseReleaseEvent(event)

This works as intended except when you press down on the checkbox > move off the checkbox while holding the button down > release the button. The custom part of the mouseReleaseEvent is being executed while the 'native' part (in the super() call) is not. For instance, the clicked signal is not emitted.

How would you make it so that the custom part of the release-event is executed in the same way as the native part i.e. not when the release-event is off the checkbox.

An observation that may be related:
When you press-down on the a checkbox, the box gets shaded. When you move off the box (while holding down) that shading disappears, and it will appear again when moving on the box again.
This behaviour is not replicated in my custom checkbox for the right button. The shading will stay when moving off the box.


Solution

  • In order to provide proper behavior also on mouse movement, you have to override mouseMoveEvent() too, but in that case the different button has to be part of the event's buttons() (note the plural).

    It's also better to let the button handle the state change and eventually override within nextCheckState(). Using a simple internal bool attribute you can then set the different state accordingly.

    class MyCheckBox(QCheckBox):
        _isRightButton = False
    
        ...
    
        def mouseMoveEvent(self, event: QMouseEvent):
            if event.buttons() == Qt.MouseButton.RightButton:
                event = QMouseEvent(
                    event.type(), event.position(), 
                    Qt.MouseButton.NoButton, 
                    Qt.MouseButton.LeftButton, 
                    event.modifiers()
                )
            super().mouseMoveEvent(event)
    
        def mouseReleaseEvent(self, event: QMouseEvent):
            isRight = event.button() == Qt.MouseButton.RightButton
            if isRight:
                self._isRightButton = True
                event = QMouseEvent(
                    event.type(), event.position(), 
                    Qt.MouseButton.LeftButton, 
                    event.buttons(), 
                    event.modifiers()
                )
            super().mouseReleaseEvent(event)
            if isRight:
                self._isRightButton = False
    
        def nextCheckState(self):
            if self._isRightButton and self.checkState() == Qt.CheckState.PartiallyChecked:
                self.setCheckState(Qt.CheckState.Unchecked)
            else:
                super().nextCheckState()