PySide2动态/静态加载UI及程序发布

Python目前已经是一个“家喻户晓”的名词了,她可能用在很多行业领域,最牛逼的人工智能(AI)、大数据(big data)。今天要介绍的是Python图形化界面实现(GUI),记得在Python刚出来的时候,开发一个功能强大而美观的GUI还是挺不方便的,最早期也就是Python自己的一个GUI模块库,叫Tkinter,当然现在这个模块功能与比当初要丰富很多了,除此之外,像WxPython、pyGtk、Jython、Pywin32、PyQt、PySide\PySide2等等。这么多的图形开发工具中,个人比较喜欢的是PySide2。下面重点要给大家介绍的是,如何将开发好的程序打包成一个可执行文件发布,可以在别人电脑上使用(这里仅介绍在Windows电脑上使用)。

PySide2介绍

PySide2其实是Python版的Qt,在Qt官方叫"Qt for Python",这样说大家应该就明白了,其实就是Qt的各种图形化库、功能模块库给Python开发调用,在Python中导入一个模块相信大家都知道"import",所以使用PySide2就这么简单。而需要学的,就是"Qt for Python"相关的API函数接口的使用,它按模块分为很多API,比如图形控件类API、网络通讯类API、串口类API、Web相关API等等,我们需要什么API就去看这些API就可以了,就这么easy!

另外,要说明一下PySide2是Qt这家公司的亲儿子,好比kotlin语言是google公司为Android APP开发而准备的一门语言一样(最初Android APP开发只能用java语言开发),现在我们发现很多Android APP都是Kotlin开发,而且功能支持也是Kotlin语言越来越好,毕竟是亲生的,所以PySide2也是这样的。那领养的又是谁呢,就是上面我提到的PyQt,它是一个专门的公司为Qt而开发Python相关API的公司,当然了,它比PySide2要出世早了。

关于版本问题,PySide2是基于Qt5而开发的,而她前面还有一个PySide,是早期的版本,基于Qt4的,也是最早的版本,现在几乎没人用了,原因是功能还不完善。而最新的是PySide6,是不是感觉这跳得有点快,从2一下子跳到6,可能是跟随老子的步伐吧,Qt现在发展到6.0版本,所以直接就PySide6了,但6.0版本由于很多API还没有完全支持,所以本文以PySide2介绍,她几乎与Qt5是同步的,Qt5有的功能她几乎也是能实现的。
(PyQt—>PyQt5—>PyQt6这是领养的版本发展图)

打包神器Pyinstaller

打包python的工具很多,除这个,比如py2exe,cx_freeze也都是不错的工具。

1.安装Pyinstaller打包工具

Microsoft Windows [版本 10.0.19041.867]
(c) 2020 Microsoft Corporation. 保留所有权利。

C:\Users\Gary>pip install -i https://pypi.douban.com/simple pyinstaller
Looking in indexes: https://pypi.douban.com/simple
Collecting pyinstaller
  Downloading https://pypi.doubanio.com/packages/b4/83/9f6ff034650abe9778c9a4f86bcead63f89a62acf02b1b47fc2bfc6bf8dd/pyinstaller-4.2.tar.gz (3.6 MB)
     |████████████████████████████████| 3.6 MB 46 kB/s
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
    Preparing wheel metadata ... done
Collecting altgraph
  Downloading https://pypi.doubanio.com/packages/ee/3d/bfca21174b162f6ce674953f1b7a640c1498357fa6184776029557c25399/altgraph-0.17-py2.py3-none-any.whl (21 kB)
Collecting pyinstaller-hooks-contrib>=2020.6
  Downloading https://pypi.doubanio.com/packages/27/c7/58a634d861e4744ac62dca4a4992ace8def8b05dab91e6b25e5043e79acf/pyinstaller_hooks_contrib-2021.1-py2.py3-none-any.whl (181 kB)
     |████████████████████████████████| 181 kB ...
Requirement already satisfied: setuptools in d:\users\python\python39\lib\site-packages (from pyinstaller) (49.2.1)
Collecting pefile>=2017.8.1; sys_platform == "win32"
  Downloading https://pypi.doubanio.com/packages/36/58/acf7f35859d541985f0a6ea3c34baaefbfaee23642cf11e85fe36453ae77/pefile-2019.4.18.tar.gz (62 kB)
     |████████████████████████████████| 62 kB 408 kB/s
