Как создать индикатор загрузки в ttk?

Я хочу показать индикатор выполнения при загрузке файла из Интернета с помощью метода urllib.urlretrive.

Как мне использовать ttk.Progressbar для выполнения этой задачи?

Вот что я сделал до сих пор:

from tkinter import ttk
from tkinter import *

root = Tk()

pb = ttk.Progressbar(root, orient="horizontal", length=200, mode="determinate")
pb.pack()
pb.start()

root.mainloop()

Но он просто продолжает зацикливаться.


person Hanix    schedule 05.09.2011    source источник


Ответы (5)


Для детерминированного режима вы не хотите вызывать start. Вместо этого просто настройте value виджета или вызовите метод step.

Если вы заранее знаете, сколько байтов вы собираетесь загрузить (и я предполагаю, что вы знаете, так как вы используете детерминированный режим), самое простое, что нужно сделать, это установить параметр maxvalue на число, которое вы собираетесь прочитать. Затем каждый раз, когда вы читаете фрагмент, вы настраиваете value как общее количество прочитанных байтов. Затем индикатор выполнения покажет процент.

Вот симуляция, чтобы дать вам общее представление:

import tkinter as tk
from tkinter import ttk


class SampleApp(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.button = ttk.Button(text="start", command=self.start)
        self.button.pack()
        self.progress = ttk.Progressbar(self, orient="horizontal",
                                        length=200, mode="determinate")
        self.progress.pack()

        self.bytes = 0
        self.maxbytes = 0

    def start(self):
        self.progress["value"] = 0
        self.maxbytes = 50000
        self.progress["maximum"] = 50000
        self.read_bytes()

    def read_bytes(self):
        '''simulate reading 500 bytes; update progress bar'''
        self.bytes += 500
        self.progress["value"] = self.bytes
        if self.bytes < self.maxbytes:
            # read more bytes after 100 ms
            self.after(100, self.read_bytes)

app = SampleApp()
app.mainloop()

Чтобы это сработало, вам нужно убедиться, что вы не блокируете поток графического интерфейса. Это означает, что либо вы читаете кусками (как в примере), либо выполняете чтение в отдельном потоке. Если вы используете потоки, вы не сможете напрямую вызывать методы индикатора выполнения, потому что tkinter является однопоточным.

Вы можете найти пример индикатора выполнения на tkdocs.com быть полезным.

person Bryan Oakley    schedule 05.09.2011
comment
Спасибо, Брайан, теперь я понимаю, как это работает, но как я могу напечатать процентное значение? - person Hanix; 05.09.2011
comment
распечатать процентное значение? Что ты имеешь в виду? Вы имеете в виду что-то вроде этого? print "%s%%" % int(float(self.bytes) / float(self.maxbytes) * 100) - person Bryan Oakley; 05.09.2011
comment
Привет, я пытаюсь заставить это работать в приложении с графическим интерфейсом, над которым я работаю, и у меня возникают проблемы с визуальным обновлением панели. Я могу распечатать значения и вижу, что они обновляются, но на самом баре ничего не происходит. Я попробовал этот код, и ничего не происходит, когда я нажимаю «Пуск». Есть ли что-то особенное, что нужно сделать, чтобы заставить это работать? Какая версия Python использовалась? - person Thomp; 20.06.2012
comment
@Thomp: вы говорите, что приведенный выше точный код не работает? У меня работает с питоном 2.7. - person Bryan Oakley; 21.06.2012
comment
Да, скопируйте/вставьте этот код. В окне появляется кнопка «Пуск» и индикатор выполнения, но когда я нажимаю «Пуск», я не вижу, чтобы что-то происходило на индикаторе выполнения. Я отладил его, и весь код выполняется, но индикатор выполнения почему-то не обновляется. Я использую Python 2.7.3 на OSX. Пробовал на машине коллеги с такой же настройкой, у него тоже не заработало. Есть идеи? - person Thomp; 21.06.2012
comment
@Thomp: извини, понятия не имею. Я протестировал приведенный выше код, используя python 2.7.1 на своем компьютере с OSX 10.7.4, и он работал, как и ожидалось. - person Bryan Oakley; 21.06.2012
comment
Хм, ну, я использую 10.6.8, так что, возможно, это как-то связано со Snow Leopard. Тоже без понятия. Если разберусь, обязательно отпишусь с решением. Спасибо, в любом случае! - person Thomp; 22.06.2012
comment
Пожалуйста, попробуйте предоставить более простые коды! (см. пример @Ufoguy) - person Apostolos; 08.03.2018
comment
Что означает length=200? - person Cool Cloud; 13.01.2021
comment
@CoolCloud: устанавливает длину индикатора выполнения. - person Bryan Oakley; 13.01.2021
comment
Нравится максимальная длина? - person Cool Cloud; 13.01.2021
comment
@CoolCloud: нет, физический размер виджета. Из официальной документации: Определяет длину длинной оси индикатора выполнения (ширина, если горизонтальная, высота, если вертикальная). - person Bryan Oakley; 13.01.2021

Я упростил код для вас.

import sys
import ttk
from Tkinter import *

mGui = Tk()

mGui.geometry('450x450')
mGui.title('Hanix Downloader')

mpb = ttk.Progressbar(mGui,orient ="horizontal",length = 200, mode ="determinate")
mpb.pack()
mpb["maximum"] = 100
mpb["value"] = 50

mGui.mainloop()

Замените 50 на процент загрузки.

person Ufoguy    schedule 23.12.2013

Если вы просто хотите, чтобы индикатор выполнения показывал, что программа занята/работает, просто измените режим с определенного на неопределенный.

pb = ttk.Progressbar(root,orient ="horizontal",length = 200, mode ="indeterminate")
person Simon Crouch    schedule 31.10.2012

Вот еще один простой пример, который также показывает перемещение индикатора выполнения. (Я упростил примеры, приведенные на https://gist.github.com/kochie/9f0b60384ccc1ab434eb)

import Tkinter
import ttk

root = Tkinter.Tk()
pb = ttk.Progressbar(root, orient='horizontal', mode='determinate')
pb.pack(expand=True, fill=Tkinter.BOTH, side=Tkinter.TOP)
pb.start(50)
root.mainloop()
person Apostolos    schedule 07.03.2018

Модальное диалоговое окно с Progressbar для более крупного проекта

Этот пример немного длинный, но протестирован на Python 3.6 и может использоваться в более крупном проекте.

# -*- coding: utf-8 -*-
# Modal dialog window with Progressbar for the bigger project
import time
import tkinter as tk
from tkinter import ttk
from tkinter import simpledialog

class MainGUI(ttk.Frame):
    ''' Main GUI window '''
    def __init__(self, master):
        ''' Init main window '''
        ttk.Frame.__init__(self, master=master)
        self.master.title('Main GUI')
        self.master.geometry('300x200')
        self.lst = [
            'Bushes01.png',  'Bushes02.png', 'Bushes03.png', 'Bushes04.png', 'Bushes05.png',
            'Forest01.png',  'Forest02.png', 'Forest03.png', 'Forest04.png', 'Road01.png',
            'Road02.png',    'Road03.png',   'Lake01.png',   'Lake02.png',   'Field01.png']
        b = ttk.Button(self.master, text='Start', command=self.start_progress)
        b.pack()
        b.focus_set()

    def start_progress(self):
        ''' Open modal window '''
        s = ProgressWindow(self, 'MyTest', self.lst)  # create progress window
        self.master.wait_window(s)  # display the window and wait for it to close

class ProgressWindow(simpledialog.Dialog):
    def __init__(self, parent, name, lst):
        ''' Init progress window '''
        tk.Toplevel.__init__(self, master=parent)
        self.name = name
        self.lst = lst
        self.length = 400
        #
        self.create_window()
        self.create_widgets()

    def create_window(self):
        ''' Create progress window '''
        self.focus_set()  # set focus on the ProgressWindow
        self.grab_set()  # make a modal window, so all events go to the ProgressWindow
        self.transient(self.master)  # show only one window in the task bar
        #
        self.title(u'Calculate something for {}'.format(self.name))
        self.resizable(False, False)  # window is not resizable
        # self.close gets fired when the window is destroyed
        self.protocol(u'WM_DELETE_WINDOW', self.close)
        # Set proper position over the parent window
        dx = (self.master.master.winfo_width() >> 1) - (self.length >> 1)
        dy = (self.master.master.winfo_height() >> 1) - 50
        self.geometry(u'+{x}+{y}'.format(x = self.master.winfo_rootx() + dx,
                                         y = self.master.winfo_rooty() + dy))
        self.bind(u'<Escape>', self.close)  # cancel progress when <Escape> key is pressed

    def create_widgets(self):
        ''' Widgets for progress window are created here '''
        self.var1 = tk.StringVar()
        self.var2 = tk.StringVar()
        self.num = tk.IntVar()
        self.maximum = len(self.lst)
        self.tmp_str = ' / ' + str(self.maximum)
        #
        # pady=(0,5) means margin 5 pixels to bottom and 0 to top
        ttk.Label(self, textvariable=self.var1).pack(anchor='w', padx=2)
        self.progress = ttk.Progressbar(self, maximum=self.maximum, orient='horizontal',
                                        length=self.length, variable=self.num, mode='determinate')
        self.progress.pack(padx=2, pady=2)
        ttk.Label(self, textvariable=self.var2).pack(side='left', padx=2)
        ttk.Button(self, text='Cancel', command=self.close).pack(anchor='e', padx=1, pady=(0, 1))
        #
        self.next()

    def next(self):
        ''' Take next file from the list and do something with it '''
        n = self.num.get()
        self.do_something_with_file(n+1, self.lst[n])  # some useful operation
        self.var1.set('File name: ' + self.lst[n])
        n += 1
        self.var2.set(str(n) + self.tmp_str)
        self.num.set(n)
        if n < self.maximum:
            self.after(500, self.next)  # call itself after some time
        else:
            self.close()  # close window

    def do_something_with_file(self, number, name):
        print(number, name)

    def close(self, event=None):
        ''' Close progress window '''
        if self.progress['value'] == self.maximum:
            print('Ok: process finished successfully')
        else:
            print('Cancel: process is cancelled')
        self.master.focus_set()  # put focus back to the parent window
        self.destroy()  # destroy progress window

root = tk.Tk()
feedback = MainGUI(root)
root.mainloop()
person FooBar167    schedule 29.03.2018