How to stop waiting for input in tkinter dialog box? - python

I am currently coding a program that will do something (e.g count numbers constantly) until something is inputted into a dialog box displayed.
However, whenever I try this, the program freezes when waiting for an input and so does not make any progress in the counting process I am trying to run in the background.
Is there any way to have a timer that continuously runs in the background so that in say 5 minutes, the counter instantly stops and the dialog box disappears? This is a basic skeleton of my code. I used the tkinter dialog box for input and tried to create a timer that will run in the background.
from time import *
from tkinter import *
from tkinter import messagebox
from tkinter import simpledialog
while timer<300:
sleep(1)
timer += 1
ROOT = Tk()
ROOT.withdraw()
USER_INP = simpledialog.askstring(title="Code Required",
prompt="What's the Code?:")
Preferably without external modules but if not that is fine. Thanks in advance :)
This is the code requested
from tkinter import *
from tkinter import simpledialog
root = Tk()
root.withdraw()
def ask():
simpledialog.askstring(title="Code Required",
prompt="What's the Code?:")
## root.after(5000, root.destroy()) #added in the root.after() to try and terminate it after set time
root.after(3000,ask) #triggers ask() after 3000 ms(3 seconds)
root.after(100000, root.destroy()) # tried to wait 10 seconds before it breaks but this doesn't show the dialog box any more
root.mainloop()

Here is a basic code with tkinter that makes the dialogbox pop up after 5 seconds.
from tkinter import *
from tkinter import simpledialog
root = Tk()
root.withdraw()
def ask():
simpledialog.askstring(title="Code Required",
prompt="What's the Code?:")
root.after(5000, root.destroy) #added in the root.after() to try and terminate it after set time
root.after(3000,ask) #triggers ask() after 3000 ms(3 seconds)
#root.after(10000, root.destroy) # tried to wait 10 seconds before it breaks but this doesn't show the dialog box any more
root.mainloop()
Here after() triggers a function after the given time, i.e, 3000 ms(3 sec), so you can adjust the timer, out there too. This is just an example and you can edit this more as you like.
Why use after() and not while and a timer?
This is because a while loop interferes a tkinter mainloop() causing the window to be unresponsive, so it is not recommended to use while or time.sleep(). Instead you could use the built-in after() method by tkinter or threading too.
Here is a bit more on after():
It takes two positional arguments,mainly, ms and func
ms - It is the time(in milliseconds) after which the specified function will be triggered.
func - It is the function to be triggered after the specified ms finises.
WARNING:
Keep in mind that the root window is not destroyed, its just hidden, so as long as the root window is not destroyed, the program keeps on running in the background, so you will have to bring back the window and close it for the task to end. For this reason, ive added root.destroy() there.
Take a look here for a bit more understanding on after()
Hope it cleared your doubts, do let me know if any errors.
Cheers

Related

How to properly close a tkinter window with multiple threads?

I'm writing a script that takes some time to complete, but want to give some feedback to the user showing that the program is actually running. I'm trying to use the progress bar from tkinter because it's simple, but when the window closes, the program continues running in the background.
import sys
from time import sleep
from threading import Thread
import tkinter as tk
import tkinter.ttk as ttk
def work(win):
sleep(10)
win.destroy()
root = tk.Tk()
p = ttk.Progressbar(root, mode="indeterminate")
p.pack()
t = Thread(target=lambda: work(root))
t.start() # start thread
p.start() # start progressbar
root.mainloop()
sys.exit()
Here's essentially what the code does. I've tried putting sys.exit() after the thread closes the window, but that yields the same result.
After some debugging it seems like the program keeps running the main loop of root even though I destroyed it, so how can I make sure the program terminates the script properly?

How to control when does the console shows in python

