You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义双柄QSlider样式表失效:如何适配PyQt5应用风格?

问题描述

我找到了一款自定义双柄滑块,希望在PyQt5应用中使用它,并修改其StyleSheet以适配应用风格。尝试通过slider.setStyleSheet(...)传入目标样式,但未生效,疑惑是否需要从头实现样式设置逻辑,还是存在更简便的修改方式。

附原代码:

from PyQt5 import QtCore, QtGui, QtWidgets

class RangeSlider(QtWidgets.QSlider):
   sliderMoved = QtCore.pyqtSignal(int, int)

    """ A slider for ranges.

        This class provides a dual-slider for ranges, where there is a defined
        maximum and minimum, as is a normal slider, but instead of having a
        single slider value, there are 2 slider values.

        This class emits the same signals as the QSlider base class, with the 
        exception of valueChanged
    """

    def __init__(self, *args):
        super(RangeSlider, self).__init__(*args)

        self._low = self.minimum()
        self._high = self.maximum()

        self.pressed_control = QtWidgets.QStyle.SC_None
        self.tick_interval = 0
        self.tick_position = QtWidgets.QSlider.NoTicks
        self.hover_control = QtWidgets.QStyle.SC_None
        self.click_offset = 0

        # 0 for the low, 1 for the high, -1 for both
        self.active_slider = 0

    def low(self):
        return self._low

    def setLow(self, low: int):
        self._low = low
        self.update()

    def high(self):
        return self._high

    def setHigh(self, high):
        self._high = high
        self.update()

    def paintEvent(self, event):

        painter = QtGui.QPainter(self)
        style = QtWidgets.QApplication.style()

        # draw groove
        opt = QtWidgets.QStyleOptionSlider()
        self.initStyleOption(opt)
        opt.siderValue = 0
        opt.sliderPosition = 0
        opt.subControls = QtWidgets.QStyle.SC_SliderGroove
        if self.tickPosition() != self.NoTicks:
            opt.subControls |= QtWidgets.QStyle.SC_SliderTickmarks
        style.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt, painter, self)
        groove = style.subControlRect(QtWidgets.QStyle.CC_Slider, opt, QtWidgets.QStyle.SC_SliderGroove, self)

        self.initStyleOption(opt)
        opt.subControls = QtWidgets.QStyle.SC_SliderGroove
        opt.siderValue = 0
        opt.sliderPosition = self._low
        low_rect = style.subControlRect(QtWidgets.QStyle.CC_Slider, opt, QtWidgets.QStyle.SC_SliderHandle, self)
        opt.sliderPosition = self._high
        high_rect = style.subControlRect(QtWidgets.QStyle.CC_Slider, opt, QtWidgets.QStyle.SC_SliderHandle, self)

        low_pos = self.__pick(low_rect.center())
        high_pos = self.__pick(high_rect.center())

        min_pos = min(low_pos, high_pos)
        max_pos = max(low_pos, high_pos)

        c = QtCore.QRect(low_rect.center(), high_rect.center()).center()
        if opt.orientation == QtCore.Qt.Horizontal:
            span_rect = QtCore.QRect(QtCore.QPoint(min_pos, c.y() - 2), QtCore.QPoint(max_pos, c.y() + 1))
        else:
            span_rect = QtCore.QRect(QtCore.QPoint(c.x() - 2, min_pos), QtCore.QPoint(c.x() + 1, max_pos))

        if opt.orientation == QtCore.Qt.Horizontal:
            groove.adjust(0, 0, -1, 0)
        else:
            groove.adjust(0, 0, 0, -1)

        if True:  # self.isEnabled():
            highlight = self.palette().color(QtGui.QPalette.Highlight)
            painter.setBrush(QtGui.QBrush(highlight))
            painter.setPen(QtGui.QPen(highlight, 0))
            painter.drawRect(span_rect.intersected(groove))

        for i, value in enumerate([self._low, self._high]):
            opt = QtWidgets.QStyleOptionSlider()
            self.initStyleOption(opt)

            # Only draw the groove for the first slider, so it doesn't get drawn
            # on top of the existing ones every time
            if i == 0:
                opt.subControls = QtWidgets.QStyle.SC_SliderHandle  # | QtWidgets.QStyle.SC_SliderGroove
            else:
                opt.subControls = QtWidgets.QStyle.SC_SliderHandle

            if self.tickPosition() != self.NoTicks:
                opt.subControls |= QtWidgets.QStyle.SC_SliderTickmarks

            if self.pressed_control:
                opt.activeSubControls = self.pressed_control
            else:
                opt.activeSubControls = self.hover_control

            opt.sliderPosition = value
            opt.sliderValue = value
            style.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt, painter, self)

    def mousePressEvent(self, event):
        event.accept()

        style = QtWidgets.QApplication.style()
        button = event.button()

        if button:
            opt = QtWidgets.QStyleOptionSlider()
            self.initStyleOption(opt)

            self.active_slider = -1

            for i, value in enumerate([self._low, self._high]):
                opt.sliderPosition = value
                hit = style.hitTestComplexControl(style.CC_Slider, opt, event.pos(), self)
                if hit == style.SC_SliderHandle:
                    self.active_slider = i
                    self.pressed_control = hit

                    self.triggerAction(self.SliderMove)
                    self.setRepeatAction(self.SliderNoAction)
                    self.setSliderDown(True)
                    break

            if self.active_slider < 0:
                self.pressed_control = QtWidgets.QStyle.SC_SliderHandle
                self.click_offset = self.__pixelPosToRangeValue(self.__pick(event.pos()))
                self.triggerAction(self.SliderMove)
                self.setRepeatAction(self.SliderNoAction)
        else:
            event.ignore()

    def mouseMoveEvent(self, event):
        if self.pressed_control != QtWidgets.QStyle.SC_SliderHandle:
            event.ignore()
            return

        event.accept()
        new_pos = self.__pixelPosToRangeValue(self.__pick(event.pos()))
        opt = QtWidgets.QStyleOptionSlider()
        self.initStyleOption(opt)

        if self.active_slider < 0:
            offset = new_pos - self.click_offset
            self._high += offset
            self._low += offset
            if self._low < self.minimum():
                diff = self.minimum() - self._low
                self._low += diff
                self._high += diff
            if self._high > self.maximum():
                diff = self.maximum() - self._high
                self._low += diff
                self._high += diff
        elif self.active_slider == 0:
            if new_pos >= self._high:
                new_pos = self._high - 1
            self._low = new_pos
        else:
            if new_pos <= self._low:
                new_pos = self._low + 1
            self._high = new_pos

        self.click_offset = new_pos

        self.update()

        self.sliderMoved.emit(self._low, self._high)

    def __pick(self, pt):
        if self.orientation() == QtCore.Qt.Horizontal:
            return pt.x()
        else:
            return pt.y()

    def __pixelPosToRangeValue(self, pos):
        opt = QtWidgets.QStyleOptionSlider()
        self.initStyleOption(opt)
        style = QtWidgets.QApplication.style()

        gr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderGroove, self)
        sr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderHandle, self)

        if self.orientation() == QtCore.Qt.Horizontal:
            slider_length = sr.width()
            slider_min = gr.x()
            slider_max = gr.right() - slider_length + 1
        else:
            slider_length = sr.height()
            slider_min = gr.y()
            slider_max = gr.bottom() - slider_length + 1

        return style.sliderValueFromPosition(self.minimum(), self.maximum(),
                                         pos - slider_min, slider_max - slider_min,
                                         opt.upsideDown)

