PyInstaller 的启动画面:主窗口未显示在顶部

问题描述 投票:0回答:2

我正在使用 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')
python-3.x pyinstaller splash-screen
2个回答
0
投票

这就是我所做的,但请注意,我设置了启动屏幕,因此它并不总是位于最上面。我还没有测试过这种情况,这种方法可能在这种情况下不起作用,但它可能会帮助你弄清楚一些事情。

它的工作方式是在名为“tk”的进程中创建 pyinstaller 启动屏幕,因此您可以枚举在自己的进程中搜索“tk”的所有窗口。启动屏幕是在与主应用程序不同的线程中创建的。

这段代码的行为是:

  • 运行应用程序,启动画面不是最上面的,因此您可以轻松切换到其他应用程序。
  • 当应用程序运行时,它将把自己的窗口移动到启动屏幕前面并关闭启动屏幕,并按 Z 顺序保留在该位置。
  • 如果初始屏幕是活动窗口,则应用程序窗口将变为活动窗口。
  • 如果启动屏幕未激活(例如,您在加载该应用程序时切换到另一个应用程序),则应用程序任务栏条目将闪烁。

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',
)

0
投票

(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

完成。

© www.soinside.com 2019 - 2024. All rights reserved.