Tkinter update progressbar from imported code - python

I am creating a gui application using Tkinter, which imports other pieces of code stored in external .py files which contain time consuming functions. What I want to do is have a progressbar on my gui window which gets updated according to some function running in my imported script.
Gui script example:
#gui script
import tkinter
from tkinter import ttk
from somefile import somefunc
progcomp = ttk.Progressbar(root, orient='horizontal', length=200, mode = 'determinate', maximum=100)
somefunc()
External function example:
#somefile.py
def somefunc():
for i in range(1000):
#dosomething
#update progressbar of gui script
My actual code is too long to show in a question like this, so I chose to represent it as simply as possible. My question is, is this possible or will I have to change the infrastructure to accomplish?

You could use threading to implement this. Below is a very rough example of this.
threaded_task.py
import threading
import time
class ThreadedTask(threading.Thread):
def __init__(self, progress):
threading.Thread.__init__(self)
self.progress = progress
def run(self):
for i in range(100):
self.progress.step(1) # Update progress bar
time.sleep(1) # Simulate long running process
main.py
from tkinter import *
from tkinter import ttk
from threaded_task import ThreadedTask
root = Tk()
progcomp = ttk.Progressbar(root, orient='horizontal', length=200, mode = 'determinate', maximum=100)
progcomp.grid()
task = ThreadedTask(progcomp)
task.start()
root.mainloop()

Related

Threading and Tkinter - How to use the threading module with my simple example?

I don't understand how to use the threading module properly. In this example I have two tkinter widgets, a button and a progress bar. The progress bar (configured in indeterminate mode) has to be active when the user pushes the button, and when the task is completed, the progress bar has to be stopped.
import tkinter as tk
from tkinter import ttk
import threading, ipaddress
class MainWindow:
def __init__(self):
self.parent=tk.Tk()
self.parent.geometry("786x524+370+100")
self.parent.title("Test")
self.parent.configure(background="#f0f0f0")
self.parent.minsize(786, 524)
self.ProBar=ttk.Progressbar(self.parent, mode="indeterminate")
self.ProBar.pack(padx=(40, 40), pady=(40, 40), fill=tk.BOTH)
self.StartButton=ttk.Button(self.parent, text="Start", command=self.MyHeavyTask)
self.StartButton.pack(padx=(40, 40), pady=(40, 40), fill=tk.BOTH)
self.parent.mainloop()
# my start function:
def Start(self):
self.ProBar.start(4)
self.MyHeavyTask()
self.ProBar.stop()
# my real start function. it's just an example, it needs time to be completed:
def MyHeavyTask(self):
ls=[]
obj=ipaddress.ip_network("10.0.0.0/8")
for obj in list(obj.hosts()):
print(obj.exploded)
# start my test:
if __name__=="__main__":
app=MainWindow()
This code has an issue, it can't run the function "MyHeavyTask" and at the same time keep active the progress bar widget. to solve it, I tried to put "MyHeavyTask" in an indipendent thread changing the line 17 with this one:
self.StartButton=ttk.Button(self.parent, text="Start",
command=threading.Thread(target=self.MyHeavyTask).start())
unfortunately this solution doesn't work. when I press the button, nothig happens…why? What is the right way to use the threading module in my example?
You can add a method to the class
def Get_Input(self):
message = input(">")
if message:
send_message(message)
and add in init class
threading.Thread(target=self.Get_Input, args=(,)).start()
Please note :
If you passing one argument, you need to use
threading.Thread(target=self.Get_Input, args=(var1,)).start()
Unlike common sense :)
Here a runnable example similar to the code in your question, that shows a way to run a background task and keep a ttk.Progressbar active simultaneously. It does this by using the universal after() widget method to repeatedly schedule calls to a method that checks whether the background task is running and updates the progress bar if it is. It also disables and re-enables the Start button appropriately so the task can't be start again while it's running.
Note I strongly suggest you read and start following the PEP 8 - Style Guide for Python Code.
from random import randint
import tkinter as tk
from tkinter import ttk
import threading
from time import sleep
class MainWindow:
def __init__(self):
self.parent = tk.Tk()
self.parent.geometry("786x524+370+100")
self.parent.title("Test")
self.parent.configure(background="#f0f0f0")
self.parent.minsize(786, 524)
self.task = threading.Thread(target=self.my_heavy_task)
self.pro_bar = ttk.Progressbar(self.parent, mode="indeterminate")
self.pro_bar.pack(padx=(40, 40), pady=(40, 40), fill=tk.BOTH)
self.start_btn = ttk.Button(self.parent, text="Start", command=self.start)
self.start_btn.pack(padx=(40, 40), pady=(40, 40), fill=tk.BOTH)
self.parent.mainloop()
def check_thread(self):
if self.task.is_alive():
self.pro_bar.step() # Update progressbar.
self.parent.after(20, self.check_thread) # Call again after delay.
else:
self.pro_bar.stop()
self.start_btn.config(state=tk.ACTIVE)
def start(self):
"""Start heavy background task."""
self.start_btn.config(state=tk.DISABLED)
self.task.start()
self.pro_bar.start()
self.check_thread() # Start checking thread.
def my_heavy_task(self):
"""Slow background task."""
for obj in (randint(0, 99) for _ in range(6)):
print(obj)
sleep(.5)
if __name__=="__main__":
app = MainWindow()
Does this help?
start_thread = MainWindow()
run_test = threading.Thread(None, start_thread.start)
run_test.start()
# start my test:
if __name__=="__main__":
app=MainWindow()

