Python: Tkinter root window not reappearing - python

I am currently experiencing some trouble getting the root window for tkinter to hide when a Toplevel window appears and then become visible when it is closed.
The toplevel window is suppose to be a configuration window and upon completing, it will configure the root window.
In my main tkinter object class I have it set up like the following:
class OthelloGUIGame:
def __init__(self):
'''Create a GUI and prompt for configurations'''
self._root_window = tkinter.Tk()
self._root_window.wm_title("Othello")
def start(self):
'''Starts the tkinter mainloop and displays the gui'''
self._root_window.withdraw()
game_config = GameSetup()
self._root_window.mainloop()
game_config.show()
self._root_window.deiconify()
This works perfectly for hiding the main Tk window, but it seems the deiconfy method is not getting called when I click the 'X' on the top level window.
Just in case it matters here is the basic setup for my toplevel class:
class GameSetup:
def __init__(self):
self._root_window = tkinter.Toplevel()
self._root_window.wm_title("Game Setup")
self._moves_first = tkinter.StringVar(self._root_window)
self._arrangement = tkinter.StringVar(self._root_window)
self._should_win = tkinter.StringVar(self._root_window)
self._moves_first.set("Black")
self._arrangement.set("Black in upper-left")
self._should_win.set("With more discs")
self._setUpEntries()
self._setUpDropDowns()
def show(self)->None:
'''Show the window'''
self._root_window.grab_set()
self._root_window.wait_wind()
Any ideas on why the window is not reappearing?

Generally speaking, you should never have code after mainloop. That is because mainloop won't return until the root window is destroyed. Since it is destroyed, any windows created as children of the root window will also be destroyed. At that point there's really nothing left for your GUI to do besides exit.
So, in your case you're creating this secondary window and waiting for it to be destroyed. That doesn't cause mainloop to exit, and there's no other code that is causing the main window to be deiconified, so it remains hidden.
You need to put the call to deiconify after the code that shows the GameConfig window. This needs to all be before calling mainloop. Typically you would start the game after the GUI initializes, which you can do with after_idle. In other words, the logic needs to be something like this:
def start(self):
'''Starts the tkinter mainloop and displays the gui'''
self._root_window.withdraw()
game_config = GameSetup()
# N.B. show will not return until the dialog is destroyed
game_config.show()
self._root_window.deiconify()
With this, you need to call mainloop somewhere else. Typically you would do that in your intialiation code. For example:
def __init__(self):
self._root_window = tkinter.Tk()
...
self.after_idle(self.start)
self._root_window.mainloop()
That creates the window, causes start to be caused once the window has been created, and then starts the event loop. The start method will then create the secondary window, wait for it to be destroyed, and then show the main window.

Related

Binding closing of toplevel window

How can I create a binding in tkinter for when someone closes a toplevel window or if it is closed with toplevel1.destroy() or something similar. I am trying to make a small pop-up and when the user closes the main window or toplevel I want to prompt the user to save a file. I have figured out that I can set the actual close button to the function but cannot figure out how to get .destroy() and the closing of the main window to call the function. What should I do to bind the destroy function or window closing function?
Tested code:
import tkinter as tk
class TestWidget(tk.toplevel):
def __init__(self, *args, **kwargs):
tk.Toplevel.__init__(*args, **kwargs)
self.protocol("WM_DELETE_WINDOW", self.close)
def close(self):
print("Closed")
self.destroy()
if __name__ == "__main__":
root = tk.Tk()
TestWidget()
root.mainloop()
So I figured out that if you make a toplevel widget integrated into a class that you can find all the classes with the code below:
for child in root.winfo_children():
print(child)
This returns all the widgets and classes used:
.!testwidget
.!testwidget2
With this I can set up a function in the main window to call the child's close function one by one and this allows me to gain access to all the needed pieces

How to put a toplevel window in front of the main window in tkinter?

