我如何修复我的PyQt5启动屏幕实现中的问题?

huangapple go评论68阅读模式
英文:

How can I fix issues with my PyQt5 splash screen implementation?

问题

以下是您的代码的翻译部分:

import sys
import time
from PyQt5.QtWidgets import QApplication, QWidget, QDialog, QProgressBar, QMainWindow, QVBoxLayout
from PyQt5.QtCore import Qt, QTimer, QEventLoop, QCoreApplication

class SplashScreen(QDialog):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('启动屏幕示例')
        self.setFixedSize(800, 400)
        self.setWindowFlag(Qt.FramelessWindowHint)
        self.setWindowFlag(Qt.WindowStaysOnTopHint)
        self.initUI()

    def initUI(self):
        layout = QVBoxLayout(self)

        self.progressBarMenu = QProgressBar()
        self.progressBarProcess = QProgressBar()

        layout.addWidget(self.progressBarMenu)
        layout.addWidget(self.progressBarProcess)

        self.setStyleSheet('''
            QProgressBar {
                background-color: #DA7B93;
                color: rgb(200, 200, 200);
                border-style: none;
                border-radius: 10px;
                text-align: center;
                font-size: 20px;
            }

            QProgressBar::chunk {
                border-radius: 10px;
                background-color: qlineargradient(spread:pad x1:0, x2:1, y1:0.511364, y2:0.523, stop:0 #1C3334, stop:1 #376E6F);
            }
        ''')

class MyApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.valueMenu = 0
        self.valueProcess = 0
        self.window_width, self.window_height = 800, 600
        self.setMinimumSize(self.window_width, self.window_height)

        self.splash_screen = SplashScreen()
        self.setCentralWidget(self.splash_screen)

        self.menu_count = 3
        self.current_menu = 1

        self.timerMenu = QTimer()
        self.timerMenu.timeout.connect(self.load_next_menu)
        self.timerMenu.start(1000)

        self.timerProcess = QTimer()
        self.timerProcess.timeout.connect(self.update_process_bar)
        self.timerProcess.start(1000)

        self.start_time = time.time()
        self.estimated_time = 10  # 每个菜单的预估加载时间(秒)
        self.remaining_time = self.estimated_time

        self.overall_progress = 0
        self.is_menu_loaded = False

    def load_next_menu(self):
        self.valueMenu = 0

        if self.current_menu == 1:
            self.load_menu_one()
        elif self.current_menu == 2:
            self.load_menu_two()
        elif self.current_menu == 3:
            self.load_menu_three()

        self.update_menu_progress(self.valueMenu)
        self.current_menu += 1

        if self.current_menu > self.menu_count:
            self.timerMenu.stop()
            self.update_menu_progress(100)
            self.is_menu_loaded = True

    def load_menu_one(self):
        menu_one_items = 10  # 菜单一中的项目数量

        for i in range(menu_one_items):
            time.sleep(0.5)
            self.valueMenu += int(100 / menu_one_items)
            self.update_menu_progress(self.valueMenu)
            QCoreApplication.processEvents()

    def load_menu_two(self):
        menu_two_items = 4  # 菜单二中的项目数量

        for i in range(menu_two_items):
            time.sleep(0.5)
            self.valueMenu += int(100 / menu_two_items)
            self.update_menu_progress(self.valueMenu)
            QCoreApplication.processEvents()

    def load_menu_three(self):
        menu_three_items = 40  # 菜单三中的项目数量

        for i in range(menu_three_items):
            time.sleep(0.5)
            self.valueMenu += int(100 / menu_three_items)
            self.update_menu_progress(self.valueMenu)
            QCoreApplication.processEvents()

    def update_menu_progress(self, value):
        self.splash_screen.progressBarMenu.setValue(value)

    def update_process_bar(self):
        elapsed_time = int(time.time() - self.start_time)
        self.remaining_time = self.estimated_time - elapsed_time

        if self.current_menu > self.menu_count:
            self.overall_progress = 100
        else:
            self.overall_progress = int((self.current_menu - 1) / self.menu_count * 100)

        if self.remaining_time <= 0:
            self.remaining_time = 0
            self.timerProcess.stop()

        self.valueProcess = int(self.overall_progress + (self.valueMenu / self.menu_count))
        self.splash_screen.progressBarProcess.setValue(self.valueProcess)

        if self.current_menu > self.menu_count and self.is_menu_loaded:
            self.timerProcess.stop()
            self.splash_screen.close()
            self.show_main_window()

    def show_main_window(self):
        pass

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MyApp()
    window.show()
    sys.exit(app.exec_())

