NewFramelessWindow
This commit is contained in:
parent
5f33be5f92
commit
fc6134aefc
4 changed files with 92 additions and 75 deletions
|
@ -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_())
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
- [调用虚拟键盘](#21调用虚拟键盘)
|
- [调用虚拟键盘](#21调用虚拟键盘)
|
||||||
- [动态忙碌光标](#22动态忙碌光标)
|
- [动态忙碌光标](#22动态忙碌光标)
|
||||||
- [屏幕变动监听](#23屏幕变动监听)
|
- [屏幕变动监听](#23屏幕变动监听)
|
||||||
|
- [无边框窗口](#24无边框窗口)
|
||||||
|
|
||||||
## 1、重启窗口Widget
|
## 1、重启窗口Widget
|
||||||
[运行 RestartWindow.py](RestartWindow.py)
|
[运行 RestartWindow.py](RestartWindow.py)
|
||||||
|
@ -232,3 +233,12 @@ 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)
|
BIN
Demo/ScreenShot/NewFramelessWindow.gif
Normal file
BIN
Demo/ScreenShot/NewFramelessWindow.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 927 KiB |
|
@ -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群
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue