3

Is It possible to improve my progressbar in Tkinter-Python adding a label in the middle (ex: reading file)?

I tried to find a elegant coding solution but without a real result

from Tkinter import *
import ttk
import tkFileDialog
import time

class MainWindow(Frame):
    def __init__(self):
        Frame.__init__(self)
        self.master.title("ProgressBar example")
        self.master.minsize(200, 100)
        self.grid(sticky=E+W+N+S)

        top = self.winfo_toplevel()
        top.rowconfigure(0, weight=1)
        top.columnconfigure(0, weight=1)

        self.start_ind = Button(self, text='Start indeterminate', command=self.start_ind, activeforeground="red")
        self.start_ind.grid(row=0, column=0, pady=2, padx=2, sticky=E+W+N+S)

        self.pbar_ind = ttk.Progressbar(self, orient="horizontal", length=300, mode="indeterminate")
        self.pbar_ind.grid(row=1, column=0, pady=2, padx=2, sticky=E+W+N+S)

   def start_ind(self):
        for i in xrange(50):
            self.pbar_ind.step(1)
            self.update()
            # Busy-wait
            time.sleep(0.1)

if __name__=="__main__":
   d = MainWindow()
   d.mainloop()
Gianni Spear
  • 7,033
  • 22
  • 82
  • 131
  • 2
    The documentation makes no mention of embedding a label in the progressbar widget, which usually means that it's unsupported. You would have to look at writing a custom widget. – Mike Driscoll Jul 15 '14 at 21:10
  • Does this answer your question? [Progressbar with Percentage Label?](https://stackoverflow.com/questions/47896881/progressbar-with-percentage-label) – j_4321 Jun 23 '22 at 11:58

3 Answers3

8

I added the label inside the progressbar by creating a custom ttk style layout. The text of the label is changed by configuring the style:

from tkinter import Tk
from tkinter.ttk import Progressbar, Style, Button
from time import sleep


root = Tk()
s = Style(root)
# add the label to the progressbar style
s.layout("LabeledProgressbar",
         [('LabeledProgressbar.trough',
           {'children': [('LabeledProgressbar.pbar',
                          {'side': 'left', 'sticky': 'ns'}),
                         ("LabeledProgressbar.label",   # label inside the bar
                          {"sticky": ""})],
           'sticky': 'nswe'})])

p = Progressbar(root, orient="horizontal", length=300,
                style="LabeledProgressbar")
p.pack()

# change the text of the progressbar, 
# the trailing spaces are here to properly center the text
s.configure("LabeledProgressbar", text="0 %      ")

def fct():
    for i in range(1, 101):
        sleep(0.1)
        p.step()
        s.configure("LabeledProgressbar", text="{0} %      ".format(i))
        root.update()

Button(root, command=fct, text="launch").pack()

root.mainloop()

screenshot

j_4321
  • 15,431
  • 3
  • 34
  • 61
  • Why it looks more like a button instead of a progressbar in Windows? – Yılmaz Alpaslan Jun 29 '21 at 22:40
  • 1
    @YılmazAlpaslan Maybe that's because of Windows default theme which is not as customizable as other themes. Try to add `s.theme_use("clam")` just after `s = Style(root)`. – j_4321 Jun 30 '21 at 07:25
  • For me, changing the `pbar` child to `('LabelledProgressbar.pbar', {'sticky': 'nswe'})` centers the label w/o trailing spaces. – Stepan Zakharov Jun 19 '22 at 13:14
  • Also, the solution works for a single bar, because multiple bars will share the same style... and text property. – Stepan Zakharov Jun 19 '22 at 13:19
1

Have you tried creating a text Label and putting it in the same row/column and setting it the same size like so:

self.Lab = Label(self,length=200)
self.Lab.grid(row=1,column=0,pady=2,padx=2,sticky=E+W+N+S))

But you would want to put it after the progress bar widget.

  • 1
    This doesn't work. It just covers up the progress bar, and labels cannot have transparent backgrounds. – boof Nov 30 '20 at 03:38
1

I created a solution for it, which works.

I use a label that is placed on top of the progressbar and the background of the label updates in sync with the progressbar using relwidth and the same color as the progressbar.

from threading import Thread
from tkinter import *
from tkinter import ttk
import time

#Tkinter window
class GUI(Frame, object):
    def __init__(self, progress_frame):
        super().__init__(progress_frame)
        self.progress_frame = progress_frame
        self.progress_frame.geometry('300x100')
        self.progress_frame.title('Progressbar')
        self.progressbar = ttk.Progressbar(self.progress_frame, orient='horizontal', mode='determinate', length=280)
        # place the progressbar
        self.progressbar.grid(column=0, row=1, columnspan=2, padx=10, ipady=3)
        # initialize label
        self.value = StringVar()
        self.value.set(self.update_progress_label("0 MB/s"))
        self.value_label = Label(self.progress_frame, textvariable=self.value, font='default 10', borderwidth=0)
        #set background to grey
        self.value_label['bg'] = '#e6e6e6'
        self.value_label.grid(column=0, row=1, columnspan=2, padx=10, pady=20)
        self.current_value = 0
        self.start_download()

    def update_progress_label(self, mb_s): #the value you want to show in the label
        return f"{self.progressbar['value']}% / {mb_s}"

    def start_download(self): #start thread that does calculation
        download_thread = Download()
        download_thread.start()
        self.monitor(download_thread)

    def monitor(self, download_thread): # monitor download progress
        """ Monitor the download thread """
        if download_thread.is_alive():
            progress = download_thread.value
            # update the label
            self.value.set(self.update_progress_label(download_thread.mb_s))
            widget_x, widget_width = self.value_label.winfo_x(), self.value_label.winfo_width()  # get position and width of text label
            progressbar_pixellen = self.progressbar.winfo_width() # get total width of progressbar
            # get the current position in pxl of progressbar
            calculation_ppixel = progress*progressbar_pixellen/100
            # get the overlap with the text label
            calculation_ptext = calculation_ppixel-widget_x+self.progressbar.winfo_x()
            # ensure that no negative value is set
            calculation_text = max(calculation_ptext/widget_width, 0)
            if calculation_text>0:  # if calculation_text (relwidth) is 0 it will still show a small bar, so don't update the label
                # update the label with the new text value and the color of the progressbar
                self.pblabel = Label(self.value_label, textvariable=self.value, font='default 10', background='#06b025', borderwidth=0, anchor="w")
                # relwidth will only show the given percentage of the text in the color
                self.pblabel.place(x=0, y=0, anchor="nw", relwidth=calculation_text)
            # update the progressbar progress
            self.progressbar['value'] = progress
            # update the label
            self.value_label.update_idletasks()
            # rerun this method in 100ms
            self.after(100, lambda: self.monitor(download_thread))


class Download(Thread):
    def __init__(self):
        super().__init__()

        self.picture_file = None
        self.value = 0
        self.mb_s = 0
    def run(self):
        """ do some calculation like downloading a file """
        for i in range(100):
            time.sleep(1)
            self.value = i
            self.mb_s = "100 MB/s"



if __name__ == '__main__':
    progress_frame = Tk()
    app = GUI(progress_frame)
    app.mainloop()

Example

NiC
  • 11
  • 1