언어/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