在大佬文子阳的基础上二次开发
@原作者:pyside6 控件一:自定义窗口(缩放和标题)_pyside6设置窗口名称-CSDN博客
qt版本:pyside6
python测试版本:3.9.19
效果:
代码:
import enum
from dataclasses import dataclass
from PySide6 import QtWidgets, QtGui
from PySide6.QtCore import Qt, QSize
from PySide6.QtGui import QPainter, QPaintEvent, QFont
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QSizePolicy, QHBoxLayout, QLayout
class Edge(enum.Flag):
NoEdge: Qt.Edge = 0 # 不在边缘地带
TopEdge: Qt.Edge = 1 # 顶部边缘
LeftEdge: Qt.Edge = 2 # 左部边缘
RightEdge: Qt.Edge = 3 # 右部边缘
BottomEdge: Qt.Edge = 4 # 底部边缘
LeftTopEdge: Qt.Edge = 5 # 左顶部边缘
RightTopEdge: Qt.Edge = 6 # 右顶部边缘
LeftBottomEdge: Qt.Edge = 7 # 左底部边缘
RightBottomEdge: Qt.Edge = 8 # 右底部边缘
@dataclass
class EdgePress:
leftEdgePress: bool = False
rightEdgePress: bool = False
topEdgePress: bool = False
bottomEdgePress: bool = False
leftTopEdgePress: bool = False
rightBottomEdgePress: bool = False
leftBottomEdgePress: bool = False
rightTopEdgePress: bool = False
moveEdgePress: bool = False
# 点击时的窗口初始位置
movePosition = None
class ElWindow(QWidget):
def __init__(self):
super().__init__()
self.__title = None
self.__contentWidget = None
self.setMouseTracking(True)
self.resize(100, 100)
self.setWindowFlags(
Qt.WindowType.Window
| Qt.WindowType.FramelessWindowHint
| Qt.WindowType.WindowSystemMenuHint
| Qt.WindowType.WindowMinimizeButtonHint
| Qt.WindowType.WindowMaximizeButtonHint
)
# 设置窗体透明
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
# 窗体边缘尺寸(出现缩放标记的范围)
self.edge_size = 5
# 窗体圆角X半径
self.xRadius = 5
# 窗体圆角Y半径
self.yRadius = 5
# 窗体的最小宽度
self.min_width = 50
# 窗体的最小高度
self.min_height = 25
# 拖放标记
self.edge_press = EdgePress()
# 顶部可移动窗口高度
self.move_event_height = 25
self.__baseLayout = QVBoxLayout()
self.__baseLayout.setSizeConstraint(QLayout.SizeConstraint.SetMaximumSize)
self.__baseLayout.setContentsMargins(0, 0, 0, 0)
self.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.__baseLayout)
def setTitle(self, title="title", close_url=None, max_url=None, min_url=None, restore_url=None,
title_style=None):
if self.__title is not None:
raise ValueError("title 只能设置一次")
self.__title = title
_ElTitleBar(self, self.__title, close_url, max_url, min_url, restore_url, title_style)
def setWidget(self, widget: QWidget):
if self.__contentWidget is not None:
raise ValueError("widget 只能设置一次")
self.__contentWidget = widget
self.__baseLayout.addWidget(self.__contentWidget)
def widget(self):
return self.__contentWidget
# 设置边框圆角
def paintEvent(self, event: QPaintEvent) -> None:
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing) # 设置抗锯齿,不然边框会有明显锯齿
painter.setBrush(Qt.GlobalColor.white) # 设置窗体颜色
painter.drawRoundedRect(self.rect(), self.xRadius, self.yRadius)
super().paintEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() == Qt.MouseButton.NoButton:
# 没有鼠标按钮按下,检查鼠标位置并更新窗口光标
pos = event.globalPosition().toPoint() - self.pos()
edges = self._get_edges(pos)
if edges == Edge.LeftEdge or edges == Edge.RightEdge:
self.setCursor(Qt.CursorShape.SizeHorCursor)
elif edges == Edge.TopEdge or edges == Edge.BottomEdge:
self.setCursor(Qt.CursorShape.SizeVerCursor)
elif edges == Edge.LeftTopEdge or edges == Edge.RightBottomEdge:
self.setCursor(Qt.CursorShape.SizeFDiagCursor)
elif edges == Edge.LeftBottomEdge or edges == Edge.RightTopEdge:
self.setCursor(Qt.CursorShape.SizeBDiagCursor)
else:
self.setCursor(Qt.CursorShape.ArrowCursor)
elif event.buttons() == Qt.MouseButton.LeftButton:
if self.edge_press.moveEdgePress:
"""移动窗口"""
self.move(event.globalPosition().toPoint() - self.edge_press.movePosition) # 更改窗口位置
elif self._get_edge_press() is not Edge.NoEdge:
"""缩放窗口"""
self._resize_window(event.globalPosition().toPoint() - self.pos())
def mousePressEvent(self, event) -> None:
pos = event.globalPosition().toPoint() - self.pos()
edges = self._get_edges(pos)
if edges == Edge.LeftEdge:
self.edge_press.leftEdgePress = True
elif edges == Edge.RightEdge:
self.edge_press.rightEdgePress = True
elif edges == Edge.TopEdge:
self.edge_press.topEdgePress = True
elif edges == Edge.BottomEdge:
self.edge_press.bottomEdgePress = True
elif edges == Edge.LeftTopEdge:
self.edge_press.leftTopEdgePress = True
elif edges == Edge.RightBottomEdge:
self.edge_press.rightBottomEdgePress = True
elif edges == Edge.LeftBottomEdge:
self.edge_press.leftBottomEdgePress = True
elif edges == Edge.RightTopEdge:
self.edge_press.rightTopEdgePress = True
else:
# 移动事件
if self._get_move_edges(pos):
self.edge_press.moveEdgePress = True
self.edge_press.movePosition = event.globalPosition().toPoint() - self.pos()
self.setCursor(Qt.CursorShape.OpenHandCursor)
def mouseReleaseEvent(self, event) -> None:
self.edge_press.leftEdgePress = False
self.edge_press.rightEdgePress = False
self.edge_press.topEdgePress = False
self.edge_press.bottomEdgePress = False
self.edge_press.leftTopEdgePress = False
self.edge_press.rightBottomEdgePress = False
self.edge_press.leftBottomEdgePress = False
self.edge_press.rightTopEdgePress = False
self.edge_press.moveEdgePress = False
self.setCursor(Qt.CursorShape.ArrowCursor)
def _get_move_edges(self, pos):
"""获取移动窗口事件标记"""
in_move_edge: bool = pos.y() <= self.move_event_height # 是否在可移动区域内
not_in_edges: bool = self._get_edges(pos) == Edge.NoEdge # 是否在非缩放区域内
return in_move_edge and not_in_edges
def _get_edges(self, pos):
"""获取边缘类型"""
edges = Edge.NoEdge
in_left_edge: bool = pos.x() <= self.edge_size # 左
in_right_edge: bool = pos.x() >= (self.width() - self.edge_size) # 右
in_top_edge: bool = pos.y() <= self.edge_size # 上
in_bottom_edge: bool = pos.y() >= (self.height() - self.edge_size) # 下
size = len([i for i in [in_left_edge, in_right_edge, in_top_edge, in_bottom_edge] if i])
if size == 0:
return edges
if size == 1:
if in_left_edge:
return Edge.LeftEdge
if in_right_edge:
return Edge.RightEdge
if in_top_edge:
return Edge.TopEdge
if in_bottom_edge:
return Edge.BottomEdge
if size == 2:
if in_left_edge and in_top_edge:
return Edge.LeftTopEdge
if in_left_edge and in_bottom_edge:
return Edge.LeftBottomEdge
if in_right_edge and in_top_edge:
return Edge.RightTopEdge
if in_right_edge and in_bottom_edge:
return Edge.RightBottomEdge
def _get_edge_press(self):
"""
边缘按下区域类型
"""
if self.edge_press.leftEdgePress:
return Edge.LeftEdge
elif self.edge_press.rightEdgePress:
return Edge.RightEdge
elif self.edge_press.topEdgePress:
return Edge.TopEdge
elif self.edge_press.bottomEdgePress:
return Edge.BottomEdge
elif self.edge_press.leftTopEdgePress:
return Edge.LeftTopEdge
elif self.edge_press.rightBottomEdgePress:
return Edge.RightBottomEdge
elif self.edge_press.leftBottomEdgePress:
return Edge.LeftBottomEdge
elif self.edge_press.rightTopEdgePress:
return Edge.RightTopEdge
else:
return Edge.NoEdge
def _resize_window(self, pos):
"""缩放窗口"""
# 判断按下时的区域是哪一块
edges = self._get_edge_press()
# 获取窗口的初始大小和位置
geo = self.frameGeometry()
x, y, width, height = geo.x(), geo.y(), geo.width(), geo.height()
if edges is Edge.LeftEdge:
width -= pos.x()
if width <= self.min_width:
return
else:
x += pos.x()
elif edges is Edge.RightEdge:
width = pos.x()
if width <= self.min_width:
return
elif edges is Edge.TopEdge:
height -= pos.y()
if height <= self.min_height:
return
else:
y += pos.y()
elif edges is Edge.BottomEdge:
height = pos.y()
if height <= self.min_height:
return
elif edges is Edge.LeftTopEdge:
width -= pos.x()
if width <= self.min_width:
width = geo.width()
else:
x += pos.x()
height -= pos.y()
if height <= self.min_height:
height = geo.height()
else:
y += pos.y()
elif edges is Edge.LeftBottomEdge:
width -= pos.x()
if width <= self.min_width:
width = geo.width()
else:
x += pos.x()
height = pos.y()
if height <= self.min_height:
height = geo.height()
elif edges is Edge.RightTopEdge:
width = pos.x()
if width <= self.min_width:
width = geo.width()
height -= pos.y()
if height <= self.min_height:
height = geo.height()
else:
y += pos.y()
elif edges is Edge.RightBottomEdge:
width = pos.x()
if width <= self.min_width:
width = geo.width()
height = pos.y()
if height <= self.min_height:
height = geo.height()
self.setGeometry(x, y, width, height)
def resizeEvent(self, event):
# 处理窗口大小更改事件
self.setCursor(Qt.CursorShape.ArrowCursor)
# 自定义标题栏
class _ElTitleBar:
__title_style = "background-color: rgb(242, 241, 245);" \
"color:#333333;border:1px solid #c6c6c6;" \
"padding-left:10px;" \
"border-top-left-radius:4px;" \
"border-top-right-radius:4px;"
def closeStyle(self):
return f"border-image:url({self.close_url});background:rgb(242, 241, " \
"245);border-radius:2px;margin-bottom:3px "
def maxStyle(self):
return f"border-image:url({self.max_url});background:rgb(242, 241, " \
"245);border-radius:2px;margin-bottom:3px "
def minStyle(self):
return f"border-image:url({self.min_url});background:rgb(242, 241, " \
"245);border-radius:2px;margin-bottom:3px "
def restoreStyle(self):
return f"border-image:url({self.restore_url});background:rgb(242, 241, " \
"245);border-radius:2px;margin-bottom:3px "
def titleStyle(self):
return self.__title_style
def __init__(self, window: QWidget, window_title: str = "", close_url=None, max_url=None, min_url=None,
restore_url=None, title_style=None):
if close_url is not None:
self.close_url = close_url
if max_url is not None:
self.max_url = max_url
if min_url is not None:
self.min_url = min_url
if restore_url is not None:
self.restore_url = restore_url
if title_style is not None:
self.__title_style = title_style
self.window = window
# 默认标题栏高度 必须设
self.DEFAULT_TITLE_BAR_HEIGHT = 35
# 存储父类的双击事件
self.mouseDoubleClickEvent_parent = self.window.mouseDoubleClickEvent
# 将本类的双击事件赋值给将父类的双击事件
self.window.mouseDoubleClickEvent = self.mouseDoubleClickEvent
# 存储父类的窗口大小改变事件
self.resizeEvent_parent = self.window.resizeEvent
# 将本类的窗口大小改变事件赋值给将父类的窗口大小改变事件
self.window.resizeEvent = self.resizeEvent
# 设置ui文件里main_layout上边距,以免遮挡标题栏
self.window.setContentsMargins(0, self.DEFAULT_TITLE_BAR_HEIGHT, 0, 0)
# 1.设置无边框 和 透明背景 无边框必须设置全,不然会导致点击任务栏不能最小化窗口
self.window.setWindowFlags(
Qt.WindowType.Window
| Qt.WindowType.FramelessWindowHint
| Qt.WindowType.WindowSystemMenuHint
| Qt.WindowType.WindowMinimizeButtonHint
| Qt.WindowType.WindowMaximizeButtonHint
)
# self.window.setAttribute(Qt.WA_TranslucentBackground)
# 2.添加自定义的标题栏到最顶部
self.title = QLabel(window_title, window)
self.title.setFont(QFont("STKaiti", 10))
# 3.设置标题栏样式
self.setStyle()
# 4.添加按钮
# 添加关闭按钮
self.close_btn = QPushButton("", window)
self.close_btn.setGeometry(self.window.width() - 33, 10, 20, 20)
# 添加最大化按钮
self.max_btn = QPushButton("", window)
self.max_btn.setGeometry(self.window.width() - 66, 10, 20, 20)
# 添加最小化按钮
self.min_btn = QPushButton("", window)
self.min_btn.setGeometry(self.window.width() - 99, 10, 20, 20)
# 设置三个按钮的鼠标样式
self.close_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.max_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.min_btn.setCursor(Qt.CursorShape.PointingHandCursor)
# 设置三个按钮的样式
self.close_btn.setStyleSheet(self.closeStyle())
self.max_btn.setStyleSheet(self.maxStyle())
self.min_btn.setStyleSheet(self.minStyle())
# 5.添加工具栏按钮事件
# 关闭按钮点击绑定窗口关闭事件
self.close_btn.clicked.connect(self.window.close)
# 最大化按钮绑定窗口最大化事件
self.max_btn.clicked.connect(self.setMaxEvent)
# 最小化按钮绑定窗口最小化事件
self.min_btn.clicked.connect(self.window.showMinimized)
# 6.记录全屏窗口的大小-ps非常有用
self.window_max_size = None
# 7.设置标题栏鼠标跟踪 鼠标移入触发,不设置,移入标题栏不触发
self.title.setMouseTracking(True)
def setMaxEvent(self, flag=False):
"""
@description 最大化按钮绑定窗口最大化事件和事件 拿出来是因为拖动标题栏时需要恢复界面大小
@param flag 是否是拖动标题栏 bool
@return
"""
if flag:
if self.window.isMaximized():
self.window.showNormal()
self.max_btn.setStyleSheet(self.maxStyle())
return self.window_max_size
return None
else:
if self.window.isMaximized():
self.window.showNormal()
self.max_btn.setStyleSheet(self.maxStyle())
else:
self.window.showMaximized()
self.max_btn.setStyleSheet(self.restoreStyle())
# 记录最大化窗口的大小 用于返回最大化时拖动窗口恢复前的大小 这个程序循环帧会取不到恢复前的宽度
self.window_max_size = QSize(self.window.width(), self.window.height())
def setStyle(self, style: str = ""):
"""
@description 设置自定义标题栏样式
@param
@return
"""
# 想要边框 加上border:1px solid #cccccc;
DEFAULT_STYLE = self.titleStyle()
self.title.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
# 设置样式
self.title.setStyleSheet(DEFAULT_STYLE if not style else DEFAULT_STYLE + style)
# 设置大小
self.title.setGeometry(0, 0, self.window.width(), self.DEFAULT_TITLE_BAR_HEIGHT)
def mouseDoubleClickEvent(self, a0: QtGui.QMouseEvent) -> None:
"""
@description 鼠标双击事件
@param
@return
"""
# 如果双击的是鼠标左键 且在标题栏范围内 则放大缩小窗口
if a0.button() == Qt.MouseButton.LeftButton and a0.position().y() < self.title.height():
self.setMaxEvent()
return self.mouseDoubleClickEvent_parent(a0)
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
"""
@description 窗口缩放事件
@param
@return
"""
# 最大化最小化的时候,需要去改变按钮组位置
self.close_btn.move(self.window.width() - 33, 10)
self.max_btn.move(self.window.width() - 66, 10)
self.min_btn.move(self.window.width() - 99, 10)
self.title.resize(self.window.width(), self.DEFAULT_TITLE_BAR_HEIGHT)
return self.resizeEvent_parent(a0)
使用:
class MainWindow(ElWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(450, 200)
self.setTitle("这是标题",
close_url=":/icon/close.png",
#close_url="../../resources/icon/img.png",
max_url=":/icon/max.png",
min_url=":/icon/min.png",
restore_url=":/icon/restore.png")
self.contentWidget = QWidget()
self.setWidget(self.contentWidget)
self.contentWidget.setContentsMargins(0, 0, 0, 0)
#基本布局
self.__baseLayout = QHBoxLayout(self.contentWidget)
#左面是导航,右面是内容
if __name__ == '__main__':
app = QApplication()
mainWindow = MainWindow()
mainWindow.show()
app.exec()