Issue
I wonder if it is possible and what would be the simplest way to add special ticks at arbitrary indices of a QSlider. Any info or documentation in this direction would be highly appreciated.
To shed a bit of light into what I would like to achieve, here is an application case: I have a QSlider with a given amount of ticks, which I can control using the functions pasted in the figure (screenshot from the documentation):
How could I add the little black triangles, or any other "special" tick, at given tick indices? Also, I will want to redraw them at other arbitrary positions, meaning they won't remain at static positions.
(I started with this SO answer, but from there I could not progress towards my goal).
Solution
The sliderPositionFromValue
cannot be used only with the width (or the height) of the slider itself, because every style draws the slider in different ways, and the space to be considered for the handle movement is usually less than the actual size of the widget.
The actual space used by the handle movement is considered for the whole extent (the pixel metric PM_SliderSpaceAvailable
), which includes the size of the handle itself.
So, you need to consider that space when computing the position of the indicators, subtract half of the handle size and also subtract half of the indicator size (otherwise the top of the triangle won't coincide with the correct position).
This is a corrected version of your answer:
class NewSlider(QtWidgets.QSlider):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._secondary_slider_pos = []
@property
def indicator(self):
try:
return self._indicator
except AttributeError:
image = QtGui.QPixmap('triangle.png')
if self.orientation() == QtCore.Qt.Horizontal:
height = self.height() / 2
if image.height() > height:
image = image.scaledToHeight(
height, QtCore.Qt.SmoothTransformation)
else:
width = self.width() / 2
if image.height() > width:
image = image.scaledToHeight(
width, QtCore.Qt.SmoothTransformation)
rotated = QtGui.QPixmap(image.height(), image.width())
rotated.fill(QtCore.Qt.transparent)
qp = QtGui.QPainter(rotated)
qp.rotate(-90)
qp.drawPixmap(-image.width(), 0, image)
qp.end()
image = rotated
self._indicator = image
return self._indicator
def set_secondary_slider_pos(self, other_pos):
self._secondary_slider_pos = other_pos
self.update()
def paintEvent(self, event):
super().paintEvent(event)
if not self._secondary_slider_pos:
return
style = self.style()
opt = QtWidgets.QStyleOptionSlider()
self.initStyleOption(opt)
# the available space for the handle
available = style.pixelMetric(style.PM_SliderSpaceAvailable, opt, self)
# the extent of the slider handle
sLen = style.pixelMetric(style.PM_SliderLength, opt, self) / 2
x = self.width() / 2
y = self.height() / 2
horizontal = self.orientation() == QtCore.Qt.Horizontal
if horizontal:
delta = self.indicator.width() / 2
else:
delta = self.indicator.height() / 2
minimum = self.minimum()
maximum = self.maximum()
qp = QtGui.QPainter(self)
# just in case
qp.translate(opt.rect.x(), opt.rect.y())
for value in self._secondary_slider_pos:
# get the actual position based on the available space and add half
# the slider handle size for the correct position
pos = style.sliderPositionFromValue(
minimum, maximum, value, available, opt.upsideDown) + sLen
# draw the image by removing half of its size in order to center it
if horizontal:
qp.drawPixmap(pos - delta, y, self.indicator)
else:
qp.drawPixmap(x, pos - delta, self.indicator)
def resizeEvent(self, event):
# delete the "cached" image so that it gets generated when necessary
if (self.orientation() == QtCore.Qt.Horizontal and
event.size().height() != event.oldSize().height() or
self.orientation() == QtCore.Qt.Vertical and
event.size().width() != event.oldSize().width()):
try:
del self._indicator
except AttributeError:
pass
Note that, in any case, this approach has its limits: the triangle will always be shown above the handle, which is not a very good thing from the UX perspective. A proper solution would require a partial rewriting of the paintEvent()
with multiple calls to drawComplexControl
in order to paint all elements in the proper order: the groove and tickmarks, then the indicators and, finally, the handle; it can be done, but you need to add more aspects (including considering the currently active control for visual consistency with the current style).
I suggest you to study the sources of QSlider in order to understand how to do it.
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.