Update a progressbar in a thread - multithreading

I have a Python code where I create a progressbar. The Tkinter environment is created in the Gui function with the progressbar and it is launched as a thread. Then in an other thread I calculate the value that the progressbar must have, but the problem is that I dont know how to update the Gui thread with the new value of the progressbar. Here is my code:
import tkinter as tk
from tkinter import ttk
import thread
def Gui():
root = tk.Tk()
root.geometry('450x450')
root.title('Hanix Downloader')
button1 = tk.Button(root, text='Salir', width=25,command=root.destroy)
button1.pack()
s = ttk.Style()
s.theme_use('clam')
s.configure("green.Horizontal.TProgressbar", foreground='green', background='green')
mpb = ttk.Progressbar(root,style="green.Horizontal.TProgressbar",orient ="horizontal",length = 200, mode ="determinate")
mpb.pack()
mpb["maximum"] = 3620
mpb["value"] = 1000
root.mainloop()
def main():
while True:
#Calculate the new value of the progress bar.
mpb["value"] = 100 #Does not work
root.update_idletasks()#Does not work
#Do some other tasks.
if __name__ == '__main__':
thread.start_new_thread( Gui,() )
thread.start_new_thread( main,() )
The error I get is that mpb and root do no exist. Thanks in advance.

You should get error because mpb and root are local variables which exist only in Gui but not in main. You have to use global to inform both functions to use global variables - and then main will have access to mpb created in Gui
I also add time.sleep(1) before while True: because sometimes main may start faster then Gui and it may not find mpb (because Gui had no time to create progressbar)
import tkinter as tk
from tkinter import ttk
import _thread
import time
def Gui():
global root, mpb
root = tk.Tk()
button1 = tk.Button(root, text='Exit', command=root.destroy)
button1.pack()
mpb = ttk.Progressbar(root, mode="determinate")
mpb.pack()
mpb["maximum"] = 3000
mpb["value"] = 1000
root.mainloop()
def main():
global root, mpb
time.sleep(1)
while True:
mpb["value"] += 100
#root.update_idletasks() # works without it
#Do some other tasks.
time.sleep(0.2)
if __name__ == '__main__':
_thread.start_new_thread(Gui, ())
_thread.start_new_thread(main, ())
Tested on Python 3.6.2, Linux Mint 18.2
EDIT: more precisely: you need global only in Gui because it assigns values to variables
root = ..., mpb = ....

Related

how to display a timer in python

hey i am making a program which records desktop screen.
So what i want to do is when ever i click on my start button(tkinter Gui) in my gui window.
It should start a timer like 3.... ,2.... ,1.... in big font directly on my desktop screen and not on my tkinter window. and then my function should start.
How can i do that ..
import tkinter as tk
from tkinter import *
root = Tk()
root.title("our program")
start_cap =tk.button(text='start recording' command=start_capute)
start_cap.pack()
root.mainloop()
Not mentioning the functions and the entire code here as not necessary the code is working fine and i just want to add a new feature of the timer in it.
An minimal example:
import tkinter as tk
# from tkinter import *
def Start():
def Count(Number):
if Number == -1:
win.withdraw()
print("Start") # what you want to do
return False
NumberLabel["text"] = Number
win.after(1000,Count,Number-1)
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
win = tk.Toplevel()
win.geometry("+%d+%d"%((screen_width-win.winfo_width())/2,(screen_height-win.winfo_height())/2)) # make it in the center.
win.overrideredirect(1)
win.wm_attributes('-topmost',1) # top window
win.wm_attributes('-transparentcolor',win['bg']) # background transparent.
NumberLabel = tk.Label(win,font=("",40,"bold"),fg='white')
NumberLabel.pack()
win.after(0,lambda :Count(3))
win.mainloop()
root = tk.Tk()
root.title("our program")
start_cap = tk.Button(text='start recording',command=Start)
start_cap.pack()
root.mainloop()

How to enable threading with tkinter?