Winsound causing my tkinter GUI to open slowly

I'm working on a tkinter GUI in Python to produce error messages in a new window. When running the code as shown below, the error noise plays, then it pauses for several seconds before opening the window. If I comment out the line with winsound, it opens it just fine.
import tkinter as tk
import winsound
class Error_Window:
def __init__(self, txt):
self.root = tk.Tk()
self.root.title("Error")
self.lbl = tk.Label(self.root, text=txt)
self.lbl.pack()
winsound.PlaySound("SystemExit", winsound.SND_ALIAS)
self.root.mainloop()
I suspect that it may be due to the error noise playing in full before reaching the mainloop command. One solution to this could be running the sound in a separate thread, but I've heard multithreading with tkinter should be avoided. Any tips on getting it to open smoothly at the same time as the noise is played?
Try this, the reason why it does that is the whole program is should we say in ONE THREAD/ MAIN THREAD so it would do first or execute first the sound then pop up the window. I think there's no problem with working with threads in tkinter just like what #jasonharper said
import tkinter as tk
import winsound
import threading
class Error_Window:
def __init__(self, txt):
self.root = tk.Tk()
self.root.title("Error")
self.lbl = tk.Label(self.root, text=txt)
th = threading.Thread(target=self.__play_sound,args=[])
th.start()
self.lbl.pack()
self.root.mainloop()
def __play_sound(self):
winsound.PlaySound("SystemExit", winsound.SND_ALIAS)
Error_Window("Hi")

how do i display a widget in a Frame written in another script Tkinter

i have a two python scripts with two classes. How do i display a widget which exists in the second script to the a frame in the first script for example
I have the main script where i have my frames in the first script. in the second script(Toplevel) i ran some functions to perform some tasks. after the tasks are performed, the results are written to the database.
while the tasks are running, i wanted a progress bar to be displayed in the Main so that after the tasks is finished then the progress bar can stop.
the challenge is the condition used to start and stop the progressbar occurs in the second script. but i want the progressbar to be displayed in the main
from tkinter import *
from tkinter import ttk
#import script2
class Main(object):
def __init__(self,master):
self.master = master
#main frames
self.mainFrame = Frame(self.master)
self.mainFrame.pack()
self.topFrame= Frame(self.mainFrame,width=200,height =150, bg="#f8f8f8",padx =20, relief =SUNKEN,
borderwidth=2)
self.topFrame.pack(side=TOP,fill = X)
self.btnseldate = Button(self.topFrame, text="open script2", font="arial 12 bold")
self.btnseldate.configure(compound=LEFT, command=self.sc)
self.btnseldate.pack()
def sc(self):
s = script2.class2(self.master,self.mainFrame)
def main():
root =Tk()
app = Main(root)
root.title("two variables")
root.geometry("200x200+350+200")
root.mainloop()
if __name__=="__main__":
main()
this is the second script
####In the second script titled "script2.py"
from tkinter import *
from tkinter import ttk
class class2(Toplevel):
def __init__(self,parent, mainFrame):
Toplevel.__init__(self)
self.geometry("200x200+350+200")
self.title("display")
#object1 = script1.Main()
#self.script1Frame = object1.topFrame()
##this button should be displayed just under the first button in script 1
self.progbar = ttk.Progressbar(self.topFrame, orient=HORIZONTAL, length=200)
self.progbar.pack()
self.progbar.config(mode='determinate')
self.progbar.start()
self.self.topFrame.pack()
#this is a dummy function just to describe what i have
def funcdotask():
#this function contains so many local variables that are only known in
#this script
pass
so the widget actually depends on a condition in the second script , thats why i didn't just place it in the first script.
i need ideas on even how i inherit variables from script2 to script 1 for example
or if there is a way i can get the results from the performed function in script 1(Main). it could help

