我正在使用 pyinstaller 捆绑我的 Windows(操作系统)应用程序,并添加了启动屏幕选项。加载主窗口并关闭启动画面后,该窗口将停留在背景上(如果您打开了任何窗口,则位于其他窗口后面)。 我尝试过 .raise_() .ActivateWindow() .setVisible(True) 。但他们不会把窗户带到顶部。如果我禁用启动画面,它会按预期工作,但我需要启动画面,因为加载需要一点时间。 我已经没有什么可以尝试的了,有人有建议吗?
最低限度是下一个:
'''
Created on Oct 17, 2022
@author: mdelu
'''
import sys
from PyQt5 import QtWidgets
try:
import pyi_splash
except:
pass
# print('Ejecucion en eclipse sin splash')
if __name__ == '__main__':
try:
if (pyi_splash.is_alive()):
pyi_splash.close()
except:
pass
app = QtWidgets.QApplication(sys.argv)
main_window = QtWidgets.QMainWindow()
ui = QtWidgets.QWidget(main_window)
main_window.resize(800, 600)
main_window.show()
sys.exit(app.exec_())
我的 *.spec 文件是:
a = Analysis(['main.py'],
binaries=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
noarchive=False,
)
splash_image = 'path'
splash = Splash(splash_image,
binaries=a.binaries,
datas=a.datas,
minify_script=False)
pyz = PYZ(a.pure, a.zipped_data)
exe = EXE(pyz,
splash,
a.scripts,
[],
exclude_binaries=True,
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None)
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
splash.binaries,
strip=False,
upx=True,
upx_exclude=[],
name='exe')
这就是我所做的,但请注意,我设置了启动屏幕,因此它并不总是位于最上面。我还没有测试过这种情况,这种方法可能在这种情况下不起作用,但它可能会帮助你弄清楚一些事情。
它的工作方式是在名为“tk”的进程中创建 pyinstaller 启动屏幕,因此您可以枚举在自己的进程中搜索“tk”的所有窗口。启动屏幕是在与主应用程序不同的线程中创建的。
这段代码的行为是:
Python 主要代码(它是从我的真实应用程序复制并粘贴的,我现在无法轻松提取最小代码):
import sys
from PySide6 import QtCore
from PySide6.QtWidgets import QApplication, QMainWindow
import win32api
import win32gui
import win32con
import win32process
import platform
import importlib
from ui_MainWindow import Ui_MainWindow
pyi_splash_spec = importlib.util.find_spec("pyi_splash")
if pyi_splash_spec is not None:
import pyi_splash
# Simple data class for storing our process ID and the found splash window handle.
class EnumWindowHandlerContext:
processId: int
splashHWnd: int
#
class MyMainWindow(QMainWindow):
#
def __init__(self, parent=None):
super(MyMainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
def winEnumHandler(hwnd, ctx: EnumWindowHandlerContext):
if win32gui.IsWindowVisible(hwnd):
# Get process and name of window
(threadId, processId) = win32process.GetWindowThreadProcessId(hwnd)
n = win32gui.GetWindowText(hwnd)
# If our process and called 'tk' assume it is the splash window and store the handle
if (processId == ctx.processId) and (n == 'tk'):
ctx.splashHWnd = hwnd
def MoveAboveSplash(hwnd):
enumContext = EnumWindowHandlerContext()
enumContext.processId = win32api.GetCurrentProcessId()
win32gui.EnumWindows(winEnumHandler, enumContext)
if enumContext.splashHWnd != 0:
if win32gui.GetForegroundWindow() == enumContext.splashHWnd:
flags = win32con.SWP_NOMOVE | win32con.SWP_NOSIZE
else:
flags = win32con.SWP_NOACTIVATE | win32con.SWP_NOMOVE | win32con.SWP_NOSIZE
# Move our window so it is immediately after the splash window.
# Activate if splash was foreground, otherwise not.
win32gui.SetWindowPos(hwnd, enumContext.splashHWnd, 0, 0, 0, 0, flags)
# Now move the splash window so that it is behind the main window.
# Always "no activate", we don't want to reactivate the splash screen.
win32gui.SetWindowPos(enumContext.splashHWnd, hwnd, 0, 0, 0, 0, win32con.SWP_NOACTIVATE | win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)
# Make ourselves the foreground window. Throws an exception if it fails
# but failure is just that it won't do it, not an error condition, so
# ignore the exception.
try:
win32gui.SetForegroundWindow(hwnd)
except:
pass
#
def main():
QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
#app.setStyle('WindowsVista')
mainWindow = MyMainWindow()
mainWindow.show()
if pyi_splash_spec is not None and pyi_splash.is_alive():
if platform.system() == 'Windows':
MoveAboveSplash(mainWindow.winId())
pyi_splash.close()
app.exec()
#
if (__name__ == '__main__'):
main()
pyinstaller 的规范文件:
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['MyApp/minimal.py'],
pathex=['MyApp'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=['sqlite', 'tbb'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
splash = Splash(
'splash.png',
binaries=a.binaries,
datas=a.datas,
text_pos=None,
text_size=12,
minify_script=True,
always_on_top=False,
)
exe = EXE(
pyz,
a.scripts,
splash,
[],
exclude_binaries=True,
name='MyApp',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
splash.binaries,
strip=False,
upx=True,
upx_exclude=[],
name='MyApp',
)
(1) 使用qt的splash来代替
我正在使用pyside6(也许pyqt5有相同的方法)。不要在 pyinstaller 中使用splash,而应在 qt 中使用。使用 win API 和 pyinstaller 确实对抗 qt 的跨平台功能。
这是我的代码:
from PySide6.QtWidgets import QSplashScreen
from PySide6.QtGui import QPixmap
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
# pyside6 splash screen
splash = QSplashScreen()
splash.setPixmap(QPixmap(":/splash.png"))
splash.show()
mainWindow = MainWindow()
mainWindow.show()
# close splash screen
splash.finish(mainWindow)
sys.exit(app.exec())
“:/splash.png”是由 pyside6-rcc 定义的,用于跨平台使用。
(2) pyinstaller 的正确使用方法
仅在单文件模式下工作。
我再试一次,下面的代码有效: 添加新的 Load_Splash.py 文件
import pyi_splash
pyi_splash.update_text('loaded...')
# https://pyinstaller.readthedocs.io/en/stable/advanced-topics.html#module-pyi_splash
pyi_splash.close()
然后在其他导入结束时将其导入到 main.py 中。
import Load_Splash.py
完成。