import sys, os
from PyQt5 import QtCore, QtGui, QtWidgets

def echo(low_value, high_value):
    print(low_value, high_value)

def main(argv):
    app = QtWidgets.QApplication(sys.argv)

    slider = RangeSlider(QtCore.Qt.Horizontal)
    slider.setMinimumHeight(30)
    slider.setMinimum(0)
    slider.setMaximum(255)
    slider.setLow(15)
    slider.setHigh(35)
    slider.setTickPosition(QtWidgets.QSlider.NoTicks)
    slider.sliderMoved.connect(echo)
    slider.setStyleSheet("QSlider::groove:horizontal {\n"
                         "  border-radius: 5px;\n"
                         "  height: 10px;\n"
                         "  margin: 0px;\n"
                         "  background-color: rgb(52, 59, 72);}\n"
                         "QSlider::groove:horizontal:hover {\n"
                         "  background-color: rgb(55, 62, 76);\n}\n"
                         "QSlider::handle:horizontal {\n"
                         "  background-color:#ff5555;\n"
                         "  border: none;\n"
                         "  height: 10px;\n"
                         "  width: 10px;\n"
                         "  margin: 0px;\n  "
                         "  border-radius: 5px;}\n"
                         "QSlider::handle:horizontal:hover {\n"
                         "  background-color: rgb(195, 155, 255);}\n"
                         "QSlider::handle:horizontal:pressed {\n"
                         "  background-color: rgb(255, 121, 198);}\n"
                         "QSlider::groove:vertical {\n"
                         "  border-radius: 5px;\n"
                         "  width: 10px;\n"
                         "  margin: 0px;\n"
                         "  background-color: rgb(52, 59, 72);}\n"
                         "QSlider::groove:vertical:hover {\n"
                         "  background-color: rgb(55, 62, 76);}\n"
                         "QSlider::handle:vertical {\n"
                         "  background-color:#ff5555;\n"
                         "  border: none;\n"
                         "  height: 10px;\b"
                         "  width: 10px;\n"
                         "  margin: 0px;\n"
                         "  border-radius: 5px;}\n"
                         "QSlider::handle:vertical:hover {\n"
                         "  background-color: rgb(195, 155, 255);}\n"
                         "QSlider::handle:vertical:pressed {\n"
                         "  background-color: rgb(255, 121, 198);}")
    slider.show()
    slider.raise_()
    app.exec_()

