前言

使用 qss 可以很方便地改变 QSlider 的样式,但是有些情况下 qss 无法满足我们的需求。比如下图所示样式:

如果直接使用 qss 将 handle 的内圆设置为透明背景,会看到 handle 下面的 groove ,而且画出来的圆环还不圆,如下图所示:

这时候就需要使用 QStyle 来重绘 QSlider,关于 QStyle 的介绍可以参见 《QStyle设置界面的外观和QCommonStyle继承关系图讲解和使用》,这里不过多赘述(才不是因为我自己也说不清楚)。

实现过程

对于 QSlider 这种比较复杂小部件,需要重写 QProxyStyledrawComplexControl()subControlRect(),前者决定了 QSlider 的样式,后者用来获取各个子控件所在的矩形区域。为了演示的方便,代码中只重绘了水平滑动条的样式。

  1. 首先绘制 groove 子控件,从动图中可以看到,要想防止看到 handle 下面的 groove,需将 groove 拆分成两段来绘制:一段是 sub-page 部分,另一段是 add-page 部分;
  2. 接着绘制 handle 子控件,handle 子控件有三个部分:透明内圆、不透明圆环以及透明外边距。绘制圆环可以实例化 QPainterPathaddEllipse() 添加两个不同半径的同心圆之后再 painter.drawPath(path)。为了响应 hoverpressed,需要分别在 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_())

如何在pyqt中使用 QStyle 重绘 QSlider的更多相关文章

  1. 如何在pyqt中自定义无边框窗口

    前言 之前写过很多关于无边框窗口并给窗口添加特效的博客,按照时间线罗列如下: 如何在pyqt中实现窗口磨砂效果 如何在pyqt中实现win10亚克力效果 如何在pyqt中通过调用SetWindowCo ...

  2. 如何在pyqt中实现窗口磨砂效果

    磨砂效果的实现思路 这两周一直在思考怎么在pyqt上实现窗口磨砂效果,网上搜了一圈,全都是 C++ 的实现方法.正好今天查python的官方文档的时候看到了 ctypes 里面的 HWND,想想倒不如 ...

  3. 如何在pyqt中实现win10亚克力效果

    亚克力效果的实现思路 上一篇博客<如何在pyqt中实现窗口磨砂效果> 中实现了win7中的Aero效果,但是和win10的亚克力效果相比,Aero还是差了点内味.所以今天早上又在网上搜了一 ...

  4. 如何在pyqt中通过调用 SetWindowCompositionAttribute 实现Win10亚克力效果

    亚克力效果 在<如何在pyqt中实现窗口磨砂效果>和<如何在pyqt中实现win10亚克力效果>中,我们调用C++ dll来实现窗口效果,这种方法要求电脑上必须装有MSVC.V ...

  5. 如何在pyqt中在实现无边框窗口的同时保留Windows窗口动画效果(一)

    无边框窗体的实现思路 在pyqt中只要 self.setWindowFlags(Qt.FramelessWindowHint) 就可以实现边框的去除,但是没了标题栏也意味着窗口大小无法改变.窗口无法拖 ...

  6. 如何在pyqt中给无边框窗口添加DWM环绕阴影

    前言 在之前的博客<如何在pyqt中通过调用SetWindowCompositionAttribute实现Win10亚克力效果>中,我们实现了窗口的亚克力效果,同时也用SetWindowC ...

  7. 如何在pyqt中实现带动画的动态QMenu

    弹出菜单的视觉效果 QLineEdit 原生的菜单弹出效果十分生硬,而且样式很丑.所以照着Groove中单行输入框弹出菜单的样式和动画效果写了一个可以实现动态变化Item的弹出菜单,根据剪贴板的内容是 ...

  8. 如何在 pyqt 中捕获并处理 Alt+F4 快捷键

    前言 如果在 Windows 系统的任意一个窗口中按下 Alt+F4,默认行为是关闭窗口(或者最小化到托盘).对于使用了亚克力效果的窗口,使用 Alt+F4 最小化到托盘,再次弹出窗口的时候可能出现亚 ...

  9. 『转载』C# winform 中dataGridView的重绘(进度条,虚线,单元格合并等)

    原文转载自:http://hi.baidu.com/suming/item/81e45b1ab9b4585f2a3e2243 最近比较浅的研究了一下dataGridView的重绘,发现里面还是有很多东 ...

随机推荐

  1. 【LeetCode】722. Remove Comments 解题报告(Python)

    [LeetCode]722. Remove Comments 解题报告(Python) 标签: LeetCode 题目地址:https://leetcode.com/problems/remove-c ...

  2. 【LeetCode】154. Find Minimum in Rotated Sorted Array II 解题报告(Python)

    [LeetCode]154. Find Minimum in Rotated Sorted Array II 解题报告(Python) 标签: LeetCode 题目地址:https://leetco ...

  3. 【LeetCode】673. Number of Longest Increasing Subsequence 解题报告(Python)

    [LeetCode]673. Number of Longest Increasing Subsequence 解题报告(Python) 标签(空格分隔): LeetCode 题目地址:https:/ ...

  4. libecc:一个可移植的椭圆曲线密码学库

    libecc:一个可移植的椭圆曲线密码学库 这段时间要写毕设关于椭圆曲线的部分,就参考了一个椭圆曲线库的代码来编写.这个库中的代码的结构.风格和封装在我看来是十分完善的.用起来也比较方便,当作一个密码 ...

  5. 编写Java程序,跑道长 50m,模拟 3 名学生同时在跑道跑步的场景 3名同学相当于3个线程对象

    查看本章节 查看作业目录 需求说明: 跑道长 50m,模拟 3 名学生同时在跑道跑步的场景 3名同学相当于3个线程对象 实现思路: 创建 Java 项目,在项目中创建StudentRunning类,该 ...

  6. shell3-循环

    常用的循环语句有3种: <1>for <2>while <3>utile 1.for语句的格式: for 变量名 in 列表: do 循环体 done 如何生成列表 ...

  7. centos6.5-搭建Apache-地址限制和用户访问

    对于企业内部的WEB管理平台,例如日志分析系统(Awstats)为了更好实现访问控制,在通过客户端访问时,提交一些用户密码或对客户IP地址进行授权,避免服务器的安全隐患. 常见的访问控制方式:应用于配 ...

  8. unittest_认识unittest(1)

    unittest是python内置的单元测试框架,具备编写用例.组织用例.执行用例.输出报告等自动化框架的条件. 使用unittest前需要了解该框架的五个概念: 即test case,test su ...

  9. python 面向对象:封装---对象的属性可以是另一个类创建的对象

    # 对象封装:对象的属性可以是另一个类创建的对象 # 案例需求: # 1.士兵许三多有一把AK47 # 2.士兵用枪射击标靶 # 3.枪能装填和发射子弹 class Gun: # 分析: # 枪的属性 ...

  10. C语言 运算符优先级和结合方向

    运算符优先级和结合方向 初级运算符( ).[ ].->..  高于  单目运算符  高于  算数运算符(先乘除后加减)  高于  关系运算符  高于  逻辑运算符(不包括!)  高于  条件运算 ...