NewFramelessWindow

This commit is contained in:
Irony 2021-04-23 00:36:03 +08:00
parent 5f33be5f92
commit fc6134aefc
4 changed files with 92 additions and 75 deletions

View file

@ -12,89 +12,105 @@ Created on 2018年4月30日
from Lib.ui_frameless import Ui_FormFrameless
from PyQt5.QtCore import QTimer, Qt, QEvent, QObject
from PyQt5.QtGui import QWindow, QPainter, QPen, QColor
from PyQt5.QtGui import QWindow, QPainter, QColor, QMouseEvent
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox
class FramelessObject(QObject):
Margins = 3 # 边缘边距
TitleHeight = 36 # 标题栏高度
Widgets = set() # 无边框窗口集合
@staticmethod
def _get_edges(pos):
@classmethod
def set_margins(cls, margins):
cls.Margins = margins
@classmethod
def set_title_height(cls, height):
cls.TitleHeight = height
@classmethod
def add_widget(cls, widget):
cls.Widgets.add(widget)
@classmethod
def del_widget(cls, widget):
if widget in cls.Widgets:
cls.Widgets.remove(widget)
def _get_edges(self, pos, width, height):
"""根据坐标获取方向
:param pos: QPoint
:return: Qt::Edges
:param width: int
:param height: int
:return: Qt.Edges
"""
edge = 0
x, y = pos.x(), pos.y()
widget = QApplication.instance().activeWindow()
if not widget:
return edge
left, top, right, bottom = widget.layout().getContentsMargins()
if y <= top:
if y <= self.Margins:
edge |= Qt.TopEdge
if x <= left:
if x <= self.Margins:
edge |= Qt.LeftEdge
if x >= widget.width() - right:
if x >= width - self.Margins:
edge |= Qt.RightEdge
if y >= widget.height() - bottom:
if y >= height - self.Margins:
edge |= Qt.BottomEdge
return edge
@staticmethod
def _adjust_cursor(edges):
def _get_cursor(self, edges):
"""调整鼠标样式
:param edges: int or None
:return: Qt.CursorShape
"""
# print('edges', edges)
widget = QApplication.instance().activeWindow()
if not widget:
return
if edges == Qt.LeftEdge | Qt.TopEdge or edges == Qt.RightEdge | Qt.BottomEdge:
widget.setCursor(Qt.SizeFDiagCursor)
return Qt.SizeFDiagCursor
elif edges == Qt.RightEdge | Qt.TopEdge or edges == Qt.LeftEdge | Qt.BottomEdge:
widget.setCursor(Qt.SizeBDiagCursor)
return Qt.SizeBDiagCursor
elif edges == Qt.LeftEdge or edges == Qt.RightEdge:
widget.setCursor(Qt.SizeHorCursor)
return Qt.SizeHorCursor
elif edges == Qt.TopEdge or edges == Qt.BottomEdge:
widget.setCursor(Qt.SizeVerCursor)
return Qt.SizeVerCursor
return Qt.ArrowCursor
def is_titlebar(self, pos):
"""判断是否是标题栏
:param pos: QPoint
:return: bool
"""
return pos.y() <= self.TitleHeight
def moveOrResize(self, window, pos, width, height):
edges = self._get_edges(pos, width, height)
if edges:
if window.windowState() == Qt.WindowNoState:
window.startSystemResize(edges)
else:
widget.setCursor(Qt.ArrowCursor)
if self.is_titlebar(pos):
window.startSystemMove()
def eventFilter(self, obj, event):
# 鼠标移动改变光标
if event.type() == QEvent.MouseMove:
widget = QApplication.instance().activeWindow()
if widget and event.buttons() == Qt.NoButton and not (
widget.isMaximized() or widget.isFullScreen()):
# 鼠标移动变更光标样式
# print('MouseMove', event.pos())
self._adjust_cursor(self._get_edges(event.pos()))
if obj.isWindowType():
# top window 处理光标样式
if event.type() == QEvent.MouseMove and obj.windowState() == Qt.WindowNoState:
obj.setCursor(self._get_cursor(self._get_edges(event.pos(), obj.width(), obj.height())))
elif event.type() == QEvent.TouchUpdate:
self.moveOrResize(obj, event.pos(), obj.width(), obj.height())
elif obj in self.Widgets and isinstance(event, QMouseEvent) and event.button() == Qt.LeftButton:
if event.type() == QEvent.MouseButtonDblClick:
# 双击最大化还原
if self.is_titlebar(event.pos()):
if obj.windowState() == Qt.WindowFullScreen:
pass
elif obj.windowState() == Qt.WindowMaximized:
obj.showNormal()
else:
obj.showMaximized()
elif event.type() == QEvent.MouseButtonPress:
self.moveOrResize(obj.windowHandle(), event.pos(), obj.width(), obj.height())
if event.type() == QEvent.MouseButtonPress and obj.objectName() == 'FramelessWindow':
# 鼠标按下
edges = self._get_edges(event.pos())
# 标题栏高度
try:
height = obj.titleHeight()
except AttributeError:
height = 36
# 优先判断边缘
widget = QApplication.instance().activeWindow()
if edges != 0 and not (widget.isMaximized() or widget.isFullScreen()):
# 按下窗口边缘边距位置
self._adjust_cursor(edges)
# 交给系统去调整大小
print('startSystemResize')
obj.windowHandle().startSystemResize(edges)
elif event.y() <= height:
# 标题栏交给系统去移动
print('startSystemMove')
obj.windowHandle().startSystemMove()
elif event.type() == QEvent.MouseButtonRelease:
# 鼠标释放
self._adjust_cursor(None)
return False
@ -103,7 +119,6 @@ class FramelessWindow(QWidget, Ui_FormFrameless):
def __init__(self, *args, **kwargs):
super(FramelessWindow, self).__init__(*args, **kwargs)
self.setupUi(self)
self.setObjectName('FramelessWindow') # 过滤器中的名字一致
# 无边框
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground, True)
@ -117,22 +132,6 @@ class FramelessWindow(QWidget, Ui_FormFrameless):
self.buttonClose.clicked.connect(self.close)
self.setStyleSheet('#widgetTitleBar{background: rgb(232, 232, 232);}')
def titleHeight(self):
"""标题栏高度
:return: int
"""
return self.widgetTitleBar.height()
def mouseDoubleClickEvent(self, event):
"""双击最大化还原
:param event: QMouseEvent
"""
if self.isMaximized():
self.showNormal()
else:
self.showMaximized()
event.accept()
def changeEvent(self, event):
"""窗口状态改变
:param event:
@ -146,7 +145,8 @@ class FramelessWindow(QWidget, Ui_FormFrameless):
self.layout().setContentsMargins(0, 0, 0, 0)
else:
# TODO 与UI文件中的布局边距一致
self.layout().setContentsMargins(3, 3, 3, 3)
m = FramelessObject.Margins
self.layout().setContentsMargins(m, m, m, m)
def paintEvent(self, event):
# 透明背景但是需要留下一个透明度用于鼠标捕获
@ -170,6 +170,12 @@ if __name__ == '__main__':
# 安装全局事件过滤器
fo = FramelessObject()
app.installEventFilter(fo)
w = FramelessWindow()
w.show()
w1 = FramelessWindow()
fo.add_widget(w1)
w1.show()
w2 = FramelessWindow()
fo.add_widget(w2)
w2.show()
sys.exit(app.exec_())

View file

@ -24,6 +24,7 @@
- [调用虚拟键盘](#21调用虚拟键盘)
- [动态忙碌光标](#22动态忙碌光标)
- [屏幕变动监听](#23屏幕变动监听)
- [无边框窗口](#24无边框窗口)
## 1、重启窗口Widget
[运行 RestartWindow.py](RestartWindow.py)
@ -231,4 +232,13 @@ PyQt 结合 Opencv 进行人脸检测;
通过定时器减少不同的变化信号,尽量保证只调用一次槽函数来获取信息
![ScreenNotify](ScreenShot/ScreenNotify.png)
![ScreenNotify](ScreenShot/ScreenNotify.png)
## 24、无边框窗口
[运行 NewFramelessWindow.py](NewFramelessWindow.py)
1. 该方法只针对 `Qt5.15` 以上版本有效
2. 通过事件过滤器判断边缘设置鼠标样式
3. 处理点击事件交通过 `QWindow.startSystemMove``QWindow.startSystemResize` 传递给系统处理
![NewFramelessWindow](ScreenShot/NewFramelessWindow.gif)

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 KiB

View file

@ -256,6 +256,7 @@ https://pyqt.site 论坛是专门针对PyQt5学习和提升开设的网站
- [调用虚拟键盘](Demo/CallVirtualKeyboard.py)
- [动态忙碌光标](Demo/GifCursor.py)
- [屏幕变动监听](Demo/ScreenNotify.py)
- [无边框窗口](Demo/NewFramelessWindow.py)
# QQ群