Intercept Tkinter "Exit" command? - python

I'm writing a client-server program in Python with Tkinter. I need the server to keep track of the connected clients. For this, I would like to have the client send an automated message to the server after the exit button(the standard "X" in the corner) is clicked. How can I know when the user is exiting the program?

You want to use the wm_protocol method of the toplevel window. Specifically, you are interested in the WM_DELETE_WINDOW protocol. If you use that method, it allows you to register a callback which is called when the window is being destroyed.
Usage:
root.protocol("WM_DELETE_WINDOW", app.on_delete)

You can use python atexit module.
For example:
import atexit
def doSomethingOnExit():
pass
atexit.register(doSomethingOnExit)

In my case, the following code didn't work:
root.protocol("WM_DELETE_WINDOW", app.on_delete) # doesn't work
However, it worked using this form:
root.wm_protocol ("WM_DELETE_WINDOW", app.on_delete) # does work

FWIW: It is also possible to assign a widget-specific behavior.
If you want an action to occur when a specific widget is destroyed, you may consider overriding the destroy() method. See the following example:
class MyButton(Tkinter.Button):
def destroy(self):
print "Yo!"
Tkinter.Button.destroy(self)
root = Tkinter.Tk()
f = Tkinter.Frame(root)
b1 = MyButton(f, text="Do nothing")
b1.pack()
f.pack()
b2 = Tkinter.Button(root, text="f.destroy", command=f.destroy)
b2.pack()
root.mainloop()
When the button 'b2' is pressed, the frame 'f' is destroyed, with the child 'b1' and "Yo!" is printed.
I posted the same answer on this topic.

Don't forget to use a lambda like this:
class App:
def run(self):
self.root.protocol("WM_DELETE_WINDOW", lambda: self.quit())
def quit(self):
self.root.destroy()

Related

How to run multiple threads in tkinter

Let's say if we write something like this:
import threading
Button(root, command=threading.Thread(target=func1).start)
Now if we click the button once then it will be fine but we try to click the button again then a error comes "Thread can only be executed once".
So, how to avoid this
Edited Answer::
As you clarified it in comments, you can redefine the Button every time it is clicked allowing it to accept multiple clicks and thus creating multiple threads as required.
You could do that inside the target func1() or callback function for threading.Thread object.
A working example example would be like this:
import tkinter as tk
import threading
def func1():
theButton.configure(command=threading.Thread(target=func1).start)
print('Do everything else here')
root = tk.Tk()
theButton = tk.Button(root, text='Start', command=threading.Thread(target=func1).start)
theButton.pack()
root.mainloop()
Edit: Thanks to CoolCloud for suggesting a better way to configure the Button inside callback func1().
It is because you create one instance of threading.Thread() and pass its start method to command option of the button. You should create new instance whenever the button is clicked by using lambda:
import tkinter as tk
import threading
root = tk.Tk()
def func1():
print('Hello')
tk.Button(root, text='Go', command=lambda: threading.Thread(target=func1).start()).pack()
root.mainloop()

Python Tkinter how to hide a widget without removing it

I know similar things have been asked a lot, but I've tried to figure this out for two hours now and I'm not getting anywhere. I want to have a button in a Tkinter window that is only visible on mouseover. So far I failed at making the button invisible in the first place (I'm familiar with events and stuff, that's not what this question is about) pack_forget() won't work, because I want the widget to stay in place. I'd like some way to do it like I indicated in the code below:
import tkinter as tki
class MyApp(object):
def __init__(self, root_win):
self.root_win = root_win
self.create_widgets()
def create_widgets(self):
self.frame1 = tki.Frame(self.root_win)
self.frame1.pack()
self.btn1 = tki.Button(self.frame1, text='I\'m a button')
self.btn1.pack()
self.btn1.visible=False #This doesnt't work
def main():
root_win = tki.Tk()
my_app = MyApp(root_win)
root_win.mainloop()
if __name__ == '__main__':
main()
Is there any way to set the visibility of widgets directly? If not, what other options are there?
Use grid as geometry manager and use:
self.btn1.grid_remove()
which will remember its place.
You can try using event to call function.
If "Enter" occurs for button then call a function that calls pack()
and if "Leave" occurs for button then call a function that calls pack_forget().
Check this link for event description:List of All Tkinter Events
If you wish your button to stay at a defined place then you can use place(x,y) instead of pack()

How can I perform an action after mainloop() has been called in Tkinter?

