目录
一、自定义窗口缩放(使用者可直接Copy)
import enum
from dataclasses import dataclass
from PySide6.QtCore import Qt
from PySide6.QtGui import QPainter, QPaintEvent
from PySide6.QtWidgets import QApplication, QWidget
# 边缘类型枚举
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, parent=None):
super().__init__(parent)
self.setMouseTracking(True)
self.resize(100, 100)
self.setWindowFlags(
Qt.Window
| Qt.FramelessWindowHint
| Qt.WindowSystemMenuHint
| Qt.WindowMinimizeButtonHint
| Qt.WindowMaximizeButtonHint
)
# 设置窗体透明
self.setAttribute(Qt.WA_TranslucentBackground)
# 窗体边缘尺寸(出现缩放标记的范围)
self.edge_size = 5
# 窗体圆角X半径
self.xRadius = 5
# 窗体圆角Y半径
self.yRadius = 5
# 窗体的最小宽度
self.min_width = 50
# 窗体的最小高度
self.min_height = 50
# 拖放标记
self.edge_press = EdgePress()
# 顶部可移动窗口高度
self.move_event_height = 40
# 设置边框圆角
def paintEvent(self, event: QPaintEvent) -> None:
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing) # 设置抗锯齿,不然边框会有明显锯齿
painter.setBrush(Qt.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.SizeHorCursor)
elif edges == Edge.TopEdge or edges == Edge.BottomEdge:
self.setCursor(Qt.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)
案例:
if __name__ == '__main__':
# 只需继承ElWindow即可
class MindWindow(ElWindow):
def __init__(self):
super(MindWindow, self).__init__()
pass
app = QApplication([])
window = MindWindow()
window.show()
app.exec()
二、自定义窗口标题(使用者可直接Copy)
from PySide6 import QtGui, QtWidgets
from PySide6.QtCore import Qt, QSize
from PySide6.QtGui import QIcon, QFont
from PySide6.QtWidgets import QPushButton, QLabel, QWidget, QApplication
from components.el_title_bar.el_window import ElWindow
# 自定义标题栏
class ElTitleBar:
def __init__(self, window: QtWidgets, window_title: str = ""):
self.window = window
# 默认标题栏高度 必须设
self.DEFAULT_TITILE_BAR_HEIGHT = 40
# 存储父类的双击事件
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_TITILE_BAR_HEIGHT, 0, 0)
# 1.设置无边框 和 透明背景 无边框必须设置全,不然会导致点击任务栏不能最小化窗口
self.window.setWindowFlags(
Qt.Window
| Qt.FramelessWindowHint
| Qt.WindowSystemMenuHint
| Qt.WindowMinimizeButtonHint
| Qt.WindowMaximizeButtonHint
)
# self.window.setAttribute(Qt.WA_TranslucentBackground)
# 2.添加自定义的标题栏到最顶部
self.title = QLabel(window_title, self.window)
self.title.setFont(QFont("STKaiti", 14))
# 3.设置标题栏样式
self.setStyle()
# 4.添加按钮
# 添加关闭按钮
self.close_btn = QPushButton("", self.window)
self.close_btn.setGeometry(self.window.width() - 33, 10, 20, 20)
# 添加最大化按钮
self.max_btn = QPushButton("", self.window)
self.max_btn.setGeometry(self.window.width() - 66, 10, 20, 20)
# 添加最小化按钮
self.min_btn = QPushButton("", self.window)
self.min_btn.setGeometry(self.window.width() - 99, 10, 20, 20)
# 设置三个按钮的鼠标样式
self.close_btn.setCursor(Qt.PointingHandCursor)
self.max_btn.setCursor(Qt.PointingHandCursor)
self.min_btn.setCursor(Qt.PointingHandCursor)
# 设置三个按钮的样式
self.close_btn.setStyleSheet(
"QPushButton{border-image:url('./images/close.png');background:#ff625f;border-radius:10px;}"
"QPushButton:hover{background:#eb4845;}"
)
self.max_btn.setStyleSheet(
"QPushButton{border-image:url('./images/max.png');background:#ffbe2f;border-radius:10px;}"
"QPushButton:hover{background:#ecae27;}"
)
self.min_btn.setStyleSheet(
"QPushButton{border-image:url('./images/min.png');background:#29c941;border-radius:10px;}"
"QPushButton:hover{background:#1ac033;}"
)
# 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(
"QPushButton{border-image:url('./images/max.png');background:#ffbe2f;border-radius:10px;}"
"QPushButton:hover{background:#ecae27;}"
)
return self.window_max_size
return None
else:
if self.window.isMaximized():
self.window.showNormal()
self.max_btn.setStyleSheet(
"QPushButton{border-image:url('./images/max.png');background:#ffbe2f;border-radius:10px;}"
"QPushButton:hover{background:#ecae27;}"
)
else:
self.window.showMaximized()
self.max_btn.setStyleSheet(
"QPushButton{border-image:url('./images/restore.png');background:#ffbe2f;border-radius:10px;}"
"QPushButton:hover{background:#ecae27;}"
)
# 记录最大化窗口的大小 用于返回最大化时拖动窗口恢复前的大小 这个程序循环帧会取不到恢复前的宽度
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 = """
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,stop:0 #fafafa,stop:1 #d1d1d1);
color:#333333;padding:10px;border:1px solid #c6c6c6;
border-top-left-radius:4px;
border-top-right-radius:4px;
"""
self.title.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
# 设置样式
self.title.setStyleSheet(DEFAULT_STYLE if not style else DEFAULT_STYLE + style)
# 设置大小
self.title.setGeometry(0, 0, self.window.width(), self.DEFAULT_TITILE_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_TITILE_BAR_HEIGHT)
return self.resizeEvent_parent(a0)
案例:(此时还不能移动和缩放)
if __name__ == '__main__':
class MindWindow(QWidget):
def __init__(self):
super(MindWindow, self).__init__()
# 挂载标题栏即可
ElTitleBar(self, window_title="地址管理")
app = QApplication([])
window = MindWindow()
window.show()
app.exec()
三、缩放和标题一起使用案例:
if __name__ == '__main__':
# 继承ElWindow
class MindWindow(ElWindow):
def __init__(self):
super(MindWindow, self).__init__()
self.resize(400,200)
# 挂载标题栏
ElTitleBar(self, window_title="地址管理")
app = QApplication([])
window = MindWindow()
window.show()
app.exec()