When I want to build a program like a clock on Python3,there is a problem about threading & tkinter.
my_code :
#!/usr/bin/python3
#-*-coding:utf-8-*-
import tkinter as tk,time,threading,queue
def update_time(in_q):
while True:
in_q.put(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()))
class App_gui:
def __init__(self,parent):
self.top_frame = tk.Frame(parent)
self.clock = tk.Label(self.top_frame)
self.clock.pack()
self.top_frame.pack()
self.begin_thread()
def begin_thread(self):
self.clock_q = queue.Queue()
self.clock_thread = threading.Thread(target=update_time(self.clock_q))
self.clock_thread.start()
self.listen()
def listen(self):
gate_time = self.clock_q.get()
self.clock.config(text=gate_time)
self.clock.after(200,self.listen)
if __name__ == '__main__':
root = tk.Tk()
my_app = App_gui(root)
root.mainloop()
when I run this code,there's nothing happen.
Well threading is not all that difficult however in this case threading is overkill for a simple time loop.
We can use after() to manage a label for time without having to use threading.
import tkinter as tk
from time import strftime
class AppGUI(tk.Tk):
def __init__(self):
super().__init__()
self.time_label = tk.Label(self)
self.time_label.pack()
self.track_time()
def track_time(self):
self.time_label.config(text="{}".format(strftime('%Y-%m-%d %H:%M:%S')))
self.time_label.after(1000, self.track_time)
if __name__ == '__main__':
AppGUI().mainloop()
solved
#!/usr/bin/env python3
#-*-coding:utf-8-*-
import threading,time,tkinter as tk
def clock_task():
global clock_time
while True:
clock_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
time_label.config(text=clock_time)
time.sleep(1)
clock_t = threading.Thread(target=clock_task)
clock_t.setDaemon(True)
root = tk.Tk()
time_label = tk.Label(root)
time_label.pack()
# start thread before mainloop
clock_t.start()
root.mainloop()

How to use multithreading with tkinter to update a label and simultaneously perform calculations in a thread controlled by button events?

