Issue
I have a media player developed in Python and I have created a button that extracts the actual value of the slider's tick, the problem is it would be great if I can see it on the slider just to know the position of the mark I've already extracted. The idea is every time I push the button a mark should appear on the slider in that value's position. Anyone knows how to do it?
Solution
While QSlider offers the possibility to draw tick marks, they are only possible for constant intervals.
A possible solution is to create a custom widget that "embeds" the slider in a layout with an appropriate margin (on top, for example) that will be then painted over with the requested tickmarks. While this solution is effective, it might not be the most consistent, as it results in making the whole slider occupy a lot of space for the ticks.
class TickSlider(QtWidgets.QWidget):
valueChanged = QtCore.pyqtSignal(int)
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 12, 0, 0)
layout.setSpacing(0)
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal, maximum=101)
layout.addWidget(self.slider)
# link slider functions; be aware that this is not usually advised
# and you should better write specific functions that call the actual
# slider methods
self.value = self.slider.value
self.setValue = self.slider.setValue
self.minimum = self.slider.minimum
self.setMinimum = self.slider.setMinimum
self.maximum = self.slider.maximum
self.setMaximum = self.slider.setMaximum
self.ticks = set()
self.slider.valueChanged.connect(self.valueChanged)
def addTick(self, value=None):
if isinstance(value, bool) or value is None:
value = self.slider.value()
if not value in self.ticks and self.minimum() <= value <= self.maximum():
self.ticks.add(value)
self.update()
def removeTick(self, value=None):
if isinstance(value, bool) or value is None:
value = self.slider.value()
if value in self.ticks:
self.ticks.discard(value)
self.update()
def paintEvent(self, event):
if not self.ticks:
return
sliderMin = self.slider.minimum()
sliderMax = self.slider.maximum()
style = self.style()
opt = QtWidgets.QStyleOptionSlider()
self.slider.initStyleOption(opt)
sliderLength = style.pixelMetric(
style.PM_SliderLength, opt, self.slider)
span = style.pixelMetric(
style.PM_SliderSpaceAvailable, opt, self.slider)
qp = QtGui.QPainter(self)
qp.translate(opt.rect.x() + sliderLength / 2, 0)
y = self.slider.y() - 2
for value in sorted(self.ticks):
x = style.sliderPositionFromValue(
sliderMin, sliderMax, value, span)
qp.drawLine(x, 0, x, y)
Another possibility implies some "hacking" around the current style.
The major problem comes from the fact that in some systems and styles, Qt uses pixmaps to draw objects, and this "overpaints" any previous attempt to draw something "under" the actual slider.
The trick is to paint components of the slider individually, so that the ticks can be painted over the background (including the groove in which the handle moves) and before the handle is actually painted.
This approach offers the major benefit of directly using a QSlider instead of a container widget.
class TickOverride(QtWidgets.QSlider):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ticks = set()
self.setTickPosition(self.TicksAbove)
def addTick(self, value=None):
if isinstance(value, bool) or value is None:
value = self.value()
if not value in self.ticks and self.minimum() <= value <= self.maximum():
self.ticks.add(value)
self.update()
def removeTick(self, value=None):
if isinstance(value, bool) or value is None:
value = self.value()
if value in self.ticks:
self.ticks.discard(value)
self.update()
def paintEvent(self, event):
qp = QtWidgets.QStylePainter(self)
opt = QtWidgets.QStyleOptionSlider()
style = self.style()
self.initStyleOption(opt)
# draw the groove only
opt.subControls = style.SC_SliderGroove
qp.drawComplexControl(style.CC_Slider, opt)
sliderMin = self.minimum()
sliderMax = self.maximum()
sliderLength = style.pixelMetric(style.PM_SliderLength, opt, self)
span = style.pixelMetric(style.PM_SliderSpaceAvailable, opt, self)
# if the tick option is set and ticks actually exist, draw them
if self.ticks and self.tickPosition():
qp.save()
qp.translate(opt.rect.x() + sliderLength / 2, 0)
grooveRect = style.subControlRect(
style.CC_Slider, opt, style.SC_SliderGroove)
grooveTop = grooveRect.top() - 1
grooveBottom = grooveRect.bottom() + 1
ticks = self.tickPosition()
bottom = self.height()
for value in sorted(self.ticks):
x = style.sliderPositionFromValue(
sliderMin, sliderMax, value, span)
if ticks & self.TicksAbove:
qp.drawLine(x, 0, x, grooveTop)
if ticks & self.TicksBelow:
qp.drawLine(x, grooveBottom, x, bottom)
qp.restore()
opt.subControls = style.SC_SliderHandle
opt.activeSubControls = style.SC_SliderHandle
if self.isSliderDown():
opt.state |= style.State_Sunken
qp.drawComplexControl(style.CC_Slider, opt)
Note: this answer obviously covers only horizontal sliders for simplicity, and considering the purpose required by the OP.
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.