我需要在 Qslider 上做几个标记

I need to put several marks on a Qslider

我有一个在 Python 开发的媒体播放器,我创建了一个按钮来提取滑块刻度的实际值,问题是如果我能在滑块上看到它就太好了知道我已经提取的标记的位置。这个想法是每次我按下按钮时,一个标记应该出现在该值位置的滑块上。有人知道怎么做吗?

虽然 QSlider 提供了绘制刻度线的可能性,但它们只能用于固定间隔。

一个可能的解决方案是创建一个自定义小部件,将滑块“嵌入”在具有适当边距(例如顶部)的布局中,然后用请求的刻度标记覆盖。虽然此解决方案很有效,但它可能不是最一致的,因为它会导致整个滑块占用大量 space 的刻度。

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)

另一种可能性意味着对当前样式进行一些“黑客攻击”。
主要问题来自这样一个事实,即在某些系统和样式中,Qt 使用像素图来绘制对象,并且这会“覆盖”任何先前尝试在实际滑块“下方”绘制内容的尝试。

诀窍是单独绘制滑块的组件,这样刻度线就可以绘制覆盖背景(包括凹槽其中手柄移动)和手柄实际涂漆之前。

这种方法的主要好处是直接使用 QSlider 而不是容器小部件。

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)

注意:为简单起见,此答案显然仅涵盖水平滑块,并考虑到 OP 所需的目的。