提交 02499b8f 编写于 作者: 之一Yo's avatar 之一Yo

第一次提交

上级
# 忽略.vscode文件夹
.vscode/
.VSCodeCounter
# 忽略工作区文件
*.code-workspace
# 忽略python缓存文件
*/__pycache__
*.py[cod]
# 忽略日志文件
log/
*.log
# 忽略测试文件
test.py
# PyQt-Fluent-Widgets
A collection of commonly used widgets.
## Detailed list
<table>
<tbody>
<tr>
<td colspan="2" align="center"></td>
</tr>
<tr>
<td>
Switch Button
</td>
<td>
<code>SwitchButton</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/switch_button.gif" />
</td>
</tr>
<tr>
<td>
Radio Button
</td>
<td>
<code>QRadioButton</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/radio_button.gif" />
</td>
</tr>
<tr>
<td>
Slider
</td>
<td>
<code>LineEdit</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/slider.gif" />
</td>
</tr>
<tr>
<td>
Line Edit
</td>
<td>
<code>LineEdit</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/line_edit.gif" />
</td>
</tr>
<tr>
<td>
Dialog
</td>
<td>
<code>Dialog</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/dialog.gif" />
</td>
</tr>
<tr>
<td>
Dialog with Mask
</td>
<td>
<code>MaskDialog</code>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<img src="screenshot/dialog_with_mask.gif" />
</td>
</tr>
</tbody>
<tr>
<td>
Smooth Scroll Area
</td>
<td>
<code>ScrollArea</code>
</td>
</tr>
</tbody>
</table>
# coding:utf-8
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from dialog import Dialog
class Window(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.resize(1000, 500)
self.btn = QPushButton('点我', parent=self)
self.btn.move(425, 225)
self.btn.clicked.connect(self.showDialog)
with open('resource/demo.qss', encoding='utf-8') as f:
self.setStyleSheet(f.read())
def showDialog(self):
content = '如果将"音乐"文件夹从音乐中移除,则该文件夹不会再出现在音乐中,但不会被删除。'
w = Dialog('删除此文件夹吗?', content, self)
w.exec()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
# coding:utf-8
import textwrap
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QDialog, QLabel, QPushButton
class Dialog(QDialog):
yesSignal = pyqtSignal()
cancelSignal = pyqtSignal()
def __init__(self, title: str, content: str, parent=None):
super().__init__(parent, Qt.WindowTitleHint | Qt.CustomizeWindowHint)
self.resize(300, 200)
self.setWindowTitle(title)
self.content = content
self.titleLabel = QLabel(title, self)
self.contentLabel = QLabel(content, self)
self.yesButton = QPushButton('确定', self)
self.cancelButton = QPushButton('取消', self)
self.__initWidget()
def __initWidget(self):
""" 初始化小部件 """
self.yesButton.setFocus()
self.titleLabel.move(30, 22)
self.contentLabel.setMaximumWidth(900)
self.contentLabel.setText('\n'.join(textwrap.wrap(self.content, 51)))
self.contentLabel.move(30, self.titleLabel.y()+50)
# 设置层叠样式
self.titleLabel.setObjectName("titleLabel")
self.contentLabel.setObjectName("contentLabel")
with open('resource/dialog.qss', encoding='utf-8') as f:
self.setStyleSheet(f.read())
# 调整窗口大小
self.yesButton.adjustSize()
self.cancelButton.adjustSize()
self.contentLabel.adjustSize()
rect = self.contentLabel.rect()
self.setFixedSize(60+rect.right()+self.cancelButton.width(),
self.contentLabel.y()+self.contentLabel.height()+self.yesButton.height()+60)
# 信号连接到槽
self.yesButton.clicked.connect(self.__onYesButtonClicked)
self.cancelButton.clicked.connect(self.__onCancelButtonClicked)
def resizeEvent(self, e):
self.cancelButton.move(self.width()-self.cancelButton.width()-30,
self.height()-self.cancelButton.height()-30)
self.yesButton.move(self.cancelButton.x() -
self.yesButton.width()-30, self.cancelButton.y())
def __onCancelButtonClicked(self):
self.cancelSignal.emit()
self.deleteLater()
def __onYesButtonClicked(self):
self.yesSignal.emit()
self.deleteLater()
QWidget {
background-color: white;
}
QPushButton {
background-color: rgb(204, 204, 204);
padding: 10px 64px 10px 64px;
font: 19px 'Microsoft YaHei';
border: transparent;
border-radius: 4px;
/* height: 40px; */
}
QPushButton:pressed:hover {
background-color: rgb(153, 153, 153);
}
QPushButton:hover {
background-color: rgb(230, 230, 230);
}
QPushButton:disabled {
background-color: rgb(204, 204, 204);
color: rgb(122, 122, 122);
}
\ No newline at end of file
QDialog {
background-color: rgb(0, 126, 153);
}
QLabel {
background-color: transparent;
}
QLabel#titleLabel {
color: white;
font: 33px 'Microsoft YaHei Light';
font-weight: bold;
}
QLabel#contentLabel {
color: white;
font: 18px 'Microsoft YaHei';
font-weight: 400;
}
QPushButton {
color: white;
border: 3px solid white;
border-radius: 0px;
padding: 5px 35px 5px 35px;
background: rgb(0, 126, 153);
font: 18px 'Microsoft YaHei';
font-weight: bold;
}
QPushButton:hover {
background: rgb(14, 179, 214);
}
QPushButton:pressed:focus {
color: black;
background-color: white;
}
QPushButton:focus{
background-color: rgb(0, 154, 187);
}
\ No newline at end of file
# coding:utf-8
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from dialog import MaskDialog
class Window(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.resize(1000, 500)
self.btn = QPushButton('点我', parent=self)
self.btn.move(425, 25)
self.btn.clicked.connect(self.showDialog)
with open('resource/demo.qss', encoding='utf-8') as f:
self.setStyleSheet(f.read())
def showDialog(self):
content = '如果将"音乐"文件夹从音乐中移除,则该文件夹不会再出现在音乐中。'
w = MaskDialog('删除此文件夹吗?', content, self)
w.exec()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
# coding:utf-8
import textwrap
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QDialog, QGraphicsDropShadowEffect, QLabel, QPushButton, QWidget
class MaskDialog(QDialog):
yesSignal = pyqtSignal()
cancelSignal = pyqtSignal()
def __init__(self, title: str, content: str, parent):
super().__init__(parent=parent)
self.content = content
self.windowMask = QWidget(self)
self.widget = QWidget(self)
self.titleLabel = QLabel(title, self.widget)
self.contentLabel = QLabel(content, self.widget)
self.yesButton = QPushButton('确定', self.widget)
self.cancelButton = QPushButton('取消', self.widget)
self.__initWidget()
def __initWidget(self):
""" 初始化小部件 """
self.setWindowFlags(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setGeometry(0, 0, self.parent().width(), self.parent().height())
self.windowMask.resize(self.size())
self.widget.setMaximumWidth(675)
self.titleLabel.move(30, 30)
self.contentLabel.move(30, 70)
self.__setShadowEffect()
self.contentLabel.setText('\n'.join(textwrap.wrap(self.content, 36)))
# 设置层叠样式
self.windowMask.setObjectName('windowMask')
self.titleLabel.setObjectName('titleLabel')
self.contentLabel.setObjectName('contentLabel')
with open('resource/dialog.qss', encoding='utf-8') as f:
self.setStyleSheet(f.read())
# 调节内部对话框大小并居中
self.__initLayout()
# 信号连接到槽
self.yesButton.clicked.connect(self.__onYesButtonClicked)
self.cancelButton.clicked.connect(self.__onCancelButtonClicked)
def __setShadowEffect(self):
""" 添加阴影 """
shadowEffect = QGraphicsDropShadowEffect(self.widget)
shadowEffect.setBlurRadius(50)
shadowEffect.setOffset(0, 5)
self.widget.setGraphicsEffect(shadowEffect)
def __initLayout(self):
""" 初始化布局 """
self.contentLabel.adjustSize()
self.widget.setFixedSize(60+self.contentLabel.width(),
self.contentLabel.y() + self.contentLabel.height()+115)
self.widget.move(self.width()//2 - self.widget.width()//2,
self.height()//2 - self.widget.height()//2)
self.yesButton.resize((self.widget.width() - 68) // 2, 40)
self.cancelButton.resize(self.yesButton.width(), 40)
self.yesButton.move(30, self.widget.height()-70)
self.cancelButton.move(
self.widget.width()-30-self.cancelButton.width(), self.widget.height()-70)
def __onCancelButtonClicked(self):
self.cancelSignal.emit()
self.deleteLater()
def __onYesButtonClicked(self):
self.yesSignal.emit()
self.deleteLater()
QWidget {
background-color: white;
}
QPushButton {
background-color: rgb(204, 204, 204);
padding: 10px 64px 10px 64px;
font: 19px 'Microsoft YaHei';
border: transparent;
border-radius: 4px;
/* height: 40px; */
}
QPushButton:pressed:hover {
background-color: rgb(153, 153, 153);
}
QPushButton:hover {
background-color: rgb(230, 230, 230);
}
QPushButton:disabled {
background-color: rgb(204, 204, 204);
color: rgb(122, 122, 122);
}
\ No newline at end of file
QWidget {
background-color: white;
border: 1px solid rgb(200, 200, 200);
}
QWidget#windowMask{
background-color: rgba(255, 255, 255, 0.6);
border: none;
}
QLabel {
background-color: transparent;
color: black;
font-family: 'Microsoft YaHei';
border: none;
}
QLabel#titleLabel {
font-size: 22px;
}
QLabel#contentLabel {
font-size: 11pt;
}
QPushButton {
background-color: rgb(204, 204, 204);
font: 19px 'Microsoft YaHei';
border-radius: 4px;
border: transparent;
}
QPushButton:pressed:hover {
background-color: rgb(153, 153, 153);
}
QPushButton:hover {
background-color: rgb(230, 230, 230);
}
\ No newline at end of file
# coding:utf-8
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget
from line_edit import LineEdit
class Window(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.resize(400, 260)
self.lineEdit1 = LineEdit(parent=self)
self.lineEdit1.move(50, 25)
with open('resource/style/line_edit.qss', encoding='utf-8') as f:
self.setStyleSheet(f.read())
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
# coding:utf-8
from PyQt5.QtCore import QEasingCurve, QEvent, QPropertyAnimation, QRect, Qt
from PyQt5.QtGui import QContextMenuEvent, QIcon
from PyQt5.QtWidgets import QAction, QApplication, QLineEdit, QMenu
from three_state_button import ThreeStateButton
from window_effect import WindowEffect
class LineEdit(QLineEdit):
""" 包含清空按钮的单行输入框 """
def __init__(self, text=None, parent=None):
super().__init__(text, parent)
# 鼠标点击次数
iconPath_dict = {
"normal": "resource/images/清空_normal.png",
"hover": "resource/images/清空_hover.png",
"pressed": "resource/images/清空_pressed.png",
}
self.clearButton = ThreeStateButton(iconPath_dict, self)
self.menu = LineEditMenu(self)
self.__clickedTime = 0
self.__initWidget()
def __initWidget(self):
""" 初始化小部件 """
self.resize(300, 40)
self.setTextMargins(0, 0, self.clearButton.width(), 0)
self.clearButton.hide()
self.textChanged.connect(self.textChangedEvent)
# 安装事件过滤器
self.clearButton.installEventFilter(self)
# 设置层叠样式
with open('resource/style/line_edit.qss', encoding='utf-8') as f:
self.setStyleSheet(f.read())
def mousePressEvent(self, e):
if e.button() == Qt.LeftButton:
# 如果已经全选了再次点击就取消全选
if self.__clickedTime == 0:
self.selectAll()
else:
# 需要调用父类的鼠标点击事件,不然无法部分选中
super().mousePressEvent(e)
self.setFocus()
# 如果输入框中有文本,就设置为只读并显示清空按钮
if self.text():
self.clearButton.show()
self.__clickedTime += 1
def contextMenuEvent(self, e: QContextMenuEvent):
""" 设置右击菜单 """
self.menu.exec_(e.globalPos())
def focusOutEvent(self, e):
""" 当焦点移到别的输入框时隐藏按钮 """
# 调用父类的函数,消除焦点
super().focusOutEvent(e)
self.__clickedTime = 0
self.clearButton.hide()
def textChangedEvent(self):
""" 如果输入框中文本改变且此时清空按钮不可见,就显示清空按钮 """
if self.text() and not self.clearButton.isVisible():
self.clearButton.show()
def resizeEvent(self, e):
""" 改变大小时需要移动按钮的位置 """
self.clearButton.move(self.width() - self.clearButton.width()-1, 1)
def eventFilter(self, obj, e):
""" 清空按钮按下时清空内容并隐藏按钮 """
if obj == self.clearButton:
if e.type() == QEvent.MouseButtonRelease and e.button() == Qt.LeftButton:
self.clear()
self.clearButton.hide()
return True
return super().eventFilter(obj, e)
class LineEditMenu(QMenu):
""" 单行输入框右击菜单 """
def __init__(self, parent):
super().__init__("", parent)
self.windowEffect = WindowEffect()
self.setWindowFlags(
Qt.FramelessWindowHint | Qt.Popup | Qt.NoDropShadowWindowHint
)
self.setAttribute(Qt.WA_TranslucentBackground | Qt.WA_StyledBackground)
# 不能直接改width
self.animation = QPropertyAnimation(self, b"geometry")
self.animation.setDuration(300)
self.animation.setEasingCurve(QEasingCurve.OutQuad)
def event(self, e: QEvent):
if e.type() == QEvent.WinIdChange:
self.windowEffect.addShadowEffect(self.winId())
return QMenu.event(self, e)
def createActions(self):
# 创建动作
self.cutAct = QAction(
QIcon("resource/images/剪切.png"),
"剪切",
self,
shortcut="Ctrl+X",
triggered=self.parent().cut,
)
self.copyAct = QAction(
QIcon("resource/images/复制.png"),
"复制",
self,
shortcut="Ctrl+C",
triggered=self.parent().copy,
)
self.pasteAct = QAction(
QIcon("resource/images/粘贴.png"),
"粘贴",
self,
shortcut="Ctrl+V",
triggered=self.parent().paste,
)
self.cancelAct = QAction(
QIcon("resource/images/撤销.png"),
"取消操作",
self,
shortcut="Ctrl+Z",
triggered=self.parent().undo,
)
self.selectAllAct = QAction(
"全选", self, shortcut="Ctrl+A", triggered=self.parent().selectAll
)
# 创建动作列表
self.action_list = [
self.cutAct,
self.copyAct,
self.pasteAct,
self.cancelAct,
self.selectAllAct,
]
def exec_(self, pos):
# 删除所有动作
self.clear()
# clear之后之前的动作已不再存在故需重新创建
self.createActions()
# 初始化属性
self.setProperty("hasCancelAct", "false")
width = 176
actionNum = len(self.action_list)
# 访问系统剪贴板
self.clipboard = QApplication.clipboard()
# 根据剪贴板内容是否为text分两种情况讨论
if self.clipboard.mimeData().hasText():
# 再根据3种情况分类讨论
if self.parent().text():
self.setProperty("hasCancelAct", "true")
width = 213
if self.parent().selectedText():
self.addActions(self.action_list)
else:
self.addActions(self.action_list[2:])
actionNum -= 2
else:
self.addAction(self.pasteAct)
actionNum = 1
else:
if self.parent().text():
self.setProperty("hasCancelAct", "true")
width = 213
if self.parent().selectedText():
self.addActions(
self.action_list[:2] + self.action_list[3:])
actionNum -= 1
else:
self.addActions(self.action_list[3:])
actionNum -= 3
else:
return
# 每个item的高度为38px,10为上下的内边距和
height = actionNum * 38 + 10
# 不能把初始的宽度设置为0px,不然会报警
self.animation.setStartValue(QRect(pos.x(), pos.y(), 1, 1))
self.animation.setEndValue(QRect(pos.x(), pos.y(), width, height))
self.setStyle(QApplication.style())
# 开始动画
self.animation.start()
super().exec_(pos)
QWidget{
background-color: white;
}
/* 单行输入框 */
QLineEdit {
padding: 8px 13px 7px 13px;
font: 17px 'Microsoft YaHei';
selection-background-color: rgb(0, 153, 188);
}
QLineEdit:focus {
border: 1px solid rgb(0, 153, 188);
}
QLineEdit:disabled {
color: rgb(122, 122, 122);
border: none;
background-color: rgb(204, 204, 204);
}
/* 右击菜单 */
QMenu {
width: 1px;
height: 1px;
background-color: rgb(242, 242, 242);
font-size: 18px;
font: 18px "Microsoft YaHei";
padding: 5px 0px 5px 0px;
border: 1px solid rgb(196, 199, 200);
}
QMenu::right-arrow {
width: 16px;
height: 16px;
right: 16px;
border-image: url(resource/images/子菜单右箭头.png);
}
QMenu::separator {
height: 1px;
width: 140px;
background: rgba(0, 0, 0, 104);
margin-right: 13px;
margin-top: 5px;
margin-bottom: 5px;
margin-left: 13;
}
QMenu::item {
padding: 7px 20px 7px 26px;
}
QMenu::item:selected {
border-width: 1px;
border-color: rgb(212, 212, 212);
background: rgba(0, 0, 0, 25);
color: black;
}
QMenu[hasCancelAct='true'] {
width: 1px;
}
QMenu::icon {
position: absolute;
left: 16px;
}
# coding:utf-8
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import QToolButton
class ThreeStateButton(QToolButton):
""" 三种状态对应三种图标的按钮 """
def __init__(self, iconPath_dict: dict, parent=None):
"""
Parameters
----------
iconPath_dict: dict
提供按钮 normal、hover、pressed 三种状态下的图标地址
parent:
父级窗口 """
super().__init__(parent)
# 引用图标地址字典
self.iconPath_dict = iconPath_dict
self.resize(QPixmap(self.iconPath_dict['normal']).size())
# 初始化小部件
self.initWidget()
def initWidget(self):
""" 初始化小部件 """
self.setCursor(Qt.ArrowCursor)
self.setIcon(QIcon(self.iconPath_dict['normal']))
self.setIconSize(QSize(self.width(), self.height()))
self.setStyleSheet('border: none; margin: 0px')
def enterEvent(self, e):
""" hover时更换图标 """
self.setIcon(QIcon(self.iconPath_dict['hover']))
def leaveEvent(self, e):
""" leave时更换图标 """
self.setIcon(QIcon(self.iconPath_dict['normal']))
def mousePressEvent(self, e):
""" 鼠标左键按下时更换图标 """
if e.button() == Qt.RightButton:
return
self.setIcon(QIcon(self.iconPath_dict['pressed']))
super().mousePressEvent(e)
def mouseReleaseEvent(self, e):
""" 鼠标左键松开时更换图标 """
if e.button() == Qt.RightButton:
return
self.setIcon(QIcon(self.iconPath_dict['normal']))
super().mouseReleaseEvent(e)
from .window_effect import WindowEffect
\ No newline at end of file
# coding:utf-8
from ctypes import POINTER, Structure, c_int
from ctypes.wintypes import DWORD, HWND, ULONG, POINT, RECT, UINT
from enum import Enum
class WINDOWCOMPOSITIONATTRIB(Enum):
WCA_UNDEFINED = 0,
WCA_NCRENDERING_ENABLED = 1,
WCA_NCRENDERING_POLICY = 2,
WCA_TRANSITIONS_FORCEDISABLED = 3,
WCA_ALLOW_NCPAINT = 4,
WCA_CAPTION_BUTTON_BOUNDS = 5,
WCA_NONCLIENT_RTL_LAYOUT = 6,
WCA_FORCE_ICONIC_REPRESENTATION = 7,
WCA_EXTENDED_FRAME_BOUNDS = 8,
WCA_HAS_ICONIC_BITMAP = 9,
WCA_THEME_ATTRIBUTES = 10,
WCA_NCRENDERING_EXILED = 11,
WCA_NCADORNMENTINFO = 12,
WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
WCA_VIDEO_OVERLAY_ACTIVE = 14,
WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
WCA_DISALLOW_PEEK = 16,
WCA_CLOAK = 17,
WCA_CLOAKED = 18,
WCA_ACCENT_POLICY = 19,
WCA_FREEZE_REPRESENTATION = 20,
WCA_EVER_UNCLOAKED = 21,
WCA_VISUAL_OWNER = 22,
WCA_LAST = 23
class ACCENT_STATE(Enum):
""" 客户区状态枚举类 """
ACCENT_DISABLED = 0,
ACCENT_ENABLE_GRADIENT = 1,
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
ACCENT_ENABLE_BLURBEHIND = 3, # Aero效果
ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, # 亚克力效果
ACCENT_INVALID_STATE = 5
class ACCENT_POLICY(Structure):
""" 设置客户区的具体属性 """
_fields_ = [
("AccentState", DWORD),
("AccentFlags", DWORD),
("GradientColor", DWORD),
("AnimationId", DWORD),
]
class WINDOWCOMPOSITIONATTRIBDATA(Structure):
_fields_ = [
("Attribute", DWORD),
# POINTER()接收任何ctypes类型,并返回一个指针类型
("Data", POINTER(ACCENT_POLICY)),
("SizeOfData", ULONG),
]
class DWMNCRENDERINGPOLICY(Enum):
DWMNCRP_USEWINDOWSTYLE = 0
DWMNCRP_DISABLED = 1
DWMNCRP_ENABLED = 2
DWMNCRP_LAS = 3
class DWMWINDOWATTRIBUTE(Enum):
DWMWA_NCRENDERING_ENABLED = 1
DWMWA_NCRENDERING_POLICY = 2
DWMWA_TRANSITIONS_FORCEDISABLED = 3
DWMWA_ALLOW_NCPAINT = 4
DWMWA_CAPTION_BUTTON_BOUNDS = 5
DWMWA_NONCLIENT_RTL_LAYOUT = 6
DWMWA_FORCE_ICONIC_REPRESENTATION = 7
DWMWA_FLIP3D_POLICY = 8
DWMWA_EXTENDED_FRAME_BOUNDS = 9
DWMWA_HAS_ICONIC_BITMAP = 10
DWMWA_DISALLOW_PEEK = 11
DWMWA_EXCLUDED_FROM_PEEK = 12
DWMWA_CLOAK = 13
DWMWA_CLOAKED = 14
DWMWA_FREEZE_REPRESENTATION = 25
DWMWA_LAST = 16
class MARGINS(Structure):
_fields_ = [
("cxLeftWidth", c_int),
("cxRightWidth", c_int),
("cyTopHeight", c_int),
("cyBottomHeight", c_int),
]
class MINMAXINFO(Structure):
_fields_ = [
("ptReserved", POINT),
("ptMaxSize", POINT),
("ptMaxPosition", POINT),
("ptMinTrackSize", POINT),
("ptMaxTrackSize", POINT),
]
class PWINDOWPOS(Structure):
_fields_ = [
('hWnd', HWND),
('hwndInsertAfter', HWND),
('x', c_int),
('y', c_int),
('cx', c_int),
('cy', c_int),
('flags', UINT)
]
class NCCALCSIZE_PARAMS(Structure):
_fields_ = [
('rgrc', RECT*3),
('lppos', POINTER(PWINDOWPOS))
]
# coding:utf-8
from ctypes import POINTER, WinDLL, byref, c_bool, c_int, pointer, sizeof
from ctypes.wintypes import DWORD, LONG, LPCVOID
from win32 import win32api, win32gui
from win32.lib import win32con
from .c_structures import (ACCENT_POLICY, ACCENT_STATE, DWMNCRENDERINGPOLICY,
DWMWINDOWATTRIBUTE, MARGINS,
WINDOWCOMPOSITIONATTRIB,
WINDOWCOMPOSITIONATTRIBDATA)
class WindowEffect:
""" 调用windows api实现窗口效果 """
def __init__(self):
# 调用api
self.user32 = WinDLL("user32")
self.dwmapi = WinDLL("dwmapi")
self.SetWindowCompositionAttribute = self.user32.SetWindowCompositionAttribute
self.DwmExtendFrameIntoClientArea = self.dwmapi.DwmExtendFrameIntoClientArea
self.DwmSetWindowAttribute = self.dwmapi.DwmSetWindowAttribute
self.SetWindowCompositionAttribute.restype = c_bool
self.DwmExtendFrameIntoClientArea.restype = LONG
self.DwmSetWindowAttribute.restype = LONG
self.SetWindowCompositionAttribute.argtypes = [
c_int,
POINTER(WINDOWCOMPOSITIONATTRIBDATA),
]
self.DwmSetWindowAttribute.argtypes = [c_int, DWORD, LPCVOID, DWORD]
self.DwmExtendFrameIntoClientArea.argtypes = [c_int, POINTER(MARGINS)]
# 初始化结构体
self.accentPolicy = ACCENT_POLICY()
self.winCompAttrData = WINDOWCOMPOSITIONATTRIBDATA()
self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value[
0
]
self.winCompAttrData.SizeOfData = sizeof(self.accentPolicy)
self.winCompAttrData.Data = pointer(self.accentPolicy)
def setAcrylicEffect(self, hWnd, gradientColor: str = "F2F2F230", isEnableShadow: bool = True, animationId: int = 0):
""" 给窗口开启Win10的亚克力效果
Parameter
----------
hWnd: int or `sip.voidptr`
窗口句柄
gradientColor: str
十六进制亚克力混合色,对应rgba四个分量
isEnableShadow: bool
控制是否启用窗口阴影
animationId: int
控制磨砂动画
"""
# 亚克力混合色
gradientColor = (
gradientColor[6:]
+ gradientColor[4:6]
+ gradientColor[2:4]
+ gradientColor[:2]
)
gradientColor = DWORD(int(gradientColor, base=16))
# 磨砂动画
animationId = DWORD(animationId)
# 窗口阴影
accentFlags = DWORD(0x20 | 0x40 | 0x80 |
0x100) if isEnableShadow else DWORD(0)
self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_ACRYLICBLURBEHIND.value[
0
]
self.accentPolicy.GradientColor = gradientColor
self.accentPolicy.AccentFlags = accentFlags
self.accentPolicy.AnimationId = animationId
# 开启亚克力
self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData))
def setAeroEffect(self, hWnd):
""" 给窗口开启Aero效果
Parameter
----------
hWnd: int or `sip.voidptr`
窗口句柄
"""
self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_BLURBEHIND.value[0]
# 开启Aero
self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData))
def moveWindow(self, hWnd):
""" 移动窗口
Parameter
----------
hWnd: int or `sip.voidptr`
窗口句柄
"""
win32gui.ReleaseCapture()
win32api.SendMessage(
hWnd, win32con.WM_SYSCOMMAND, win32con.SC_MOVE + win32con.HTCAPTION, 0
)
def addShadowEffect(self, hWnd):
""" 给窗口添加阴影
Parameter
----------
hWnd: int or `sip.voidptr`
窗口句柄
"""
hWnd = int(hWnd)
self.DwmSetWindowAttribute(
hWnd,
DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value,
byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_ENABLED.value)),
4,
)
margins = MARGINS(-1, -1, -1, -1)
self.DwmExtendFrameIntoClientArea(hWnd, byref(margins))
def addWindowAnimation(self, hWnd):
""" 打开窗口动画效果
Parameters
----------
hWnd : int or `sip.voidptr`
窗口句柄
"""
style = win32gui.GetWindowLong(hWnd, win32con.GWL_STYLE)
win32gui.SetWindowLong(
hWnd,
win32con.GWL_STYLE,
style
| win32con.WS_MAXIMIZEBOX
| win32con.WS_CAPTION
| win32con.CS_DBLCLKS
| win32con.WS_THICKFRAME,
)
def setWindowStayOnTop(self, hWnd, isStayOnTop: bool):
""" 设置窗口是否置顶
Parameters
----------
hWnd : int or `sip.voidptr`
窗口句柄
"""
flag = win32con.HWND_TOPMOST if isStayOnTop else win32con.HWND_NOTOPMOST
win32gui.SetWindowPos(hWnd, flag, 0, 0, 0, 0,
win32con.SWP_NOMOVE |
win32con.SWP_NOSIZE |
win32con.SWP_NOACTIVATE)
# coding:utf-8
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
class Window(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.resize(200, 200)
self.btn = QPushButton('点我', parent=self)
self.btn.move(18, 75)
self.btn.clicked.connect(lambda: print('按下按钮'))
with open('resource/push_button.qss', encoding='utf-8') as f:
self.setStyleSheet(f.read())
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
QWidget{
background-color: white;
}
QPushButton {
background-color: rgb(204, 204, 204);
padding: 10px 64px 10px 64px;
font: 19px 'Microsoft YaHei';
border: transparent;
border-radius: 4px;
/* height: 40px; */
}
QPushButton:pressed:hover {
background-color: rgb(153, 153, 153);
}
QPushButton:hover {
background-color: rgb(230, 230, 230);
}
QPushButton:disabled {
background-color: rgb(204, 204, 204);
color: rgb(122, 122, 122);
}
\ No newline at end of file
# coding:utf-8
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QRadioButton
class Window(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.resize(200, 100)
self.btn1 = QRadioButton('按钮1', self)
self.btn2 = QRadioButton('按钮2', self)
self.btn1.move(57, 15)
self.btn2.move(57, 55)
self.btn1.setChecked(True)
with open('resource/radio_button.qss', encoding='utf-8') as f:
self.setStyleSheet(f.read())
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
QWidget{
background-color: white;
}
QRadioButton {
spacing: 10px;
color: black;
font: 18px 'Microsoft YaHei';
background-color: transparent;
}
QRadioButton::indicator {
background-color: transparent;
}
QRadioButton::indicator:unchecked {
height: 22px;
width: 22px;
border: 2px solid rgb(154, 154, 154);
border-radius: 13px;
}
QRadioButton::indicator:checked {
height: 12px;
width: 12px;
border: 7px solid rgb(0, 153, 188);
border-radius: 13px;
}
QRadioButton::indicator:unchecked:hover {
height: 22px;
width: 22px;
border: 2px solid rgb(120, 120, 120);
border-radius: 13px;
}
QRadioButton::indicator:checked:hover {
height: 12px;
width: 12px;
border: 7px solid rgb(72, 210, 242);
border-radius: 13px;
}
QRadioButton::indicator:unchecked:pressed {
height: 22px;
width: 22px;
background-color: rgb(153, 153, 153);
border: 2px solid rgb(84, 84, 84);
border-radius: 13px;
}
QRadioButton::indicator:checked:pressed {
height: 12px;
width: 12px;
border: 7px solid rgb(0, 107, 131);
border-radius: 13px;
}
\ No newline at end of file
# coding:utf-8
from collections import deque
from enum import Enum
from math import cos, pi
from PyQt5.QtCore import QDateTime, Qt, QTimer, QT_VERSION_STR
from PyQt5.QtGui import QWheelEvent
from PyQt5.QtWidgets import QApplication, QScrollArea
class ScrollArea(QScrollArea):
""" 一个可以平滑滚动的区域 """
def __init__(self, parent=None):
super().__init__(parent)
self.fps = 60
self.duration = 400
self.stepsTotal = 0
self.stepRatio = 1.5
self.acceleration = 1
self.lastWheelEvent = None
self.scrollStamps = deque()
self.stepsLeftQueue = deque()
self.smoothMoveTimer = QTimer(self)
self.smoothMode = SmoothMode(SmoothMode.COSINE)
self.smoothMoveTimer.timeout.connect(self.__smoothMove)
def setSMoothMode(self, smoothMode):
""" 设置滚动模式 """
self.smoothMode = smoothMode
def wheelEvent(self, e: QWheelEvent):
""" 实现平滑滚动效果 """
if self.smoothMode == SmoothMode.NO_SMOOTH or QT_VERSION_STR != '5.13.2':
super().wheelEvent(e)
return
# 将当前时间点插入队尾
now = QDateTime.currentDateTime().toMSecsSinceEpoch()
self.scrollStamps.append(now)
while now - self.scrollStamps[0] > 500:
self.scrollStamps.popleft()
# 根据未处理完的事件调整移动速率增益
accerationRatio = min(len(self.scrollStamps) / 15, 1)
if not self.lastWheelEvent:
self.lastWheelEvent = QWheelEvent(e)
else:
self.lastWheelEvent = e
# 计算步数
self.stepsTotal = self.fps * self.duration / 1000
# 计算每一个事件对应的移动距离
delta = e.angleDelta().y() * self.stepRatio
if self.acceleration > 0:
delta += delta * self.acceleration * accerationRatio
# 将移动距离和步数组成列表,插入队列等待处理
self.stepsLeftQueue.append([delta, self.stepsTotal])
# 定时器的溢出时间t=1000ms/帧数
self.smoothMoveTimer.start(1000 / self.fps)
def __smoothMove(self):
""" 计时器溢出时进行平滑滚动 """
totalDelta = 0
# 计算所有未处理完事件的滚动距离,定时器每溢出一次就将步数-1
for i in self.stepsLeftQueue:
totalDelta += self.__subDelta(i[0], i[1])
i[1] -= 1
# 如果事件已处理完,就将其移出队列
while self.stepsLeftQueue and self.stepsLeftQueue[0][1] == 0:
self.stepsLeftQueue.popleft()
# 构造滚轮事件
e = QWheelEvent(self.lastWheelEvent.pos(),
self.lastWheelEvent.globalPos(),
self.lastWheelEvent.pos(),
self.lastWheelEvent.globalPos(),
round(totalDelta),
Qt.Vertical,
self.lastWheelEvent.buttons(),
Qt.NoModifier)
# 将构造出来的滚轮事件发送给app处理
QApplication.sendEvent(self.verticalScrollBar(), e)
# 如果队列已空,停止滚动
if not self.stepsLeftQueue:
self.smoothMoveTimer.stop()
def __subDelta(self, delta, stepsLeft):
""" 计算每一步的插值 """
m = self.stepsTotal / 2
x = abs(self.stepsTotal - stepsLeft - m)
# 根据滚动模式计算插值
res = 0
if self.smoothMode == SmoothMode.NO_SMOOTH:
res = 0
elif self.smoothMode == SmoothMode.CONSTANT:
res = delta / self.stepsTotal
elif self.smoothMode == SmoothMode.LINEAR:
res = 2 * delta / self.stepsTotal * (m - x) / m
elif self.smoothMode == SmoothMode.QUADRATI:
res = 3 / 4 / m * (1 - x * x / m / m) * delta
elif self.smoothMode == SmoothMode.COSINE:
res = (cos(x * pi / m) + 1) / (2 * m) * delta
return res
class SmoothMode(Enum):
""" 滚动模式 """
NO_SMOOTH = 0
CONSTANT = 1
LINEAR = 2
QUADRATI = 3
COSINE = 4
# coding:utf-8
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget
from slider import Slider
class Window(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.resize(300, 90)
self.hSlider = Slider(Qt.Horizontal, self)
self.hSlider.move(56, 30)
with open('resource/slider.qss', encoding='utf-8') as f:
self.setStyleSheet(f.read())
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
QWidget{
background-color: white;
}
/* 水平滑动条 */
QSlider:horizontal {
height: 26px;
width: 188px;
background-color: transparent;
}
QSlider::groove:horizontal {
width: 188px;
border: none;
background-color: transparent;
}
QSlider::handle:horizontal {
width: 26px;
height: 26px;
border-radius: 13px;
background-color: rgb(0, 153, 188);
}
QSlider::handle:horizontal:hover {
background-color: rgb(72, 210, 242);
}
QSlider::handle:horizontal:pressed {
background-color: rgb(0, 107, 131);
}
QSlider::sub-page:horizontal {
/*已划过部分的颜色*/
background: rgb(0, 153, 187);
margin-top: 11px;
margin-bottom: 11px;
}
QSlider::add-page:horizontal {
background-color: rgb(130, 131, 134);
margin-top: 11px;
margin-bottom: 11px;
}
/* 竖直滑动条 */
QSlider:vertical {
width: 26px;
height: 188px;
background-color: transparent;
}
QSlider::groove:vertical {
width: 26px;
border: none;
background-color: transparent;
}
QSlider::handle:vertical {
width: 26px;
height: 26px;
border-radius: 13px;
background-color: rgb(0, 153, 188);
}
QSlider::handle:vertical:hover {
background-color: rgb(72, 210, 242);
}
QSlider::handle:vertical:pressed {
background-color: rgb(0, 107, 131);
}
QSlider::add-page:vertical {
background: rgb(0, 153, 187);
margin-left: 11px;
margin-right: 11px;
}
QSlider::sub-page:vertical {
background-color: rgb(130, 131, 134);
margin-left: 11px;
margin-right: 11px;
}
\ No newline at end of file
# coding:utf-8
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QSlider
class Slider(QSlider):
""" 可点击的滑动条 """
clicked = pyqtSignal()
def __init__(self, QtOrientation, parent=None):
super().__init__(QtOrientation, parent=parent)
def mousePressEvent(self, e: QMouseEvent):
""" 鼠标点击时移动到鼠标所在处 """
super().mousePressEvent(e)
if self.orientation() == Qt.Horizontal:
value = int(e.pos().x() / self.width() * self.maximum())
else:
value = int((self.height()-e.pos().y()) /
self.height() * self.maximum())
self.setValue(value)
self.clicked.emit()
# coding:utf-8
import sys
from PyQt5.QtCore import Qt
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_())
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;
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{
qproperty-sliderOffColor: rgb(155, 154, 153);
qproperty-sliderOnColor: rgb(155, 154, 153);
border: 1px solid rgb(194, 194, 191);
background-color: rgb(194, 194, 191);
}
\ No newline at end of file
# coding: utf-8
from PyQt5.QtCore import Qt, QTimer, pyqtProperty, pyqtSignal
from PyQt5.QtGui import QColor, QPainter, QMouseEvent
from PyQt5.QtWidgets import (QApplication, QHBoxLayout, QLabel, QToolButton,
QWidget)
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.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)
color = self.__sliderOnColor if self.isChecked() else self.__sliderOffColor
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()
sliderOnColor = pyqtProperty(QColor, getSliderOnColor, setSliderOnColor)
sliderOffColor = pyqtProperty(QColor, getSliderOffColor, setSliderOffColor)
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)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册