Python Button Bind two functions and send arguments with the binding - python

I am somewhat new to python still, and am working on a project including a GUI. I using with Tkinter and buttons a lot, and am curious if there was a way to run a function with a bind because I want one thing to happen when it is presses and something else when it is released.
s = str(x+1) + ":" + str(y+1)
img = ImageTk.PhotoImage(Image.open('button.png'))
b = Tkinter.Button(field_hid, image=img, borderwidth=0, highlightthickness=0, background='grey')
b.bind("<ButtonPress-1>", lambda s=s, button=b: location_down(event,s,button))
b.bind("<ButtonRelease-1>", lambda s=s, button=b: location_up(event,s,button))
b.img = img
b.pack()
b.grid(row=x, column=y)
I don't understand how I am to do this, as the only thing that can be passed to the function is the event, but my program requires the arguments.

The only way to use bind is to have it call a function. When you use lambda you are just creating an anonymous function. You can easily do:
b.bind("<ButtonPress-1>", self.SomeOtherFunction)
lambda is useful when you want to pass additional arguments to the function. Unlike when using the command option, with bindings you get an event object with lots of useful information so you may not need to pass any additional information.
For example, you could do this:
def OnPress(event):
print "widget %s was pressed" % event.widget
def OnRelease(event):
print "widget %s was released" % event.widget
b = Button(...)
b.bind("<ButtonPress-1>", OnPress)
b.bind("<ButtonRelease-1>", OnRelease)
For a good introduction to binding see Events and Bindings on effbot.org.

Related

Passing functions as input to another function in another script (Python)

to avoid writing many times similar GUIs for my very basic python programs, I've written a GUI generator function in a standalone script like this:
def GUI_builder_function(entry_list, entry_default_list, button_list, function_list, panel_title, background_colour):
#Entries Generation
....
#Buttons Generation
for i in range(0, N_buttons,1):
button = tk.Button(text = button_list[i], command= (lambda i=i: eval(function_list[i].__name__ +"()")), bg = 'firebrick', fg = 'white', font = ('helvetica', 9, 'bold'))
input_data_panel.create_window(0.5*pos_width+300, 0.35*pos_height*(2*i+1) + 20, window = button)
buttons.append(button)
...
It's a very simple GUIgenerator which creates some entries and buttons corresponding to the input entry list and button list, and space them in a decent way.
Now, my problem is that: when the user clicks the generic i-th button, it must execute the function function_list[i] that the caller function has sent to the gui builder function.
However, despite the gui builder function has received function_list[i] as an input, it cannot execute it when the button is pressed. The python engine says:
"Name 'function name' is not defined".
Of course it is defined in the caller function. But it will be dirty to import each caller function of each program inside the common gui builder function. What I'm looking for is how to send to gui builder the function definition as an input to the gui builder function, which is in a standalone script as it must be utilized by different programs.
You just reference the function directly, if I understand the problem correctly, and assuming function_list is actually a list of functions rather than a list of strings representing function names.
def foo():
pass
def bar():
pass
function_list = [foo, bar]
...
button = tk.Button(..., command= function_list[i], ...)

how to use Tcl/Tk bind function on Tkinter's widgets in Python?