Building a tkinter GUI module, so the main program doesn't need to import tkinter

I want to create a GUI module that I can import in my main program without having to import tkinter there, letting just the module handle everything. Here's how I imagine that it might work:
main.py
import gui as g
def update():
#Update the GUI with new Data from this main program
GUI = g.gui()
gui.after(1000, update)
gui.mainloop()
gui.py
import tkinter as tk
class viewer(tk.Frame):
#Some variables
def __init__(self, parent):
tk.Frame.__init(self, parent)
self.parent = parent
self.initialize(400, 100)
def initialize(self, width, height):
#Initialize some widgets, place them on grid, etc
def start(self):
#Do some other stuff, make a main window, configurations, etc
print('Started!')
Edit: "Don't ask for opinion"
How do I make this work?
import tkinter as tk
import gui as g
root = tk.Tk()
GUI = g.gui(root)
GUI.after(1000, update)
GUI.mainloop()
The above is what I don't want.
I used a workaround that seemed plausible to me:
main.py
import gui
GUI = gui.start()
GUI.after(1000, update)
GUI.mainloop()
gui.py
import tkinter as tk
def start():
root = tk.Tk()
run = viewer(root) # <- The class provided above
return run

Tkinter Show splash screen and hide main screen until __init__ has finished

I have a main tkinter window that can take up to a few seconds to load properly. Because of this, I wish to have a splash screen that shows until the init method of the main class has finished, and the main tkinter application can be shown. How can this be achieved?
Splash screen code:
from Tkinter import *
from PIL import Image, ImageTk
import ttk
class DemoSplashScreen:
def __init__(self, parent):
self.parent = parent
self.aturSplash()
self.aturWindow()
def aturSplash(self):
self.gambar = Image.open('../output5.png')
self.imgSplash = ImageTk.PhotoImage(self.gambar)
def aturWindow(self):
lebar, tinggi = self.gambar.size
setengahLebar = (self.parent.winfo_screenwidth()-lebar)//2
setengahTinggi = (self.parent.winfo_screenheight()-tinggi)//2
self.parent.geometry("%ix%i+%i+%i" %(lebar, tinggi, setengahLebar,setengahTinggi))
Label(self.parent, image=self.imgSplash).pack()
if __name__ == '__main__':
root = Tk()
root.overrideredirect(True)
progressbar = ttk.Progressbar(orient=HORIZONTAL, length=10000, mode='determinate')
progressbar.pack(side="bottom")
app = DemoSplashScreen(root)
progressbar.start()
root.after(6010, root.destroy)
root.mainloop()
Main tkinter window minimum working example:
import tkinter as tk
root = tk.Tk()
class Controller(tk.Frame):
def __init__(self, parent):
'''Initialises basic variables and GUI elements.'''
frame = tk.Frame.__init__(self, parent,relief=tk.GROOVE,width=100,height=100,bd=1)
control = Controller(root)
control.pack()
root.mainloop()
EDIT: I can use the main window until it has finished loading using the .withdraw() and .deiconify() methods. However my problem is that I cannot find a way to have the splash screen running in the period between these two method calls.
a simple example for python3:
#!python3
import tkinter as tk
import time
class Splash(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.title("Splash")
## required to make window show before the program gets to the mainloop
self.update()
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.withdraw()
splash = Splash(self)
## setup stuff goes here
self.title("Main Window")
## simulate a delay while loading
time.sleep(6)
## finished loading so destroy splash
splash.destroy()
## show window again
self.deiconify()
if __name__ == "__main__":
app = App()
app.mainloop()
one of the reasons things like this are difficult in tkinter is that windows are only updated when the program isn't running particular functions and so reaches the mainloop. for simple things like this you can use the update or update_idletasks commands to make it show/update, however if the delay is too long then on windows the window can become "unresponsive"
one way around this is to put multiple update or update_idletasks command throughout your loading routine, or alternatively use threading.
however if you use threading i would suggest that instead of putting the splash into its own thread (probably easier to implement) you would be better served putting the loading tasks into its own thread, keeping worker threads and GUI threads separate, as this tends to give a smoother user experience.

Categories

Resources