import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QHBoxLayout,
QVBoxLayout, QLabel, QPushButton, QFrame)
from PyQt6.QtCore import Qt, QPoint, QPropertyAnimation, QEasingCurve, QRect, pyqtSignal
from PyQt6.QtGui import QFont, QColor, QPainter, QPen, QLinearGradient
class AdvancedTitleBar(QWidget):
"""高级自定义标题栏(带阴影和动画)"""
def __init__(self, parent=None):
super().__init__(parent)
self._parent = parent
self._dragging = False
self._drag_position = QPoint()
self._gradient_pos = 0.0
self._button_animations = {}
# 初始化UI组件(在__init__中定义所有实例变量)
self._icon_label = None
self._title_label = None
self._minimize_btn = None
self._maximize_btn = None
self._close_btn = None
self._gradient_animation = None
# 初始化UI
self._init_ui()
# 应用样式
self._apply_style()
# 设置动画
self._setup_animations()
def _init_ui(self):
"""初始化UI"""
# 设置标题栏固定高度
self.setFixedHeight(50)
self.setObjectName("advancedTitleBar")
# 创建主布局
layout = QHBoxLayout()
layout.setContentsMargins(15, 0, 15, 0)
layout.setSpacing(10)
# 窗口图标
self._icon_label = QLabel("🔷")
self._icon_label.setFixedSize(24, 24)
self._icon_label.setObjectName("iconLabel")
self._icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
# 窗口标题
self._title_label = QLabel("高级应用程序")
self._title_label.setObjectName("titleLabel")
title_font = QFont()
title_font.setPointSize(11)
title_font.setBold(True)
self._title_label.setFont(title_font)
# 添加弹性空间
layout.addWidget(self._icon_label)
layout.addWidget(self._title_label)
layout.addStretch()
# 创建窗口控制按钮
self._create_control_buttons()
# 添加按钮到布局
layout.addWidget(self._minimize_btn)
layout.addWidget(self._maximize_btn)
layout.addWidget(self._close_btn)
self.setLayout(layout)
def _create_control_buttons(self):
"""创建窗口控制按钮"""
# 最小化按钮
self._minimize_btn = QPushButton("—")
self._minimize_btn.setFixedSize(30, 30)
self._minimize_btn.setObjectName("minimizeButton")
self._minimize_btn.clicked.connect(self._parent.showMinimized)
# 最大化/还原按钮
self._maximize_btn = QPushButton("□")
self._maximize_btn.setFixedSize(30, 30)
self._maximize_btn.setObjectName("maximizeButton")
self._maximize_btn.clicked.connect(self._toggle_maximize)
# 关闭按钮
self._close_btn = QPushButton("×")
self._close_btn.setFixedSize(30, 30)
self._close_btn.setObjectName("closeButton")
self._close_btn.clicked.connect(self._parent.close)
def _setup_animations(self):
"""设置动画效果"""
# 为每个按钮创建悬停动画
for btn in [self._minimize_btn, self._maximize_btn, self._close_btn]:
# 大小动画
size_animation = QPropertyAnimation(btn, b"geometry")
size_animation.setDuration(200)
size_animation.setEasingCurve(QEasingCurve.Type.OutQuad)
# 透明度动画
opacity_animation = QPropertyAnimation(btn, b"windowOpacity")
opacity_animation.setDuration(200)
opacity_animation.setEasingCurve(QEasingCurve.Type.InOutQuad)
self._button_animations[btn] = {
'size': size_animation,
'opacity': opacity_animation
}
# 标题栏渐变动画
self._gradient_animation = QPropertyAnimation(self, b"gradientPosition")
self._gradient_animation.setDuration(3000)
self._gradient_animation.setLoopCount(-1) # 无限循环
self._gradient_animation.setStartValue(0.0)
self._gradient_animation.setEndValue(1.0)
self._gradient_animation.setEasingCurve(QEasingCurve.Type.InOutSine)
def _get_gradient_position(self):
"""获取渐变位置"""
return self._gradient_pos
def _set_gradient_position(self, pos):
"""设置渐变位置"""
self._gradient_pos = pos
self.update() # 触发重绘,应用新的渐变
# 定义属性(不使用pyqtProperty,使用标准Python属性)
gradientPosition = property(_get_gradient_position, _set_gradient_position)
def paintEvent(self, event):
"""自定义绘制事件,实现渐变背景"""
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
# 创建渐变
gradient = QLinearGradient(0, 0, self.width(), 0)
# 根据渐变位置设置颜色
if self._gradient_pos <= 0.5:
# 从蓝色到紫色的渐变
pos = self._gradient_pos * 2
gradient.setColorAt(0, QColor(33, 150, 243)) # 蓝色
gradient.setColorAt(pos, QColor(156, 39, 176)) # 紫色
gradient.setColorAt(1, QColor(33, 150, 243)) # 蓝色
else:
# 从紫色到蓝色的渐变
pos = (self._gradient_pos - 0.5) * 2
gradient.setColorAt(0, QColor(156, 39, 176)) # 紫色
gradient.setColorAt(pos, QColor(33, 150, 243)) # 蓝色
gradient.setColorAt(1, QColor(156, 39, 176)) # 紫色
# 填充背景
painter.fillRect(self.rect(), gradient)
# 绘制底部边框
painter.setPen(QPen(QColor(255, 255, 255, 30), 1))
painter.drawLine(0, self.height() - 1, self.width(), self.height() - 1)
def enterEvent(self, event):
"""鼠标进入事件"""
# 启动渐变动画
if self._gradient_animation:
self._gradient_animation.start()
# 为所有按钮启动进入动画
for btn in [self._minimize_btn, self._maximize_btn, self._close_btn]:
self._animate_button_enter(btn)
super().enterEvent(event)
def leaveEvent(self, event):
"""鼠标离开事件"""
# 暂停渐变动画
if self._gradient_animation:
self._gradient_animation.stop()
# 为所有按钮启动离开动画
for btn in [self._minimize_btn, self._maximize_btn, self._close_btn]:
self._animate_button_leave(btn)
# 重置渐变
self._gradient_pos = 0.0
self.update()
super().leaveEvent(event)
def _animate_button_enter(self, button):
"""按钮进入动画"""
# 获取当前几何
current_rect = button.geometry()
# 创建放大效果
enlarged_rect = QRect(
current_rect.x() - 2,
current_rect.y() - 2,
current_rect.width() + 4,
current_rect.height() + 4
)
# 大小动画
if button in self._button_animations:
size_anim = self._button_animations[button]['size']
size_anim.stop()
size_anim.setStartValue(current_rect)
size_anim.setEndValue(enlarged_rect)
size_anim.start()
# 透明度动画
opacity_anim = self._button_animations[button]['opacity']
opacity_anim.stop()
opacity_anim.setStartValue(0.8)
opacity_anim.setEndValue(1.0)
opacity_anim.start()
def _animate_button_leave(self, button):
"""按钮离开动画"""
# 获取当前几何
current_rect = button.geometry()
# 创建还原效果
original_rect = QRect(
current_rect.x() + 2,
current_rect.y() + 2,
current_rect.width() - 4,
current_rect.height() - 4
)
# 大小动画
if button in self._button_animations:
size_anim = self._button_animations[button]['size']
size_anim.stop()
size_anim.setStartValue(current_rect)
size_anim.setEndValue(original_rect)
size_anim.start()
# 透明度动画
opacity_anim = self._button_animations[button]['opacity']
opacity_anim.stop()
opacity_anim.setStartValue(1.0)
opacity_anim.setEndValue(0.8)
opacity_anim.start()
def _toggle_maximize(self):
"""切换最大化/还原状态"""
if self._parent.isMaximized():
self._parent.showNormal()
self._maximize_btn.setText("□")
else:
self._parent.showMaximized()
self._maximize_btn.setText("❐")
# 按钮闪烁动画
self._animate_button_flash(self._maximize_btn)
def _animate_button_flash(self, button):
"""按钮闪烁动画"""
flash_animation = QPropertyAnimation(button, b"windowOpacity")
flash_animation.setDuration(300)
flash_animation.setKeyValueAt(0, 1.0)
flash_animation.setKeyValueAt(0.5, 0.5)
flash_animation.setKeyValueAt(1, 1.0)
flash_animation.start()
def _apply_style(self):
"""应用样式表"""
self.setStyleSheet("""
#advancedTitleBar {
background-color: transparent;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
#titleLabel {
color: white;
font-weight: bold;
letter-spacing: 0.5px;
}
#iconLabel {
color: white;
font-size: 18px;
}
#minimizeButton, #maximizeButton {
color: white;
background-color: rgba(255, 255, 255, 0.1);
border: none;
font-size: 16px;
font-weight: bold;
border-radius: 4px;
}
#minimizeButton:hover, #maximizeButton:hover {
background-color: rgba(255, 255, 255, 0.2);
}
#closeButton {
color: white;
background-color: rgba(255, 255, 255, 0.1);
border: none;
font-size: 18px;
font-weight: bold;
border-radius: 4px;
}
#closeButton:hover {
background-color: #f44336;
color: white;
}
""")
# 窗口拖动功能
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self._dragging = True
self._drag_position = event.globalPosition().toPoint()
event.accept()
def mouseMoveEvent(self, event):
if self._dragging:
self._parent.move(self._parent.pos() + event.globalPosition().toPoint() - self._drag_position)
self._drag_position = event.globalPosition().toPoint()
event.accept()
def mouseReleaseEvent(self, event):
self._dragging = False
event.accept()
# 双击标题栏最大化/还原
def mouseDoubleClickEvent(self, event):
self._toggle_maximize()
class MainWindow(QMainWindow):
"""主窗口"""
def __init__(self):
super().__init__()
# 设置窗口无边框
self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
# 设置窗口属性
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
# 初始化实例变量
self._title_bar = None
# 初始化UI
self._init_ui()
def _init_ui(self):
"""初始化UI"""
self.setGeometry(300, 300, 800, 600)
self.setMinimumSize(600, 400)
# 创建中央部件
central_widget = QWidget()
central_widget.setObjectName("centralWidget")
self.setCentralWidget(central_widget)
# 主布局
main_layout = QVBoxLayout(central_widget)
main_layout.setContentsMargins(5, 5, 5, 5)
main_layout.setSpacing(0)
# 添加自定义标题栏
self._title_bar = AdvancedTitleBar(self)
main_layout.addWidget(self._title_bar)
# 内容区域
content_widget = QWidget()
content_widget.setObjectName("contentWidget")
content_layout = QVBoxLayout(content_widget)
content_layout.setContentsMargins(30, 30, 30, 30)
# 添加标题
title_label = QLabel("欢迎使用高级自定义窗口")
title_label.setObjectName("contentTitle")
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
# 添加描述文本
desc_label = QLabel(
"这是一个带有阴影和动画效果的高级自定义标题栏示例。\n"
"功能特点:\n"
"• 动态渐变背景动画\n"
"• 按钮悬停放大效果\n"
"• 窗口拖动功能\n"
"• 双击最大化/还原\n"
"• 阴影效果增强视觉层次"
)
desc_label.setObjectName("contentDesc")
desc_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
desc_label.setWordWrap(True)
# 添加装饰卡片
cards_layout = QHBoxLayout()
cards_layout.setSpacing(20)
for i in range(3):
card = self._create_info_card(i)
cards_layout.addWidget(card)
# 添加到内容布局
content_layout.addWidget(title_label)
content_layout.addSpacing(20)
content_layout.addWidget(desc_label)
content_layout.addSpacing(40)
content_layout.addLayout(cards_layout)
content_layout.addStretch()
main_layout.addWidget(content_widget)
# 应用样式
self._apply_style()
def _create_info_card(self, index):
"""创建信息卡片"""
card = QFrame()
card.setObjectName(f"card{index + 1}")
card.setFixedSize(180, 120)
card.setFrameShape(QFrame.Shape.StyledPanel)
card_layout = QVBoxLayout(card)
card_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
icons = ["🎨", "⚡", "✨"]
texts = ["精美动画", "高性能", "易定制"]
icon_label = QLabel(icons[index])
icon_label.setObjectName("cardIcon")
icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
text_label = QLabel(texts[index])
text_label.setObjectName("cardText")
text_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
card_layout.addWidget(icon_label)
card_layout.addWidget(text_label)
return card
def _apply_style(self):
"""应用样式表"""
self.setStyleSheet("""
#centralWidget {
background-color: transparent;
}
#contentWidget {
background-color: white;
border: 2px solid #2196F3;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
background-color: #f8f9fa;
}
#contentTitle {
font-size: 24px;
font-weight: bold;
color: #1976D2;
margin: 20px 0;
}
#contentDesc {
font-size: 14px;
color: #666;
line-height: 1.8;
background-color: white;
padding: 20px;
border-radius: 8px;
border: 1px solid #e0e0e0;
min-height: 100px;
}
#card1, #card2, #card3 {
background-color: white;
border-radius: 10px;
border: 1px solid #e0e0e0;
}
#card1:hover, #card2:hover, #card3:hover {
border: 2px solid #2196F3;
background-color: #f5f5f5;
}
#cardIcon {
font-size: 40px;
color: #1976D2;
margin-bottom: 10px;
}
#cardText {
font-size: 14px;
font-weight: bold;
color: #333;
}
""")
def paintEvent(self, event):
"""绘制窗口阴影"""
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
# 绘制窗口阴影
rect = self.rect().adjusted(1, 1, -1, -1)
# 绘制半透明阴影
for i in range(5, 0, -1):
opacity = 20 - i * 3
painter.setPen(QPen(QColor(0, 0, 0, opacity), 1))
painter.setBrush(Qt.BrushStyle.NoBrush)
shadow_rect = rect.adjusted(-i, -i, i, i)
painter.drawRoundedRect(shadow_rect, 15, 15)
def main():
"""主函数"""
app = QApplication(sys.argv)
# 创建主窗口
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
自定义PyQt6窗口标题栏
最新推荐文章于 2026-06-19 17:00:49 发布

1051

被折叠的 条评论
为什么被折叠?