I want to make a GUI command line using the Text widget. For debugging purposes, I am trying to print whatever the user types into the separate GUI window to the system terminal. I know that it is frowned upon to mix GUI and Text Based commands into the same script, but I am just debugging, so forgive me 😉
Here is my code:
from Tkinter import *
main = Tk()
console = Text(main)
console.pack()
main.mainloop()
while True:
text = console.get("1.0", "end-1c")
print(text)
My current issue is that when the mainloop starts, (of course) the while loop doesn't. If I were to move the while loop in front of the mainloop call, it would never call mainloop. I really want it to continuously check for new text.
Is there a way to like "pause" the mainloop, or just carry out the command, maybe on a new thread or something?
I want to avoid using main.after(), but if that is the only way, then so be it. ¯\(°_o)/¯
I recommend using main.after(), as it's the canonical way to do things like this in Tkinter. The following will also ensure that it only tries to print every second, instead of as fast as the console can handle it (as the while loop in your code would do if it worked).
def print_console():
print(console.get("1.0", "end-1c"))
main.after(1000, print_console)
print_console()
main.mainloop()
You can also bind widgets to "Modified"
from Tkinter import *
class TextModified():
def __init__(self):
root = Tk()
self.txt = Text(root)
self.txt.pack()
self.txt.focus_set()
self.txt.bind('<<Modified>>', self.changed)
Button(text='Exit', command=root.quit).pack()
root.mainloop()
def changed(self, value=None):
flag = self.txt.edit_modified()
if flag: # prevent from getting called twice
print "changed called", self.txt.get("1.0", "end-1c")
## reset so this will be called on the next change
self.txt.edit_modified(False)
TM=TextModified()

How can I trigger and listen for events in python?

How can I trigger and listen for events in python ?
I need a practical example..
thanks
Update
#add Isosurface button
def addIso():
#trigger event
self.addButton = tk.Button(self.leftFrame, text="Add", command=addIso) #.grid(column=3, row=1)
self.addButton.pack(in_=self.leftFrame, side="right", pady=2)
There's a bind function for TKinter objects that takes two parameters: the first is a string representing the name of the event you want to listen for (probably "" in this case), and the second is a method which takes the triggering event as a parameter.
Example:
def addButton_click(event):
print 'button clicked'
self.addButton.bind("<Button-1>", addButton_click)
I believe this will still work even if used from another class:
class X:
def addButton_click(self, event):
print 'button clicked'
...
inst = X()
self.addButton.bind("<Button-1>", inst.addButton_click)
Something like this:
#add Isosurface button
def addIso(event):
#trigger event
self.addButton = tk.Button(self.leftFrame, text="Add") #.grid(column=3, row=1)
self.addButton.pack(in_=self.leftFrame, side="right", pady=2)
self.addButton.bind("<Button-1>", addIso)
From: http://www.bembry.org/technology/python/notes/tkinter_3.php
Based on your comments to some of the existing answers, I think what you want is something like the pubsub module. In the context of Tkinter, events are single purpose -- one event fires on one widget, and some handler handles the event (though there could be multiple handlers involved).
What it sounds like you want is more of a broadcast model -- your widget says "hey, the user did something" and any other module can register interest and get notifications without knowing specifically what widget or low level event caused that to happen.
I use this in an app that supports plugins. Plugins can say something like "call my 'open' method when the user opens a new file". They don't care how the user does it (ie: whether it was from the File menu, or a toolbar icon or a shortcut), only that it happened.
In this case, you would configure your button to call a specific method, typically in the same module that creates the button. That method would then use the pubsub module to publish some generic sort of event that other modules listen for and respond to.
If you are using IronPython with Windows Presentation Foundation (WPF), you can find pyevent.py in the Ironpython\Tutorial directory. This lets you write stuff like:
import clr
clr.AddReferenceByPartialName("PresentationFramework")
import System
import pyevent
class myclass(object):
def __init__(self):
self._PropertyChanged, self._OnPropertyChanged = pyevent.make_event()
self.initialize()
def add_PropertyChanged(self, handler):
self._PropertyChanged += handler
def remove_PropertyChanged(self, handler):
self._PropertyChanged -= handler
def raiseAPropertyChangedEvent(self, name):
self._OnPropertyChanged(self, System.ComponentModel.PropertyChangedEventArgs(name))

Call back in Python

Could some explain how call back methods work, and if possible, give me an example in Python? So as far as I understand them, they are methods which are provided by the user of an API, to the API, so that the user doesn't have to wait till that particular API function completes. So does the user program continue executing, and once the callback method is called by the API, return to point in the program where the callback method was provided? How does a callback method essentially affect the 'flow' of a program?
Sorry if I'm being vague here.
Callbacks are just user-supplied hooks. They allow you to specify what function to call in case of certain events. re.sub has a callback, but it sounds like you are dealing with a GUI, so I'll give a GUI example:
Here is a very simple example of a callback:
from Tkinter import *
master = Tk()
def my_callback():
print('Running my_callback')
b = Button(master, text="OK", command=my_callback)
b.pack()
mainloop()
When you press the OK button, the program prints "Running my_callback".
If you play around with this code:
from Tkinter import *
import time
master = Tk()
def my_callback():
print('Starting my_callback')
time.sleep(5)
print('Ending my_callback')
def my_callback2():
print('Starting my_callback2')
time.sleep(5)
print('Ending my_callback2')
b = Button(master, text="OK", command=my_callback)
b.pack()
b = Button(master, text="OK2", command=my_callback2)
b.pack()
mainloop()
you'll see that pressing either button blocks the GUI from responding until the callback finishes. So the "user does have to wait till that particular API function completes".

Categories