Is there any way to put a toplevel window in front of the main window?
Here's the code:
from tkinter import *
root = Tk()
root.geometry('1280x720')
def create_new_window():
root2 = Toplevel()
root2.geometry('500x500')
create_new_window()
mainloop()
Here, I want the root2 window to always stay in front of the root window.
I tried using root2.attributes('-topmost' , 1), but the problem is that this line puts the window on top of all the other programs as well.
What I want is that the toplevel window should only be in front of the main window, and it should never go back when I click on the main window.
Is there any way to achieve this in tkinter?
It would be great if anyone could help me out.
What you want, i think, is a transient window, you nedd to do:
root2.wm_transient(root)
From the manual:
wm transient window ?master?
If master is specified, then the window manager is informed that window is a transient window (e.g. pull-down menu) working on behalf of master (where master is the path name for a top-level window). If master is specified as an empty string then window is marked as not being a transient window any more. Otherwise the command returns the path name of window's current master, or an empty string if window isn't currently a transient window. A transient window will mirror state changes in the master and inherit the state of the master when initially mapped. It is an error to attempt to make a window a transient of itself.
So you could do something like this, but it seems buggy for me.
What I have done is to bind the FocusOut event to the toplevel that was created, so every time it looses the focus it triggers the event stackingorder to put the windos in the right order. You may need to expire this code for several events of your choice, but to get you the idea..
Here is the code:
import tkinter as tk
def add_toplevel(idx, toplevel):
if idx == 'end':
idx = len(toplevels)
toplevels.insert(idx,toplevel)
def create_new_window():
root2 = tk.Toplevel()
root2.geometry('500x500')
add_toplevel('end',root2)
root2.bind('<FocusOut>', stackingorder)
def stackingorder(event):
for toplevel in toplevels:
toplevel.lift()
toplevel.update_idletasks()
toplevels = [] #stacking order by index
root = tk.Tk()
create_new_window()
root.mainloop()
You are maybe also intrested in this:
https://stackoverflow.com/a/10391659/13629335

window freezes in Python 3

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

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.

Overriding Tkinter "X" button control (the button that close the window) [duplicate]

This question already has answers here:
How do I handle the window close event in Tkinter?
(11 answers)
Closed 7 years ago.
When the user presses a close Button that I created, some tasks are performed before exiting. However, if the user clicks on the [X] button in the top-right of the window to close the window, I cannot perform these tasks.
How can I override what happens when the user clicks [X] button?
It sounds as if your save window should be modal.
If this is a basic save window, why are you reinventing the wheel?
Tk has a tkFileDialog for this purpose.
If what you want is to override the default behaviour of destroying the window, you can simply do:
root.protocol('WM_DELETE_WINDOW', doSomething) # root is your root window
def doSomething():
# check if saving
# if not:
root.destroy()
This way, you can intercept the destroy() call when someone closes the window (by any means) and do what you like.
Using the method procotol, we can redefine the WM_DELETE_WINDOW protocol by associating with it the call to a function, in this case the function is called on_exit:
import tkinter as tk
from tkinter import messagebox
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("Handling WM_DELETE_WINDOW protocol")
self.geometry("500x300+500+200")
self.make_topmost()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
def on_exit(self):
"""When you click to exit, this function is called"""
if messagebox.askyesno("Exit", "Do you want to quit the application?"):
self.destroy()
def center(self):
"""Centers this Tk window"""
self.eval('tk::PlaceWindow %s center' % app.winfo_pathname(app.winfo_id()))
def make_topmost(self):
"""Makes this window the topmost window"""
self.lift()
self.attributes("-topmost", 1)
self.attributes("-topmost", 0)
if __name__ == '__main__':
App().mainloop()
The command you are looking for is wm_protocol, giving it "WM_DELETE_WINDOW" as the protocol to bind to. It lets you define a procedure to call when the window manager closes the window (which is what happens when you click the [x]).
I found a reference on Tkinter here. It's not perfect, but covers nearly everything I ever needed. I figure section 30.3 (Event types) helps, it tells us that there's a "Destroy" event for widgets. I suppose .bind()ing your saving jobs to that event of your main window should do the trick.
You could also call mainwindow.overrideredirect(True) (section 24), which disables minimizing, resizing and closing via the buttons in the title bar.

Categories

Resources