前言 QT文档:https://runebook.dev/zh-CN/docs/qt/
QT Quick和Qt widgets这两种技术,官方是强推QT Quick的。
https://player.bilibili.com/player.html?aid=582748160&bvid=BV1p64y1T79J&cid=177627338&page=1
窗口设置 窗口的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import QtQuick 2.15 import QtQuick .Window 2.15 import QtQuick .Controls 2.15 Window { title : qsTr ("一个普通标题窗口" ) width : 640 height : 480 visible : true color : "#ffffff" flags : Qt .Window opacity : 1 x :0 y :0 }
无边框 1 2 3 4 5 6 7 8 Window { width : 640 height : 480 visible : true color : "#fefefe" title : qsTr ("主页面" ) flags : Qt .FramelessWindowHint }
注意
flags: Qt.FramelessWindowHint会让窗口无边框并且不再任务栏显示。
如果想无边框并在任务栏显示可以设置:flags: Qt.Window | Qt.FramelessWindowHint
只显示标题栏 显示标题栏,但是没有关闭最大最小化按钮
1 2 3 4 5 6 7 8 Window { width : 640 height : 480 visible : true color : "#fefefe" title : qsTr ("主页面" ) flags : "CustomizeWindowHint" }
背景透明无边框窗口 1 2 3 4 5 6 7 8 9 Window { width : 640 height : 480 visible : true color : "#00000000" title : qsTr ("主页面" ) flags : Qt .FramelessWindowHint |Qt .Window |Qt .WindowStaysOnTopHint opacity :1 }
opacity这个属性是对当前组件以及子组件都设置不透明度,所以不太适用
color: Qt.rgba(0,0,0,0)是对当前设置透明度,不会影响子组件
注意
Qt.FramelessWindowHint 无边框
Qt.Window 显示在状态栏
Qt.WindowStaysOnTopHint 窗口置顶
窗口显示 窗口置顶-属性设置 Qt.WindowStaysOnTopHint : 这个标志使得窗口保持在所有其他常规窗口之上,但可能仍然会在一些系统窗口(如任务栏、通知窗格等)之下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import QtQuick 2.15 import QtQuick.Controls 2.15 ApplicationWindow { id: appWindow visible: true width: 640 height: 480 title: "Topmost Window" flags: Qt.WindowStaysOnTopHint Button { text: "Close" anchors.centerIn: parent onClicked: appWindow.close() } }
窗口置顶-事件触发 永远置顶
1 mainWin.flags = Qt.Window | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint;
临时置顶
1 2 3 mainWin.flags = Qt.Window | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint; mainWin.requestActivate(); mainWin.flags = Qt.Window | Qt.FramelessWindowHint;
窗口激活 窗口激活会让窗口处于激活状态,但是并不会显示在最前方。
1 mainWin.requestActivate();
窗口恢复 最小化
1 mainWin.showMinimized();
恢复
1 2 3 if (mainWin.windowState === Qt.WindowMinimized){ mainWin.showNormal(); }
万能窗口临时置顶 无论窗口处于什么状态都能临时置顶的状态
1 2 3 4 5 6 mainWin.flags = Qt.Window | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint; mainWin.requestActivate(); mainWin.flags = Qt.Window | Qt.FramelessWindowHint; if (mainWin.windowState === Qt.WindowMinimized){ mainWin.showNormal(); }
窗口穿透 局部穿透 https://doc.qt.io/qt-5/qwindow.html#setMask
官网是这么说的
The mask is a hint to the windowing system that the application does not want to receive mouse or touch input outside the given region .
意思是
应用不接收给定的区域之外的事件。
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from PySide2 import QtCorefrom PySide2.QtCore import Slot, QPointfrom PySide2.QtGui import QPolygon, QRegion, QWindowclass ZMaskUtil (QtCore.QObject): points = [] @Slot() def clearPoints (self ): self .points.clear() @Slot(QPoint ) def addPoint (self, point ): self .points.append(point) @Slot(QWindow ) def setMask (self, window ): if window: polygon = QPolygon(self .points) region = QRegion(polygon) window.setMask(region)
布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Window 2.15 Window { id :mywin visible: true width: 600 ; height: 400 color: "#33000000" title: qsTr("主页面" ) flags: Qt.FramelessWindowHint|Qt.Window|Qt.WindowStaysOnTopHint function renderMask(){ maskutil.clearPoints(); maskutil.addPoint(Qt.point(0 ,0 )); maskutil.addPoint(Qt.point(100 ,0 )); maskutil.addPoint(Qt.point(100 ,100 )); maskutil.addPoint(Qt.point(0 ,100 )); maskutil.addPoint(Qt.point(0 ,0 )); maskutil.setMask(mywin) } Component.onCompleted: { renderMask() } Button { text: "hello" width: 100 height: 100 onClicked: { } } }
程序入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import sysfrom PySide2.QtCore import *from PySide2.QtQml import QQmlApplicationEnginefrom PySide2.QtWidgets import QApplicationfrom ZMaskUtil import ZMaskUtilif __name__ == '__main__' : app = QApplication() engine = QQmlApplicationEngine() maskutil = ZMaskUtil() engine.rootContext().setContextProperty('maskutil' , maskutil) engine.load(QUrl.fromLocalFile('./main.qml' )) sys.exit(app.exec_())
注意
这样其实窗口背景不是透明的也没关系,因为这个方法相当于把穿透的地方都裁剪掉了。
注意上面的例子中传入的坐标区域是不穿透的,
如果想要传入的地方穿透
1 2 >fillscreen = window.screen().geometry() region = region.xored(fillscreen)
把选择的区域反向就行了。
整体穿透 如果整体穿透直接用flags属性设置就行了
1 2 3 4 5 6 7 8 Window { id :mywin visible: true width: 600 ; height: 400 color: "#33000000" title: qsTr("主页面" ) flags: Qt.WindowTransparentForInput | Qt.FramelessWindowHint|Qt.Window|Qt.WindowStaysOnTopHint }
主要是这个属性Qt.WindowTransparentForInput
窗口拖动 DraggableBar.qml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Window 2.15 Rectangle { id: draggableBar width: parent.width height: parent.height color: "transparent" MouseArea { id: dragMouseArea anchors.fill: parent cursorShape: Qt.SizeAllCursor onPressed: { var m_win = draggableBar.Window.window if (m_win) { m_win.startSystemMove() } } } }
拖动区域直接添加
1 2 3 4 import "components" as ComponentsComponents.DraggableBar { anchors.fill: parent }
窗口拖动/菜单/托盘 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.15 as Controlsimport Qt.labs.platform 1.1 Window { id: mainWindow visible: true width: 200 height: 200 title: qsTr("tiny monitor" ) flags: Qt.FramelessWindowHint | Qt.WindowSystemMenuHint | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint color: Qt.rgba(0.5 , 0.5 , 0.5 , 0.9 ) MouseArea { property point clickPos: "0,0" anchors.fill: parent onPressed: { mainWindow.requestActivate() clickPos = Qt.point(mouseX, mouseY) } onPositionChanged: { var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y) var tempx = mainWindow.x + delta.x; var tempy = mainWindow.y + delta.y if (tempx<0 ){ tempx = 0 ; } if (tempx>Screen.desktopAvailableWidth -mainWindow.width){ tempx = Screen.desktopAvailableWidth -mainWindow.width; } if (tempy<0 ){ tempy = 0 ; } if (tempy>Screen.desktopAvailableHeight -mainWindow.height){ tempy = Screen.desktopAvailableHeight -mainWindow.height; } mainWindow.x = tempx mainWindow.y = tempy } acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: { if (mouse.button === Qt.RightButton) { contentMenu.popup() } } } Controls.Menu { id: contentMenu Controls.MenuItem { id:hideItem text: qsTr("隐藏" ) onTriggered: { if (trayIcon==null ){ console.log("系统托盘不存在" ); contentMenu.removeItem(hideItem); return ; }else { if (trayIcon.available){ console.log("系统托盘存在" ); }else { console.log("系统托盘不存在" ); contentMenu.removeItem(hideItem) } } mainWindow.hide () } } Controls.MenuItem { text: qsTr("退出" ) onTriggered: Qt.quit() } } Menu { id: systemTrayMenu MenuItem { text: qsTr("隐藏" ) shortcut: "Ctrl+z" onTriggered: mainWindow.hide () } MenuItem { text: qsTr("退出" ) onTriggered: Qt.quit() } } SystemTrayIcon { id:trayIcon visible: true icon.source: "qrc:/imgs/logo.png" tooltip: "客户端" onActivated: { mainWindow.show () mainWindow.raise() mainWindow.requestActivate() } menu: systemTrayMenu } }
多窗口 在使用 Qt Quick 和 PySide2 创建多个窗口时,可以通过 QML 和 Python 代码相结合的方式来实现。
以下是一个详细的示例,演示如何在 Python 代码中创建多个窗口。
安装 PySide2 首先确保你已经安装了 PySide2。
你可以使用 pip 来安装:
创建 QML 文件 main.qml
这是主窗口的 QML 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.12 ApplicationWindow { visible: true width: 640 height: 480 title: "主窗口" RowLayout { Button { text: "打开次窗口" onClicked: windowManager.openSecondaryWindow() } Button { text: "打开第三个窗口" onClicked: windowManager.openTertiaryWindow() } } }
secondary.qml
这是次窗口的 QML 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Window 2.15 import QtQuick.Layouts 1.12 ApplicationWindow { id: secondaryWindow visible: true width: 400 height: 300 title: "次窗口" Button { id:rootView text: "关闭" anchors.centerIn: parent onClicked: secondaryWindow.close() } Component.onCompleted: { console.log("onCompleted" ); } onClosing: { console.log("窗口即将关闭" ) windowManager.onWindowClose(secondaryWindow); } Component.onDestruction: { console.log("窗口被销毁" ) } }
tertiary.qml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import QtQuick 2.15 import QtQuick.Controls 2.15 ApplicationWindow { id: tertiaryWindow visible: true width: 300 height: 200 title: "第三个窗口" Button { text: "关闭" anchors.centerIn: parent onClicked: tertiaryWindow.close() } onClosing: { console.log("窗口即将关闭" ) windowManager.onWindowClose(tertiaryWindow); } Component.onDestruction: { console.log("窗口被销毁" ) } }
Python管理窗口 window_manager.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 from PySide2.QtCore import QObject, Slot, QUrlfrom PySide2.QtQuick import QQuickWindowclass WindowManager (QObject ): def __init__ (self, engine, parent=None ): super ().__init__(parent) self .engine = engine self .windows = [] self .engine.objectCreated.connect(self .onObjectCreated) @Slot() def openSecondaryWindow (self ): win_name = "secondaryWindow" if any (window.objectName() == win_name for window in self .windows): return self .engine.load(QUrl("qrc:/win/secondary.qml" )) root_objects = self .engine.rootObjects() if root_objects: window = root_objects[-1 ] window.setObjectName(win_name) window.destroyed.connect(self .onWindowDestroyed) self .windows.append(window) @Slot() def openTertiaryWindow (self ): win_name = "tertiaryWindow" if any (window.objectName() == win_name for window in self .windows): return self .engine.load(QUrl("qrc:/win/tertiary.qml" )) root_objects = self .engine.rootObjects() if root_objects: window = root_objects[-1 ] window.setObjectName(win_name) window.destroyed.connect(self .onWindowDestroyed) self .windows.append(window) @Slot(QQuickWindow ) def onWindowClose (self, window ): if window in self .windows: print (f"窗口关闭: {window.objectName()} " ) window.deleteLater() @Slot(QQuickWindow ) def onWindowDestroyed (self, window ): if window in self .windows: print (f"窗口销毁: {window.objectName()} " ) self .windows.remove(window) root_objects = self .engine.rootObjects() print (len (root_objects)) @Slot(QObject, QUrl ) def onObjectCreated (self, obj, url ): if obj: print (f"窗口创建: {url} " )
主类 main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import sysfrom PySide2.QtGui import QGuiApplicationfrom PySide2.QtQml import QQmlApplicationEnginefrom PySide2.QtCore import QUrlfrom window_manager import WindowManagerimport resource_qrcif __name__ == "__main__" : app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() window_manager = WindowManager(engine) engine.rootContext().setContextProperty("windowManager" , window_manager) engine.load(QUrl("qrc:/win/main.qml" )) if not engine.rootObjects(): sys.exit(-1 ) sys.exit(app.exec_())
窗口的销毁机制 只有一个顶层窗口的情况:
顶层窗口调用close,本窗口销毁并销毁所有子部件;非顶层窗口调用close,窗口只是隐藏,并不销毁。
对于有多个顶层窗口的程序:
情况并不是如此,由于所有窗口都生存在一个QApplication中,只有最后一个窗口关闭close()之后 程序才会调用主窗口的析构函数来销毁窗口。
Qt Quick中设置窗口关闭自动销毁的属性是无效的,窗口关闭并不会销毁,需要调用deleteLater()进行销毁。
获取窗口及窗口句柄 1 2 3 4 root_window = engine.rootObjects()[0 ] hwnd = root_window.winId() hwnd_int = int (hwnd)