if __name__ == "__main__":
    main(sys.argv)
问题原因与解决方法

核心问题

这个自定义RangeSlider的paintEvent是手动绘制的,存在三个关键问题导致StyleSheet失效:

  1. 中间高亮区域直接调用palette().color(QPalette.Highlight)绘制,完全忽略StyleSheet配置
  2. 绘制滑块手柄时,未正确传递hover、pressed等状态到QStyleOptionSlider,导致StyleSheet的状态伪类无法生效
  3. 原代码存在笔误:opt.siderValue应为opt.sliderValue,导致样式解析异常

修复步骤

1. 修复代码笔误

在paintEvent中,将两处opt.siderValue = 0修改为opt.sliderValue = 0

2. 让中间高亮区域支持StyleSheet

替换原手动绘制高亮区域的代码,改用Qt内置的SC_SliderSubPage子控件,自动适配StyleSheet:

# 替换原高亮绘制代码
opt_sub = QtWidgets.QStyleOptionSlider(opt)
opt_sub.sliderPosition = self._low
opt_sub.sliderValue = self._low
opt_sub.subControls = QtWidgets.QStyle.SC_SliderSubPage
style.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt_sub, painter, self)

3. 正确传递滑块状态到StyleOption

在绘制两个手柄时,添加状态判断,将hover、pressed状态注入QStyleOptionSlider:

for i, value in enumerate([self._low, self._high]):
    opt = QtWidgets.QStyleOptionSlider()
    self.initStyleOption(opt)

    opt.subControls = QtWidgets.QStyle.SC_SliderHandle
    if self.tickPosition() != self.NoTicks:
        opt.subControls |= QtWidgets.QStyle.SC_SliderTickmarks

    # 注入状态,适配StyleSheet伪类
    if self.pressed_control == QtWidgets.QStyle.SC_SliderHandle and self.active_slider == i:
        opt.state |= QtWidgets.QStyle.State_Sunken
    elif self.hover_control == QtWidgets.QStyle.SC_SliderHandle and self.active_slider == i:
        opt.state |= QtWidgets.QStyle.State_MouseOver

    opt.sliderPosition = value
    opt.sliderValue = value
    style.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt, painter, self)

4. 修正并调整StyleSheet

修复原StyleSheet中的笔误(将\b改为\n),并添加sub-page样式控制中间高亮区域:

QSlider::groove:horizontal {
  border-radius: 5px;
  height: 10px;
  margin: 0px;
  background-color: rgb(52, 59, 72);
}
QSlider::groove:horizontal:hover {
  background-color: rgb(55, 62, 76);
}
QSlider::sub-page:horizontal {
  background-color: #ff5555;
  border-radius: 5px;
}
QSlider::handle:horizontal {
  background-color:#ffffff;
  border: none;
  height: 10px;
  width: 10px;
  margin: 0px;
  border-radius: 5px;
}
QSlider::handle:horizontal:hover {
  background-color: rgb(195, 155, 255);
}
QSlider::handle:horizontal:pressed {
  background-color: rgb(255, 121, 198);
}
/* 垂直滑块样式可参照上述格式调整 */

将上述修改整合到原代码后,StyleSheet即可正常生效,无需从头实现样式逻辑。

内容的提问来源于stack exchange,提问作者Igor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 20:15:54