New FrameLess Window

This commit is contained in:
Irony 2021-04-21 22:58:30 +08:00
parent 2a54e678ae
commit 0ca3f3c90b
4 changed files with 472 additions and 2 deletions

2
.gitignore vendored
View file

@ -24,8 +24,6 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/

197
Demo/Data/frameless.ui Normal file
View file

@ -0,0 +1,197 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FormFrameless</class>
<widget class="QWidget" name="FormFrameless">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QWidget" name="widgetTitleBar" native="true">
<property name="font">
<font>
<family>Symbola</family>
</font>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>253</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonMinimum">
<property name="minimumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="font">
<font>
<family>webdings</family>
</font>
</property>
<property name="toolTip">
<string>Minimum</string>
</property>
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonMaximum">
<property name="minimumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="font">
<font>
<family>webdings</family>
</font>
</property>
<property name="toolTip">
<string>Maximum</string>
</property>
<property name="text">
<string>1</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonNormal">
<property name="minimumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="font">
<font>
<family>webdings</family>
</font>
</property>
<property name="toolTip">
<string>Normal</string>
</property>
<property name="text">
<string>2</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonClose">
<property name="minimumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="font">
<font>
<family>webdings</family>
</font>
</property>
<property name="toolTip">
<string>Close</string>
</property>
<property name="text">
<string>r</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTextEdit" name="textEdit">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;frameless window with move and resize&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

100
Demo/Lib/ui_frameless.py Normal file
View file

