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

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.

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

the window close event, not run in a window without the window manager?

I am trying to execute a function with the closing event: self.toplevel_1.protocol ('WM_DELETE_WINDOW', lambda: self.close_windows (3)) but it does not execute, I have thought that it has to do with the window manager since it I have removed and I have an independent closing button, I have looked for some other closing event for windows without the window manager but I can't find it, I would like to know if there is any closing event for this case, thank you very much.
def windows_open(self):
self.Toplevel_3 = Toplevel(self)
#self.toplevel_3 .protocol('WM_DELETE_WINDOW',
lambda: self.close_windows(3)) # ancient
self.toplevel_STUF.bind('<Destroy>',lambda e: self.close_windows(3))
def close_windows((self, number, event=None):
var = event.widget()
if number is 3:
if var is: # How `var` gives an error, I don't know what it returns, here I stay
self.toplevel_3. destroy()
self._open_3 = False
#.....
The WM_DELETE_WINDOW protocol and other protocols are specifically related to window manager protocols. If you don't have a window manager, they don't apply.
From the protocol man page:
This command is used to manage window manager protocols...
In the absence of a window manager, you can bind to the <Destroy> event which should fire whether you have a window manager or not. You have to be careful with this if you bind to the root window. Bindings on the root widget or a Toplevel will apply to all descendant widgets, so in the bound function you'll want to run code only if event.widget refers to the root or Toplevel window.

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

Python: Tkinter root window not reappearing

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.

Disable, hide or remove close "X" button in Tkinter

I want to show a GUI to client, but I don't want to give the possibility to the client to close the window through the [X] button.
How do I disable, hide or remove the close [X] button of Tkinter window?
I found the following answers:
Python Tkinter “X” button control
Removing minimize/maximize buttons in Tkinter
However, these posts are not answering my question. I want to disable, hide or completely remove the [X] button.
When I use protocol:
def __init__(self):
Frame.__init__(self, bg = "black")
self.protocol('WM_DELETE_WINDOW', self.doSomething)
self.pack(expand = 1, fill = BOTH)
def doSomething(self):
if showinfo.askokcancel("Quit?", "Are you sure you want to quit?"):
self.quit()
I receive the following error:
self.protocol('WM_DELETE_WINDOW', self.doSomething)
AttributeError: 'GUI' object has no attribute 'protocol'
The problem with calling the protocol method is that it's a method on a root window but your GUI object is not a root window. Your code will work if you call the protocol method on the root window.
As for how to remove the button completely -- there's no method to simply remove that one button. You can remove all of the window manager buttons and frame by setting the overrideredirect flag.

Categories

Resources