I am making a python script that shows content in the console for the first 10-20 seconds after executed before the tkinter UI is shown. So is there any lines I can add to control the visibility of the console? .pyw file extension completely disables the console but I need the console to occasionally show up. Any suggestions?
First of all, you need to write your entire Tkinter code under a function.
def main_program():
# root = Tk() ...
Below this you need to write an empty print statement for the console. Below this, you can write anything. But the problem is sometimes, console quickly opens, prints the statement and closes it, so you barely have time to see
So, use time.sleep() -
import time
from tkinter import *
def main():
root = Tk()
root.mainloop()
print('Wait for a second')
time.sleep(1)
main()
This will show the print statement, delay the program for 1 second, and then open Tkinter window.
There are 2 ways to do it.
You can withdraw the window, print whatever you want and at the last, make it visible. .withdraw basically hides the window without any changes to the widgets.
from tkinter import *
root=Tk()
root.withdraw()
print("Hey there! Please wait for 10 seconds for the app to start...")
root.after(10000,lambda: root.deiconify())
root.mainloop()
Or, the second one is
from tkinter import *
import time
def create_window():
root = Tk()
root.mainloop()
print('Hey there! Please wait for 10 seconds for the app to start...')
time.sleep(10)
create_window()

Tkinter TopLevel not showing when it's supposed to

I have a very simple python code: a tkitner button that process some images in the background. I wanted to open a tkinter toplevel to show the user that it was doing something, but for my surprise is not working as I thought it would. The command on the tk.Button is the next method:
def processing(self):
"""Starts the images processing"""
# Open a Tk.Toplevel
aux_topLevel = Splash(self.window) # a simple Tk.Toplevel class, that works perfectly
self._process_images() # starts processing the images
# I wanted to kill here the topLevel created before
aux_topLevel.destroy()
My surprise: the window is displayed once the processing images is done (tried it out adding prints and time.sleep), however, i couldn't display the TopLevel when I wanted to.
Is there anything am I doing wrong? Any help is appreciated. Thank you.
Consider the following example and try to run it.
What you'd think should happen is that the new Toplevel window should open, some event happens for a period of time and then the window is destroyed.
What actually happens is the window is opened, but never displayed, the task occurs and then the window is destroyed.
from tkinter import *
import time
def processing():
new = Toplevel(root)
new.geometry("200x150")
lbl = Label(new,text="--")
lbl.grid()
for i in range(50):
time.sleep(0.1)
#Un-comment the line below to fix
#root.update()
print(i)
lbl['text'] = "{}".format(i)
new.destroy()
root = Tk()
root.geometry('200x100')
btnGo = Button(root,text="Go",command=processing)
btnGo.grid()
root.mainloop()
If you un-comment out the root.update() line and re-run the code, the window will be displayed.
There are better ways to deal with tasks that takes a while to process, such as threading.

Tkinter , root update

Why i am getting this error with root.update() when i destroy the window:
_tkinter.TclError: invalid command name ".!label"
or
_tkinter.TclError: invalid command name ".!frame.!text"
here is an example code:
from tkinter import *
import random
r=Tk()
a=Label(r)
b=[1,2,3,4,5,6,7]
while(True):
a.configure(text=f'{random.choice(b)}')
a.pack()
r.update()
r.mainloop()
any way to fix this error?
The error is because you are running an infinite loop
After closing the window also loop is try to run the program and try to access the Tk() window, but it has been destroyed.
the solution is break the loop also when you close the window
try:
a.configure(text=f'{random.choice(b)}')
r.update()
except:
break
In above code program will try to access the window, if success it will run the program
Otherwise failed it will execute 'except' condition and break the loop
your full code will be
from tkinter import *
import random
r=Tk()
a=Label(r)
a.pack()
b=[1,2,3,4,5,6,7]
while(True):
try:
a.configure(text=f'{random.choice(b)}')
r.update()
except:
break
r.mainloop()
That, or similar, happens any time you close a tkinter window and there is an uncompleted action waiting to process. Frankly, it can be ignored...
..but, if you really want to get rid of it, you need to manage your ongoing processes and wait for them to finish when closing. You will need to overwrite the destroy() method in a customized version of the Tk class to have that, and you will probably be better off using after() if you want active processes going on with the program. The way you are doing it right now, it never hits the mainloop so some functionality will not work.
Here is a simple fix:
from tkinter import *
import random
class Globals:
stop = False
class MyTk(Tk):
def trueDestroy(self):
Tk.destroy(self)
def destroy(self):
Globals.stop = (self)
def myUpdate():
a.configure(text=f'{random.choice(b)}')
if Globals.stop:
r.trueDestroy()
else:
r.after(1, myUpdate)
r=MyTk()
a=Label(r)
a.pack()
b=[1,2,3,4,5,6,7]
r.after(1, myUpdate)
r.mainloop()
Really, you should make a queue for each process you have running instead of just one 'stop' function, and then only destroy it once they all are stopped, but hopefully you can extrapolate form this.

