window freezes in Python 3 - python

I am using python3 on a mac and run scripts with the IDLE which comes automatically with the python3 installation.
I am trying to make an alert to the user and found the command
tkinter.messagebox.showinfo("title","some text")
So I i tried a minimal script to check if I can get along with that command
import tkinter
tkinter.messagebox.showinfo("test" , "blabla")
The window is displayed correctly but it doesn't respond when I click on the "OK" button.
Addtionally there is a second empty window which appears when I start the script.
What is the explanation for this or at least how can I fix that?

tkinter isn't designed to work this way. Every tkinter requires a root window. If you don't explicitly create one (and you didn't), one will be created for you. That's what the blank window is.
Also, a tkinter GUI can't function properly unless it has a running event loop. This is necessary because some functions, such as responding to buttons and redrawing the window, only happens in response to events. If the event loop isn't running, events can't be processed.
Bottom line: the dialogs aren't designed to be used outside of the context of a proper tkinter app.
Wrapper for standalone use
The following code can be used to display one of the dialogs in standalone mode. It works by creating and hiding a root window, displaying the dialog, and then destroying the root window.
import tkinter as tk
from tkinter import messagebox
def show_dialog(func, *args, **kwargs):
# create root window, then hide it
root = tk.Tk()
root.withdraw()
# create a mutable variable for storing the result
result = []
# local function to call the dialog after the
# event loop starts
def show_dialog():
# show the dialog; this will block until the
# dialog is dismissed by the user
result.append(func(*args, **kwargs))
# destroy the root window when the dialog is dismissed
# note: this will cause the event loop (mainloop) to end
root.destroy()
# run the function after the event loop is initialized
root.after_idle(show_dialog)
# start the event loop, then kill the tcl interpreter
# once the root window has been destroyed
root.mainloop()
root.quit()
# pop the result and return
return result.pop()
To use it, pass the dialog you want as the first option, followed by dialog-specific options.
For example:
result = show_dialog(messagebox.askokcancel, "title", "Are you sure?")
if result:
print("you answered OK")
else:
print("you cancelled")

Related

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.

In Tkinter, Is there a way to stop a callback function from being executed upon running the program?

If I create a tkinter GUI program with a widget that has a callback function, is there a way to ensure that this callback is not executed until the user actually interacts with the widget?
It appears that when the widget is created (scale in the example below), the callback function is executed before the user even clicks on the scale/slider.
I would like for the message "I got called" not to appear until the user clicks the slider, rather than just by running the program.
I'm using Python 2.7.13 (I need to use 2.7 for certain reasons).
M.W.E.
from Tkinter import *
top = Tk()
def Callback_param11(val):
print('\n\nI got called\n\n')
# some commands will go here
p1 = DoubleVar()
p1_slider = Scale(top, variable=p1, from_=-10, to=10, command=Callback_param11)
p1_slider.pack()
top.mainloop()

Tkinter Python Pop-Up window that shows message but still lets another function run when pop-up is up.

I'm trying to have a "Downloading..." pop-up window show up as another function is running, and then when that function is done to close that pop-up window.
Thanks
import tkinter as t
from tkinter import ttk
root = t.Tk()
t.Label(root, text='Downloading...').pack()
pb = ttk.Progressbar(root, length=200, mode='indeterminate')
pb.pack()
pb.start()
root.update()
This code will immediately display this window:
Also, this won't stop execution... If you write more code below, python will continue executing your program; but there is a caveat - you have to call root.update() from time to time while you're doing something else, to allow tkinter to update the window, otherwise your window will seem frozen and the window manager will mark the window as "not responding".
Create a Tk() window and put a progress bar inside it.
You can find out how to create a popup window by searching StackOverflow for [tkinter] pop-up window. When you have done that, here is a generic 'downloading' wrapper function.
def downloading(parent, function, args, kwargs):
popup = <code or function call to create popup using parent)
ret = function(*args, **kwargs)
popup.destroy()
return ret
args is a tuple or list of positional arguments. kwargs is a key-value dict of keyword names and arguments.

Python- Displaying a message box that can be closed in the code (no user intervention)

I am creating test scripts using Python. I need to have a message displayed to the user while the script continues to run. This is to have some status update , for eg: "Saving test results" which should not wait for the user to click "Ok". Essentially , I need to create a message that pops up and closes without the user having to do it.
Currently,I am using easygui module for adding GUI.Easygui can be used for creating such message boxes but they cannot be closed in the code and need to wait for the user to close them for the script to continue running.
Thanks in advance for your time and help.
Kavitha
To forcibly remove on timeout a message box created with easygui you could use .after() method:
from Tkinter import Tk
from contextlib import contextmanager
#contextmanager
def tk(timeout=5):
root = Tk() # default root
root.withdraw() # remove from the screen
# destroy all widgets in `timeout` seconds
func_id = root.after(int(1000*timeout), root.quit)
try:
yield root
finally: # cleanup
root.after_cancel(func_id) # cancel callback
root.destroy()
Example
import easygui
with tk(timeout=1.5):
easygui.msgbox('message') # it blocks for at most `timeout` seconds
easygui is not very suitable for your use case. Consider
unittestgui.py or Jenkins.
If you have started to create a GUI, you should be able to use the textbox() function. A text box could be used as a place for your status messages, rather than making a separate dialog window appear.
I got the following description of textbox() here:
textbox(msg='', title=' ', text='', codebox=0)
Display some text in a
proportional font with line wrapping at word breaks. This function is
suitable for displaying general written text. The text parameter
should be a string, or a list or tuple of lines to be displayed in the
textbox.

Tkinter: one or more mainloops?

I have an already large Tkinter program, so that I have an init file, where the root = Tk() window is defined (containing basically a Text widget and a few other things), some more code, and last the call to mainloop() function.
Everything works, until I needed to call a procedure before the mainloop, and I wanted to raise a wait window at the begin, to be destroyed at procedure's end.
I wrote something like:
msg = Message(root, text='wait a few seconds...')
msg.pack()
But it doesn't and cannot work, since mainloop() has not been called yet!
If I instead do:
msg = Message(root, text='wait a few seconds...')
msg.pack()
mainloop()
The program stops at this first mainloop, doesn't finish the procedure call.
mainloop() should be used as your last program line, after which the Tkinter program works by a logic driven by user clicks and interactions, etc.
Here, I need a sequence of raise window > do stuff > destroy window > mainloop
You are correct that mainloop needs to be called once, after your program has initialized. This is necessary to start the event loop, which is necessary for windows to draw themselves, respond to events, and so on.
What you can do is break your initialization into two parts. The first -- creating the wait window -- happens prior to starting the event loop. The second -- doing the rest of the initialization -- happens once the event loop has started. You can do this by scheduling the second phase via the after method.
Here's a simple example:
import Tkinter as tk
import time
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
# initialize Tkinter
tk.Tk.__init__(self, *args, **kwargs)
# hide main window
self.wm_withdraw()
# show "please wait..." window
self.wait = tk.Toplevel(self)
label = tk.Label(self.wait, text="Please wait...")
label.pack()
# schedule the rest of the initialization to happen
# after the event loop has started
self.after(100, self.init_phase_2)
def init_phase_2(self):
# simulate doing something...
time.sleep(10)
# we're done. Close the wait window, show the main window
self.wait.destroy()
self.wm_deiconify()
app = SampleApp()
app.mainloop()
You should use Tkinter's method to run asyncore's loop function, but you should use asyncore.poll(0) instead of asyncore.loop(). If you call function asyncore.poll(0) every x ms, it has no longer an effect on Tkinter's main loop.

Categories

Resources