Although Tkinter's derived from Tcl/Tk but it's not as complete as Tcl/Tk. Tcl/Tk bind function has some attributes which tkinter doesn't (e.g. %d that returns The detail field from the event https://www.tcl.tk/man/tcl8.4/TkCmd/bind.htm#M24).
Tcl/Tk scripts can be used by "eval" function in python but i don't know how to declare a tkinter widget in Tcl/Tk script.
so how can i use this function and its attributes on Tkinter widget?
If you are asking how to create a binding that makes use of the "data" field (ie: %d substitution), you will have to call some tcl code to make that happen. This requires two steps: create a tcl command that calls a python function, and use tcl to bind an event to that function.
First, let's create a python program that can create an event and set the "data" field (this assumes the existence of a global variable named root, which we'll create later). When this function is called, it will generate a custom event with the data field populated by a string.
def create_custom_event():
root.event_generate("<<Custom>>", data="Hello, world")
Next, lets create a program to call that function on a button press
import tkinter as tk
root = tk.Tk()
button = tk.Button(root, text="click me", command=create_custom_event)
button.pack(side="top", padx=20, pady=20)
root.mainloop()
Next we need to create a function that is called when the event is generated. We plan to set the data field, so this function must accept one value which is the value of the %d substitution.
def callback(detail):
print("detail: %s" % detail)
Because you want to use the %d substitution we'll have to create the binding via Tcl. However, tcl doesn't automatically know about our python functions so we have to register the function with tcl. Then it's just a matter of calling bind via the tcl interface to set up the binding.
The first step, then, is to register the callback. We've already created the function, we just have to create a tcl function that calls this function. Fortunately, tkinter gives us a way to do that with the register command. You use it like this:
cmd = root.register(callback)
This takes a python function (callback in this case), and creates a tcl command which will call that function. register returns the name of that tcl command which we store in a variable named cmd (the name is irrelevant)
Next, we need to set up a binding via Tcl to call this command. If we were doing this in an actual tcl script, the command would look something like this (where "." represents the root window):
bind . <<Custom>> {callback %d}
The python equivalent is this:
root.tk.call("bind", root, "<<Custom>>", cmd + " %d")
Notice that there is a 1:1 correspondence between an argument to call and a tcl argument. Conveniently, the default string representation of a tkinter widget is the internal tcl name, so we can directly use the widget in the call (though, pedantically, perhaps we should use str(root)).
Putting it all together gives us this, which prints out "detail: Hello, world" when you click the button:
import tkinter as tk
def callback(detail):
print("detail: %s" % detail)
def create_custom_event():
root.event_generate("<<Custom>>", data="Hello, world")
root = tk.Tk()
button = tk.Button(root, text="click me", command=create_custom_event)
button.pack(side="top", padx=20, pady=20)
cmd = root.register(callback)
root.tk.call("bind", root, "<<Custom>>", cmd + " %d")
root.mainloop()

What is the difference between command and bind in tkinter?

I'm trying to make a button print a string when it's pressed and print another when it's released. I know about the command atribute and the bind method, but I would like to know if it's possible to accomplish it only using atributes or if I have to use methods. With this piece of code:
class motor:
def __init__(eleMesmo, eixo , valorZero):
eleMesmo.eixo = eixo
eleMesmo.zero = valorZero
def aumenta(self):
print(self.eixo + str(self.zero+5))
def diminui(self):
print(self.eixo + str(self.zero-5))
def para(self):
print(self.eixo + str(self.zero))
eixox = motor('x',90)
eixoy = motor('y',90)
class Interface:
def __init__(elemesmo, widget):
quadro = Frame(widget)
quadro.pack()
elemesmo.aumentarY = Button(quadro,text="Aumentar Y",height=10,width=20,command=eixoy.aumenta)
elemesmo.aumentarY.pack(side=TOP)
elemesmo.diminuirY = Button(quadro,text="Diminuir Y",height=10,width=20,command=eixoy.diminui)
I can call the method aumenta for object eixo y when button aumentarY is pressed. I would like to call the method para for object eixo y when button aumentarY is released. How I can do it?
All Event types are outlined here, you are looking for <Button-1> (click down on button 1 (left mouse button if you are right handed)) and <ButtonRelease-1> (release mouse button 1 (left button if you are right handed)).
Note I wouldn't use command if you bind both of these.
elemesmo.aumentarY = Button(quadro,text="Aumentar Y",height=10,width=20)
elemesmo.aumentarY.bind("<Button-1>",eixoy.aumenta)
elemesmo.aumentarY.bind("<ButtonRelease-1>",eixoy.para)
However you must know that when using bind the callback is called with an Event object, if you don't need it you can just add an optional and unused parameter to the callback:
def aumenta(self, event=None):
print(self.eixo + str(self.zero+5))
def diminui(self, event=None):
print(self.eixo + str(self.zero-5))
def para(self, event=None):
print(self.eixo + str(self.zero))
Both command and .bind method are used to add life and functionality to a button, using the tkinter module on Python.
Command Parameter:
tkinter.Button(frame, text="Exit", fg="green", bg="pink", command=master.destroy)
The above button would destroy the frame we the 'Exit' button is selected or clicked any how. Try using the tab key and pressing spacebar. You will notice that the button would work.
What if you don't want that?
What if you strictly want the user to click it using the left mouse button?
Note that, you can pass a simple zero-parameter method to the command parameter, which may or may not contain any event object.
Bind Method:
The bind method is used to add extra information/functionality to the button, the way it needs to be clicked, the specific button that needs to be used and so on. It looks something like this:
btn = Button(frame, text="Exit", fg="green", bg="pink")
btn.bind(sequence="<Button-1>", func=master.destroy)
This will work only when the Mouse Button-1 (Left-Mouse Button) is pressed. Some alternate versions to this are like:
btn.bind(sequence="<ButtonRelease-1>", func=master.destroy)
The above works when the Mouse Button-1 is pressed and released.
btn.bind(sequence="<Double-Button-1>", func=master.destroy)
Similarly, the above sequence works only when the Mouse Button-1 is Double Clicked.
Note that, in the bind method you cannot pass a simple zero-parameter method to the bind method, it must contain one event object as shown below.
def callback(event):
pass
For the entire list of sequences for bind method, refer this article.