label.configure works sometimes why?

Part of my code is as follows:
def get_songs():
label6.configure(text='Wait')
os.system('/home/norman/my-startups/grabsongs')
label6.configure(text='Done')
The label is not updated at the first .configure() but is at the second one.
Except if I cause a deliberate error immediately after the first one at which point it is updated and then the program terminates.
The system call takes about 2 minutes to complete so it isn't as if there isn't time to display the first one.
I am using Python 2.7.6
Does anyone know why please?
I'm going to guess you're using Tkinter. If so, as #albert just suggested, you'll want to call label.update_idletasks() or label.update() to tell Tkinter to refresh the display.
As a very crude example to reproduce your problem, let's make a program that will:
Wait 1 second
Do something (sleep for 2 seconds) and update the text to "wait"
Display "done" afterwards
For example:
import Tkinter as tk
import time
root = tk.Tk()
label = tk.Label(root, text='Not waiting yet')
label.pack()
def do_stuff():
label.configure(text='Wait')
time.sleep(2)
label.configure(text='Done')
label.after(1000, do_stuff)
tk.mainloop()
Notice that "Wait" will never be displayed.
To fix that, let's call update_idletasks() after initially setting the text:
import Tkinter as tk
import time
root = tk.Tk()
label = tk.Label(root, text='Not waiting yet')
label.pack()
def do_stuff():
label.configure(text='Wait')
label.update_idletasks()
time.sleep(2)
label.configure(text='Done')
label.after(1000, do_stuff)
tk.mainloop()
As far as why this happens, it actually is because Tkinter doesn't have time to update the label.
Calling configure doesn't automatically force a refresh of the display, it just queues one the next time things are idle. Because you immediately call something that will halt execution of the mainloop (calling an executable and forcing python to halt until it finishes), Tkinter never gets a chance to process the changes to the label.
Notice that while the gui displays "Wait" (while your process/sleep is running) it won't respond to resizing, etc. Python has halted execution until the other process finishes running.
To get around this, consider using subprocess.Popen (or something similar) instead of os.system. You'll then need to perodically poll the returned pipe to see if the subprocess has finished.
As an example (I'm also moving this into a class to keep the scoping from getting excessively confusing):
import Tkinter as tk
import subprocess
class Application(object):
def __init__(self, parent):
self.parent = parent
self.label = tk.Label(parent, text='Not waiting yet')
self.label.pack()
self.parent.after(1000, self.do_stuff)
def do_stuff(self):
self.label.configure(text='Wait')
self._pipe = subprocess.Popen(['/bin/sleep', '2'])
self.poll()
def poll(self):
if self._pipe.poll() is None:
self.label.after(100, self.poll)
else:
self.label.configure(text='Done')
root = tk.Tk()
app = Application(root)
tk.mainloop()
The key difference here is that we can resize/move/interact with the window while we're waiting for the external process to finish. Also note that we never needed to call update_idletasks/update, as Tkinter now does have idle time to update the display.

Categories

Resources