首页 > 解决方案 > 我需要在 Qslider 上打几个标记

问题描述

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

标签: pythonpyqtqt-designer

解决方案


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

一种可能的解决方案是创建一个自定义小部件,该小部件将滑块“嵌入”到具有适当边距(例如在顶部)的布局中,然后将使用请求的刻度线进行绘制。虽然这个解决方案是有效的,但它可能不是最一致的,因为它会导致整个滑块为刻度占用大量空间。

布局上的滑块

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 所需的目的。


推荐阅读