Home > Mobile >  Tkinter background progressbar function while another function runs
Tkinter background progressbar function while another function runs

Time:10-27

I made a GUI Python program with Tkinter. I made a progressbar using "from tkinter.ttk import Progressbar". When I press a button, I apply a function that do something and return a Boolean value. I need that the progressbar will run until that function ends the process, and after that it will stop.

from tkinter.ttk import Progressbar
import time
import threading

wheel_flag = False
root = tk.Tk()
wheel = Progressbar(row,orient=HORIZONTAL,length=100,mode="indeterminate")


def btn_function()
  loading_function = threading.Thread(target=start_loading)
  loading_function.start()
  boolean_wheel = threading.Thread(target=some_function, args = (x,y)) #"some_function" returns a boolean value
  boolean_wheel.start()
  while True:
    if not boolean_wheel.is_alive():
      break
  wheel_flag = True


def start_loading():
  while True:
    global wheel_flag
    wheel['value']  = 10
    root.update_idletasks()
    time.sleep(0.5)
        
    if wheel_flag:
        break


Here I don't get the boolean_wheel value, but I want to check if it true or false and send to the user message if function succeeded or not.

I want that when "btn_function" is applied, the progressbarwill start to load until "some_function" will finish run. Now the result I get is that the loading start only after "some_function" finished, and runs without stopping until I close the program.

CodePudding user response:

I would use root.after(millisecond, function) instead of while True-loop to run function which updates progressbar every few seconds. And I would use directly boolean_wheel.is_alive() in this function to stop updateing.

import tkinter as tk
from tkinter import ttk
import time
import threading

# --- functions ---

def some_function(x, y):
    time.sleep(10)
    
def btn_function():
    x = 100
    y = 100
    
    boolean_wheel = threading.Thread(target=some_function, args=(x,y))
    boolean_wheel.start()
   
    # block button
    button['state'] = 'disabled'
    
    # show progressbar
    wheel.grid(row=0, column=1)
    
    # start updating progressbar
    update_progressbar(boolean_wheel)  # send thread as parameter - so it doesn't need `global`
        
def update_progressbar(thread):
   
    if thread.is_alive():
        # update progressbar
        wheel['value']  = 10
        # check again after 250ms (0.25s)
        root.after(250, update_progressbar, thread)
    else:
        # hide progressbar
        wheel.grid_forget()
        # unblock button
        button['state'] = 'normal'

# --- main ---

root = tk.Tk()

wheel = ttk.Progressbar(root, orient='horizontal', length=100, mode="indeterminate")
#wheel.grid(row=0, column=1)  # don't show

button = tk.Button(root, text='Start', command=btn_function)
button.grid(row=0, column=0)

root.mainloop()

EDIT:

If you would send widgets to functions as parameters then you could run it for many progressbars

import tkinter as tk
from tkinter import ttk
import time
import threading

# --- functions ---

def some_function(x, y):
    time.sleep(10)
    
def btn_function(row, button, progressbar):
    x = 100
    y = 100
    
    boolean_wheel = threading.Thread(target=some_function, args=(x,y))
    boolean_wheel.start()
   
    start_loading(row, button, progressbar, boolean_wheel)
    
def start_loading(row, button, progressbar, thread):
    
    # block button
    button['state'] = 'disabled'
    
    # show progressbar
    progressbar.grid(row=row, column=1)
    
    # start updating progressbar
    update_progressbar(button, progressbar, thread)  # send thread as parameter - so it doesn't need `global`
        
def update_progressbar(button, progressbar, thread):
   
    if thread.is_alive():
        # update progressbar
        progressbar['value']  = 10
        # check again after 250ms (0.25s)
        root.after(250, update_progressbar, button, progressbar, thread)
    else:
        end_loading(button, progressbar)
        
def end_loading(button, progressbar):
    # hide progressbar
    progressbar.grid_forget()
    # unblock button
    button['state'] = 'normal'
    
# --- main ---

root = tk.Tk()

# ---

wheel1 = ttk.Progressbar(root, orient='horizontal', length=100, mode="indeterminate")
#wheel.grid(row=1, column=1)  # don't show

button1 = tk.Button(root, text='Start')
button1.grid(row=1, column=0)
button1['command'] = lambda:btn_function(1, button1, wheel1) # need to assign after creating button - to send button to function

# ---

wheel2 = ttk.Progressbar(root, orient='horizontal', length=100, mode="indeterminate")
#wheel2.grid(row=2, column=1)  # don't show

button2 = tk.Button(root, text='Start')
button2.grid(row=2, column=0)
button2['command'] = lambda:btn_function(2, button2, wheel2) # need to assign after creating button - to send button to function

# ---

root.mainloop()

enter image description here


I would even put it in Frame and create own widget class MyProgress(tk.Frame) to create many widgets using for-loop

enter image description here

import tkinter as tk
from tkinter import ttk
import time
import threading

# --- classes ---

class MyProgrees(tk.Frame):
    
    def __init__(self, master, target_function, target_args, *args, **kwargs):
        super().__init__(master, *args, **kwargs)

        self.target_function = target_function
        self.target_args = target_args

        self.progressbar = ttk.Progressbar(self, orient='horizontal', length=100, mode="indeterminate")
        #self.progressbar.grid(row=0, column=1)  # don't show
        
        self.button = tk.Button(self, text='Start', command=self.on_press_button)
        self.button.grid(row=0, column=0)
        
    def on_press_button(self):
        
        self.thread = threading.Thread(target=self.target_function, args=self.target_args)
        self.thread.start()
       
        self.start_loading()
    
    def start_loading(self):
        
        # block button
        self.button['state'] = 'disabled'
        
        # show progressbar
        self.progressbar.grid(row=0, column=1)
        
        # start updating progressbar
        self.update_progressbar()
        
    def update_progressbar(self):
   
        if self.thread.is_alive():
            # update progressbar
            self.progressbar['value']  = 10
            # check again after 250ms (0.25s)
            self.after(250, self.update_progressbar)
        else:
            self.end_loading()
        
    def end_loading(self):
        # hide progressbar
        self.progressbar.grid_forget()
        # unblock button
        self.button['state'] = 'normal'
    
# --- functions ---

def some_function(x, y):
    time.sleep(10)
    
# --- main ---

root = tk.Tk()

# ---

x = 100
y = 100

for _ in range(10):
    widget = MyProgrees(root, some_function, (x, y))
    widget.pack(fill='x', expand=True)

# ---

root.mainloop()

CodePudding user response:

the proper solution is to remove the loop in btn_function and instead have some_function set wheel_flag after it is done.

def some_function_wrapper(*args,**kwargs):
    global wheel_flag
    some_function(*args,**kwargs)
    wheel_flag = True

and call this instead of some_function inside your thread.

now another solution is to just run the entire btn_function inside another thread, by adding

command= lambda : threading.Thread(target=btn_function).start()

inside your button definition, but nesting threads sometimes becomes hard to track.

Edited: It works. I checked the function value inside that function

def some_function_wrapper(*args,**kwargs):
    global wheel_flag
    check = some_function(*args,**kwargs)
    if check:
      print("works")
    else:
      print("don't work")

    wheel_flag = True
  • Related