Search code examples
pythonpython-3.xpyside2qslider

Is it possible to expand the drawable area around the QSlider


My aim is to have a custom QSlider with tickmarks and tick labels in Python 3 using PySide2 module. In order to do so I edit the default paintEvent of the QSlider class in a derived class. However, it turns out that that the printable area is limited and the top/bottom labels I placed are cropped (see screenshot). The code I use to generate these sliders are as follows:

import sys
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtGui import *

slider_x = 150
slider_y = 450
slider_step = [0.01, 0.1, 1, 10, 100]  # in microns


class MySlider(QSlider):
    def __init__(self, type, parent=None):
        super(MySlider, self).__init__(parent)
        self.Type = type

    def paintEvent(self, event):
        super(MySlider, self).paintEvent(event)
        qp = QPainter(self)
        pen = QPen()
        pen.setWidth(2)
        pen.setColor(Qt.red)

        qp.setPen(pen)
        font = QFont('Times', 10)
        qp.setFont(font)
        self.setContentsMargins(50, 50, 50, 50)
        # size = self.size()
        # print(size)
        # print("margins", self.getContentsMargins())
        # print(self.getContentsMargins())
        # print(self.contentsRect())
        contents = self.contentsRect()
        self.setFixedSize(QSize(slider_x, slider_y))
        max_slider = self.maximum()
        y_inc = 0
        for i in range(max_slider):
            qp.drawText(contents.x() - font.pointSize(), y_inc + font.pointSize() / 2, '{0:2}'.format(slider_step[i]))
            qp.drawLine(contents.x() + font.pointSize(), y_inc, contents.x() + contents.width(), y_inc)
            y_inc += slider_y/4


class Window(QWidget):
    """ Inherits from QWidget """
    def __init__(self):
        super().__init__()
        self.title = 'Control Stages'
        self.left = 10
        self.top = 10
        self.width = 320
        self.height = 100
        self.AxesMapping = [0, 1, 2, 3]
        self.initUI()

    def initUI(self):
        """ Initializes the GUI either using the grid layout or the absolute position layout"""
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        Comp4 = self.createSlider("step_size")
        Comp5 = self.createSlider("speed")
        windowLayout = QGridLayout()
        windowLayout.setContentsMargins(50, 50, 50, 50)
        HGroupBox = QGroupBox()
        layout = QGridLayout()
        layout.addWidget(Comp4, 0, 0)
        layout.addWidget(Comp5, 0, 1)
        HGroupBox.setLayout(layout)
        HGroupBox.setFixedSize(QSize(740, 480))
        windowLayout.addWidget(HGroupBox, 0, 0)
        self.setLayout(windowLayout)
        self.show()

    def createSlider(self, variant):
        Slider = MySlider(Qt.Vertical)
        Slider.Type = variant
        Slider.setMaximum(5)
        Slider.setMinimum(1)
        Slider.setSingleStep(1)
        Slider.setTickInterval(1)
        Slider.valueChanged.connect(lambda: self.sliderChanged(Slider))
        return Slider

    @staticmethod
    def sliderChanged(Slider):
        print("Slider value changed to ", Slider.value(), "slider type is ", Slider.Type)
        if Slider.Type == "step_size":
            print("this is a step size slider")
        elif Slider.Type == "speed":
            print("this is a speed slider")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Window()
    sys.exit(app.exec_())

Is it possible to expand the drawable area around the QSlider and if so how can I achieve this effect? You can see on the screenshot that the red labels next to the first and last tickmarks are not displayed properly and they are cropped (i.e in the first tick label the top of 1 and 0 is missing for the label 0.01).

enter image description here

EDIT: After trying the proposed solution still a portion of the top label is clipped off. Second version below is still similar on Windows 10 64-bit with PySide2 5.12.0 and Python 3.6.6.

EDIT2 I have a dual-boot system so I tried it on Ubuntu 16.04.3 LTS with Python 3.5.2 / PySide 5.12.0 and it worked right out of the box. Here is a screenshot from there, but unfortunately it has to work on Windows.

enter image description here


