Search code examples
c++qtqslider

How to realize the slider with circular hollow handle in qt


is there a way to implement the slide bar with a circular hollow handle in QT?

image


Solution

  • 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:

    slider