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 Lib.ui_frameless import Ui_FormFrameless
from PyQt5.QtCore import QTimer, Qt, QEvent, QObject 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 from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox
class FramelessObject(QObject): class FramelessObject(QObject):
Margins = 3 # 边缘边距
TitleHeight = 36 # 标题栏高度
Widgets = set() # 无边框窗口集合
@staticmethod @classmethod
def _get_edges(pos): 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 :param pos: QPoint
:return: Qt::Edges :param width: int
:param height: int
:return: Qt.Edges
""" """
edge = 0 edge = 0
x, y = pos.x(), pos.y() 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 edge |= Qt.TopEdge
if x <= left: if x <= self.Margins:
edge |= Qt.LeftEdge edge |= Qt.LeftEdge
if x >= widget.width() - right: if x >= width - self.Margins:
edge |= Qt.RightEdge edge |= Qt.RightEdge
if y >= widget.height() - bottom: if y >= height - self.Margins:
edge |= Qt.BottomEdge edge |= Qt.BottomEdge
return edge return edge
@staticmethod def _get_cursor(self, edges):
def _adjust_cursor(edges):
"""调整鼠标样式 """调整鼠标样式
:param edges: int or None :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: 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: 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: elif edges == Qt.LeftEdge or edges == Qt.RightEdge:
widget.setCursor(Qt.SizeHorCursor) return Qt.SizeHorCursor
elif edges == Qt.TopEdge or edges == Qt.BottomEdge: 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: else:
widget.setCursor(Qt.ArrowCursor) if self.is_titlebar(pos):
window.startSystemMove()
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
# 鼠标移动改变光标 if obj.isWindowType():
if event.type() == QEvent.MouseMove: # top window 处理光标样式
widget = QApplication.instance().activeWindow() if event.type() == QEvent.MouseMove and obj.windowState() == Qt.WindowNoState:
if widget and event.buttons() == Qt.NoButton and not ( obj.setCursor(self._get_cursor(self._get_edges(event.pos(), obj.width(), obj.height())))
widget.isMaximized() or widget.isFullScreen()): elif event.type() == QEvent.TouchUpdate:
# 鼠标移动变更光标样式 self.moveOrResize(obj, event.pos(), obj.width(), obj.height())
# print('MouseMove', event.pos()) elif obj in self.Widgets and isinstance(event, QMouseEvent) and event.button() == Qt.LeftButton:
self._adjust_cursor(self._get_edges(event.pos())) 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 return False
@ -103,7 +119,6 @@ class FramelessWindow(QWidget, Ui_FormFrameless):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(FramelessWindow, self).__init__(*args, **kwargs) super(FramelessWindow, self).__init__(*args, **kwargs)
self.setupUi(self) self.setupUi(self)
self.setObjectName('FramelessWindow') # 过滤器中的名字一致
# 无边框 # 无边框
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint) self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground, True) self.setAttribute(Qt.WA_TranslucentBackground, True)
@ -117,22 +132,6 @@ class FramelessWindow(QWidget, Ui_FormFrameless):
self.buttonClose.clicked.connect(self.close) self.buttonClose.clicked.connect(self.close)
self.setStyleSheet('#widgetTitleBar{background: rgb(232, 232, 232);}') 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): def changeEvent(self, event):
"""窗口状态改变 """窗口状态改变
:param event: :param event:
@ -146,7 +145,8 @@ class FramelessWindow(QWidget, Ui_FormFrameless):
self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setContentsMargins(0, 0, 0, 0)
else: else:
# TODO 与UI文件中的布局边距一致 # TODO 与UI文件中的布局边距一致
self.layout().setContentsMargins(3, 3, 3, 3) m = FramelessObject.Margins
self.layout().setContentsMargins(m, m, m, m)
def paintEvent(self, event): def paintEvent(self, event):
# 透明背景但是需要留下一个透明度用于鼠标捕获 # 透明背景但是需要留下一个透明度用于鼠标捕获
@ -170,6 +170,12 @@ if __name__ == '__main__':
# 安装全局事件过滤器 # 安装全局事件过滤器
fo = FramelessObject() fo = FramelessObject()
app.installEventFilter(fo) 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_()) sys.exit(app.exec_())

View file

@ -24,6 +24,7 @@
- [调用虚拟键盘](#21调用虚拟键盘) - [调用虚拟键盘](#21调用虚拟键盘)
- [动态忙碌光标](#22动态忙碌光标) - [动态忙碌光标](#22动态忙碌光标)
- [屏幕变动监听](#23屏幕变动监听) - [屏幕变动监听](#23屏幕变动监听)
- [无边框窗口](#24无边框窗口)
## 1、重启窗口Widget ## 1、重启窗口Widget
[运行 RestartWindow.py](RestartWindow.py) [运行 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/CallVirtualKeyboard.py)
- [动态忙碌光标](Demo/GifCursor.py) - [动态忙碌光标](Demo/GifCursor.py)
- [屏幕变动监听](Demo/ScreenNotify.py) - [屏幕变动监听](Demo/ScreenNotify.py)
- [无边框窗口](Demo/NewFramelessWindow.py)
# QQ群 # QQ群