Solution

  • After fiddling around more I finally found a solution. It includes styling sheets and I had tried it before. However, back then I was not able to implement it correctly. II kept the widget size constant, but decreased the groove length to be able to fit the labels inside the printable area. The complete code is below:

    import sys
    from PySide2.QtCore import *
    from PySide2.QtWidgets import *
    from PySide2.QtGui import *
    
    slider_x = 150
    slider_y = 450
    slider_step = [0.01, 0.1, 1, 10, 100]  # in microns
    groove_y = 400
    handle_height = 10
    
    
    class MySlider(QSlider):
        def __init__(self, type, parent=None):
            # super(MySlider, self).__init__(parent)
            super().__init__()
            self.parent = parent
            self.Type = type
            # self.setFixedHeight(115)
            self.setStyleSheet("""QSlider::groove:vertical {
        border: 1px solid black;
        height: """ + str(groove_y) + """ px;
        width: 10px;
        border-radius: 2px;
        }
    
        QSlider::handle:vertical {
            background: red;
            border: 1px solid red;
            height: """ + str(handle_height) + """ px;
            margin: 2px 0;
            border-radius: 1px;
        }
    
        QSlider::add-page:vertical {
            background: blue;
        }
        QSlider::sub-page:vertical {
            background: red;
    """)
    
        def paintEvent(self, event):
            super(MySlider, self).paintEvent(event)
            qp = QPainter(self)
            pen = QPen()
            pen.setWidth(2)
            pen.setColor(Qt.black)
    
            qp.setPen(pen)
            font = QFont('Times', 10)
            qp.setFont(font)
            self.setContentsMargins(50, 50, 50, 50)
            self.setFixedSize(QSize(slider_x, slider_y))
            contents = self.contentsRect()
            max = self.maximum()
            min = self.minimum()
    
            y_inc = slider_y - (slider_y - groove_y) / 2
            for i in range(len(slider_step)):
                qp.drawText(contents.x() - 2 * font.pointSize(), y_inc + font.pointSize() / 2, '{0:3}'.format(slider_step[i]))
                qp.drawLine(contents.x() + 2 * font.pointSize(), y_inc, contents.x() + contents.width(), y_inc)
                y_inc -= groove_y / (max - min)
    
    
    
    class Window(QWidget):
        """ Inherits from QWidget """
        def __init__(self):
            super().__init__()
            self.title = 'Control Stages'
            self.left = 10
            self.top = 10
            self.width = 320
            self.height = 100
            self.AxesMapping = [0, 1, 2, 3]
            self.initUI()
    
        def initUI(self):
            """ Initializes the GUI either using the grid layout or the absolute position layout"""
            self.setWindowTitle(self.title)
            self.setGeometry(self.left, self.top, self.width, self.height)
            Comp4 = self.createSlider("step_size")
            Comp5 = self.createSlider("speed")
            windowLayout = QGridLayout()
            windowLayout.setContentsMargins(50, 50, 50, 50)
            HGroupBox = QGroupBox()
            layout = QGridLayout()
            layout.addWidget(Comp4, 0, 0)
            layout.addWidget(Comp5, 0, 1)
            HGroupBox.setLayout(layout)
            HGroupBox.setFixedSize(QSize(740, 480))
            windowLayout.addWidget(HGroupBox, 0, 0)
            self.setLayout(windowLayout)
            self.show()
    
        def createSlider(self, variant):
            Slider = MySlider(Qt.Vertical)
            Slider.Type = variant
            Slider.setMaximum(len(slider_step))
            Slider.setMinimum(1)
            Slider.setSingleStep(1)
            Slider.setTickInterval(1)
            Slider.valueChanged.connect(lambda: self.sliderChanged(Slider))
            return Slider
    
        @staticmethod
        def sliderChanged(Slider):
            print("Slider value changed to ", Slider.value(), "slider type is ", Slider.Type)
            if Slider.Type == "step_size":
                print("this is a step size slider")
            elif Slider.Type == "speed":
                print("this is a speed slider")
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        ex = Window()
        sys.exit(app.exec_())