Winsound causing my tkinter GUI to open slowly - python

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")

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()

tkinter user non closeable window

I am trying to write a program where i have removed the main window close options and providing a exit button to the user to close the program.
After pressing i need to do some processing in the background which would be time consuming, i don't want user to close the program while that is going on accidentally. Is there a way to remove all buttons from the messagebox which is presented ?
import tkinter as tk
from win32api import GetSystemMetrics
from tkinter import messagebox
def on_closing():
pass
def exit():
messagebox.showinfo("Wait", "Please wait for background process to complete")
root.destroy()
root = tk.Tk()
root.resizable(width=False, height=False)
root.protocol("WM_DELETE_WINDOW", on_closing)
width = GetSystemMetrics(0)
height = GetSystemMetrics(1)
root.geometry('{}x{}'.format(width,height))
exitButton = tk.Button(root,text="Exit",width=15,command=exit)
exitButton.grid(row=0,column=1,padx=6,pady=6)
root.overrideredirect(True)
root.mainloop()
In the Background : There are some files generated on user's machine and i would like to archive them using python library. The files can go maybe sometime at 1GB so i think it would take more amount of time, if the laptop on which it is run is having very less computing power. And this would be the case for my base hence i want them just to wait until that popup is closed. This i can define in user manual.
I am not sure what work you want to do, but for this example I'm doing a work of printing something and then sleeping and then printing it. So this takes about 20 seconds. And in those 20 seconds you wont be able to exit the GUI.
import tkinter as tk
from win32api import GetSystemMetrics
from tkinter import messagebox
import time
import threading
def on_closing():
if started == False: #if work is not going on, then quit
root.destroy()
else: # else show the message.
messagebox.showinfo("Wait", "Please wait for background process to complete")
def work():
global started
started = True #mentioning that the work started
print('Hey')
time.sleep(5)
print('There')
time.sleep(5)
print('Whats Up')
time.sleep(5)
print('Cool?')
time.sleep(5)
started = False #mentioning that the work stopped
started = False #initially work is not started
root = tk.Tk()
root.resizable(width=False, height=False)
root.protocol("WM_DELETE_WINDOW", on_closing)
width = GetSystemMetrics(0)
height = GetSystemMetrics(1)
root.geometry('{}x{}'.format(width,height))
exitButton = tk.Button(root,text="Exit",width=15,command=on_closing)
exitButton.grid(row=0,column=1,padx=6,pady=6)
Button = tk.Button(root,text="Work",width=15,command=threading.Thread(target=work).start)
Button.grid(row=1,column=1,padx=6,pady=6)
# root.overrideredirect(True)
root.mainloop()
Here, started acts like a flag. You have to set it to True before starting your work and set it to False after it ends.
You can ignore the fact that I created a new button and used threading, it was just to simulate to you an example of work done. Threading helps the GUI to not freeze. Though I'm not sure if this will work with root.overrideredirect(True), but I think you can get rid of it.

Tkinter update progressbar from imported code

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()

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.

Tkinter freezing with multi threading

I need an application that basically runs a progress bar for a few seconds, and then closes itself down. I used this as an example whilst adapting it first for Python 3.4 and then for my own application. However, due to the way I've structured my code, it will first run the thread and it's tasks to it's completion and only then display the programme. This is very problematic for me, and I don't see a way around it when using classes.
from tkinter import ttk as ttk
from tkinter import *
import threading
import time
class App:
def afterLoading(self):
print('Loading finished')
def process(self,master):
time.sleep(2)
print('Thread Done')
self.afterLoading()
def __init__(self, master):
print()
master.geometry("1270x800")
master.resizable(0,0)
t1 = threading.Thread(target=self.process, args=(master,))
t1.start()
self.loadingFrame(master)
t1.join()
def loadingFrame(self, master):
frame = Frame(master, width=500, height=300)
frame.pack(side=BOTTOM, pady=50)
self.bar = ttk.Progressbar(frame, orient='horizontal', mode = 'indeterminate')
self.bar.pack(fill=BOTH)
self.bar.start(50)
self.loadingLabel = Label(frame, text="Please wait whilst the programme initializes.")
self.loadingLabel.pack()
root = Tk()
b = App(root)
root.mainloop()
Well, with your example code, you can just remove the call to t1.join() to get the behavior you want. That way, you'll be able to start the Tk event loop immediately after starting the background thread, which means your GUI can actually start up while the thread runs in the background. Using the t1.join() call prevents root.mainloop() from executing until the thread is complete, which means your GUI won't display until the thread is complete, either.

Categories

Resources