Python使用PySide开发GUI应用

Python语法简洁、生态丰富、开发效率极高,在数据分析、AI开发领域不可或缺。

QT是Python开发GUI应用的最佳技术,本文介绍如何在Python项目中集成QT,以及如何打包应用。

推荐使用pixi作为项目构建工具,该工具最大优势是打通了Conda-forfe + PyPI双生态,能自动处理两套生态的依赖关系,这对于大型项目会很有优势。

构建配置

以下是pixi.toml配置文件:

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
[workspace]
authors = ["lihaohello <lihaohello@126.com>"]
channels = ["conda-forge"]
name = "test_pyside"
platforms = ["win-64"]
version = "0.1.0"

[pypi-options]
extra-index-urls = ["https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"]
index-url = "https://pypi.tuna.tsinghua.edu.cn/simple"

[tasks]
build-ui = '''
python -c "import os,subprocess,glob; \
[subprocess.run(['pyside6-uic',u,'-o', \
'ui_'+os.path.splitext(os.path.basename(u))[0]+'.py']) \
for u in glob.glob('**/*.ui',recursive=True) \
if 'build' not in u]"
'''
run = "python main.py"
pack = """
python -m nuitka \
--standalone \
--assume-yes-for-downloads \
--enable-plugin=pyside6 \
--windows-console-mode=disable \
--output-dir=build \
--remove-output \
main.py
"""

[dependencies]
pyside6 = ">=6.11.2,<7"

[feature.dev.pypi-dependencies]
nuitka = "*"

[environments]
default = {features = ["dev"]}
  • build-ui任务可以将ui界面设计文件转化成Python源代码。
  • pack任务使用nuitka工具进行软件打包。

集成QT

ui界面设计文件

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
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QWidget" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>799</width>
<height>483</height>
</rect>
</property>
<property name="windowTitle">
<string>Hello World</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="lineEdit_2"/>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

转化成Python源文件:

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
# -*- coding: utf-8 -*-

################################################################################
## Form generated from reading UI file 'main_window.ui'
##
## Created by: Qt User Interface Compiler version 6.11.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLineEdit, QPushButton,
QSizePolicy, QSpacerItem, QVBoxLayout, QWidget)

class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(799, 483)
self.verticalLayout = QVBoxLayout(MainWindow)
self.verticalLayout.setObjectName(u"verticalLayout")
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.lineEdit = QLineEdit(MainWindow)
self.lineEdit.setObjectName(u"lineEdit")

self.horizontalLayout.addWidget(self.lineEdit)

self.pushButton = QPushButton(MainWindow)
self.pushButton.setObjectName(u"pushButton")

self.horizontalLayout.addWidget(self.pushButton)


self.verticalLayout.addLayout(self.horizontalLayout)

self.horizontalLayout_2 = QHBoxLayout()
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.lineEdit_2 = QLineEdit(MainWindow)
self.lineEdit_2.setObjectName(u"lineEdit_2")

self.horizontalLayout_2.addWidget(self.lineEdit_2)

self.pushButton_2 = QPushButton(MainWindow)
self.pushButton_2.setObjectName(u"pushButton_2")

self.horizontalLayout_2.addWidget(self.pushButton_2)


self.verticalLayout.addLayout(self.horizontalLayout_2)

self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)

self.verticalLayout.addItem(self.verticalSpacer)


self.retranslateUi(MainWindow)

QMetaObject.connectSlotsByName(MainWindow)
# setupUi

def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Hello World", None))
self.pushButton.setText(QCoreApplication.translate("MainWindow", u"PushButton", None))
self.pushButton_2.setText(QCoreApplication.translate("MainWindow", u"PushButton", None))
# retranslateUi

集成

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
from PySide6.QtWidgets import QApplication, QStyleFactory, QMessageBox, QWidget
from PySide6.QtCore import Qt

from ui_main_window import Ui_MainWindow

QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)

class MainWindow(QWidget, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.pushButton.clicked.connect(self._on_button_clicked)
self.pushButton_2.clicked.connect(self._on_button2_clicked)

def _on_button_clicked(self):
text = self.lineEdit.text().strip()
QMessageBox.information(self, "输入内容", text if text else "(空)")

def _on_button2_clicked(self):
text = self.lineEdit_2.text().strip()
QMessageBox.information(self, "输入内容", text if text else "(空)")

if __name__ == "__main__":
app = QApplication([])
app.setStyle(QStyleFactory.create("Fusion"))
win = MainWindow()
win.show()
app.exec()

打包

使用nuitka工具进行打包,Nuitka可以把Python代码编译成C++,真正做到二进制打包,性能和体积方面都有所改善。

执行打包任务:pixi run pack,得到应用目录,可以进一步人为删除没有用到的DLL和pyd文件,就可以尽量压缩发行包的体积。

(转载本站文章请注明作者和出处lihaohello.top,请勿用于任何商业用途)