Search code examples
pythonpython-3.xtkinterttk

Python - Tkinter (ttk) dynamically create a progressbar with its own value that changes


So basicly I'm trying to make multiple progressbar and each progressbars value runs from 0 to 100. But right now my functions acess the values from the others too and that looks weird. Hopefully someone can help me. Thanks :)

# -*- coding: utf-8 -*-
import tkinter.ttk as ttk
import tkinter as tk
from threading import Thread
import time

class Main(object):
    def __init__(self, master):
        self.master = master

        self.frame = tk.Frame(self.master, width=400, height=400)
        self.frame.pack(expand=True)

        self.button = tk.Button(self.frame, text="Add Bar", command=self.start_thread)
        self.button.pack(fill="y")

    def start_thread(self):
        self.t = Thread(target=self.add_bar)
        self.t.start()

    def add_bar(self):
        self.var = tk.IntVar()
        self.var.set(0)

        self.progessbar = ttk.Progressbar(self.frame, variable=self.var, orient=tk.HORIZONTAL, length=200)
        self.progessbar.pack()

        self.add_values(self.var)

    def add_values(self, var):
        self.variable = var
        for self.x in range(100):
            time.sleep(0.1)
            self.variable.set(self.x)


root = tk.Tk()
app = Main(root)
root.mainloop()

Solution

  • In your original code, self.var is the only variable storing the progress of the loading bar. Since there is only one instance of the Main object, there is only one instance of self.var which is resulting in the problem you described. In order to fix this, I recommend creating a separate class for the loading bar as shown below:

    # -*- coding: utf-8 -*-
    import tkinter.ttk as ttk
    import tkinter as tk
    from threading import Thread
    import time
    
    class Main(object):
        def __init__(self, master):
            self.master = master
    
            self.frame = tk.Frame(self.master, width=400, height=400)
            self.frame.pack(expand=True)
    
            self.button = tk.Button(self.frame, text="Add Bar", command=lambda:self.createBar())
            self.button.pack(fill="y")
    
        def createBar(self):
            self.t = Thread(target=self.create)
            self.t.start()
    
        def create(self):
            newBar = LoadingBar(self.master, self.frame)
    
    class LoadingBar(object):
        def __init__(self, master, frame):
            # Must use same Tkinter frame to add loading bars into
            self.master = master
            self.frame = frame
            self.add_bar()
    
        def start_thread(self):
            self.t = Thread(target=self.add_bar)
            self.t.start()
    
        def add_bar(self):
            self.var = tk.IntVar()
            self.var.set(0)
    
            self.progessbar = ttk.Progressbar(self.frame, variable=self.var, orient=tk.HORIZONTAL, length=200)
            self.progessbar.pack()
    
            self.add_values(self.var)
    
        def add_values(self, var):
            self.variable = var
            for self.x in range(100):
                time.sleep(0.1)
                self.variable.set(self.x)
    
    root = tk.Tk()
    app = Main(root)
    root.mainloop()
    

    Creating each instance of the loading bar in a separate thread allows for the desired effect.

    Here is another approach (no new class):

    # -*- coding: utf-8 -*-
    import tkinter.ttk as ttk
    import tkinter as tk
    from threading import Thread
    import time
    
    class Main(object):
        def __init__(self, master):
            self.master = master
    
            self.frame = tk.Frame(self.master, width=400, height=400)
            self.frame.pack(expand=True)
    
            self.button = tk.Button(self.frame, text="Add Bar", command=self.start_thread)
            self.button.pack(fill="y")
    
        def start_thread(self):
            self.t = Thread(target=self.add_bar)
            self.t.start()
    
        def add_bar(self):
            var = tk.IntVar()
            var.set(0)
    
            progessbar = ttk.Progressbar(self.frame, variable=var, orient=tk.HORIZONTAL, length=200)
            progessbar.pack()
    
            self.add_values(var)
    
        def add_values(self, var):
            variable = var
            for x in range(100):
                time.sleep(0.1)
                variable.set(x)
    
    
    root = tk.Tk()
    app = Main(root)
    root.mainloop()
    

    Note: local variables are created for each thread as opposed to instance variables to avoid the overwriting of instance variables.