Gianni Spear
Gianni Spear

Reputation: 7924

progressbar in Tkinter with a label inside

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

Upvotes: 3

Views: 12352

Answers (3)

NiC
NiC

Reputation: 11

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

Upvotes: 1

j_4321
j_4321

Reputation: 16169

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

Upvotes: 9

user3808660
user3808660

Reputation:

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.

Upvotes: 1

Related Questions