is there a way to implement the slide bar with a circular hollow handle in QT?
Asked
Active
Viewed 1,248 times
0

Parisa.H.R
- 3,303
- 3
- 19
- 38

zhiyi
- 111
- 1
- 9
-
What's the problem with setting the handle to simply be hollow like that? – hyde Aug 23 '21 at 05:40
-
@hyde hi, if it is directly set to be hollow, you will see the groove below. – zhiyi Aug 23 '21 at 05:42
-
So you want the hollow part to "see through" the groove. If that's a hard requirement, you *may* have to implement your own drawing, by sublcassing `QAbstractScrollbar` or just `QScrollBar` (or if it is a QML ui, just do a custom scrollbar component in QML, not terribly hard and a good exercise in QML). – hyde Aug 23 '21 at 05:47
-
you want that in c++ or QML? – Parisa.H.R Aug 23 '21 at 05:53
-
1@Parisa.H.R QSlider belong to QtWidgets – eyllanesc Aug 23 '21 at 06:02
-
1@Parisa.H.R I want to implement it in c++. – zhiyi Aug 23 '21 at 07:12
2 Answers
4
After consulting the documentation on QStyle
, I decided to use QStyle
to redraw QSlider
to achieve the desired effect. Here is the implementation code.
# 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)
Here is an example about how to use HollowHandleStyle
:
# 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, 200)
self.setStyleSheet("Demo{background: rgb(34, 180, 127)}")
style = {
"sub-page.color": QColor(70, 23, 180),
"handle.ring-width": 4,
"handle.hollow-radius": 6,
"handle.margin": 4
}
self.slider = QSlider(Qt.Horizontal, self)
self.slider.setStyle(HollowHandleStyle(style))
self.slider.resize(200, 28)
self.slider.move(50, 100)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Demo()
w.show()
sys.exit(app.exec_())
The following figure shows the final effect:

zhiyi
- 111
- 1
- 9
3
you can transparent background of Handel and set a border for it by stylesheet but in this way you will see a line of the slider, if you choose the background color of the slider the same as its backend you can fix it by the stylesheet, else you should write it with paint and create your custom slider.
QSlider::groove:horizontal {
border-radius: 1px;
height: 2px;
margin: 20px;
background-color: rgb(52, 59, 72);
}
QSlider::groove:horizontal:hover {
background-color: rgb(55, 62, 76);
}
QSlider::handle:horizontal {
background-color: transparent;
border: 4px solid black;
height: 22px;
width: 22px;
margin: -14px 0;
border-radius: 14px;
padding: -14px 0px;
}
ui code :
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>561</width>
<height>238</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(255, 255, 255);</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSlider" name="horizontalSlider">
<property name="minimumSize">
<size>
<width>0</width>
<height>70</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QSlider::groove:horizontal {
border-radius: 1px;
height: 2px;
margin: 20px;
background-color: rgb(52, 59, 72);
}
QSlider::groove:horizontal:hover {
background-color: rgb(55, 62, 76);
}
QSlider::handle:horizontal {
background-color: white;
border: 4px solid black;
height: 22px;
width: 22px;
margin: -14px 0;
border-radius: 14px;
padding: -14px 0px;
}
</string>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>561</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

Parisa.H.R
- 3,303
- 3
- 19
- 38