前言
使用 qss 可以很方便地改變 QSlider
的樣式,但是有些情況下 qss 無法滿足我們的需求。比如下圖所示樣式:

如果直接使用 qss 將 handle 的內圓設置為透明背景,會看到 handle 下面的 groove ,而且畫出來的圓環還不圓,如下圖所示:

這時候就需要使用 QStyle
來重繪 QSlider
,關於 QStyle
的介紹可以參見 《QStyle設置界面的外觀和QCommonStyle繼承關系圖講解和使用》,這里不過多贅述(才不是因為我自己也說不清楚)。
實現過程
對於 QSlider
這種比較復雜小部件,需要重寫 QProxyStyle
的 drawComplexControl()
和 subControlRect()
,前者決定了 QSlider
的樣式,后者用來獲取各個子控件所在的矩形區域。為了演示的方便,代碼中只重繪了水平滑動條的樣式。
- 首先繪制 groove 子控件,從動圖中可以看到,要想防止看到 handle 下面的 groove,需將 groove 拆分成兩段來繪制:一段是
sub-page
部分,另一段是add-page
部分; - 接着繪制 handle 子控件,handle 子控件有三個部分:透明內圓、不透明圓環以及透明外邊距。繪制圓環可以實例化
QPainterPath
,addEllipse()
添加兩個不同半徑的同心圓之后再painter.drawPath(path)
。為了響應hover
和pressed
,需要分別在opt.activeSubControls == QProxyStyle.SC_SliderHandle
以及widget.isSliderDown()
時更新滑塊樣式。
下面是具體代碼:
# coding:utf-8
from PyQt5.QtCore import QSize, Qt, pyqtSignal, QPoint, QRectF
from PyQt5.QtGui import QColor, QMouseEvent, QPainter, QPainterPath
from PyQt5.QtWidgets import (QProxyStyle, QSlider, QStyle, QStyleOptionSlider,
QWidget)
class HollowHandleStyle(QProxyStyle):
""" 滑塊中空樣式 """
def __init__(self, config: dict = None):
"""
Parameters
----------
config: dict
樣式配置
"""
super().__init__()
self.config = {
"groove.height": 3,
"sub-page.color": QColor(255, 255, 255),
"add-page.color": QColor(255, 255, 255, 64),
"handle.color": QColor(255, 255, 255),
"handle.ring-width": 4,
"handle.hollow-radius": 6,
"handle.margin": 4
}
config = config if config else {}
self.config.update(config)
# 計算 handle 的大小
w = self.config["handle.margin"]+self.config["handle.ring-width"] + \
self.config["handle.hollow-radius"]
self.config["handle.size"] = QSize(2*w, 2*w)
def subControlRect(self, cc: QStyle.ComplexControl, opt: QStyleOptionSlider, sc: QStyle.SubControl, widget: QWidget):
""" 返回子控件所占的矩形區域 """
if cc != self.CC_Slider or opt.orientation != Qt.Horizontal or sc == self.SC_SliderTickmarks:
return super().subControlRect(cc, opt, sc, widget)
rect = opt.rect
if sc == self.SC_SliderGroove:
h = self.config["groove.height"]
grooveRect = QRectF(0, (rect.height()-h)//2, rect.width(), h)
return grooveRect.toRect()
elif sc == self.SC_SliderHandle:
size = self.config["handle.size"]
x = self.sliderPositionFromValue(
opt.minimum, opt.maximum, opt.sliderPosition, rect.width())
# 解決滑塊跑出滑動條的情況
x *= (rect.width()-size.width())/rect.width()
sliderRect = QRectF(x, 0, size.width(), size.height())
return sliderRect.toRect()
def drawComplexControl(self, cc: QStyle.ComplexControl, opt: QStyleOptionSlider, painter: QPainter, widget: QWidget):
""" 繪制子控件 """
if cc != self.CC_Slider or opt.orientation != Qt.Horizontal:
return super().drawComplexControl(cc, opt, painter, widget)
grooveRect = self.subControlRect(cc, opt, self.SC_SliderGroove, widget)
handleRect = self.subControlRect(cc, opt, self.SC_SliderHandle, widget)
painter.setRenderHints(QPainter.Antialiasing)
painter.setPen(Qt.NoPen)
# 繪制滑槽
painter.save()
painter.translate(grooveRect.topLeft())
# 繪制划過的部分
w = handleRect.x()-grooveRect.x()
h = self.config['groove.height']
painter.setBrush(self.config["sub-page.color"])
painter.drawRect(0, 0, w, h)
# 繪制未划過的部分
x = w+self.config['handle.size'].width()
painter.setBrush(self.config["add-page.color"])
painter.drawRect(x, 0, grooveRect.width()-w, h)
painter.restore()
# 繪制滑塊
ringWidth = self.config["handle.ring-width"]
hollowRadius = self.config["handle.hollow-radius"]
radius = ringWidth + hollowRadius
path = QPainterPath()
path.moveTo(0, 0)
center = handleRect.center() + QPoint(1, 1)
path.addEllipse(center, radius, radius)
path.addEllipse(center, hollowRadius, hollowRadius)
handleColor = self.config["handle.color"] # type:QColor
handleColor.setAlpha(255 if opt.activeSubControls !=
self.SC_SliderHandle else 153)
painter.setBrush(handleColor)
painter.drawPath(path)
# 滑塊按下
if widget.isSliderDown():
handleColor.setAlpha(255)
painter.setBrush(handleColor)
painter.drawEllipse(handleRect)
測試
# coding:utf-8
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QApplication, QWidget, QSlider
class Demo(QWidget):
def __init__(self):
super().__init__()
self.resize(300, 150)
self.setStyleSheet("Demo{background: rgb(184, 106, 106)}")
# 改變默認樣式
style = {
"sub-page.color": QColor(70, 23, 180)
}
self.slider = QSlider(Qt.Horizontal, self)
self.slider.setStyle(HollowHandleStyle(style))
# 需要調整高度
self.slider.resize(200, 28)
self.slider.move(50, 61)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Demo()
w.show()
sys.exit(app.exec_())