Collecting pywin32-ctypes>=0.2.0; sys_platform == "win32"
  Downloading https://pypi.doubanio.com/packages/9e/4b/3ab2720f1fa4b4bc924ef1932b842edf10007e4547ea8157b0b9fc78599a/pywin32_ctypes-0.2.0-py2.py3-none-any.whl (28 kB)
Collecting future
  Downloading https://pypi.doubanio.com/packages/45/0b/38b06fd9b92dc2b68d58b75f900e97884c45bedd2ff83203d933cf5851c9/future-0.18.2.tar.gz (829 kB)
     |████████████████████████████████| 829 kB 819 kB/s
Using legacy 'setup.py install' for pefile, since package 'wheel' is not installed.
Using legacy 'setup.py install' for future, since package 'wheel' is not installed.
Building wheels for collected packages: pyinstaller
  Building wheel for pyinstaller (PEP 517) ... done
  Created wheel for pyinstaller: filename=pyinstaller-4.2-py3-none-any.whl size=2413076 sha256=fa9cdc6ec88e2d0c3df74e7aec0f71330feb1b2b6fb751f188bb498631837d06
  Stored in directory: c:\users\gary\appdata\local\pip\cache\wheels\af\ea\26\5812f58861dc385ff5573249b18dfe1624c5f84ea67fa76397
Successfully built pyinstaller
Installing collected packages: altgraph, pyinstaller-hooks-contrib, future, pefile, pywin32-ctypes, pyinstaller
    Running setup.py install for future ... done
    Running setup.py install for pefile ... done
Successfully installed altgraph-0.17 future-0.18.2 pefile-2019.4.18 pyinstaller-4.2 pyinstaller-hooks-contrib-2021.1 pywin32-ctypes-0.2.0
WARNING: You are using pip version 20.2.3; however, version 21.0.1 is available.
You should consider upgrading via the 'd:\users\python\python39\python.exe -m pip install --upgrade pip' command.
C:\Users\Gary>

可以查看Python第三方包所在目录,发现多了两个:(Pywin32会被同时安装)

在这里插入图片描述

2.Pyinstaller常用指令参数

下面挑了一些常用参数:

D:\py\MFGTool>pyinstaller --help
usage: pyinstaller [-h] [-v] [-D] [-F] [--specpath DIR] [-n NAME] [--add-data <SRC;DEST or SRC:DEST>]
                   [--add-binary <SRC;DEST or SRC:DEST>] [-p DIR] [--hidden-import MODULENAME]
                   [--additional-hooks-dir HOOKSPATH] [--runtime-hook RUNTIME_HOOKS] [--exclude-module EXCLUDES]
                   [--key KEY] [-d {all,imports,bootloader,noarchive}] [-s] [--noupx] [--upx-exclude FILE] [-c] [-w]
                   [-i <FILE.ico or FILE.exe,ID or FILE.icns or "NONE">] [--version-file FILE] [-m <FILE or XML>]
                   [-r RESOURCE] [--uac-admin] [--uac-uiaccess] [--win-private-assemblies] [--win-no-prefer-redirects]
                   [--osx-bundle-identifier BUNDLE_IDENTIFIER] [--runtime-tmpdir PATH] [--bootloader-ignore-signals]
                   [--distpath DIR] [--workpath WORKPATH] [-y] [--upx-dir UPX_DIR] [-a] [--clean] [--log-level LEVEL]
                   scriptname [scriptname ...]

positional arguments:
  scriptname            name of scriptfiles to be processed or exactly one .spec-file. If a .spec-file is specified,
                        most options are unnecessary and are ignored.

