I'm fairly new to python and tkinter. I'm working with python 2.7 and tkinter 8.5.
I'm trying to exit my app when the escape key is pressed, and I ran into some odd behaviour:
When I pass root.destoy as the argument to bind(), the app does nothing:
root = Tk()
...
root.bind('<Escape>', root.destroy)
But if I define a function that calls root.destroy() and pass that as an argument to bind, everything works as expected:
def exit_app():
root.destroy()
root.bind('<Escape>', exit_app)
It also works if I pass a lambda like this:
root.bind('<Escape>', lambda f: root.destroy())
Can anyone explain what's happening here?
Thanks
When you bind a command to an event, that command is passed an argument which is is an object that represents the event. root.destroy does not accept any arguments, therefore it is throwing an error instead of running.
That is why your lambda works: your lambda accepts an argument (oddly, named f).
You claim in your question that it works with this exact function definition:
def exit_app():
root.destroy()
I find that impossible to believe, for the same reason described above.
Related
I am working through a tutorial on tkinter. I adapted some code, with the idea that every time you clicked on the button we'd create a new text label in the row below. What is confusing me is that when I write command=click it works, but when I write command = click() the function is executed a single time at the beginning of the script but not when the button is pressed. The first behaves as I'd expect. The second behaviour confuses me, as I would expect it to raise an error not perform the function once without pressing the button.
from tkinter import *
root = Tk()
position = {"Loc":1}
def click(position=position):
Label(root, text="ta").grid(row=position["Loc"], column=0)
position["Loc"] = position["Loc"] + 1
return
but = Button(root, text="click here to do nothing",command=click())
but.grid(row=0,column=0)
root.mainloop()
Note: I found the answer below more helpful than the answer to the related question here , because it made explicit that arguments are evaluated first before being passed into the function.
When you run a function in Python (like but = Button(root, text="click here to do nothing",command=click())), first all of the arguments get evaluated and then passed to the function. So, you are calling click() and then assigning its return value to the command. If it returns None or something else that Button can handle you will get no error. When you pass just click, you actually tell the function that it is supposed to call that function whenever you click the button.
Some simple code to clarify the issue:
def foo(bar):
print("\tCalling function {} from foo".format(bar))
if bar:
bar()
def fun():
print("\tExecuting fun function")
print("\nPassing function")
foo(fun)
print("\nPassing function return value")
foo(fun())
If you check out the outputs you will notice that they get executed in different order - in the second case, fun is executed first, returns None and than that None value is given to foo as a bar argument.
Passing function
Calling function <function fun at 0x000001A79F8863A0> from foo
Executing fun function
Passing function return value
Executing fun function
Calling function None from foo
As to why there is no error - why would there be?
Both are valid statements. You could have a function that returns other function and pass it to the button that way.
I wanted this code to create a popup error window that destroys itself after 4 seconds but can also be closed via a button.
def error(self):
top = Toplevel()
top.after(4000, lambda: top.destroy())
center_window(300,100, top)
top.title("Error")
Label(top, text="Please enter a valid code", height=3, width=200).pack()
ok = Button(top, text="OK", command=top.destroy)
ok.pack()
ok.bind("<Return>", lambda a: top.destroy())
ok.focus_set()
I have run the code and it works fine 90% of the time except sometimes it throws this error:
TypeError: <lambda>() takes exactly 1 argument (0 given)
I have done research that says it is Tkinters threading. I am not sure if this is my issue but when I take out this line of code:
top.after(4000, lambda: top.destroy())
It seems to work. If anyone could help me, I have taught myself what I know of python, so I am sure there are holes in my learning. I think I may need to somehow use the main thread of execution to destroy this window, or otherwise create my own custom widget. Any help is appreciated.
When using after or bind, you don't need to use lambda.
Instead, for example, use:
top.after(4000, top.destroy)
which references the function top.destroy directly.
You can directly bind the function to be called instead of using a lambda:
top.after(4000, top.destroy)
...
ok.bind("<Return>", top.destroy)
You would use a lambda, for instance, if you needed to pass arguments to the function; this is not the case here.
I am building a GUI application using Python and Tkinter.
I want to control the behavior of the program when the user closes it.
I've installed a new WM_DELETE_WINDOW protocol using:
root = Tk()
root.protocol("WM_DELETE_WINDOW", lambda: closes_gracefully())
This indeed is working when the user clicks the X button on the titlebar, but it is NOT working when the user presses ALT+F4.
I tried binding the key sequence: root.bind("<Alt-F4>", lambda: closes_gracefully()) but it did not work.
How can I capture the ALT+F4 event?
For this purpose, you could use atexit.register.
It works like a stack that is executed when the program gets closed. Every time you do register(function), this function gets pushed on top. If you added a, b and c they get executed in the opposite order(c, b, a).
In your case, you should do:
register(closes_gracefully)
You should note that this works almost always, except with crashes(alt-f4 works too, just tested it).
You can even use register as a decorator when the function takes no parameters:
#register
def bye():
print("I'm out!")
Goal
I want to create a popup window with buttons that closes when the mouse leaves the window. (It is a submenu of a program).
To do that I have a function that is called if the popup window should be opened. It contains following code:
master = Tk()
fpy.ui.menu.submenu(master=master,[...more parameters...])
mainloop()
and a method (fpy.ui.menu.submenu(...)) to create everything else.
This function should stay as clean as possible. It's part of an API I'm building for my future projects.
def submenu(master,[...more parameters...]):
master.overrideredirect(True)
a_frame = Frame(master, bg="grey")
#[codeblock to generate buttons]
a_frame.bind("<Leave>", destroy)
a_frame.pack()
a_frame.bind("<Leave>", destroy)
calls the function 'function' when the mouse leaves the frame. And now my problem starts. the 'destroy' function should destroy my root (in this case called master).
Problem:
def destroy():
#how can I get the master-object?
master.destroy()
This will cause an error! How can I reach the Object I want to destroy from this method?
Info:
the function 'destroy' gets started
I didn't manage to transmitt the master object as parameter
please ignore the dots at the beginning of a line. the Stack Exchange editor doesn't accept indented code.
Option 1: Pass it as an argument
You could pass it to your destroy() function using a lambda statement:
a_frame.bind("<Leave>", lambda: destroy(master))
Then modify the signature of your destroy function:
def destroy(master):
master.destroy()
Option 2: Invoke the destroy method on master directly
Same lambda statement as before, except now we just call the method directly rather than handling it in another function.
a_frame.bind("<Leave>", lambda: master.destroy())
Of course, this isn't an option if you want to do other stuff in your destroy() function.
I'm new to GUI and classes and I'm a just a bit confused, when I use a button in tkinter for python it's suppose to repeat it's command when pressed. but in my program it doesn't do that. is there something wrong with me codes that might counter it? I'm trying to make a simple program that echos whatever is typed.
-Thanks
from Tkinter import *
from PIL import Image, ImageTk
import tkMessageBox
class appsMain(Frame):
def __init__(self,parent):
Frame.__init__(self,parent)
self.parent=parent
self.initUI()
def initUI(self):
self.parent.title("OrganizedWindows")
self.send=Text(self,bg="white",height=3,width=35)
self.send.place(x=17,y=235)
self.msg=Text(self,width=35,height=12,state="disable")
self.msg.place(x=17,y=20)
sendbtn=Button(self,text=" Listen ",command=self.accept)
sendbtn.place(x=305,y=240)
self.pack(fill=BOTH, expand=1)
def accept(self,msg):
self.msg.configure(state="normal")
self.msg.insert(INSERT,msg+"\n")
self.msg.insert(INSERT,"BYE")
self.msg.configure(state="disable")
root=Tk()
root.geometry("350x300+300+300")
app=appsMain(root)
root.mainloop()
Your code has a few problems. The first is solved easily:
sendbtn=Button(self,text=" Listen ",command=self.accept)
doesn't work because when the button is clicked, self.accept is called with no additional arguments (accept expects 2 arguments, [self and msg], but it is only getting 1 [self]).
You can work around this with lambda:
sendbtn=Button(self,text=" Listen ",command=lambda : self.accept("some message here"))
(This is equivalent to):
def func:
self.accept("some message here")
sendbtn=Button(self,text=" Listen ",command=func)
But, I don't know if you want to constantly add different messages ... or where they come from, so it is difficult to give a general solution at this point.
Tkinter applications happily continue to run even after exceptions are raised. It is a good idea to watch the terminal for exceptions when you're developing a Tkinter application (In this case, it pointed me right to the source of the problem).
This is to better answer your Lambda comment question. Lambda is a quick, one-liner way to write a function. The variable you set it to is the same as the name of your function for def myFunction. Then you say the keyword lambda and the letter(s)/word(s) you put after the keyword lambda are just the parameters of your function. Next you put a colon (just like you would for a normal function-> def myFunction:). After that you write whatever you want the function to return. So if you wanted a function to square a given number, n, then you could write it normally like:
def square_num(n):
return n**2
OR as a cool Lambda:
square_num = lambda n: n**2
You can also have as many parameters as you wish, just like in a normal function, so for a given number raised to the x power you could write:
raise_num = lambda n, x: n**x