【问题标题】:progressbar finishes before set maximum amount进度条在设置最大数量之前完成
【发布时间】:2020-06-25 11:08:35
【问题描述】:

我正在尝试在 tkinter Progressbar 中显示下载的图像,它正在工作,但进度条在所有图像下载完成之前完成。我问了一个非常相似的问题tkinter updating progress bar on thread progress,我的想法是根据使用len(os.listdir('.')) 创建的文件数量来更新进度条。

import tkinter
from tkinter.ttk import Progressbar
import os,uuid,requests,threading
import numpy as np

def bar():
    temp = 0
    for lst in chunks:
        threads.append(threading.Thread(target=download_image, args=(lst)))
    for x in threads:
        x.start()
    while temp<len(links):
        progress['value'] = temp
        root.update_idletasks()
        temp =len(os.listdir('.'))
    print("closing threads")
    for i in threads:
        i.join()
    temp =len(os.listdir('.'))
    progress['value'] = temp
    print('done')
    root.destroy()

with open('image_urls.txt','r') as f:
    links = f.read().split('\n') #links to image urls

threads =[]
chunks = [i.tolist() for i in np.array_split(links, 10) if i.size>0]
root = tkinter.Tk()
root.geometry("400x300")
root.title('Downloader v1')
progress = Progressbar(root, orient = tkinter.HORIZONTAL, 
              length = 250, mode = 'determinate',maximum=len(links))
progress.pack(pady = 100)
notice = tkinter.Label(root, text=str(len(links)),
                       fg="grey2",)
notice.place(x=350, y=100)
compose_button = tkinter.Button(root, text = 'Start', command = bar)
compose_button.pack()
root.mainloop()

【问题讨论】:

  • 您使用的是maximum=len(links)),但使用progress['value'] = temp 可能不一样。此外,您的 def bar(... 使用 while temp&lt;len(links):.join() 阻塞。阅读While Loop Locks Application
  • @stovfl [docs.python.org/3/library/tkinter.ttk.html](docs)maximum 是进度条的最大值,我只是给它计数,(理想情况下)计数应该完成(在while 循环中)在加入线程之前。
  • "maximum 是进度条的最大值,":这不是重点。我可以在temp =len(os.listdir('.')) 之后看到print(maximum)print(temp) 的输出吗?
  • @stovfl 输出为“无限”,但它从 0 开始并以 len(links) 结束,但是 len(os.listdir('.')) 显示的数量与实际数量不同(我正在刷新文件夹并且值不匹配)
  • "and values don't match":这就是我的意思,差别有多大。比len(links)多还是少?

标签: python multithreading tkinter


【解决方案1】:

问题:Tkinter Progressbar 从多个Thread 更新

核心点

    .event_generate('<<Progressbar>>')

此示例使用虚拟事件'&lt;&lt;Progressbar&gt;&gt;' 来增加Progressbar['value']。这个事件驱动编程,需要,没有回调,没有轮询.after,没有queue,在Thread 的工作。


进口

import tkinter as tk
import tkinter.ttk as ttk
import threading, time
import random

工人Thread

class Task(threading.Thread):
    is_alive = 0

    def __init__(self, app, args, name):
        super().__init__(name=name, daemon=True)
        self.args = args[0]
        self.app = app
        self._lock = threading.Lock()
        self.start()

    def run(self):
        # threaded task
        with self._lock:
            Task.is_alive += 1

        time.sleep(0.01)

        for link in self.args:
            print('Thread[{}]: link:{}'.format(self.name, link))
            time.sleep(random.randint(1, 5))

            with self._lock:
                self.app.event_generate('<<Progressbar>>', when='tail')

        # on end of threaded task
        with self._lock:
            Task.is_alive -= 1
            if Task.is_alive == 0:
                # last Task has finished
                self.app.event_generate('<<COMPLETED>>', when='tail')

通过继承ttk.Progressbar自定义Progressbar

class Progressbar(ttk.Progressbar):
    def __init__(self, parent):
        super().__init__(parent, orient="horizontal", 
                         maximum=0, mode="determinate", length=250)
        parent.bind('<<Progressbar>>', self.value)

    def value(self, event):
        self['value'] += 1 

用法

class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.pb = Progressbar(self)
        self.pb.pack()

        tk.Button(self, text="Start", command=self.start).pack()
        self.bind('<<COMPLETED>>', self.on_completed)

    def start(self):
        links = (1, 2, 3, 4, 5, 6, 7, 8, 9)
        self.pb['maximum'] = len(links)
        chunks = [l for l in zip(links[0::3], links[1::3], links[2::3])]

        for i, args in enumerate(chunks, 1):
            # Task start() at once 
            Task(self, name='Task {}'.format(i), args=(args,))

    def on_completed(self, event):
        # Do cleanups before exiting
        self.destroy()


if __name__ == "__main__":
    App().mainloop()

用 Python 测试:3.5 - 'TclVersion':8.6 'TkVersion':8.6

【讨论】:

  • 所有线程完成后,您能否显示关闭窗口或执行功能的方式
  • @hadesfv 查看我的更新,我添加了一个事件&lt;&lt;COMPLETED&gt;&gt;。在def on_completed(... 中,您可以在self.destroy() 之前进行任何清理。
【解决方案2】:

我看到你已经接受了上面的答案。但是,我会发布我的答案,因为它不是基于线程,我认为这更简单并且可能更合适。由于您已接受答案,因此我没有费心调整我的解决方案以适应您的情况:

from tkinter import *
from tkinter.ttk import *
import os



root = Tk()

# I set the length and maximum as shown to demonstrate the process in the 
# proceeding function. Pay attention to the increment r
progress = Progressbar(root, orient = HORIZONTAL,
            length = 200/5, maximum=200/5, mode = 'determinate')

# Function 


def my_func():
    t=0
    r= 1/5   
    for i in range(200):
        print(i) #whatever function interests you
        t=t+r
        progress['value'] = t
        root.update_idletasks()
  

progress.pack()

# Button
Button(root, text = 'Start', command = bar).pack(pady = 10)


mainloop()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-18
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多