I'm trying to start a counter that displays the value in a label on a separate display window.
The main window has a START, STOP and DISPLAY button.
The START must start the counter, STOP must stop it and the display window must open only when I click on DISPLAY button.
Here's what I have so far. The buttons seem to be unresponsive and the display window pops up without user intervention. How can I fix this?
import tkinter as tk
import time
import threading
import queue
def Run_Device(disp_q,flaq_q):
temp_q = queue.Queue()
temp_q.put(0)
while(flaq_q.empty()):
#time.sleep(0.2)
count = temp_q.get()
count += 1
temp_q.put(count)
disp_q.put(count)
else:
flaq_q.queue.clear()
def P_Window(disp_q):
pw = tk.Tk()
value_label = tk.Label(pw, text=disp_q.get(), relief='sunken', bg='lemon chiffon', font='Helvetica 16 bold')
value_label.pack()
def update_values():
value_label.config(text=disp_q.get())
value_label.after(1000,update_values)
update_values()
pw.mainloop()
def Stop_Dev(flaq_q):
flaq_q.put("Stop")
if __name__ == "__main__":
disp_q = queue.Queue()
flaq_q = queue.Queue()
t_device = threading.Thread(target=Run_Device, args=(disp_q, flaq_q), name="Device 1")
t_disp = threading.Thread(target=P_Window, args=(disp_q, ), name="Display 1")
window = tk.Tk()
start_button = tk.Button(window, text='Start', command=t_device.start(), bg='spring green', font='Helvetica 12 bold', width=20, state='normal', relief='raised')
start_button.pack()
stop_button = tk.Button(window, text='Stop', command=lambda: Stop_Dev(flaq_q), bg='OrangeRed2', font='Helvetica 12 bold', width=20, state='normal', relief='raised')
stop_button.pack()
disp_param_button = tk.Button(window, text='Display', command=t_disp.start(), bg='sky blue', font='Helvetica 12 bold', width=20, state='normal', relief='raised')
disp_param_button.pack()
window.mainloop()
I'm trying to learn how to use multithreading in tkinter so any feedback would be appreciated
There are two issues I see with your code. The first is just simple bugs, e.g.:
start_button = tk.Button(window, text='Start', command=t_device.start(), ...
here it should be command=t_device.start otherwise the command is the function returned by t_device.start()
The second is you've not dealt with various "What if ...?" scenarios, e.g. What if the user pushes 'Start' or 'Display' multiple times?
I've tried to address the above in my rework below:
import tkinter as tk
from time import sleep
from queue import Queue, Empty
from threading import Thread
FONT = 'Helvetica 16 bold'
def run_device():
count = 0
while flaq_q.empty():
count += 1
disp_q.put(count)
sleep(0.5)
while not flaq_q.empty(): # flaq_q.queue.clear() not documented
flaq_q.get(False)
def p_window():
global pw
if pw is None:
pw = tk.Toplevel()
value_label = tk.Label(pw, text=disp_q.get(), width=10, font=FONT)
value_label.pack()
def update_values():
if not disp_q.empty():
try:
value_label.config(text=disp_q.get(False))
except Empty:
pass
pw.after(250, update_values)
update_values()
elif pw.state() == 'normal':
pw.withdraw()
else:
pw.deiconify()
def stop_device():
if flaq_q.empty():
flaq_q.put("Stop")
def start_device():
global device
if device and device.is_alive():
return
while not disp_q.empty():
disp_q.get(False)
disp_q.put(0)
device = Thread(target=run_device)
device.start()
if __name__ == "__main__":
disp_q = Queue()
flaq_q = Queue()
root = tk.Tk()
pw = None
device = None
tk.Button(root, text='Start', command=start_device, width=20, font=FONT).pack()
tk.Button(root, text='Stop', command=stop_device, width=20, font=FONT).pack()
tk.Button(root, text='Display', command=p_window, width=20, font=FONT).pack()
root.mainloop()
I've left out some details to simplify the example. Even with its additional checks, it's still nowhere near perfect. (E.g. it hangs if you don't 'Stop' before you close the window, etc.)
You might be able to adapt something like the following:
import tkinter as tk
import threading
import queue
import time
def run_device():
for i in range(4):
print(i)
pass
def process_queue(MyQueue):
"""Check if we got a complete message from our thread.
Start the next operation if we are ready"""
try:
# when our thread completes its target function (task),
# the supplied message is added to the queue
msg = MyQueue.get(0)
# check message
print(msg)
except queue.Empty:
# .get failed, come back in 100 and check again
print('no message')
threading.Timer(0.001, lambda q=MyQueue: process_queue(q)).start()
class ThreadedTask(threading.Thread):
"""threaded task handler"""
def __init__(self, queue, target, msg):
threading.Thread.__init__(self)
# message to add to queue when the task (target function) completes
self.msg = msg
# function to run
self._target = target
# queue to store completion message
self.queue = queue
def run(self):
"""called when object is instantiated"""
# start users task
try:
self._target()
except Exception as e:
self.queue.put('Thread Fail')
return
# we are done, pass the completion message
self.queue.put(self.msg)
if __name__ == '__main__':
MyQueue = queue.Queue()
MyThread = ThreadedTask(MyQueue, run_device, 'Thread Task: Run')
MyThread.start()
process_queue(MyQueue)

tkinter how to open 2 or more root windows in different locations on the screen

I have written a task reminder application in python and tkinter. It schedules the reminders using the Task Scheduler. The reminders are displayed by a small GUI program in a certain location on the screen. My problem is that the reminders overlap each other. When multiple reminders appear, how can I make them appear in distinct positions? Please note that I am referring to a separate invocation of the GUI program for each reminder.
The situation is similar to opening, say, multiple copies of the calculator program. They open in distinct locations on the screen. How does it happen?
The program that creates the reminders is as follows -
from tkinter import *
import shelve
import sys
def showTask(parent, key):
parent.title('Reminder')
parent.geometry('300x100-0-40')
parent.rowconfigure(0, weight=1)
parent.columnconfigure(0, weight=1)
shelfFile = shelve.open('C:\\Users\\hp\\pypgms\\data\\tasks')
message = shelfFile[key]['name']
shelfFile.close()
Label(parent, text=message).grid(padx=20, pady=20, sticky=NW)
btn = Button(parent, text='Ok', command=parent.quit)
btn.grid(pady=5)
btn.bind('<Return>', lambda e: parent.quit())
key = sys.argv[1]
root = Tk()
showTask(root, key)
root.mainloop()
Tkinter windows have a convenient method called geometry() to define their size and position on the screen following this format:
root.geometry('250x150+300+300') # width=250, height=150, position=(300,300)
See below how you can use it to open reminder windows next to one another:
import Tkinter as tk
class MainApp():
def __init__(self, root):
self.root = root
self.reminderWindows = []
self.button = tk.Button(self.root, text="New reminder",
command=self.open_new_reminder)
self.button.pack()
def open_new_reminder(self):
reminder = tk.Toplevel(self.root)
self.reminderWindows.append(reminder)
windowNumber = len(self.reminderWindows)
reminder.geometry("100x120+{}+200".format(str(150*windowNumber)))
if __name__ == "__main__":
app = tk.Tk()
MainApp(app)
app.mainloop()

Python 3.5 tkinter confirmation box created progress bar and reads in csv not quite working

I'm realtively new to python and am making a GUI app that does a lot of file i/o and processing. To complete this i would like to get a confirmation box to pop-up when the user commits and actions. From this when clicking 'yes' the app then runs the i/o and displays a progress bar.
From other threads on here I have gotten as far as reading about the requirement to create an addtional thread to take on one of these processes (for example Tkinter: ProgressBar with indeterminate duration and Python Tkinter indeterminate progress bar not running have been very helpful).
However, I'm getting a little lost because I'm not activating the threaded process from the Main() function. So I'm still getting lost in how, and where, I should be creating the progress bar and passing of the i/o process to another thread (reading in a csv file here).
Here is my code and I would be very grateful for any help anyone can give me:
import tkinter as tk
import tkinter.messagebox as messagebox
import csv
import tkinter.ttk as ttk
import threading
class ReadIn(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Read in file and display progress")
self.pack(fill=tk.BOTH, expand=True)
self.TestBtn = tk.Button(self.parent, text="Do Something", command=lambda: self.confirm_pb())
self.TestBtn.pack()
def confirm_pb(self):
result = messagebox.askyesno("Confirm Action", "Are you sure you want to?")
if result:
self.handle_stuff()
def handle_stuff(self):
nf = threading.Thread(target=self.import_csv)
nf.start()
self.Pbar()
nf.join()
def Pbar(self):
self.popup = tk.Tk()
self.popup.title('Loading file')
self.label = tk.Label(self.popup, text="Please wait until the file is created")
self.progressbar = ttk.Progressbar(self.popup, orient=tk.HORIZONTAL, length=200,
mode='indeterminate')
self.progressbar.pack(padx=10, pady=10)
self.label.pack()
self.progressbar.start(50)
def import_csv(self):
print("Opening File")
with open('csv.csv', newline='') as inp_csv:
reader = csv.reader(inp_csv)
for i, row in enumerate(reader):
# write something to check it reading
print("Reading Row " + str(i))
def main():
root = tk.Tk() # create a Tk root window
App = ReadIn(root)
root.geometry('400x300+760+450')
App.mainloop() # starts the mainloop
if __name__ == '__main__':
main()
The statement nf.join() in function handle_stuff() will block tkinter's main loop to show the progress bar window. Try modify handle_stuff() as below:
def handle_stuff(self):
nf = threading.Thread(target=self.import_csv)
nf.start()
self.Pbar()
#nf.join() # don't call join() as it will block tkinter's mainloop()
while nf.is_alive():
self.update() # update the progress bar window
self.popup.destroy()

Resources