Search code examples
pythontkintertextstylesprogress-bar

How to display text in two different Progressbars with tkinter?


I have a window with two different Progressbars. I want to add different text information over them (i.e. "88/1524" on the first one and "88/10000" on the second). I've found a solution for one Progressbar on this question: Displaying percentage in ttk progressbar

However it uses a style modification workaround to add text on the progress bar, and as I understand it, the style attribute text.Horizontal.TProgressbar is shared for all progress bars. This means I can't have a different text on each progress bar. When I need to update the text, I don't know how to target one specific progress bar.

Is there any other way to do it, or am I missing something? Can I instantiate the label to have something like text.Horizontal.TProgressbar1 and text.Horizontal.TProgressbar2?

Here's my current setup without labels: Progressbars

Where I'm stuck at: enter image description here

And portion of my code:

style = tkinter.ttk.Style(root)

style.layout('text.Horizontal.TProgressbar', 
            [('Horizontal.Progressbar.trough',
            {'children': [('Horizontal.Progressbar.pbar',
                            {'side': 'left', 'sticky': 'ns'})],
                'sticky': 'nswe'}), 
            ('Horizontal.Progressbar.label', {'sticky': ''})])
style.configure('text.Horizontal.TProgressbar', text='0/0')

ProgressBarOfDirectory = tkinter.ttk.Progressbar(ProgressbarFrame,
                                                 style='text.Horizontal.TProgressbar',
                                                 orient="horizontal",
                                                 length=WidthOfElements,
                                                 mode="determinate",
                                                 maximum=NbStepOfProgressBar,
                                                 variable=PourcentageOfDirectoryAnalysisDone)
ProgressBarOfDirectory.pack(side="top")

ProgressBarOfAll = tkinter.ttk.Progressbar(ProgressbarFrame,
                                           style='text.Horizontal.TProgressbar',
                                           orient="horizontal",
                                           length=WidthOfElements,
                                           mode="determinate",
                                           maximum=NbStepOfProgressBar,
                                           variable=PourcentageOfAllAnalysisDone)


Solution

  • Widget styles aren't shared unless the same one it specified for more than one widget (or the same default one gets assigned to more than one because something else wasn't specified).

    To take advantage of that here's an updated version of the code to my answer to that other question. In order to ensure the independence of the each Progressbar instance, it derives a new class from the built-in ttk.Progressbar which creates a separate style object for each instance created.

    Below is the code along with a runnable demo at the end.

    import tkinter as tk
    import tkinter.ttk as ttk
    
    class MyProgressBar(ttk.Progressbar):
        _inst_count = 0  # Number of class instances created.
    
        def __init__(self, *args, **kwargs):
            classname = type(self).__name__
            assert 'style' not in kwargs, \
                   f'{classname} initializer does not support providing a ttk "style".'
            type(self)._inst_count += 1  # Increment class attribute.
            # Create a style with a different name for each instance.
            self.style = ttk.Style(root)
            self.stylename = f'text.Horizontal.TProgressbar{self._inst_count}'
            self.style.layout(self.stylename,
                              [('Horizontal.Progressbar.trough',
                                {'children': [('Horizontal.Progressbar.pbar',
                                               {'side': 'left', 'sticky': 'ns'})],
                                 'sticky': 'nswe'}),
                               ('Horizontal.Progressbar.label', {'sticky': ''})])
            maximum = kwargs['maximum']
            self.style.configure(self.stylename, text=f'0/{maximum}')
            kwargs.update(style=self.stylename)
            super().__init__(*args, **kwargs)
    
    
    if __name__ == '__main__':
    
        DELAY1 = 100
        DELAY2 = 25
        MAX1 = 1524
        MAX2 = 10000
    
        def progress_bar_func(delay, progress_bar):
            # Start periodic progress-bar update process.
            maximum = progress_bar.cget('maximum')
            style = progress_bar.style  # Progressbar must have a style attribute.
            root.after(delay, update_progress_bar, delay, style, progress_bar, 1, maximum)
    
        def update_progress_bar(delay, style, progress_bar, num, maximum):
            if num <= maximum:
                progress_bar.config(value=num)
                style.configure(progress_bar.stylename, text=f'{num}/{maximum}')
                num += 1
                root.after(delay, update_progress_bar, delay, style, progress_bar, num, maximum)
    
        root = tk.Tk()
        root.geometry("300x300")
    
        progress_bar1 = MyProgressBar(root, length=200, maximum=MAX1, value=0)
        progress_bar1.pack()
        progress_bar2 = MyProgressBar(root, length=200, maximum=MAX2, value=0)
        progress_bar2.pack()
    
        def start_progress_bars():
            progress_bar_func(DELAY1, progress_bar1)
            progress_bar_func(DELAY2, progress_bar2)
    
        progress_button = tk.Button(root, text="Start", command=start_progress_bars)
        progress_button.pack()
    
        root.mainloop()
    

    Here's a couple of screenshots of it running:

    screenshots of it running