언어/Python

Python Slider tick label 적용건

darkdevilness 2024. 1. 17. 19:45
728x90

Slider 에 Tick Label을   넣는 것이다 

Slider 위젯 자체에는 tick label을 넣는 것이 없어서.  QPainter를 사용한다. 

tick label 에서 interval 값이 1 이면  자연스러운데  1이 아닌 값이면,  label값 표현이 맘에 안들 것이다. 

이보다는  GraphicView를 사용하여 그냥 그리는 것도 나쁘지는 않은 것 같다. 

QLabel + QPixmap 을 사용해 보면, 애매한 부분이 존재하기 때문에 

검토해보고 맘에 드는 것이 으로  사용하길 바란다. 

 

 

 

Code

import math

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtGui import QPen, QFont
from PyQt5.QtWidgets import QWidget, QVBoxLayout
from PyQt5.QtCore import Qt


class CLabeledSlider(QWidget):
    def __init__(self, minimum=-90, maximum=90, interval=10, orientation=Qt.Horizontal,
                 labels=None, p0=0, parent=None):
        super(CLabeledSlider, self).__init__(parent=parent)
        self.m_minVal = minimum
        self.m_maxVal = maximum
        self.m_Interval = interval
        self.m_labels = labels
        levels = range(minimum, maximum + interval, interval)

        if labels is not None:
            if not isinstance(labels, (tuple, list)):
                raise Exception("<labels> is a list or tuple.")
            if len(labels) != len(levels):
                raise Exception("Size of <labels> doesn't match levels.")
            self.levels = list(zip(levels, labels))
        else:
            self.levels = list(zip(levels, map(str, levels)))

        if orientation == Qt.Horizontal:
            self.layout = QVBoxLayout(self)
        elif orientation == Qt.Vertical:
            self.layout = QHBoxLayout(self)
        else:
            raise Exception("<orientation> wrong.")

        # gives some space to print labels
        self.left_margin = 10
        self.top_margin = 10
        self.right_margin = 10
        self.bottom_margin = 10

        self.layout.setContentsMargins(self.left_margin, self.top_margin,
                                       self.right_margin, self.bottom_margin)

        self.sl = QSlider(orientation, self)
        self.sl.setMinimum(minimum)
        self.sl.setMaximum(maximum)
        self.sl.setValue(minimum)
        self.sl.setSliderPosition(p0)
        if orientation == Qt.Horizontal:
            self.sl.setTickPosition(QSlider.TicksBelow)
            self.sl.setMinimumWidth(300)  # just to make it easier to read
        else:
            self.sl.setTickPosition(QSlider.TicksLeft)
            self.sl.setMinimumHeight(300)  # just to make it easier to read
        self.sl.setTickInterval(interval)
        self.sl.setSingleStep(1)
        # self.sl.setVisible(False)
        self.layout.addWidget(self.sl)

        self.sl.valueChanged.connect(self.updateLabels)

    def updateLabels(self):
        val = self.sl.value()
        self.m_minVal = -90 + val
        self.m_maxVal = 90 + val

        self.sl.setMinimum(self.m_minVal)
        self.sl.setMaximum(self.m_maxVal)

        labels = self.m_labels
        levels = range(self.m_minVal, self.m_maxVal + self.m_Interval, self.m_Interval)
        if labels is not None:
            if not isinstance(labels, (tuple, list)):
                raise Exception("<labels> is a list or tuple.")
            if len(labels) != len(levels):
                raise Exception("Size of <labels> doesn't match levels.")
            self.levels = list(zip(levels, labels))
        else:
            self.levels = list(zip(levels, map(str, levels)))

        self.update()
        return

    def paintEvent(self, e):
        # print(datetime.now().strftime('%H:%M:%S.%f')[:-1],"paintEvent")
        super(CLabeledSlider, self).paintEvent(e)
        style = self.sl.style()
        painter = QPainter(self)

        st_slider = QStyleOptionSlider()
        st_slider.initFrom(self.sl)
        st_slider.orientation = self.sl.orientation()

        length = style.pixelMetric(QStyle.PM_SliderLength, st_slider, self.sl)
        available = style.pixelMetric(QStyle.PM_SliderSpaceAvailable, st_slider, self.sl)
        # print(self.levels)
        for v, v_str in self.levels:

            # get the size of the label
            rect = painter.drawText(QRect(), Qt.TextDontPrint, v_str)

            if self.sl.orientation() == Qt.Horizontal:
                # I assume the offset is half the length of slider, therefore
                # + length//2

                x_loc = QStyle.sliderPositionFromValue(self.m_minVal,
                                                       self.m_maxVal, v, available) + length // 2

                # left bound of the text = center - half of text width + L_margin
                left = x_loc - rect.width() // 2 + self.left_margin
                bottom = self.rect().bottom()

                # enlarge margins if clipping
                if v == self.sl.minimum():
                    if left <= 0:
                        self.left_margin = rect.width() // 2 - x_loc
                    if self.bottom_margin <= rect.height():
                        self.bottom_margin = rect.height()

                    self.layout.setContentsMargins(self.left_margin,
                                                   self.top_margin, self.right_margin,
                                                   self.bottom_margin)

                if v == self.sl.maximum() and rect.width() // 2 >= self.right_margin:
                    self.right_margin = rect.width() // 2
                    self.layout.setContentsMargins(self.left_margin,
                                                   self.top_margin, self.right_margin,
                                                   self.bottom_margin)

            else:
                y_loc = QStyle.sliderPositionFromValue(self.m_minVal,
                                                       self.m_maxVal, v, available, upsideDown=True)

                bottom = y_loc + length // 2 + rect.height() // 2 + self.top_margin - 3
                # there is a 3 px offset that I can't attribute to any metric

                left = self.left_margin - rect.width()
                if left <= 0:
                    self.left_margin = rect.width() + 2
                    self.layout.setContentsMargins(self.left_margin,
                                                   self.top_margin, self.right_margin,
                                                   self.bottom_margin)
            pos = QPoint(left, bottom)
            painter.drawText(pos, v_str)

        return

class CMain(QWidget):
    signal = pyqtSignal(object, name="pyqtSignal")

    def __init__(self, *args):
        super().__init__()
        self.spinBox = QSpinBox(self)
        self.m_compassBar = CLabeledSlider()
        self.setupUI()

    def setSliceValue(self):
        val = self.spinBox.value()
        self.m_compassBar.updateTicks(val)

    def setupUI(self):
        self.spinBox.setRange(-720, 720)
        self.spinBox.valueChanged.connect(self.setSliceValue)

        centralLayout = QVBoxLayout()
        centralLayout.addWidget(self.m_compassBar)
        centralLayout.addWidget(self.spinBox)
        self.setLayout(centralLayout)


if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)
    window = CMain()
    window.show()
    sys.exit(app.exec_())
728x90