请注意,由于代码中有一些HTML实体字符(如&#39;),我已将它们还原为普通的单引号(')进行翻译。

英文:

How can I fix issues with my PyQt5 splash screen implementation?

I am working on a project that requires a splash screen displaying a GIF file while loading menus. Each menu can have a different number of items, and the time needed to load each menu can vary. I want to include two progress bars: one tracking the progress of the menu loading and one displaying the overall progress of the entire process. The first progress bar should clear its value after a time delay, even when the menu loading completed before that time. The splash screen should only close after the second progress bar reaches 100%.

Currently, my implementation has the following problems:

  • The second progress bar (displaying the overall progress of the entire process) only shows 33% progress after the first menu loaded. The subsequent menu loading does not update the second progress bar.
  • The first progress bar (tracking the progress of the menu loading) only reaches 78% when loading the third menu before the splash screen closes.

Here is the code I am using:

import sys
import time
from PyQt5.QtWidgets import QApplication, QWidget, QDialog, QProgressBar, QMainWindow, QVBoxLayout
from PyQt5.QtCore import Qt, QTimer, QEventLoop, QCoreApplication


class SplashScreen(QDialog):
    def __init__(self):
        super().__init__()
        self.setWindowTitle(&#39;Splash Screen Example&#39;)
        self.setFixedSize(800, 400)
        self.setWindowFlag(Qt.FramelessWindowHint)
        self.setWindowFlag(Qt.WindowStaysOnTopHint)
        self.initUI()

    def initUI(self):
        layout = QVBoxLayout(self)

        self.progressBarMenu = QProgressBar()
        self.progressBarProcess = QProgressBar()

        layout.addWidget(self.progressBarMenu)
        layout.addWidget(self.progressBarProcess)

        self.setStyleSheet(&#39;&#39;&#39;
            QProgressBar {
                background-color: #DA7B93;
                color: rgb(200, 200, 200);
                border-style: none;
                border-radius: 10px;
                text-align: center;
                font-size: 20px;
            }

            QProgressBar::chunk {
                border-radius: 10px;
                background-color: qlineargradient(spread:pad x1:0, x2:1, y1:0.511364, y2:0.523, stop:0 #1C3334, stop:1 #376E6F);
            }
        &#39;&#39;&#39;)


class MyApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.valueMenu = 0
        self.valueProcess = 0
        self.window_width, self.window_height = 800, 600
        self.setMinimumSize(self.window_width, self.window_height)

        self.splash_screen = SplashScreen()
        self.setCentralWidget(self.splash_screen)

        self.menu_count = 3
        self.current_menu = 1

        self.timerMenu = QTimer()
        self.timerMenu.timeout.connect(self.load_next_menu)
        self.timerMenu.start(1000)

        self.timerProcess = QTimer()
        self.timerProcess.timeout.connect(self.update_process_bar)
        self.timerProcess.start(1000)

        self.start_time = time.time()
        self.estimated_time = 10  # Estimated time in seconds for each menu
        self.remaining_time = self.estimated_time

        self.overall_progress = 0
        self.is_menu_loaded = False

    def load_next_menu(self):
        self.valueMenu = 0

        if self.current_menu == 1:
            self.load_menu_one()
        elif self.current_menu == 2:
            self.load_menu_two()
        elif self.current_menu == 3:
            self.load_menu_three()

        self.update_menu_progress(self.valueMenu)
        self.current_menu += 1

        if self.current_menu &gt; self.menu_count:
            self.timerMenu.stop()
            self.update_menu_progress(100)
            self.is_menu_loaded = True

    def load_menu_one(self):
        menu_one_items = 10  # Number of items in menu one

        for i in range(menu_one_items):
            time.sleep(0.5)
            self.valueMenu += int(100 / menu_one_items)
            self.update_menu_progress(self.valueMenu)
            QCoreApplication.processEvents()

    def load_menu_two(self):
        menu_two_items = 4  # Number of items in menu two

        for i in range(menu_two_items):
            time.sleep(0.5)
            self.valueMenu += int(100 / menu_two_items)
            self.update_menu_progress(self.valueMenu)
            QCoreApplication.processEvents()

    def load_menu_three(self):
        menu_three_items = 40  # Number of items in menu three

        for i in range(menu_three_items):
            time.sleep(0.5)
            self.valueMenu += int(100 / menu_three_items)
            self.update_menu_progress(self.valueMenu)
            QCoreApplication.processEvents()

    def update_menu_progress(self, value):
        self.splash_screen.progressBarMenu.setValue(value)

    def update_process_bar(self):
        elapsed_time = int(time.time() - self.start_time)
        self.remaining_time = self.estimated_time - elapsed_time

        if self.current_menu &gt; self.menu_count:
            self.overall_progress = 100
        else:
            self.overall_progress = int((self.current_menu - 1) / self.menu_count * 100)

        if self.remaining_time &lt;= 0:
            self.remaining_time = 0
            self.timerProcess.stop()

        self.valueProcess = int(self.overall_progress + (self.valueMenu / self.menu_count))
        self.splash_screen.progressBarProcess.setValue(self.valueProcess)

        # print(f&quot;Elapsed Time: {elapsed_time}s&quot;)
        # print(f&quot;Estimated Time: {self.estimated_time}s&quot;)
        # print(f&quot;Remaining Time: {self.remaining_time}s&quot;)

        if self.current_menu &gt; self.menu_count and self.is_menu_loaded:
            self.timerProcess.stop()
            self.splash_screen.close()
            self.show_main_window()

    def show_main_window(self):
        pass


if __name__ == &#39;__main__&#39;:
    app = QApplication(sys.argv)
    window = MyApp()
    window.show()
    sys.exit(app.exec_())

答案1

得分: 1

以下是您的翻译:

你的实现存在几个问题。

问题:

  1. 你正在使用QTimer来启动加载菜单项,但QTimer只能在几乎不会被阻塞的事件循环中正常工作,而这不适用于你的情况(我假设你使用time.sleep模拟加载);
  2. 你对加载的估计时间是完全随意的;结果是,如果进程需要更多时间,overall_progress将始终为100,即使加载尚未完成,而你的情况正是如此:因为你正在加载10+4+40个项目,每个项目基本上需要0.5秒的延迟,总共需要27秒;

上述意味着:

  • 可见的更新将在加载开始后经过10秒停止;
  • 由于不可靠地使用QTimer来加载更多菜单,更新可能在不同条件或不同机器上不一致;
  • 你永远不能依赖于任意的时间估算来“中断”进度:进程完成时才算完成;

所以,解决你的问题的“简单”方法是在加载完成之前不要停止timerProcess计时器。

不足之处:
不幸的是,这不是你问题中最小的问题,因为你的实现还有许多其他重要的问题。

以下问题与上述问题密切相关:

  • 正如前面所说,你不应该使用QTimer来任意加载更多项目;相反,由于加载应该是同步的(只有在上一个菜单加载完成后才加载下一个),你应该调用一个函数,该函数遍历菜单并加载它们;
  • self.valueMenu的增加是不可靠和不精确的,因为它使用了int():更合适的解决方案是始终使用浮点值进行增加,并使用round()设置进度条的值;

其他问题:
这些问题与特定问题不太相关,但仍然非常重要:

  • Qt已经提供了QSplashScreen类,并且在QMainWindow中使用它是错误的;要么使用适当的Qt.SplashScreen window flag,要么,如果你想要“启动画面”嵌入到窗口中,将其设置为窗口的子窗口,并且不包含在任何布局之内,最终在窗口的resizeEvent()中更新其几何形状(请参见这个相关答案);
  • QDialog,与QMainWindow类似,被设计为顶级窗口小部件(即窗口),不应该被设置为小部件的子元素(除非在适当的上下文中使用,比如使用MDI区域):在直接子元素中制作对话框绝对没有合理的理由(特别是作为QMainWindow的中央小部件),这也是一种毫无意义且不良实践(不幸的是一些不负责任的YouTuber传播了这种做法);只需使用标准的QWidget或用于容器的小部件(如QFrame或QGroupBox);
  • 你应用的窗口标志只对顶级窗口小部件(再次强调,窗口)有意义,将它们设置为将成为子元素的小部件完全没有用处,不起作用;
  • 为一个孤立的子元素小部件设置固定大小很少是一个好选择,因为它将成为一个可以调整大小的窗口的子元素(因此,用户仍然可以调整父窗口的大小,并且小部件仍然将与其顶部左上角对齐,并粘在其有限的大小上);
英文:

There are several issues with your implementation.

The problem(s) at hand

  1. You are using a QTimer to start loading menu items, but QTimer can only work properly with an event loop that is practically never blocked, which is not your case (I'm assuming that you used time.sleep to simulate loading);
  2. You are assuming an estimated time for loading that is completely arbitrary; the result if that if the process requires more time, the overall_progress will always be 100 even if loading is not completed, which is exactly your case: since you are loading 10+4+40 items with a basic 0.5s delay for each item, resulting in 27 seconds;

The above means that:

  • the visible updates will stop as soon as 10 seconds have passed since the loading started;
  • the updates will probably not be consistent on different conditions or machines due to the unreliable usage of QTimer to load further menus;
  • you can never rely on arbitrary time estimations to "interrupt" a progress: the process is complete when it is complete;

So, the "simple" solution to your issue is to not stop the timerProcess timer before the loading has finished.

Not enough

Unfortunately, this is not the least of your problems, as your implementation has many other important issues.

The following are strictly related to the issue at hand:

  • as said above, you shouldn't use a QTimer to arbitrary load further items; instead, since the loading is supposed to be synchronous (a menu is loaded only after the previous is completed), you should just call a function that iterates through the menus and loads them;
  • the self.valueMenu addition is unreliable and imprecise, since it's using int(): a more appropriate solution would be to always use floating values for the addition, and set the progress bar value using round();

Other issues

These are not that relevant to the specific problem, but are still quite important:

  • Qt already provides a QSplashScreen class, and using a QMainWindow for that purpose is just wrong; either use the proper Qt.SplashScreen window flag, or, if you want the "splash screen" to be "embedded" in the window, set it as a child and outside of any layout, and eventually update its geometry in that window's resizeEvent() (see this related answer);
  • QDialog, similarly to QMainWindow, is intended as a top level widget (aka, a window), and it should never be set as a child of a widget (unless it's done in the proper context, like using MDI areas): there's absolutely NO valid reason in making a dialog as a direct child (and especially as central widget of a QMainWindow), and it is also a pointless, bad practice (unfortunately spread by some irresponsible youtubers); just use a standard QWidget or a widget that is supposed to be a container (like QFrame or QGroupBox);
  • the window flags you're applying only make sense for top level widgets (again, windows), setting them for a widget that is going to be a child is completely useless and has absolutely no effect;
  • setting a fixed size for a lone child widget is rarely a good choice, since it will be a child of a window that could be resized (so, the user will be able to resize the parent anyway, and the widget will still be aligned on its top left corner, and stuck to its limited size);

huangapple
  • 本文由 发表于 2023年6月16日 09:20:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/76486413.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定