Search code examples
pythonqtpyqt5qt-designerpaintevent

how to call mousePressEvent under if & else of paintEvent in pyqt?


In PyQt5, I am developing a sensors based GUI where i draw a toggle power off/on button in which i want to add a functionality where i toggle the power button and my desktop gui should be closed on that toggle. like we do in close [X] button of the gui.

here is the toggle.py code and call i on my main.py code

main.py


class MainWindow(QMainWindow):

def __init__(self):
    super().__init__()
    # main dialog box
    self.turning_Icon = None
    self.setWindowTitle("Hatchery System")
    self.setStyleSheet("background-color: #2c313c;")
    self.setFixedWidth(1400)
    self.setFixedHeight(950)
    self.setWindowFlags(Qt.FramelessWindowHint)

    # Create Container and Layout
    self.container = QFrame(self)
    self.container.move(100, 50)
    self.container.resize(100, 50)
    self.container.setStyleSheet("background-color: #2c313c;")
    self.container.layout = QVBoxLayout()
    
    # toggle_power_button
    self.toggle = PyToggle()
    self.toggle.setStyleSheet("background-color: white")
    self.toggle.move(50, 50)
    self.container.layout.addWidget(self.toggle, Qt.AlignCenter, Qt.AlignCenter)
    self.container.setLayout(self.container.layout)

toggle.py code:


import pylab as p
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class PyToggle(QCheckBox):

def __init__(
        self,
        width=60,
        # height=50,
        bg_color="#777",
        circle_color="#fff",
        active_color="#00BCff"
        # active_color="red"

):
    QCheckBox.__init__(self)

    self.setFixedSize(width, 28)
    # self.setFixedSize(height, 40)
    self.setCursor(Qt.PointingHandCursor)

    # colors
    self._bg_color = bg_color
    self._circle_color = circle_color
    self._active_color = active_color

    # connect state changed
    self.stateChanged.connect(self.debug)

def debug(self):
    print(f"status: {self.isChecked()}")

def hitButton(self, pos: QPoint):
    return self.contentsRect().contains(pos)

def paintEvent(self, e):
    # SET painter
    p = QPainter(self)
    p.setRenderHint(QPainter.Antialiasing)
    p.setPen(Qt.NoPen)

    rect = QRect(0, 0, self.width(), self.height())

    p.setBrush(QColor(self._bg_color))
    p.drawRoundedRect(0, 0, rect.width(), self.height(), self.height() / 2, self.height() / 2)
    p.end()

def paintEvent(self, e):
    # SET painter
    p = QPainter(self)
    p.setRenderHint(QPainter.Antialiasing)

    # SET as No PEN
    p.setPen(Qt.NoPen)

    # draw rect
    rect = QRect(0, 0, self.width(), self.height())

    if not self.isChecked():
        # draw BG
        p.setBrush(QColor(self._bg_color))
        p.drawRoundedRect(0, 0, rect.width(), self.height(), self.height()/2, self.height()/2)

        p.setBrush(QColor(self._circle_color))
        p.drawEllipse(3, 3, 22, 22)
        p.mousePressEvent = self.clickLine
    else:
        p.setBrush(QColor(self._active_color))
        p.drawRoundedRect(0, 0, rect.width(), self.height(), self.height() / 2, self.height() / 2)

        p.setBrush(QColor(self._circle_color))
        p.drawEllipse(self.width() - 26, 3, 22, 22)

def clickLine(self, mouseEvent):
    p.clicked.connect(self.close)

here in if condition i call mousePressEvent but its not working

Output:
enter image description here
enter image description here
on unchecked it off my desktop gui should be close.


Solution

  • Since the requirement is to close the window only when the check box is unchecked again, the solution is to connect to a function that will only close in that case. Note that you should not use the stateChanged signal for dual state check boxes, as it returns a Qt.CheckState enum, which has 3 states. Use the toggled signal instead.

            self.toggled.connect(self.checkForClose)
    
        def checkForClose(self, state):
            if not state:
                self.close()
    

    An important note about your attempt.

    First of all, a paint function should never try to do something that is not directly related to drawing. This is important as those functions are called very often, potentially dozens of times each second, and in some cases even every time the mouse is moved above the widget.

    Most importantly, your overwritten method just connects the clicked signal, which is wrong for two reasons:

    1. your override doesn't do anything else except for connecting the signal, and since QCheckBox needs to handle mouse events in order to properly change its state (and eventually emit the clicked signal), you're preventing all of that - and the signal will never be emitted because it never gets checked/unchecked;
    2. every time any mouse button will be pressed, the signal would be connected; for the above reason, it will have no result at all, but this is still conceptually wrong, as a connected function will always be called as many times as it has been connected to the signal: you connect it twice? the function will be called twice; assuming that the signal would be emitted (which could still happen using keyboard), the related function will be called as many times as the user has attempted to click it.

    Another important aspect to remember is that PyQt uses reference caching for functions; after the first time a virtual function is called with the default implementation, overwriting the instance attribute will have no effect.

    Supposing you wanted to do the opposite (connect the signal when the state is True), it wouldn't have worked because at that time the default mousePressEvent handler would have been already called, and the overwriting would have had no effect at all.