如何在pyqt中自定义SwitchButton
前言
网上有很多 SwitchButton
的实现方式,大部分是通过重写 paintEvent()
来实现的,感觉灵活性不是很好。所以希望实现一个可以联合使用 qss
来更换样式的 SwitchButton
。仿照 Fluent Design 中样式,最终实现效果如下(动图中没有展示按钮禁用时的样式):
实现过程
一个 SwitchButton
可以拆分为左边的指示器 Indicator
和右边的标签 label
,由一个 QHBoxLayout
组织起来。由于 Indicator
比较复杂,所以先来实现 Indicator
。
指示器的实现
为了充分利用 qss
,一个指示器应该具有以下几种基本的伪状态:
hover
pressed
checked
而 QToolButton
正好具有这些伪状态,所以我们通过继承 QToolButton
并重写 paintEvent
来实现 Indicator
。从动图中可以看到,指示器的里面滑块应该随着指示器 checked
状态的改变而改变其位置和颜色。改变位置可以使用 QTimer
来实现,而改变其颜色可以通过定义 sliderOnColor
、sliderOffColor
和 sliderDisabledColor
这几个 pyqtProperty
并搭配 qss
来实现。下面是代码:
class Indicator(QToolButton):
""" 指示器 """
checkedChanged = pyqtSignal(bool)
def __init__(self, parent):
super().__init__(parent=parent)
self.setCheckable(True)
super().setChecked(False)
self.resize(50, 26)
self.__sliderOnColor = QColor(Qt.white)
self.__sliderOffColor = QColor(Qt.black)
self.__sliderDisabledColor = QColor(QColor(155, 154, 153))
self.timer = QTimer(self)
self.padding = self.height()//4
self.sliderX = self.padding
self.sliderRadius = (self.height()-2*self.padding)//2
self.sliderEndX = self.width()-2*self.sliderRadius
self.sliderStep = self.width()/50
self.timer.timeout.connect(self.__updateSliderPos)
def __updateSliderPos(self):
""" 更新滑块位置 """
if self.isChecked():
if self.sliderX+self.sliderStep < self.sliderEndX:
self.sliderX += self.sliderStep
else:
self.sliderX = self.sliderEndX
self.timer.stop()
else:
if self.sliderX-self.sliderStep > self.sliderEndX:
self.sliderX -= self.sliderStep
else:
self.sliderX = self.sliderEndX
self.timer.stop()
self.style().polish(self)
def setChecked(self, isChecked: bool):
""" 设置选中状态 """
if isChecked == self.isChecked():
return
super().setChecked(isChecked)
self.sliderEndX = self.width()-2*self.sliderRadius - \
self.padding if self.isChecked() else self.padding
self.timer.start(5)
def mouseReleaseEvent(self, e):
""" 鼠标点击更新选中状态 """
super().mouseReleaseEvent(e)
self.sliderEndX = self.width()-2*self.sliderRadius - \
self.padding if self.isChecked() else self.padding
self.timer.start(5)
self.checkedChanged.emit(self.isChecked())
def resizeEvent(self, e):
self.padding = self.height()//4
self.sliderRadius = (self.height()-2*self.padding)//2
self.sliderStep = self.width()/50
self.sliderEndX = self.width()-2*self.sliderRadius - \
self.padding if self.isChecked() else self.padding
self.update()
def paintEvent(self, e):
""" 绘制指示器 """
super().paintEvent(e) # 背景和边框由 qss 指定
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
painter.setPen(Qt.NoPen)
if self.isEnabled():
color = self.sliderOnColor if self.isChecked() else self.sliderOffColor
else:
color = self.sliderDisabledColor
painter.setBrush(color)
painter.drawEllipse(self.sliderX, self.padding,
self.sliderRadius*2, self.sliderRadius*2)
def getSliderOnColor(self):
return self.__sliderOnColor
def setSliderOnColor(self, color: QColor):
self.__sliderOnColor = color
self.update()
def getSliderOffColor(self):
return self.__sliderOffColor
def setSliderOffColor(self, color: QColor):
self.__sliderOffColor = color
self.update()
def getSliderDisabledColor(self):
return self.__sliderDisabledColor
def setSliderDisabledColor(self, color: QColor):
self.__sliderDisabledColor = color
self.update()
sliderOnColor = pyqtProperty(QColor, getSliderOnColor, setSliderOnColor)
sliderOffColor = pyqtProperty(QColor, getSliderOffColor, setSliderOffColor)
sliderDisabledColor = pyqtProperty(
QColor, getSliderDisabledColor, setSliderDisabledColor)
开关按钮的实现
SwitchButton
的实现较为简单,只要将 Indicator
和 QLabel
添加到水平布局中即可。不过为了控制 Indicator
和 QLabel
之间的间隔,我们定义一个属性 spacing
,这样就可以搭配 qss
来使用。
class SwitchButton(QWidget):
checkedChanged = pyqtSignal(bool)
def __init__(self, text='关', parent=None):
super().__init__(parent=parent)
self.text = text
self.__spacing = 15
self.hBox = QHBoxLayout(self)
self.indicator = Indicator(self)
self.label = QLabel(text, self)
self.__initWidget()
def __initWidget(self):
""" 初始化小部件 """
# 设置布局
self.hBox.addWidget(self.indicator)
self.hBox.addWidget(self.label)
self.hBox.setSpacing(self.__spacing)
self.hBox.setAlignment(Qt.AlignLeft)
self.setAttribute(Qt.WA_StyledBackground)
self.hBox.setContentsMargins(0, 0, 0, 0)
# 设置默认样式
with open('resource/switch_button.qss', encoding='utf-8') as f:
self.setStyleSheet(f.read())
# 信号连接到槽
self.indicator.checkedChanged.connect(self.checkedChanged)
def isChecked(self):
return self.indicator.isChecked()
def setChecked(self, isChecked: bool):
""" 设置选中状态 """
self.indicator.setChecked(isChecked)
def toggleChecked(self):
""" 切换选中状态 """
self.indicator.setChecked(not self.indicator.isChecked())
def setText(self, text: str):
self.text = text
self.label.setText(text)
self.adjustSize()
def getSpacing(self):
return self.__spacing
def setSpacing(self, spacing: int):
self.__spacing = spacing
self.hBox.setSpacing(spacing)
self.update()
spacing = pyqtProperty(int, getSpacing, setSpacing)
样式表
动图中的样式由下面的样式表给出:
QWidget{
background-color: white;
}
SwitchButton {
qproperty-spacing: 15;
}
SwitchButton > QLabel {
color: black;
font: 18px 'Microsoft YaHei';
}
Indicator {
height: 22px;
width: 50px;
qproperty-sliderOnColor: white;
qproperty-sliderOffColor: black;
qproperty-sliderDisabledColor: rgb(155, 154, 153);
border-radius: 13px;
}
Indicator:!checked {
background-color: transparent;
border: 1px solid rgb(102, 102, 102);
}
Indicator:!checked:hover {
border: 1px solid rgb(51, 51, 51);
background-color: transparent;
}
Indicator:!checked:pressed {
border: 1px solid rgb(0, 0, 0);
background-color: rgb(153, 153, 153);
}
Indicator:checked {
border: 1px solid rgb(0, 153, 188);
background-color: rgb(0, 153, 188);
}
Indicator:checked:hover {
border: 1px solid rgb(72, 210, 242);
background-color: rgb(72, 210, 242);
}
Indicator:checked:pressed {
border: 1px solid rgb(0, 107, 131);
background-color: rgb(0, 107, 131);
}
Indicator:disabled{
border: 1px solid rgb(194, 194, 191);
background-color: rgb(194, 194, 191);
}
测试
下面是测试代码:
# coding:utf-8
import sys
from PyQt5.QtWidgets import QApplication, QWidget
from switch_button import SwitchButton
class Window(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.resize(200, 100)
self.switchButton = SwitchButton(parent=self)
self.switchButton.move(60, 30)
self.switchButton.checkedChanged.connect(self.onCheckedChanged)
with open('resource/switch_button.qss', encoding='utf-8') as f:
self.setStyleSheet(f.read())
def onCheckedChanged(self, isChecked: bool):
""" 开关按钮选中状态改变的槽函数 """
text = '开' if isChecked else '关'
self.switchButton.setText(text)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
后记
开关按钮的实现方式已经介绍完毕,更多关于 Fluent Design 的自定义小部件可以参见 PyQt-Fluent-Widgets,以上~~
如何在pyqt中自定义SwitchButton的更多相关文章
- 如何在pyqt中自定义无边框窗口
前言 之前写过很多关于无边框窗口并给窗口添加特效的博客,按照时间线罗列如下: 如何在pyqt中实现窗口磨砂效果 如何在pyqt中实现win10亚克力效果 如何在pyqt中通过调用SetWindowCo ...
- 如何在 pyqt 中自定义工具提示 ToolTip
前言 Qt 自带的工具提示样式不太好看,就算加了样式表也时不时会失效,同时工具提示没有阴影,看起来就更难受了.所以本篇博客将会介绍自定义工具提示的方法,效果如下图所示: 实现过程 工具提示其实就是一个 ...
- 如何在pyqt中通过调用 SetWindowCompositionAttribute 实现Win10亚克力效果
亚克力效果 在<如何在pyqt中实现窗口磨砂效果>和<如何在pyqt中实现win10亚克力效果>中,我们调用C++ dll来实现窗口效果,这种方法要求电脑上必须装有MSVC.V ...
- 如何在pyqt中在实现无边框窗口的同时保留Windows窗口动画效果(一)
无边框窗体的实现思路 在pyqt中只要 self.setWindowFlags(Qt.FramelessWindowHint) 就可以实现边框的去除,但是没了标题栏也意味着窗口大小无法改变.窗口无法拖 ...
- 如何在pyqt中给无边框窗口添加DWM环绕阴影
前言 在之前的博客<如何在pyqt中通过调用SetWindowCompositionAttribute实现Win10亚克力效果>中,我们实现了窗口的亚克力效果,同时也用SetWindowC ...
- 如何在 pyqt 中捕获并处理 Alt+F4 快捷键
前言 如果在 Windows 系统的任意一个窗口中按下 Alt+F4,默认行为是关闭窗口(或者最小化到托盘).对于使用了亚克力效果的窗口,使用 Alt+F4 最小化到托盘,再次弹出窗口的时候可能出现亚 ...
- 如何在pyqt中实现带动画的动态QMenu
弹出菜单的视觉效果 QLineEdit 原生的菜单弹出效果十分生硬,而且样式很丑.所以照着Groove中单行输入框弹出菜单的样式和动画效果写了一个可以实现动态变化Item的弹出菜单,根据剪贴板的内容是 ...
- 6.1 如何在spring中自定义xml标签
dubbo自定义了很多xml标签,例如<dubbo:application>,那么这些自定义标签是怎么与spring结合起来的呢?我们先看一个简单的例子. 一 编写模型类 package ...
- 如何在pyqt中实现窗口磨砂效果
磨砂效果的实现思路 这两周一直在思考怎么在pyqt上实现窗口磨砂效果,网上搜了一圈,全都是 C++ 的实现方法.正好今天查python的官方文档的时候看到了 ctypes 里面的 HWND,想想倒不如 ...
随机推荐
- Boost的反射库PFR
目录 目录 简介 使用方法 限制 总结 简介 Boost.PFR是一个Boost 1.75版本出的C++14的基础反射库,其使用非常简单,非常便捷,但是适用性也比较差,有很多的地方无法使用,适合比较简 ...
- Dimension reduction in principal component analysis for trees
目录 问题 重要的定义 距离 支撑树 交树 序 tree-line path 重要的性质 其它 Alfaro C A, Aydin B, Valencia C E, et al. Dimension ...
- Java Web项目 慧心人力资源管理系统
题目:慧心人力资源管理系统 文档下载:https://download.csdn.net/download/weixin_44893902/16336711 完整项目下载:https://downlo ...
- Java Web程序设计笔记 • 【第8章 会话跟踪技术进阶】
全部章节 >>>> 本章目录 8.1 Session机制 8.1.1 Session 简介 8.1.2 创建 HttpSession 实例 8.1.3 HttpSesiso ...
- Go项目开源规范
我们为什么一定要知道开源规范呢? 一是,开源项目在代码质量.代码规范.文档等方面,要比非开源项目要求更高,在项目开发中按照开源项目的要求来规范自己的项目,可以更好地驱动项目质量的提高: 二是,一些大公 ...
- CSS基础 行内元素/行内块元素设置垂直对齐方式及常见使用案例
vertical-align 属性值 效果 baseline 基线对齐 top 顶部对齐 middle 中心对齐 bottom 底部对齐 使用案例1:百度搜索框左边和右边底部没有对齐 使用vertic ...
- Selenium_使用execute_script执行JavaScript(11)
selenium的包含的方法已能完全满足UI自动化,但是有些时候又不得不用到执行JS的情况,比如在一个富文本框中输入1W个字,使用send_keys方法将经历漫长的输入过程,如果换成使用JS的inne ...
- 使用 try-catch
ECMA-262 第 3 版引入了 try-catch 语旬,当 try-catch 语句中发生错误时, 浏览器会认为错误已经被处理了 ,因而不会报告错误.对于那些不要求用户懂技术,也不需要用户理解错 ...
- Python调用aiohttp
1. aiohttp安装 pip install aiohttp 1.1. 基本请求用法 async with aiohttp.get('https://github.com') as r: awai ...
- Windows 重装系统,配置 WSL,美化终端,部署 WebDAV 服务器,并备份系统分区
最新博客文章链接 最近发现我 Windows11 上的 WSL 打不开了,一直提示我虚拟化功能没有打开,但我看了下配置,发现虚拟化功能其实是开着的.然后试了各种方法,重装了好几次系统,我一个软件一个软 ...