optional arguments:
  -h, --help            show this help message and exit
  -v, --version         Show program version info and exit.
  --distpath DIR        Where to put the bundled app (default: .\dist)指定打包后的文件保存在哪里,默认是dist这个目录
  --workpath WORKPATH   Where to put all the temporary work files, .log, .pyz and etc. (default: .\build)
  -y, --noconfirm       Replace output directory (default: SPECPATH\dist\SPECNAME) without asking for confirmation
  --upx-dir UPX_DIR     Path to UPX utility (default: search the execution path)
  -a, --ascii           Do not include unicode encoding support (default: included if available)
  --clean               Clean PyInstaller cache and remove temporary files before building.打包成功后会把打包过程中产生的文件清理掉

  -D, --onedir          Create a one-folder bundle containing an executable (default) 这是默认的,会把与可执行文件相关的所有文件都复制到目录dist中
  -F, --onefile         Create a one-file bundled executable. 这只会生成一个exe文件,文件会很大
  --hidden-import MODULENAME, --hiddenimport MODULENAME
                        Name an import not visible in the code of the script(s). This option can be used multiple
                        times.隐藏掉某些模块,这些模块通常不是实际程序中的,是编译器、或者打包器所需要的模块,如果不隐藏会打包不成功,说缺少某个模块
  -p DIR, --paths DIR   A path to search for imports (like using PYTHONPATH). Multiple paths are allowed, separated by
                        ';', or use this option multiple times
指定需要包含的库的路径,如果没有要指定的这个参数是不需要的
  -c, --console, --nowindowed
                        Open a console window for standard i/o (default). On Windows this option will have no effect
                        if the first script is a '.pyw' file.
  -w, --windowed, --noconsole
                        Windows and Mac OS X: do not provide a console window for standard i/o. On Mac OS X this also
                        triggers building an OS X .app bundle. On Windows this option will be set if the first script
                        is a '.pyw' file. This option is ignored in *NIX systems.
上面-c或-w指打包后是否要显示控制台,也就是类似dos这样一个界面

打包程序

GUI界面是动态方式加载:

import os
import sys

from PySide2.QtWidgets import QApplication, QMainWindow, QMessageBox
from PySide2.QtCore import QFile, QRegExp, QByteArray, QIODevice
from PySide2.QtUiTools import QUiLoader
from PySide2.QtGui import QRegExpValidator
from PySide2 import QtSerialPort
#from ui_mfgtool import Ui_MfgTool


class MfgTool(QMainWindow):
    def __init__(self):
        super(MfgTool, self).__init__()
        self.load_ui()
        #self.ui = Ui_MfgTool() #静态加载UI
        #self.ui.setupUi(self)
        self.initUI()
        self.ConnectFun()
        self.m_ba = QByteArray(b"")

    def load_ui(self):
        loader = QUiLoader() #动态加载UI
        path = os.path.join(os.path.dirname(__file__), "mfgtool.ui")
        ui_file = QFile(path)
        ui_file.open(QFile.ReadOnly)
        self.ui = loader.load(ui_file, self)
        ui_file.close()
        self.ui.show()

执行打包指令如下:

D:\py\MFGTool>pyinstaller mfgtool.py
171 INFO: PyInstaller: 4.2
171 INFO: Python: 3.9.2
171 INFO: Platform: Windows-10-10.0.19041-SP0
171 INFO: wrote D:\py\MFGTool\mfgtool.spec
171 INFO: UPX is not available.
188 INFO: Extending PYTHONPATH with paths
['D:\\py\\MFGTool', 'D:\\py\\MFGTool']
202 INFO: checking Analysis
327 INFO: Building because hiddenimports changed
327 INFO: Initializing module dependency graph...
327 INFO: Caching module graph hooks...
344 WARNING: Several hooks defined for module 'win32ctypes.core'. Please take care they do not conflict.
359 INFO: Analyzing base_library.zip ...
4468 INFO: Processing pre-find module path hook distutils from 'd:\\users\\python\\python39\\lib\\site-packages\\PyInstaller\\hooks\\pre_find_module_path\\hook-distutils.py'.
4468 INFO: distutils: retargeting to non-venv dir 'd:\\users\\python\\python39\\lib'
.........
23921 INFO: Bootloader d:\users\python\python39\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe
23921 INFO: checking EXE
23953 INFO: Building because icon changed
23953 INFO: Building EXE from EXE-00.toc
23953 INFO: Copying icons from ['d:\\users\\python\\python39\\lib\\site-packages\\PyInstaller\\bootloader\\images\\icon-console.ico']
24140 INFO: Writing RT_GROUP_ICON 0 resource with 104 bytes
24140 INFO: Writing RT_ICON 1 resource with 3752 bytes
24140 INFO: Writing RT_ICON 2 resource with 2216 bytes
24140 INFO: Writing RT_ICON 3 resource with 1384 bytes
24140 INFO: Writing RT_ICON 4 resource with 37019 bytes
24140 INFO: Writing RT_ICON 5 resource with 9640 bytes
24140 INFO: Writing RT_ICON 6 resource with 4264 bytes
24140 INFO: Writing RT_ICON 7 resource with 1128 bytes
24156 INFO: Appending archive to EXE D:\py\MFGTool\build\mfgtool\mfgtool.exe
24327 INFO: Building EXE from EXE-00.toc completed successfully.
24327 INFO: checking COLLECT
WARNING: The output directory "D:\py\MFGTool\dist\mfgtool" and ALL ITS CONTENTS will be REMOVED! Continue? (y/N)y
On your own risk, you can use the option `--noconfirm` to get rid of this question.
36014 INFO: Removing dir D:\py\MFGTool\dist\mfgtool
36093 INFO: Building COLLECT COLLECT-00.toc
37921 INFO: Building COLLECT COLLECT-00.toc completed successfully.

