I am new to PyQt4 and i am making a gui with QSlider.
By default the QSlider doesnt show you the values of your ticks. I searched and didnt find an easy way to do it
I've cooked up a solution that deals with adding labels to sliders, in vertical or horizontal orientations.
Result screenshot:
Code here (note that there are some import changes between PyQt4 and 5).
import sys
from PyQt5 import QtWidgets
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QStyle, QStyleOptionSlider
from PyQt5.QtCore import QRect, QPoint, Qt
class LabeledSlider(QtWidgets.QWidget):
def __init__(self, minimum, maximum, interval=1, orientation=Qt.Horizontal,
labels=None, parent=None):
super(LabeledSlider, self).__init__(parent=parent)
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=QtWidgets.QVBoxLayout(self)
elif orientation==Qt.Vertical:
self.layout=QtWidgets.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=QtWidgets.QSlider(orientation, self)
self.sl.setMinimum(minimum)
self.sl.setMaximum(maximum)
self.sl.setValue(minimum)
if orientation==Qt.Horizontal:
self.sl.setTickPosition(QtWidgets.QSlider.TicksBelow)
self.sl.setMinimumWidth(300) # just to make it easier to read
else:
self.sl.setTickPosition(QtWidgets.QSlider.TicksLeft)
self.sl.setMinimumHeight(300) # just to make it easier to read
self.sl.setTickInterval(interval)
self.sl.setSingleStep(1)
self.layout.addWidget(self.sl)
def paintEvent(self, e):
super(LabeledSlider,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)
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.sl.minimum(),
self.sl.maximum(), 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.sl.minimum(),
self.sl.maximum(), 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
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
frame=QtWidgets.QWidget()
ha=QtWidgets.QHBoxLayout()
frame.setLayout(ha)
w = LabeledSlider(1, 10 , 1, orientation=Qt.Horizontal)
ha.addWidget(w)
frame.show()
sys.exit(app.exec_())
Sadly, there's no easy way to do this. You will have to make it manually. I just had to do it too, so you can have my implementation. Take into account that I only re-implemented what I needed, I hacked a bit the alignments and the valueChanged() API exhibits a very different behaviour.
from PyQt4 import QtCore
from PyQt4.QtGui import QSlider, QWidget, QVBoxLayout, QHBoxLayout, QLabel
__author__ = 'Andres'
class QCustomSlider(QWidget):
def __init__(self, sliderOrientation=None):
super(QCustomSlider, self).__init__()
self._slider = QSlider(sliderOrientation)
self.setLayout(QVBoxLayout())
self._labelTicksWidget = QWidget(self)
self._labelTicksWidget.setLayout(QHBoxLayout())
self._labelTicksWidget.layout().setContentsMargins(0, 0, 0, 0)
self.layout().addWidget(self._slider)
self.layout().addWidget(self._labelTicksWidget)
def setTickLabels(self, listWithLabels):
lengthOfList = len(listWithLabels)
for index, label in enumerate(listWithLabels):
label = QLabel(str(label))
label.setContentsMargins(0, 0, 0, 0)
if index > lengthOfList/3:
label.setAlignment(QtCore.Qt.AlignCenter)
if index > 2*lengthOfList/3:
label.setAlignment(QtCore.Qt.AlignRight)
self._labelTicksWidget.layout().addWidget(label)
def setRange(self, mini, maxi):
self._slider.setRange(mini, maxi)
def setPageStep(self, value):
self._slider.setPageStep(value)
def setTickInterval(self, value):
self._slider.setTickInterval(value)
def setTickPosition(self, position):
self._slider.setTickPosition(position)
def setValue(self, value):
self._slider.setValue(value)
def onValueChangedCall(self, function):
self._slider.valueChanged.connect(function)