33

I want to show a progress bar while downloading a file from the web using the urllib.urlretrive method.

How do I use the ttk.Progressbar to do this task?

Here is what I have done so far:

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()

But it just keeps looping.

nbro
  • 15,395
  • 32
  • 113
  • 196
Hanix
  • 333
  • 1
  • 4
  • 4

5 Answers5

40

For determinate mode you do not want to call start. Instead, simply configure the value of the widget or call the step method.

If you know in advance how many bytes you are going to download (and I assume you do since you're using determinate mode), the simplest thing to do is set the maxvalue option to the number you are going to read. Then, each time you read a chunk you configure the value to be the total number of bytes read. The progress bar will then figure out the percentage.

Here's a simulation to give you a rough idea:

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()

For this to work you're going to need to make sure you don't block the GUI thread. That means either you read in chunks (like in the example) or do the reading in a separate thread. If you use threads you will not be able to directly call the progressbar methods because tkinter is single threaded.

You might find the progressbar example on tkdocs.com to be useful.

Rafa Viotti
  • 9,998
  • 4
  • 42
  • 62
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Thank's Bryan now i understand how it works , but how can i print the percentage value? – Hanix Sep 05 '11 at 16:53
  • print the percentage value? What do you mean? Do you mean something like this? `print "%s%%" % int(float(self.bytes) / float(self.maxbytes) * 100)` – Bryan Oakley Sep 05 '11 at 17:05
  • 1
    Hi, I'm trying to get this to work in a GUI application I'm working on, and am having trouble seeing the bar update visually. I can print the values out, and I see that they're getting updated, but nothing is happening on the actual bar. I tried this code, and nothing happens when I press start. Is there anything special one has to do to get this to work? What version of Python was this using? – Thomp Jun 20 '12 at 18:57
  • @Thomp: are you saying the above _exact_ code doesn't work? It works for me with python 2.7. – Bryan Oakley Jun 20 '12 at 20:40
  • Yes, copy/pasted this code. The window comes up with the start button and progress bar showing, but when I hit start, I don't see anything happening on the progress bar. I've debugged it, and all the code is executing, but the progress bar isn't updating for some reason. I'm using Python 2.7.3, on OSX. Tried it on a co-worker's machine with the same setup, and it didn't work for him either. Any idea? – Thomp Jun 20 '12 at 22:21
  • 1
    @Thomp: sorry, I have no idea. I tested the above code using python 2.7.1 on my OSX 10.7.4 box and it worked as expected. – Bryan Oakley Jun 21 '12 at 11:07
  • Hmmm, well I am using 10.6.8, so perhaps it has something to do with Snow Leopard. No idea either. If I manage to figure it out, I'll be sure to post back with the solution. Thanks anyway! – Thomp Jun 21 '12 at 20:23
  • Please try to provide simpler codes! (See @Ufoguy example) – Apostolos Mar 07 '18 at 22:56
  • What does `length=200` mean? – Delrius Euphoria Jan 13 '21 at 05:12
  • 1
    @CoolCloud: it sets the length of the progressbar. – Bryan Oakley Jan 13 '21 at 15:01
  • Like the maximum length? – Delrius Euphoria Jan 13 '21 at 17:12
  • 1
    @CoolCloud: no, the physical size of the widget. From the official documentation: _"Specifies the length of the long axis of the progress bar (width if horizontal, height if vertical)."_ – Bryan Oakley Jan 13 '21 at 17:31
10

I simplified the code for you.

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()

Replace 50 with the percentage of the download.

Ufoguy
  • 897
  • 3
  • 9
  • 15
5

If you just want a progress bar to show that the program is busy/working just change the mode from determinate to indeterminate

pb = ttk.Progressbar(root,orient ="horizontal",length = 200, mode ="indeterminate")
Simon Crouch
  • 59
  • 1
  • 1
3

Here's another simple example that also shows a progress bar moving. (I have simplified the examples given at 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()
Apostolos
  • 3,115
  • 25
  • 28
2

Modal dialog window with Progressbar for the bigger project

This example is a bit long, but tested on Python 3.6 and can be used in the bigger project.

# -*- 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()
FooBar167
  • 2,721
  • 1
  • 26
  • 37