Home > Net >  PyQt6 Setting custom rect in QSlider's paintEvent
PyQt6 Setting custom rect in QSlider's paintEvent

Time:04-15

How can I make QSlider's paintEvent's rect a little smaller? I want to have a really thin slider but I want it to be able to receive clicks easily so it's actual size has to be a bit bigger than what's painted. If the slider is really thin, 2 pixels for example, it's so annoying to try and catch the handle or click on it. I tried creating custom QPaintEvent within paintEvent method and making it's rect height equal to 1-2 and then passing it to super().paintEvent(my_new_paint_event) but it didn't work. How can this be done?

CodePudding user response:

Besides using a QSS (which requires to style all properties), the only reliable solution is to use a proxy style.

While overriding the paintEvent() of the slider is possible, there are two main issues:

  • changing the event region is completely pointless, as it will only change the exposed region that the function will draw upon (and that region might also be completely ignored);
  • the default paintEvent() of QSlider updates its option based on the current state and active/hovered subcontrols before calling the style functions, and unless you're willing to override a bunch of other functions (enter/leave, mouse press/move/release, keyboard events, etc), you'll only get a partially working result that won't reflect the actual widget state;

A QSlider draws itself using the drawComplexControl() function of QStyle, and this is achieved by providing a set of flags used for the subControls and activeSubControls of QStyleOptionSlider.

Since one of those controls is SC_SliderGroove, which is the part of the slider on which the handle moves, the solution is to remove that from the subControls, and do the painting on your own. Remember that painting happens from bottom to top, so the custom groove must be drawn before calling the base implementation.

class Style(QtWidgets.QProxyStyle):
    def drawComplexControl(self, control, opt, qp, widget=None):
        if control == self.CC_Slider:
            # get the default rectangle of the groove
            groove = self.subControlRect(
                control, opt, self.SC_SliderGroove, widget)
            # create a small one
            if opt.orientation == QtCore.Qt.Horizontal:
                rect = QtCore.QRectF(
                    groove.x(), groove.center().y() - .5, 
                    groove.width(), 2)
            else:
                rect = QtCore.QRectF(
                    groove.center().x() - .5, groove.y(), 
                    2, groove.height())
            qp.save()
            qp.setBrush(opt.palette.mid())
            qp.setPen(opt.palette.dark().color())
            qp.setRenderHints(qp.Antialiasing)
            qp.drawRoundedRect(rect, 1, 1)
            qp.restore()

            # remove the groove flag from the subcontrol list
            opt.subControls &= ~self.SC_SliderGroove

        super().drawComplexControl(control, opt, qp, widget)

The above is generic for PyQt5 and PySide, for PyQt6 you need to change the flag names using their type, like Qt.Orientation.Horizontal, etc.

  • Related