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.
Related
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
I work with Python 3.5 and TKinter.
I defined a label and file dialog that updates this label.
A button is responsible to launch this dialog.
self.sel_folder_val = the label that will be updated.
The code:
self.sel_folder_val['text']=filedialog.askdirectory()
After pressing the button in order to launch this dialog, the button stays pressed. Any dialog that a button is responsible to open cause the button to stay low (pressed) after closing this dialog.
I have tried this also with no help...:
self.select_folder_btn.config(relief=RAISED)
Code example:
self.select_folder_btn = Button(self.top)
self.select_folder_btn.place(relx=0.07, rely=0.57, height=34, width=187)
self.select_folder_btn.configure(activebackground="#d9d9d9")
self.select_folder_btn.configure(activeforeground="#000000")
self.select_folder_btn.configure(background="#d9d9d9")
self.select_folder_btn.configure(disabledforeground="#a3a3a3")
self.select_folder_btn.configure(font=self.font3)
self.select_folder_btn.configure(foreground="#000000")
self.select_folder_btn.configure(highlightbackground="#d9d9d9")
self.select_folder_btn.configure(highlightcolor="black")
self.select_folder_btn.configure(pady="0")
self.select_folder_btn.configure(text='''Select destination folder''')
self.select_folder_btn.bind('<Button-1>',self.update_folder_value)
def update_folder_value(self,event):
self.sel_folder_val['text']=filedialog.askdirectory()
return
After executing update_folder_value() function, self.select_folder_btn stays down.
I used the command:
self.select_folder_btn.configure(command=self.update_folder_value)
Instead of bind:
self.select_folder_btn.bind('<Button-1>',self.update_folder_value)
It solved my problem.
Thanks
First for future reference this is a minimal working example:
from Tkinter import *
import tkFileDialog as filedialog
class app:
def __init__(self):
self.top = Tk()
self.select_folder_btn = Button(self.top)
self.select_folder_btn.place(relx=0.07, rely=0.57, height=34, width=187)
self.select_folder_btn.configure(activebackground="#d9d9d9")
self.select_folder_btn.configure(activeforeground="#000000")
self.select_folder_btn.configure(background="#d9d9d9")
self.select_folder_btn.configure(disabledforeground="#a3a3a3")
#self.select_folder_btn.configure(font=self.font3)
self.select_folder_btn.configure(foreground="#000000")
self.select_folder_btn.configure(highlightbackground="#d9d9d9")
self.select_folder_btn.configure(highlightcolor="black")
self.select_folder_btn.configure(pady="0")
self.select_folder_btn.configure(text='''Select destination folder''')
self.select_folder_btn.configure(command=self.update_folder_value)
self.sel_folder_val = {}
self.top.mainloop()
def update_folder_value(self):
self.sel_folder_val['text']=filedialog.askdirectory()
self.top.update_idletasks()
app()
and even that's not minimal. Second your problem is hard to find since this isn't minimal- you're doing something really weird - binding the button to a click. You're overriding the built-in binding, and apparently it still affects the state of the button on press, but not going back. What you wanted is:
self.select_folder_btn.configure(command=self.update_folder_value)
instead of your:
self.select_folder_btn.bind('<Button-1>',self.update_folder_value)
You could also define that in the Button command. What you did is bypassed the button mechanism, so apparently only half of it is executed, and the relief is not raised. Note you have to remove the event parameter your method accepts.
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.
How would that be done?
I have been unable to find it on here or with Google.
#Refrences
from tkinter import *
class Interface:
def __init__(self,stage):
topLeft = Frame(stage,bg='black')
test = Entry(topLeft,bg='#2a2a2a',fg='white',insertontime=0)
test.config(insertbackground='white', exportselection=0)
test.grid()
topLeft.grid(row=0,column=0)
def launch():
window = Tk()
lobby = Interface(window)
window.mainloop()
launch()
I assume you wish for users to not be able to edit the Entry box? If so, you must use the config parameter of (state="disabled"), eg.
test.config(insertbackground='white', exportselection=0, state="disabled")
be wary though, I could not find a way to keep the background of your entry box black. Hope this helps
You can set the text highlight color to match the background of entry widget. Note that the text in the widget can still be selected but the user will not see it visually which will give the illusion that selection is disabled.
test = Entry(topLeft,bg='#2a2a2a',fg='white',insertontime=0)
test.configure(selectbackground=test.cget('bg'), inactiveselectbackground=test.cget('bg'))
When we select a text we tkinter trigger(fire) 3 events, which are ButtonPress, Motion and ButtonRelease all these 3 events call a event handler fucntion.
The function run select_range(start, end) method which highlight selected text.
To disable this we have to hanlde ButtonPress and Motion events together and call select_clear method on the widget.
If you handle the events and call select_clear method it work but not properly, the text will be highlighted and when another event occured highligh color will be cleared.
This happend because of execution order of events. That's mean you have to tell to tk handle your event after default event handler. We can change order of events execution with bindtags and bin_class methods
example:
from tkinter import *
from tkinter import ttk
def on_select(event):
event.widget.select_clear() # clear selected text.
root = Tk()
name_entry = ttk.Entry(root)
name_entry.pack()
# with PostEvent tag, on_select the event hanlde after default event handler
name_entry.bindtags((str(name_entry), "TEntry", "PostEvent", ".", "all"))
name_entry.bind_class("PostEvent", "<ButtonPress-1><Motion>", on_select)
root.mainloop()
A useful solution to Tkinter not having tons of built-in widgets (like, say, JavaFX does), is that you can easily make your own if you don't mind them being not-quite what you wanted :) Here's a rough-around-the-edges example of using a canvas to emulate an entry field that can't be selected. All I've given is the insert functionality (and delete too, I suppose, if you were clever about it), but you may want more. (Another plus is that, because it's a canvas item, you can do nifty formatting with it).
Am I right that by a not-selectable entry widget, you mean a canvas with text written on it? If you want to disable text-highlighting in ALL widgets, including the top-level frame, you could highjack the Button-1 event, deselect everything, and then consume the event whenever text is selected.
from tkinter import *
class NewEntry(Canvas):
def __init__( self, master, width, height ):
Canvas.__init__( self, master, width=width,height=height )
self.width = width
self.height = height
self.text = ''
def insert( self, index, text ):
self.text =''.join([self.text[i] for i in range(index)] + [text] + [self.text[i] for i in range(index,len(self.text))])
self.delete(ALL)
self.create_text(self.width//2,self.height//2,text=self.text)
root = Tk()
entry = NewEntry(root,100,100)
entry.insert(0,'hello world')
entry.insert(5,'world')
entry.pack()
root.mainloop()
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.