【问题标题】:Gtk.ProgressBar not working in PythonGtk.ProgressBar 在 Python 中不起作用
【发布时间】:2013-04-10 00:05:14
【问题描述】:

我正在尝试在 PythonGtk3 中使用进度条,但它没有得到更新。我已经阅读了这个论坛中的this 文档和一些问题(主要针对 pygtk),但我真的不明白!

我编写的代码仅用于测试目的。当您单击一个按钮时,它会递归地读取目录的内容。我的意图是在阅读所有这些文件时使用进度条。

这是整个代码:

import os
from gi.repository import Gtk

class MyWindow(Gtk.Window):
    """Progress Bar"""

    def __init__(self):
        Gtk.Window.__init__(self, title='Progress Bar')
        self.set_default_size(300, 75)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_border_width(10)

        # read dir contents
        mydir = 'Documents'
        home_path = os.environ.get('HOME')
        dir_path = os.path.join(home_path, mydir)
        self.dir_files_list(dir_path)

        # create a grid
        self.grid = Gtk.Grid(column_homogeneous=True, row_homogeneous=True,
                             column_spacing=10, row_spacing=10)
        self.add(self.grid)

        # create a progress bar
        self.progressbar = Gtk.ProgressBar()
        self.grid.add(self.progressbar)

        # create a button
        self.button = Gtk.Button(stock=Gtk.STOCK_APPLY)
        self.button.connect('clicked', self.on_button_pressed)
        self.grid.attach(self.button, 0, 1, 1, 1)

    # function to read the dir contents
    def dir_files_list(self, dir_path):
        self.dir_list = []
        for root, dirs, files in os.walk(dir_path):
            for fn in files:
                f = os.path.join(root, fn)
                self.dir_list.append(f)

    # function to update the progressbar
    def on_button_pressed(self, widget):
        self.progressbar.set_fraction(0.0)
        frac = 1.0 / len(self.dir_list)
        for f in self.dir_list:
            new_val = self.progressbar.get_fraction() + frac
            print new_val, f
            self.progressbar.set_fraction(new_val)
        return True

def main():
    """Show the window"""
    win = MyWindow()
    win.connect('delete-event', Gtk.main_quit)
    win.show_all()
    Gtk.main()
    return 0

if __name__ == '__main__':
    main()

感谢更有经验的程序员的任何帮助!