D:\py\MFGTool>

直接到dist\mfgtool\目录下运行打包后的mfgtool.exe,发现直接弹出一个框报错:

在这里插入图片描述

上面都是默认参数,带有控制台的(运行时类似DOS的界面)。
下面直接在命令行运行:

D:\py\MFGTool\dist\mfgtool>mfgtool.exe
Traceback (most recent call last):
  File "mfgtool.py", line 8, in <module>
  File "C:\Users\Gary\AppData\Local\Temp\embedded.r2luoun3.zip\shibokensupport\__feature__.py", line 142, in _import
ImportError: could not import module 'PySide2.QtXml'
[544] Failed to execute script mfgtool

D:\py\MFGTool\dist\mfgtool>

运行提示缺少’PySide2.QtXml’,这个库其实在我们的实际代码开发中并没有,而是打包时需要的,所以要用上面提到的参数"–hidden-import"将其隐藏。另外,为什么打包时先带上控制台,因为这样方便查找问题,如果没有控制台直接运行,就看不到真正出错在哪里。

下面带上这个参数再试一下:

D:\py\MFGTool>pyinstaller mfgtool.py --hidden-import PySide2.QtXml
D:\py\MFGTool\dist\mfgtool>mfgtool.exe
QIODevice::read (QFile, "mfgtool.ui"): device not open
Designer: An error has occurred while reading the UI file at line 1, column 0: Premature end of document.
Traceback (most recent call last):
  File "mfgtool.py", line 245, in <module>
  File "mfgtool.py", line 17, in __init__
  File "mfgtool.py", line 29, in load_ui
RuntimeError: Unable to open/read ui device
[440] Failed to execute script mfgtool

D:\py\MFGTool\dist\mfgtool>

运行后以报了一个新错,“RuntimeError: Unable to open/read ui device”,这是因为我们的程序加载界面是动态的,需要将界面对应的.ui文件复制到exe程序所在位置。我们这个程序是一个串口通讯演示程序,对应的ui界面文件叫mfgtool.ui,所以将这个文件复制到mfgtool.exe(打包后的可执行文件)所在目录,也就是默认dist\mfgtool\下面。
复制进去后直接双击exe文件就可看到UI界面,这时会在后面看到一个黑色的dos界面,去掉的方法就是加一个参数“-w”重新打包一下,然后再运行就看不到后面的控制台窗口了,如下:

D:\py\MFGTool>pyinstaller mfgtool.py --hidden-import PySide2.QtXml -w

动态加载UI运行后的程序界面如下:

在这里插入图片描述

这个程序界面其实有一个地方显示不全,下面是程序初化的部分代码,从代码中可以看到,我们有设置这个程序的主题title,但这里的主题名称却是“Mfgtool”(默认的主题名)。

    def initUI(self):
        desktop = QApplication.desktop()
        self.screenWidth = desktop.width() * 0.4
        self.screenHeight = desktop.height() * 0.6
#        print("Screen width:", self.screenWidth, "height:", self.screenHeight)
        self.setGeometry(0, 0, self.screenWidth, self.screenHeight)
        self.setWindowTitle("MfgTool V1.0.0") #正确应该是self.ui.setWindowTitle("MfgTool V1.0.0")

