自定义PyQt6窗口标题栏

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()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值