Python Tkinter - Passing values with a button

How do I pass parameters to a function through a button?
variable = str()
def RandomFunction(variable):
print (variable)
EntryBox = Entry(MainWindow, textvariable = variable).pack()
FunctionCall = Button(MainWindow, text="Enter", command=RandomFunction(variable))
It seems like it just doesnt print anything when the button is pressed. I've searched around and it seems that using lambda can fix it and allow (variable) to be passed to the function but after experimenting with lambda variable:variable I still can't get it to work.
The other answers here work, but like a lot of things in life, there's more than one way to do what you're trying to do.
The code in your question actually mixes a couple of methods of getting data from the Entry widget. You're using textvariable and lambda, but you only need one. It seems like lambda has been covered, so here's a quick answer about textvariable:
First, you need to make your variable of a Tkinter string type like this:
variable = StringVar()
Your entry widget is fine, it's connected to the StringVar(). Your button doesn't need lambda, though, because you don't need to pass an argument to your RandomFunction().
FunctionCall = Button(MainWindow, text='Enter', command=RandomFunction).pack()
Lastly, your function needs a little rework, because it's not taking an argument anymore, it's just going to use the .get() method on your StringVar() whenever it's called:
def RandomFunction():
print(variable.get())
You can read more about StringVar()s here: http://effbot.org/tkinterbook/variable.htm
You use .get() to get the contents of an Entry. From the effbot page http://effbot.org/tkinterbook/entry.htm
from Tkinter import *
master = Tk()
e = Entry(master)
e.pack()
e.focus_set()
def callback():
print e.get()
b = Button(master, text="get", width=10, command=callback)
b.pack()
master.mainloop()
Using lambda to create a function that calls the function with the argument is fine (as long as you do it correctly):
FunctionCall = Button(MainWindow, text="Enter", command=lambda: RandomFunction(EntryBox.get))
Python will be happy with this because the lambda doesn't take any arguments.

Trying to create a dynamic menu with a common command function

I'm trying to dynamically create a RECENT FILES menu from a list, then use a common function to process the selected file.
Windows 7, Python 2.7
I can successfully create the menu, but haven't been able to find a way to pass an indicator to the common function to identify the user's selection. I keep slamming up against Python's pass-by-reference; no matter what I try, the last menu argument value created is passed to the command function.
I've searched extensively, and nothing I've found has helped. (I ALMOST got it working with an 'exec', but I'm doing this processing inside a class and 'exec' doesn't seem to like self. functions)
I don't care what I can get - an index, a file name, ANYTHING I can use to determine which entry has been selected. If I'm completely off track with my approach, PLEASE point me elsewhere. I'm a Python newbie, but a veteran programmer and would truly appreciate constructive guidance/ criticism.
Here's my last attempt, which simply demonstrates my problem. Everything I've tried has the same result - only ever returning the last list iteration:
from Tkinter import *
def processFile(fileIndex):
print fileIndex
file_list = [('01','File01'),('02','File02'),('03','File03')]
root = Tk()
menu_bar = Menu(root)
file_menu = Menu(menu_bar)
recent_menu = Menu(file_menu)
Menu(file_menu)
for i, file in enumerate(file_list):
file_display = '%d %s' % (int(file[0]), file[1]) # Just making the menu pretty
recent_menu.add_command(label=file_display, command=lambda: processFile('%d' % i))
file_menu.add_cascade(label='Recent Files', menu=recent_menu)
menu_bar.add_cascade(label="File", menu=file_menu)
root.config(menu=menu_bar)
mainloop()
recent_menu.add_command(label=file_display, command=lambda i=i: processFile(i))
If you do not use the i=i, the lambda variable are evaluated at run time, in stead of declaration time. - This is default for lambda functions.

Categories

Resources