以上这个问题,其实是动态加载UI的一点不足,这可能是PySide2设计的问题,也许后续会有改善。下面说下出现这个问题的场景:

  1. UI的加载过程放在一个自定义的类中

  2. 在main主函数中创建上面这个类的实例

  3. 代码程序是这样的:

class MfgTool(QMainWindow):
    def __init__(self):
        super(MfgTool, self).__init__()
        self.load_ui()

    def load_ui(self):
        loader = QUiLoader()
        path = os.path.join(os.path.dirname(__file__), "mfgtool.ui")
        ui_file = QFile(path)
        ui_file.open(QFile.ReadOnly)
        self.ui = loader.load(ui_file, self)
        ui_file.close()
        self.ui.show()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    mainUI = MfgTool()
    sys.exit(app.exec_())

上面动态加载UI场景存在的问题:

  1. self是我们自定义类的,不是ui的,所以上面主题设置无效

  2. 1的问题不算大问题,最要命的问题是,我们的主窗口QMainWindow与我们的ui这个窗口相互独立了,也就是说不会在主窗口QMainWindow中加载ui。所以上面代码运行后界面就如上图所示,但细心的人可能会发现怎么在我的win10(本人开发主机是win10)任务栏里没有看到程序图标啊,为什么没有呢,因为那个图标是主程序的,而上面代码主程序QMainWindow根本就没有show出来,那下面我们就show出来看看,需要在main中添加一行:

mainUI.show()

或者,直接在load_ui函数的末尾添加一行,效果也是一样:

self.show()

在这里插入图片描述
显示的界面怎么是两个,的确是两个,那个空的是QMainWindow主程序的,而且win10任务栏也有一个主程序图标(默认是python图标),这个显示结果也说明了上面2的问题,主程序与我们UI是两个独立的界面。

  1. 另外,上面问题2还会带来一系列问题,比如不会触发QMainWindow窗口的事件等等。当然要能接收窗口事件,需要重写对应的事件接口,而且这样重写出来的效果也不是最佳,所以这里不作详细说明了。同样的问题,别人也遇到过,下面贴一段老外写的贴子,大致问题跟我上面遇到的是一样的。

https://stackoverflow.com/questions/53828666/pyside2-qmainwindow-loaded-from-ui-file-not-triggering-window-events

总结:

  1. 看到这,是不是对PySide2开发python GUI有点失望了,不用担心,这只是PySide2的一个设计,如果研究过PyQt5,你就会发现它没有这个问题,它是将ui作为主界面QMainWindow的一个部件来处理的,也就是类中的父子关系来处理。
    另外,按Qt官方的做法,将加载ui的动作直接放在main里,也就不会有上面这些问题了,下面是直接copy官方的例子:
# File: main.py
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QFile, QIODevice

if __name__ == "__main__":
    app = QApplication(sys.argv)

    ui_file_name = "mainwindow.ui"
    ui_file = QFile(ui_file_name)
    if not ui_file.open(QIODevice.ReadOnly):
        print("Cannot open {}: {}".format(ui_file_name, ui_file.errorString()))
        sys.exit(-1)
    loader = QUiLoader()
    window = loader.load(ui_file)
    ui_file.close()
    if not window:
        print(loader.errorString())
        sys.exit(-1)
    window.show()

    sys.exit(app.exec_())
  1. 最好的UI调用方式,采用静态方式加载,这样事件也可以覆盖,非常类似于直接使用Qt IDE开发工具一样方便。

GUI界面是静态方式加载:
首先,PySide2提供了一个工具,用来将ui转成py文件:

pyside2-uic mfgtool.ui > ui_mfgtool.py

转换后的py文件内容,如果你对Qt开发很熟悉,很像是Qt Creator对ui界面文件转化后的文件,是的,其实它们原理是类似的,都是将UI内容构建成一个类,然后直接调用这个类就可以了。下面是部分转换后的代码:

## Created by: Qt User Interface Compiler version 5.15.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *


