Bug:
当窗口不处于顶层时,如果点击窗体试图将其置于顶层,窗体自带的白边框会突然显示,最长两秒。
完整性:
尚未添加窗口状态的过渡动画和淡入、淡出动画。
其他问题:
由于 Qt 官方在版本 6 去掉了 QtWin,目前暂未找到 PyQt 6 / PySide 6 的解决方案。
准备工作:
在同目录下放四张照片:
max.png
min.png
normal.png
close.png
完整代码:
main.py
import ctypes.wintypes
import sys
from ctypes import c_int,cast
from ctypes.wintypes import POINT,HWND,UINT,RECT,MSG
import win32api
import win32con
import win32gui
from PyQt5.QtCore import Qt, QSize, QTimer
from PyQt5.QtGui import QCursor, QCloseEvent, QFont, QIcon
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QLabel, QMainWindow
from PyQt5.QtWinExtras import QtWin
from ctypes import Structure, POINTER
class MINMAXINFO(ctypes.Structure):
_fields_ = [
("ptReserved", POINT),
("ptMaxSize", POINT),
("ptMaxPosition", POINT),
("ptMinTrackSize", POINT),
("ptMaxTrackSize", POINT),
]
class PWINDOWPOS(ctypes.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))
]
class Window(QMainWindow):
BORDER_WIDTH = 3
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
# 主屏幕的可用大小(去掉任务栏)
self._rect = QApplication.instance().desktop().availableGeometry(self)
self.title_height = 30
self.setWindowFlags(Qt.Window |
Qt.FramelessWindowHint |
Qt.WindowMinimizeButtonHint |
Qt.WindowMaximizeButtonHint |
Qt.WindowCloseButtonHint |
Qt.WindowSystemMenuHint)
self.timer = QTimer(self)
self.timer.timeout.connect(self.maxornormal2)
self.timer.start(50)
self.setStyleSheet("Window{background:transparent}")
# 增加薄边框
style = win32gui.GetWindowLong(int(self.winId()), win32con.GWL_STYLE)
win32gui.SetWindowLong(int(self.winId()), win32con.GWL_STYLE,
style | win32con.WS_THICKFRAME)
if QtWin.isCompositionEnabled():
# 加上 Aero 边框阴影
QtWin.extendFrameIntoClientArea(self, -1, -1, -1, -1)
else:
QtWin.resetExtendedFrame(self)
def setWindowTitle(self, title: str):
super().setWindowTitle(title)
self.titlebackground = QLabel(self)
self.titlebackground.setStyleSheet("QLabel {"
"background-color:rgba(30,30,30,1);"
"}")
self.titleBar = QLabel(f" {title}", self)
self.titleBar.setStyleSheet("QLabel {"
'font: normal normal 14px "微软雅黑";'
"background-color:rgba(0,0,0,0);"
"color:rgb(255,255,255)"
"}")
self.titlebackground.setGeometry(0,0,self.size().width(), self.title_height)
self.titleBar.setGeometry(self.title_height, 0, self.size().width(), self.title_height)
self.raiseEvent()
self.setMinimumHeight(self.title_height)
self.setthreebutton()
def setWindowIcon(self, icon):
super().setWindowIcon(icon)
self.iconimage = QPushButton(self)
self.iconimage.setIcon(icon)
self.iconimage.setIconSize(QSize(self.title_height, self.title_height))
self.iconimage.setFixedSize(self.title_height, self.title_height)
self.raiseEvent()
def setthreebutton(self):
clqss = ("QPushButton {"
"background-color:rgba(0,0,0,0);"
"border:none;"
"}"
"QPushButton:hover {"
"background-color:rgba(255,0,0,1);"
"}"
"QPushButton:pressed {"
"background-color:rgba(255,100,100,1);"
"}"
)
blqss = ("QPushButton {"
"background-color:rgba(0,0,0,0);"
"border:none;"
"}"
"QPushButton:hover {"
"background-color:rgba(60,60,60,1);"
"}"
"QPushButton:pressed {"
"background-color:rgba(100,100,100,1);"
"}"
)
psize = QSize(self.title_height + 15, self.title_height)
isize = QSize(self.title_height - 15, self.title_height - 15)
self.exitbutton = QPushButton(self)
self.exitbutton.setIcon(QIcon("close.png"))
self.exitbutton.setIconSize(isize)
self.exitbutton.resize(psize)
self.exitbutton.setStyleSheet(clqss)
self.exitbutton.clicked.connect(self.close)
self.maxbutton = QPushButton(self)
self.maxbutton.setIcon(QIcon("max.png"))
self.maxbutton.setIconSize(isize)
self.maxbutton.resize(psize)
self.maxbutton.setStyleSheet(blqss)
self.maxbutton.clicked.connect(self.maxornormal)
self.minbutton = QPushButton(self)
self.minbutton.setIcon(QIcon("min.png"))
self.minbutton.setIconSize(isize)
self.minbutton.resize(psize)
self.minbutton.setStyleSheet(blqss)
self.minbutton.clicked.connect(self.showMinimized)
def showNormal(self):
super().showNormal()
self.maxbutton.setIcon(QIcon("max.png"))
QCursor.setPos(QCursor.pos().x(), QCursor.pos().y() - 1)
def showMaximized(self):
super().showMaximized()
self.maxbutton.setIcon(QIcon("normal.png"))
QCursor.setPos(QCursor.pos().x(), QCursor.pos().y() - 1)
def maxornormal(self):
if self.isMaximized():
self.showNormal()
else:
self.showMaximized()
def maxornormal2(self):
if self.isMaximized():
self.maxbutton.setIcon(QIcon("normal.png"))
else:
self.maxbutton.setIcon(QIcon("max.png"))
def raiseEvent(self):
try:
self.titlebackground.raise_()
self.titleBar.raise_()
self.exitbutton.raise_()
except:
pass
try:
self.iconimage.raise_()
except:
pass
def resizeEvent(self, a0):
super().resizeEvent(a0)
try:
self.titlebackground.setGeometry(0, 0, self.size().width(), self.title_height)
self.titleBar.setGeometry(self.title_height, 0, self.size().width(), self.title_height)
self.exitbutton.move(self.size().width() - self.exitbutton.size().width(), 0)
self.maxbutton.move(self.exitbutton.pos().x() - self.maxbutton.size().width(), 0)
self.minbutton.move(self.maxbutton.pos().x() - self.minbutton.size().width(), 0)
except:
pass
def isWindowMaximized(self, hWnd) -> bool:
""" 判断窗口是否最大化 """
# 返回指定窗口的显示状态以及被恢复的、最大化的和最小化的窗口位置,返回值为元组
windowPlacement = win32gui.GetWindowPlacement(hWnd)
if not windowPlacement:
return False
return windowPlacement[1] == win32con.SW_MAXIMIZE
def nativeEvent(self, eventType, message):
""" 处理windows消息 """
msg = MSG.from_address(message.__int__())
pos = QCursor.pos()
x = pos.x() - self.frameGeometry().x()
y = pos.y() - self.frameGeometry().y()
if msg.message == win32con.WM_NCHITTEST:
xPos = win32api.LOWORD(msg.lParam) - self.frameGeometry().x()
yPos = win32api.HIWORD(msg.lParam) - self.frameGeometry().y()
w, h = self.width(), self.height()
lx = xPos < self.BORDER_WIDTH
rx = xPos + 9 > w - self.BORDER_WIDTH
ty = yPos < self.BORDER_WIDTH
by = yPos > h - self.BORDER_WIDTH
if lx and ty:
return True, win32con.HTTOPLEFT
elif rx and by:
return True, win32con.HTBOTTOMRIGHT
elif rx and ty:
return True, win32con.HTTOPRIGHT
elif lx and by:
return True, win32con.HTBOTTOMLEFT
elif ty:
return True, win32con.HTTOP
elif by:
return True, win32con.HTBOTTOM
elif lx:
return True, win32con.HTLEFT
elif rx:
return True, win32con.HTRIGHT
elif self.pos().y() <= QCursor.pos().y() <= self.pos().y() + self.title_height and self.childAt(x,y) == self.titleBar:
return True, win32con.HTCAPTION
elif msg.message == win32con.WM_NCCALCSIZE:
if self.isWindowMaximized(msg.hWnd):
self.monitorNCCALCSIZE(msg)
return True, 0
elif msg.message == win32con.WM_GETMINMAXINFO:
if self.isWindowMaximized(msg.hWnd):
window_rect = win32gui.GetWindowRect(msg.hWnd)
if not window_rect:
return False, 0
# 获取显示器句柄
monitor = win32api.MonitorFromRect(window_rect)
if not monitor:
return False, 0
# 获取显示器信息
monitor_info = win32api.GetMonitorInfo(monitor)
monitor_rect = monitor_info['Monitor']
work_area = monitor_info['Work']
# 将lParam转换为MINMAXINFO指针
info = cast(msg.lParam, POINTER(MINMAXINFO)).contents
# 调整窗口大小
info.ptMaxSize.x = work_area[2] - work_area[0]
info.ptMaxSize.y = work_area[3] - work_area[1] + 10
info.ptMaxTrackSize.x = info.ptMaxSize.x
info.ptMaxTrackSize.y = info.ptMaxSize.y
# 修改左上角坐标
info.ptMaxPosition.x = abs(window_rect[0] - monitor_rect[0])
info.ptMaxPosition.y = abs(window_rect[1] - monitor_rect[1])
return True, 1
elif msg.message == win32con.WM_SYSKEYDOWN:
if msg.wParam == win32con.VK_F4:
QApplication.sendEvent(self, QCloseEvent())
return True, 0
return QWidget.nativeEvent(self, eventType, message)
def monitorNCCALCSIZE(self, msg: MSG):
""" 处理 WM_NCCALCSIZE 消息 """
monitor = win32api.MonitorFromWindow(msg.hWnd)
# 如果没有保存显示器信息就直接返回,否则接着调整窗口大小
if monitor is None and not self.monitor_info:
return
elif monitor is not None:
self.monitor_info = win32api.GetMonitorInfo(monitor)
# 调整窗口大小
params = cast(msg.lParam, POINTER(NCCALCSIZE_PARAMS)).contents
params.rgrc[0].left = self.monitor_info['Work'][0]
params.rgrc[0].top = self.monitor_info['Work'][1]
params.rgrc[0].right = self.monitor_info['Work'][2]
params.rgrc[0].bottom = self.monitor_info['Work'][3]
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.setWindowTitle("Win32PyQtFramelessWindow 示例")
w.resize(800,450)
w.show()
sys.exit(app.exec_())
标签:窗口边框,自定义,PySide,title,self,height,maxbutton,win32con,import
From: https://blog.csdn.net/2402_84665876/article/details/141487635