@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'frameless.ui'
#
# Created by: PyQt5 UI code generator 5.15.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_FormFrameless(object):
def setupUi(self, FormFrameless):
FormFrameless.setObjectName("FormFrameless")
FormFrameless.resize(400, 300)
self.verticalLayout = QtWidgets.QVBoxLayout(FormFrameless)
self.verticalLayout.setContentsMargins(3, 3, 3, 3)
self.verticalLayout.setSpacing(0)
self.verticalLayout.setObjectName("verticalLayout")
self.widgetTitleBar = QtWidgets.QWidget(FormFrameless)
font = QtGui.QFont()
font.setFamily("Symbola")
self.widgetTitleBar.setFont(font)
self.widgetTitleBar.setObjectName("widgetTitleBar")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.widgetTitleBar)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName("horizontalLayout")
spacerItem = QtWidgets.QSpacerItem(253, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.buttonMinimum = QtWidgets.QPushButton(self.widgetTitleBar)
self.buttonMinimum.setMinimumSize(QtCore.QSize(36, 36))
self.buttonMinimum.setMaximumSize(QtCore.QSize(36, 36))
font = QtGui.QFont()
font.setFamily("webdings")
self.buttonMinimum.setFont(font)
self.buttonMinimum.setObjectName("buttonMinimum")
self.horizontalLayout.addWidget(self.buttonMinimum)
self.buttonMaximum = QtWidgets.QPushButton(self.widgetTitleBar)
self.buttonMaximum.setMinimumSize(QtCore.QSize(36, 36))
self.buttonMaximum.setMaximumSize(QtCore.QSize(36, 36))
font = QtGui.QFont()
font.setFamily("webdings")
self.buttonMaximum.setFont(font)
self.buttonMaximum.setObjectName("buttonMaximum")
self.horizontalLayout.addWidget(self.buttonMaximum)
self.buttonNormal = QtWidgets.QPushButton(self.widgetTitleBar)
self.buttonNormal.setMinimumSize(QtCore.QSize(36, 36))
self.buttonNormal.setMaximumSize(QtCore.QSize(36, 36))
font = QtGui.QFont()
font.setFamily("webdings")
self.buttonNormal.setFont(font)
self.buttonNormal.setObjectName("buttonNormal")
self.horizontalLayout.addWidget(self.buttonNormal)
self.buttonClose = QtWidgets.QPushButton(self.widgetTitleBar)
self.buttonClose.setMinimumSize(QtCore.QSize(36, 36))
self.buttonClose.setMaximumSize(QtCore.QSize(36, 36))
font = QtGui.QFont()
font.setFamily("webdings")
self.buttonClose.setFont(font)
self.buttonClose.setObjectName("buttonClose")
self.horizontalLayout.addWidget(self.buttonClose)
self.verticalLayout.addWidget(self.widgetTitleBar)
self.textEdit = QtWidgets.QTextEdit(FormFrameless)
self.textEdit.setFrameShape(QtWidgets.QFrame.NoFrame)
self.textEdit.setObjectName("textEdit")
self.verticalLayout.addWidget(self.textEdit)
self.verticalLayout.setStretch(1, 1)
self.retranslateUi(FormFrameless)
QtCore.QMetaObject.connectSlotsByName(FormFrameless)
def retranslateUi(self, FormFrameless):
_translate = QtCore.QCoreApplication.translate
FormFrameless.setWindowTitle(_translate("FormFrameless", "Form"))
self.buttonMinimum.setToolTip(_translate("FormFrameless", "Minimum"))
self.buttonMinimum.setText(_translate("FormFrameless", "0"))
self.buttonMaximum.setToolTip(_translate("FormFrameless", "Maximum"))
self.buttonMaximum.setText(_translate("FormFrameless", "1"))
self.buttonNormal.setToolTip(_translate("FormFrameless", "Normal"))
self.buttonNormal.setText(_translate("FormFrameless", "2"))
self.buttonClose.setToolTip(_translate("FormFrameless", "Close"))
self.buttonClose.setText(_translate("FormFrameless", "r"))
self.textEdit.setHtml(_translate("FormFrameless", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Sans Serif\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">frameless window with move and resize</p></body></html>"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
FormFrameless = QtWidgets.QWidget()
ui = Ui_FormFrameless()
ui.setupUi(FormFrameless)
FormFrameless.show()
sys.exit(app.exec_())

175
Demo/NewFramelessWindow.py Normal file
View file

@ -0,0 +1,175 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on 2018年4月30日
@author: Irony
@site: https://pyqt.site , https://github.com/PyQt5
@email: 892768447@qq.com
@file: NewFramelessWindow
@description:
"""
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.QtWidgets import QApplication, QWidget, QMessageBox
class FramelessObject(QObject):
@staticmethod
def _get_edges(pos):
"""根据坐标获取方向
:param pos: QPoint
: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:
edge |= Qt.TopEdge
if x <= left:
edge |= Qt.LeftEdge
if x >= widget.width() - right:
edge |= Qt.RightEdge
if y >= widget.height() - bottom:
edge |= Qt.BottomEdge
return edge
@staticmethod
def _adjust_cursor(edges):
"""调整鼠标样式
:param edges: int or None
"""
# 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)
elif edges == Qt.RightEdge | Qt.TopEdge or edges == Qt.LeftEdge | Qt.BottomEdge:
widget.setCursor(Qt.SizeBDiagCursor)
elif edges == Qt.LeftEdge or edges == Qt.RightEdge:
widget.setCursor(Qt.SizeHorCursor)
elif edges == Qt.TopEdge or edges == Qt.BottomEdge:
widget.setCursor(Qt.SizeVerCursor)
else:
widget.setCursor(Qt.ArrowCursor)
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 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
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)
self.setMouseTracking(True)
# 隐藏还原按钮
self.buttonNormal.setVisible(False)
# 标题栏按钮信号
self.buttonMinimum.clicked.connect(self.showMinimized)
self.buttonMaximum.clicked.connect(self.showMaximized)
self.buttonNormal.clicked.connect(self.showNormal)
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:
"""
super(FramelessWindow, self).changeEvent(event)
# 窗口状态改变时修改标题栏控制按钮
visible = self.isMaximized()
self.buttonMaximum.setVisible(not visible)
self.buttonNormal.setVisible(visible)
if visible:
self.layout().setContentsMargins(0, 0, 0, 0)
else:
# TODO 与UI文件中的布局边距一致
self.layout().setContentsMargins(3, 3, 3, 3)
def paintEvent(self, event):
# 透明背景但是需要留下一个透明度用于鼠标捕获
painter = QPainter(self)
painter.fillRect(self.rect(), QColor(255, 255, 255, 1))
if __name__ == '__main__':
import sys
import cgitb
cgitb.enable(format='text')
app = QApplication(sys.argv)
if not hasattr(QWindow, 'startSystemMove'):
QWindow.startSystemResize()
# 不支持
QMessageBox.critical(None, '错误', '当前Qt版本不支持该例子')
QTimer.singleShot(100, app.quit)
else:
# 安装全局事件过滤器
fo = FramelessObject()
app.installEventFilter(fo)
w = FramelessWindow()
w.show()
sys.exit(app.exec_())