diff --git a/.gitignore b/.gitignore index fb1435f..9f409db 100644 --- a/.gitignore +++ b/.gitignore @@ -24,8 +24,6 @@ dist/ downloads/ eggs/ .eggs/ -lib/ -lib64/ parts/ sdist/ var/ diff --git a/Demo/Data/frameless.ui b/Demo/Data/frameless.ui new file mode 100644 index 0000000..a7919b0 --- /dev/null +++ b/Demo/Data/frameless.ui @@ -0,0 +1,197 @@ + + + FormFrameless + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + Symbola + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 253 + 20 + + + + + + + + + 36 + 36 + + + + + 36 + 36 + + + + + webdings + + + + Minimum + + + 0 + + + + + + + + 36 + 36 + + + + + 36 + 36 + + + + + webdings + + + + Maximum + + + 1 + + + + + + + + 36 + 36 + + + + + 36 + 36 + + + + + webdings + + + + Normal + + + 2 + + + + + + + + 36 + 36 + + + + + 36 + 36 + + + + + webdings + + + + Close + + + r + + + + + + + + + + QFrame::NoFrame + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<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> + + + + + + + + diff --git a/Demo/Lib/ui_frameless.py b/Demo/Lib/ui_frameless.py new file mode 100644 index 0000000..31d0e9f --- /dev/null +++ b/Demo/Lib/ui_frameless.py @@ -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", "\n" +"\n" +"

frameless window with move and resize

")) + + +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_()) diff --git a/Demo/NewFramelessWindow.py b/Demo/NewFramelessWindow.py new file mode 100644 index 0000000..f094890 --- /dev/null +++ b/Demo/NewFramelessWindow.py @@ -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_())