Gtk.ProgressBar 在 Python 中不起作用



我正在尝试在PythonGtk3中使用进度条,但它没有得到更新。我已经阅读了这个文档和这个论坛中的一些问题(主要是针对 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()

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

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

至少有 2 种可能的解决方案:使用线程或使用迭代器。 对于您的特定用例,我认为迭代器就足够了,除了更简单和更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()(,因为它在处理大目录时可能很有用(取决于您对它们做什么(。

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

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

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

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

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

希望解释有帮助。

编辑:

我刚刚将上述内容移植到 PyGObject,您可以在此处找到示例:https://gist.github.com/carlos-jenkins/5358445

最新更新