class Ui_MfgTool(object):
    def setupUi(self, MfgTool):
        if not MfgTool.objectName():
            MfgTool.setObjectName(u"MfgTool")
        MfgTool.resize(800, 600)
        MfgTool.setStyleSheet(u"QLabel{\n"
"font:20px;\n"
"}\n"
"QPushButton{\n"
"font:25px;\n"
"}\n"
"QComboBox,QLineEdit{\n"
"font:18px;\n"
"}")
        self.centralwidget = QWidget(MfgTool)
        self.centralwidget.setObjectName(u"centralwidget")
        self.label_port = QLabel(self.centralwidget)
        self.label_port.setObjectName(u"label_port")
        self.label_port.setGeometry(QRect(139, 30, 141, 31))
        self.label_port.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
......
        MfgTool.setStatusBar(self.statusbar)

        self.retranslateUi(MfgTool)

        QMetaObject.connectSlotsByName(MfgTool)
    # setupUi

    def retranslateUi(self, MfgTool):
        MfgTool.setWindowTitle(QCoreApplication.translate("MfgTool", u"MfgTool", None))
        self.label_port.setText(QCoreApplication.translate("MfgTool", u"Serial Port:", None))
        self.pushButton_refresh.setText(QCoreApplication.translate("MfgTool", u"Refresh", None))
        self.pushButton_open.setText(QCoreApplication.translate("MfgTool", u"Open", None))
        self.pushButton_read.setText(QCoreApplication.translate("MfgTool", u"Read", None))
        self.pushButton_write.setText(QCoreApplication.translate("MfgTool", u"Write", None))
        self.label_type.setText(QCoreApplication.translate("MfgTool", u"Device Type:", None))
        self.label_sn.setText(QCoreApplication.translate("MfgTool", u"Device S/N:", None))
        self.label_mfgsn.setText(QCoreApplication.translate("MfgTool", u"MFG S/N:", None))
        self.label_pdate.setText(QCoreApplication.translate("MfgTool", u"PDate:", None))
        self.label_hwver.setText(QCoreApplication.translate("MfgTool", u"HW Version:", None))
    # retranslateUi

再看下主程序是怎样的:

from ui_mfgtool import Ui_MfgTool


class MfgTool(QMainWindow):
    def __init__(self):
        super(MfgTool, self).__init__()
#        self.load_ui()
        self.ui = Ui_MfgTool()
        self.ui.setupUi(self)
        self.initUI()
        self.ConnectFun()

........

if __name__ == "__main__":
    app = QApplication(sys.argv)
    mainUI = MfgTool()
    mainUI.show()
    sys.exit(app.exec_())

这样我们先用命令行方式运行,界面如下:

在这里插入图片描述

从上图发现,程序的主题title也显示正常,win10任务栏也有主程序图标了,另外也没有显示两个界面(QMainWindow与ui),这就是静态加载的好处,完全跟在Qt Creator下开发一样的效果,完美了!

最后再演示下,主程序窗口与Ui之间已经完美对接,完全是父子类的关系。

    def initUI(self):
        desktop = QApplication.desktop()
        self.screenWidth = desktop.width() * 0.4
        self.screenHeight = desktop.height() * 0.6
#        print("Screen width:", self.screenWidth, "height:", self.screenHeight)
        self.setGeometry(0, 0, self.screenWidth, self.screenHeight)
        self.ui.setWindowTitle("MfgTool V1.0.0")#静态加载应该写成self.setWindowTitle("MfgTool V1.0.0")

上面代码中设置title按照动态加载方式写,就会遇到下面报错:

D:\py\MFGTool>python mfgtool.py
Traceback (most recent call last):
  File "D:\py\MFGTool\mfgtool.py", line 245, in <module>
    mainUI = MfgTool()
  File "D:\py\MFGTool\mfgtool.py", line 20, in __init__
    self.initUI()
  File "D:\py\MFGTool\mfgtool.py", line 39, in initUI
    self.ui.setWindowTitle("MfgTool V1.0.0")
AttributeError: 'Ui_MfgTool' object has no attribute 'setWindowTitle'

到这就把静态加载UI也介绍完了,至打包成exe,方式与动态打包所用的指令一样,不重复了,说明的是不用再把ui文件复制到打包文件夹下面了。

另外,如果只想打包成一个exe执行文件,不需要其他关联的库等文件,可以使用“-F”参数,这样只会打包成一个exe文件,只是这个exe文件有点大。

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JXES智能生态系统

如文章对你有用,请作者喝个咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值