【问题讨论】:

    标签: python progress-bar gtk3 pygobject


    【解决方案1】:

    问题是您在创建窗口时正在处理整个目录。甚至在您展示它之前(与self.dir_files_list(dir_path) 一致)。在我看来,您想在按下应用按钮后调用它。

    至少有两种可能的解决方案:使用线程或使用迭代器。对于您的特定用例,我认为迭代器就足够了,除了更简单和更pythonic。我只会在您真正需要线程时推荐线程。

    您可以一次处理每个目录中的每个文件,而不是事先遍历整个目录并稍后处理它们。

    在您的示例中,我会将方法 dir_files_list(重命名为 walk)和 on_button_pressed 更改为:

    from gc import collect
    from gi.repository import Gtk, GObject
    

    我们现在还需要导入 GObject 以使用idle_add,只要没有更高优先级的事件挂起,它就会调用回调。此外,一旦任务完成,我们需要删除回调以不再调用它(为此我们需要source_remove)。

    我将您的方法 dir_files_list 重命名为 walk,因为它在语义上似乎更接近迭代器。当我们遍历每个目录和文件时,我们会暂时“返回”(使用yield)。请记住,yield True 表示有待处理的项目要处理。 yield False 表示我们停止迭代。

    所以,方法是:

    def walk(self, dir_path):
        self.dir_list = []
        for root, dirs, files in os.walk(dir_path):
            i = 0.0
            self.set_title(os.path.dirname(root))
    
            for fn in files:
                i = i + 1.0
                f = os.path.join(root, fn)
                self.dir_list.append(f)
    
                self.progressbar.set_fraction(i / len(files))
    
                collect()
                yield True
    
           yield True
    
        GObject.source_remove(self.id)
        yield False
    

    现在,我们在此处更新progressbar。在这个特定的部分中,我正在更新每个目录中的进度条。也就是说,它会在每个子目录中重新启动,并且进度条会在每个子目录中重新启动。要了解正在访问的目录,请将窗口标题设置为当前目录。这里要理解的重要部分是yield。您可以根据自己的意愿对其进行调整。

    遍历整个目录后,我们必须返回yield False,但在此之前我们删除回调(GObject.source_remove(self.id))。

    我认为self.dir_list 在这里不再有用,但您可能有不同的想法。

    您可能想知道何时以及如何调用 walk?可以在按下按钮时设置:

    def on_button_pressed(self, button, *args):
        homedir = os.path.expanduser('~')
        rootdir = os.path.join(homedir, 'Documents')
    
        self.task = self.walk(rootdir)
        self.id = GObject.idle_add(self.task.next)
    

    self.task 是一个迭代器,它具有从self.task 检索下一项的方法next(),只要没有未决事件(使用idle_add)就会调用该方法。我们得到id,以便在完成后删除回调。

    您必须从 __init__ 方法中删除行 self.dir_files_list(dir_path)

    还有一件事:我手动调用了垃圾收集器 (gc.collect()),因为它在处理大型目录时可能很有用(取决于您对它们执行的操作)。

    【讨论】:

    • 非常感谢@gpoo 的帮助!我将花一些时间阅读本文并尝试理解和吸收所有内容:-)
    • 如有任何看起来晦涩难懂或需要更多解释的问题,请随时提出。
    【解决方案2】:

    首先,问题是上面的代码运行得太快了,所以你看到进度条更新得太快了,好像没有更新。这样做是因为您没有同时搜索目录并显示结果。当 __init__ 类进行搜索时,然后当您单击按钮时,列表将被读取并全速显示在进度条中。我很确定如果目录很大,当您启动脚本时,窗口将需要几秒钟才能显示,并且进度条最终会在几毫秒内完成。

    基本上你遇到的问题是你在同一个线程中做所有事情。 Gtk 在它自己的线程中,监听来自 GUI 的事件并更新 GUI。大多数时候,您将使用 Gtk.ProgressBar 来完成需要一些时间才能完成的事情,并且您需要同时执行 GUI 线程和工作线程(正在做某事的线程),并从工作线程到 GUI 线程,以便更新进度条。如果像上面的代码一样,你在同一个线程中运行所有东西,你最终会遇到这种问题,例如当 GUI 冻结时,也就是说,它变得无响应,因为突然 GUI 线程正在做一些与 GUI 无关的工作.

    在 PyGTK 中,您有 gobject.idle_add(function, parameters) 方法,因此您可以从工作线程与 GUI 线程通信,因此当 GUI 线程空闲时,它将使用这些参数执行该函数,例如,更新 Gtk。进度条。

    我解决这个问题的方法在这里实现,请注意PyGTK:https://github.com/carlos-jenkins/nested/blob/master/src/lib/nested/core/gallery/loading.py

    基本上,它是整个应用程序共享的“LoadingWindow”。当您想开始加载某些东西或执行一些繁重的工作时,您必须继承 WorkingThread 类 (example)。然后,您只需使用 WorkingThread 子类实例作为参数调用 show() 方法并完成。在 WorkingThread 子类中,您必须实现 payload() 函数,即完成繁重工作的函数。您可以直接从 WorkingThread 调用 LoadingWindow 中的pulse 方法来更新 ProgressBar,并且不应该关心线程通信,因为该逻辑在那里实现。

    希望解释有所帮助。

    编辑:

    我只是将上面的内容移植到 PyGObject,你可以在这里找到示例:https://gist.github.com/carlos-jenkins/5358445

    【讨论】:

    • 非常感谢。现在有了你的解释,我对 Gtk+ 和线程有了更多的了解,但我不确定我是否能够在我的程序中实现这一点......好吧,至少我要去尝试...感谢您为解释我的问题之外隐藏的内容所做的努力。
    • 我更新了答案,查看PyGObject版本:gist.github.com/carlos-jenkins/5358445
    • 非常感谢您的宝贵时间。我要去看看那个版本。谢谢!
    • 我放弃了,这对我的技能来说太复杂了。我需要一些更简单的东西,如果可能的话,放在一个文件中:) 无论如何,非常感谢!
    猜你喜欢
    • 2012-06-07
    • 1970-01-01
    • 2017-09-08
    • 2021-01-06
    • 2016-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多