【问题标题】:tkinter progress bar won't update when called from an other app从其他应用程序调用时,tkinter 进度条不会更新
【发布时间】:2018-09-20 02:49:31
【问题描述】:

我正在使用 tkinter 开发一个 GUI 来管理数据库中的图像(导入文件、加载文件、查询......)

当扫描新目录及其子目录以查找要放入数据库的新图像时,会启动专用 GUI: 它由一个 Text 小部件组成,其中打印了当前分析的目录的名称,以及一个显示扫描进度的进度条。 当我单独调用此 GUI 时,只要我在每次更改进度条后使用 update(),进度条就会更新并正确进行。另一方面, 即使我不使用更新,文本小部件也会正确更新。

但是,当我从主 GUI 调用进度条时,进度条不会按应有的方式更新,而 Text 小部件会正确更新。

希望有人能帮忙!

以下是进度条 GUI 的代码。我正在使用 Python 3.6。

from tkinter.filedialog import *
from tkinter.ttk import *


class ScanDirectoryForJPG(Tk):
    """
    Inherited from the Tk class
    """

    def __init__(self, parent, Path=None):
        Tk.__init__(self, parent)
        self.parent = parent
        self.PathDicom = Path
        if self.Path == None:
            self.Path = askdirectory(title='Select a directory to scan')

        self.title('Scan {} for JPG files'.format(self.Path))
        self.status_string = 'Scanning the content of {} folder\n'.format(self.Path)
        self.initialize_gui()
        self.scan_directory()

    def initialize_gui(self):

        # Style
        self.style = Style()
        self.style.theme_use('vista')

        # Main window
        self.grid()
        self.grid_columnconfigure([0], weight=1)
        self.grid_rowconfigure([0], weight=1)

        # Status
        self.status_label = Text(self)
        self.status_label.grid(row=0, column=0, sticky='NSEW')
        self.status_label.insert(END, 'Looking for JPG files in {}\n'.format(self.Path))

        # Progress Bar
        self.p = DoubleVar()
        self.progress_bar = Progressbar(self, orient='horizontal', mode='determinate', variable=self.p, maximum=100)
        self.p.set(0)
        self.progress_bar.grid(row=1, column=0, rowspan=1, sticky='EW')

    def scan_directory(self):
        """

        """
        number_of_files = sum([len(files) for r, d, files in os.walk(self.Path)])
        count = 0

        for dirName, subdirList, fileList in os.walk(self.Path):
            self.status_label.insert(END, '\t-exploring: {}\n'.format(dirName))
            self.update()

            for filename in fileList:
                count += 1
                value = count / number_of_files * self.progress_bar['maximum']
                if value >= (self.progress_bar['value'] + 1):
                    # update the progress bar only when its value is increased by at least 1 (avoid too much updates of the progressbar)
                    self.p.set(self.progress_bar['value'] + 1)
                    self.update()

                file = os.path.join(dirName, filename)

                # if the file is a JPG, load it into the database
                # ...
                # ...
                # ..

        self.status_label.insert(END, 'FINISH\n')
        self.update()


if __name__ == '__main__':
    app = ScanDirectoryForJPG(None, Path='D:\Data\Test')
    app.mainloop()
    print('App closed')

【问题讨论】:

  • 您的问题似乎可以归结为:“为什么从主 GUI 调用进度条时没有更新?”当你说“打电话”时,你到底是什么意思?我怀疑您的意思是“呼叫班级”,这意味着 Tk.__init__() 被称为结果。如果它发生在程序的另一部分也已经调用它之后,这可能就是问题所在,因为您不能同时运行两个 Tk 实例——它们不能很好地协同工作。不过,有种方法可以创建多个顶级窗口,而不是您[可能] 正在这样做的方式。
  • 是的,你是对的,我的意思是给班级打电话。我在一些论坛上看到有人使用线程和队列,但对于我的水平来说太复杂了。所以看来我被我的问题困住了?但是为什么 Text 小部件会正确更新呢?
  • 创建第二个窗口的常用方法是创建(调用)tkinter.TopLevel 的实例。这样做需要Tk 类派生ScanDirectoryForJPG 类。这实际上可能相当容易做到。如果您编辑帖子并提供 Minimal, Complete, and Verifiable example 来演示问题,那么有人可能会向您展示如何执行此操作。

标签: python python-3.x tkinter ttk


【解决方案1】:

如果你必须在 tkinter 中调用 update(),那你就错了。

在这种情况下,您有一个遍历目录树的循环。在整个循环中,您的代码都没有及时处理事件,因此您必须一直调用update()

相反,捕获os.walk 的输出或简单地收集顶级目录内容,然后使用after 一次处理单个项目,传递迭代器或列表,因此一旦处理单个项目,您再次调用后处理下一个。这样,主循环将及时处理 UI 事件,您的目录树处理与其他所有事件一起作为事件排队。以这种方式重新设计应用程序后,您应该会更好地响应应用程序。

示例

为了证明这一点,os.walk 返回了一个生成器,因此我们可以使用 after events 来安排每个目录,因为每次调用 next(generator) 都会产生下一个目录及其文件。

为了监控进度,我们需要一些方法来计算要访问的目录或文件的数量,以及该演示是否用于整个文件系统,即应用程序似乎会冻结的地方。这也可以分解为基于事件的代码来防止这种影响。

我使用after(10, ...) 让它显示效果,但为了获得最大速度,请改用after_idle

import sys
import os
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.filedialog import askdirectory

class App(ttk.Frame):
    def __init__(self, parent, title):
        #tk.Frame.__init__(self, parent)
        super(App, self).__init__(parent)
        parent.wm_withdraw()
        parent.wm_title(title)
        self.create_ui()
        self.grid(sticky = "news")
        parent.wm_protocol("WM_DELETE_WINDOW", self.on_destroy)
        parent.grid_rowconfigure(0, weight=1)
        parent.grid_columnconfigure(0, weight=1)
        parent.wm_deiconify()

    def create_ui(self):
        textframe = ttk.Frame(self)
        self.text = text = tk.Text(textframe)
        vs = ttk.Scrollbar(textframe, orient=tk.VERTICAL, command=text.yview)
        text.configure(yscrollcommand=vs.set)
        text.grid(row=0, column=0, sticky=tk.NSEW)
        vs.grid(row=0, column=1, sticky=tk.NS)

        textframe.grid_columnconfigure(0, weight=1)
        textframe.grid_rowconfigure(0, weight=1)
        textframe.grid(row=0, column=0, columnspan=2, sticky=tk.NSEW)
        self.progressvar = tk.IntVar()
        self.progress = ttk.Progressbar(self, variable=self.progressvar)
        test_button = ttk.Button(self, text="Walk", command=self.on_walk)
        exit_button = ttk.Button(self, text="Exit", command=self.on_destroy)
        self.progress.grid(row=1, column=0, sticky=tk.NSEW)
        test_button.grid(row=1, column=0, sticky=tk.SE)
        exit_button.grid(row=1, column=1, sticky=tk.SE)
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

    def on_destroy(self):
        self.master.destroy()

    def on_walk(self):
        root = askdirectory()
        self.walk(root)

    def walk(self, root=None):
        if root:
            # this is potentially costly, but how to find the number of files to be examined?
            count = sum([len(files) for (root,dirs,files) in os.walk(root)])
            self.text.delete("1.0", "end")
            self.progress.configure(maximum=count)
            self.progressvar.set(0)
            walker = os.walk(root)
            self.after(100, self.do_one, walker)

    def do_one(self, walker):
        try:
            root,dirs,files = next(walker)
            for file in files:
                self.text.insert(tk.END, os.path.join(root, file), "PATH", "\n", "")
                self.text.see(tk.END)
                self.progressvar.set(self.progressvar.get() + 1)
            self.after(10, self.do_one, walker)
        except StopIteration:
            pass


def main(args):
    root = tk.Tk()
    app = App(root, "Walk directory tree")
    root.mainloop()

if __name__ == '__main__':
    sys.exit(main(sys.argv))

【讨论】:

  • 感谢您的回答。但是,我对 并不熟悉。你能举例说明如何处理这个问题吗?谢谢!
  • 非常感谢您的帮助。我可以理解并调整您在我的申请中发送的内容!最好的问候
  • 回到您提出的解决方案,我在没有 after() 或 after_idle() 的情况下尝试了它,我可以检查进度条是否没有正确更新。但我不明白 100% 的原因。谢谢
猜你喜欢
  • 2012-08-12
  • 1970-01-01
  • 2018-11-02
  • 1970-01-01
  • 2021-07-11
  • 1970-01-01
  • 1970-01-01
  • 2019-11-15
  • 1970-01-01
相关资源
最近更新 更多