Home > Software engineering >  PyQt5: Why does the default style of a QGroupBox disappear when painting?
PyQt5: Why does the default style of a QGroupBox disappear when painting?

Time:07-09

My application uses a variety of QGroupBoxes arranged in a QGridLayout to display information. I would like one of them to display custom shapes that I draw. Since I want the shapes to be it's GroupBox and it's difficult and not good to get the widget's position relative to the window, I decided to subclass the GroupBox and add the paint event to the subclass. This works beautifully, however it completely eliminates the default style of the GroupBox, including the title.

The following code creates a simple window with two GroupBoxes, one using the standard class and one with the paint event in a sub class. You should see that the one on the right only has the painted rectangle and none of the GroupBox style.

If you comment out the paint event, then the GroupBox displays as usual. Why is this happening and what should I do to keep the GroupBox style? Is there another way to use the painter within a GroupBox that doesn't use a paint event in a subclass?

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *


class MainWindow(QWidget):
    def __init__(self, *args, **kwargs):
        ## Set up Window layout
        super(MainWindow, self).__init__(*args, **kwargs)

        self.layout = QGridLayout()
        self.setLayout(self.layout)
        self.setGeometry(300, 50, 1000, 700)

        leftGroup = QGroupBox('Left Group')
        self.layout.addWidget(leftGroup, 0, 0)

        rightGroup = RightGroup()
        self.layout.addWidget(rightGroup, 0, 1)


class RightGroup(QGroupBox):
    def __init__(self):
        super(RightGroup, self).__init__('Right Group')

    def paintEvent(self, event):
        painter = QPainter(self)

        # Paint Style
        painter.setPen(QPen(Qt.black, .5, Qt.SolidLine))

        painter.drawRect(QRectF(0, 0, self.width(), self.height()))


if __name__ == '__main__':
    ## Creates a QT Application
    app = QApplication([])

    ## Creates a window
    window = MainWindow()

    ## Shows the window
    window.show()
    app.exec_()

Window with paint event enabled

Window with paint event commented out

CodePudding user response:

You are overriding the whole painting, and "overriding" means that you ignore the default behavior.

The default behavior of paintEvent() of a standard Qt widget results in painting that widget (whether it's a button, a label, a groupbox, etc.). If you just override that, that widget will never be painted, unless you call the default implementation (i.e.: super().paintEvent(event)).

For instance, consider the following:

class RightGroup(QGroupBox):
    pass

which will properly show a normal group box.

Now, this:

class RightGroup(QGroupBox):
    def paintEvent(self, event):
        pass

will show nothing, as you're explicitly ignoring the default painting behavior. To "revert" this and also add custom drawing:

class RightGroup(QGroupBox):
    def paintEvent(self, event):
        super().paintEvent(event)
        qp = QPainter(self)
        qp.drawRect(self.rect().adjusted(0, 0, -1, -1)

Note: unless you use antialiasing and the pen width is smaller or equal than 0.5, you should always restrict the right/bottom edges of the object rectangle to an integer smaller or equal to half of the pen width. That's the reason of the adjusted(0, 0, -1, -1) above. For your above code, it would have been QRectF(0, 0, self.width() - 1, self.height() - 1)

That said, you probably want to draw inside that groupbox, so, a better solution would be to create a subclass for the contents of that groupbox, add that to the groupbox and override the paint event of that subclass instead.

class MainWindow(QWidget):
    def __init__(self, *args, **kwargs):
        # ...
        rightGroup = QGroupBox('Right Group')
        self.layout.addWidget(rightGroup, 0, 1)

        rightLayout = QVBoxLayout(rightGroup)
        rightLayout.addWidget(Canvas())


class Canvas(QWidget):
    def __init__(self):
        super().__init__()
        self.path = QPainterPath()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.path.moveTo(event.pos())

    def mouseMoveEvent(self, event):
        # note: here we use "buttons", not "button".
        if event.buttons() == Qt.LeftButton:
            self.path.lineTo(event.pos())
            self.update()

    def mouseReleaseEvent(self, event):
        self.update()

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHints(painter.Antialiasing)

        painter.setPen(QPen(Qt.black, .5, Qt.SolidLine))

        painter.drawRect(self.rect())

        painter.drawPath(self.path)
  • Related