In Tkinter, I have a multiple widgets bound to the left-mouse button. They all trigger the same event when clicked.
How do I recover which widget was clicked?
I want it so when say Label2 was pressed, I'd be able to recover that Label2 was the widget that was pressed within the event it triggered.
def f(event):
caller = event.widget
You have a couple options. One way is to access the widget attribute of the event object. Another way is to pass an object reference in to your function. Here's an example that uses one of each.
import Tkinter as tk
def onClickA(event):
print "you clicked on", event.widget
event.widget.config(text="Thank you!")
def onClickB(event, obj):
print "you clicked on", obj
obj.config(text="Thank you!")
root = tk.Tk()
l1 = tk.Label(root, text="Click me")
l2 = tk.Label(root, text="No, click me!")
l1.pack()
l2.pack()
l1.bind("<1>", onClickA)
l2.bind("<1>", lambda event, obj=l2: onClickB(event, obj))
root.mainloop()
Sounds like all your widgets are sharing an event handler. This excerpt from the Tkinter 8.5 Reference by John W. Shipman - NM Tech, may help.
54.7. The extra arguments trick
Sometimes you would like to pass other
arguments to a handler besides the
event.
Here is an example. Suppose your
application has an array of ten
checkbuttons whose widgets are stored
in a list self.cbList, indexed by
the checkbutton number in range(10).
Suppose further that you want to write
one handler named .__cbHandler for
<Button-1> events in all ten of
these checkbuttons. The handler can
get the actual Checkbutton widget
that triggered it by referring to the
.widget attribute of the Event
object that gets passed in, but how
does it find out that checkbutton's
index in self.cbList?
It would be nice to write our handler
with an extra argument for the
checkbutton number, something like
this:
def __cbHandler(self, event, cbNumber):
But event handlers are passed only one
argument, the event. So we can't use
the function above because of a
mismatch in the number of arguments.
Fortunately, Python's ability to
provide default values for function
arguments gives us a way out. Have a
look at this code:
def __createWidgets(self):
...
self.cbList = [] # Create the checkbutton list
for i in range(10):
cb = Checkbutton(self, ...)
self.cbList.append(cb)
cb.grid(row=1, column=i)
def handler(event, self=self, i=i): # [1]
return self.__cbHandler(event, i)
cb.bind("<Button-1>", handler)
...
def __cbHandler(self, event, cbNumber):
...
[1] These lines define a new function
handler that expects three
arguments. The first argument is the
Event object passed to all event
handlers, and the second and third
arguments will be set to their default
values?the extra arguments we need to
pass it.
This technique can be extended to
supply any number of additional
arguments to handlers.
A slightly more concise way to do this, as #Bryan Oakley does for the second button in his answer, is to define each handler function in-line with a lambda expression, i.e.:
def __createWidgets(self):
...
self.cbList = [] # Create the checkbutton list
for i in range(10):
cb = Checkbutton(self, ...)
self.cbList.append(cb)
cb.grid(row=1, column=i)
cb.bind("<Button-1>", lambda event, self=self, i=i:
self.__cbHandler(event, i))
...
def __cbHandler(self, event, cbNumber):
...
Related
I am trying to make a small program where a user can click a button and it prints text, but if the user clicks the space bar it will also print the text. I know how to connect a button and a function using "command=....." not sure how to bind keys though. any help would be appreciated.
import tkinter as tk
root = tk.Tk()
def yes():
print("yes")
okbtn = tk.Button(text='OK', font=("Helvetica",50), bg = "red", command=yes, width=10, height=3)
okbtn.pack()
root.mainloop()
You can bind functions to keys, using the .bind method which takes a key (or combination of modifiers and keys) and a function to execute
So in your example, adding the line below, would bind the yes function to the spacebar
root.bind('<space>', lambda event: yes())
Note that any bound function will take a tkinter event as argument (which contains mouse coordinates, time of execution, reference to master widget, and more) - I have ignored the event argument in this case, by making a dummy lambda. However, it can often be useful
Here is an example of a function where the event is actually being used (prints the mouse position at the time where the function was called)
def motion(event):
print("Mouse position: (%s %s)" % (event.x, event.y))
You can check out this link for more information about even binding in tkinter https://www.pythontutorial.net/tkinter/tkinter-event-binding/
Is it possible to run a function with both a button press and an event? When I try to run this function by pressing the button, it, understandably, gives an error
TypeError: listFiller() missing 1 required positional argument: 'event'.
Couldn't find a similar problem, but maybe that is just my lack of programming knowledge.
Code:
class MyClass:
def __init__(self, master):
self.myButton = tk.Button(master, text='ok', command=self.listFiller)
self.myButton.pack()
self.myEntry = tk.Entry(master)
self.myEntry.bind("<Return>",self.listFiller)
self.myEntry.pack()
def listFiller(self, event):
data = self.myEntry.get()
print(data)
Try setting event=None for the function and then passing event from the bind only, like:
self.myButton = tk.Button(master, text='ok', command=self.listFiller)
.....
self.myEntry.bind("<Return>", lambda e: self.listFiller()) # Same as lambda e: self.listFiller(e)
def listFiller(self, event=None):
data = self.myEntry.get()
This way, when you press the button, event is taken as None. But when bind is triggered(i.e.,Enter is hit), event is passed on implicitly(works even if you pass it explicitly) as e.
So after you have understood how this works behind the hood, now even if you remove lambda, e would still get passed as event as it happens implicitly.
self.myEntry.bind("<Return>",self.listFiller) # As mentioned by Lafexlos
Though keep in mind, when you press a tkinter button, you cannot use any attributes from event, like event.keysym or anything, but it would work if you use Enter key.
I'm playing around with tkinter and am struggling somewhat with the use of listboxes. I want to bind an arbitrary function to a selection event. As I understand it - it should go something like this.
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
lb = tk.Listbox(self)
lb.insert("end", "one")
lb.insert("end", "two")
lb.insert("end", "three")
lb.bind("<Double-Button-1>", self.OnDouble)
lb.pack(side="top", fill="both", expand=True)
def OnDouble(self, event):
widget = event.widget
selection=widget.curselection()
value = widget.get(selection[0])
print "selection:", selection, ": '%s'" % value
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
My understanding is that when an element in the list box is double-clicked, a virtual event is created which can be bound to a callback function - in this case the OnDouble function defined.
I'm a little confused about the intricacies of this, would someone be able to explain exactly what is happening in this OnDouble function?
I'm trying to trigger an event when an element is selected from a listbox - i'm using pygubu to design the GUI but I understand that the syntax still follows tkinter conventions:
class TestApp:
def __init__(self):
self.builder = builder = pygubu.Builder()
builder.add_resource_path(PROJECT_PATH)
builder.add_from_file(PROJECT_UI)
self.mainwindow = builder.get_object('toplevel_1')
builder.connect_callbacks(self)
self.box = builder.get_object('listbox_1')
self.list = [1,2,3,4]
self.box.insert(tk.END, self.list[0])
self.box.insert(tk.END, self.list[1])
self.box.insert(tk.END, self.list[2])
self.box.insert(tk.END, self.list[3])
self.console = builder.get_object('text_1')
def print_console(self, msg):
self.console.configure(state="normal")
self.console.insert(tk.END, msg+"\n")
self.console.configure(state="disabled")
self.console.update_idletasks()
def run(self):
self.mainwindow.mainloop()
if __name__ == '__main__':
app = TestApp()
app.run()
Essentially I want the console to print (using the defined command) something like f'You have selected number {number}' when said number is double clicked on the list box. How can I do this? I would appreciate the workings behind it, so I can apply it to more complex exercises, and understand the solution rather than just copy/paste it.
Items in a listbox can be selected with a single click, but they can also be selected via the keyboard. For this reason, binding to a specific physical event such as <Double-1> is not the best way to be notified when the selection changes.
In the specific case of the listbox, binding to <<ListboxSelect>> is the best event to bind to, as that event will be generated after the selection has been set regardless of how it was set.
For example:
lb.bind("<<ListboxSelect>>", self.onSelect)
When a function is called via a binding it will automatically be passed an argument that contains details about the event that triggered the function call. One of the parameters is widget, which is a reference to the widget that received the call.
In your specific case you could ignore that and use self.listbox within the bound function, but using event.widget is a good habit to get into. That is why the code you copied starts with widget = event.widget
The next step is to get the selected item or items. In your case only a single selection is allowed at a time. However, the curselection method always returns a list. selection[0] will refer to the item selected, if there is a selection.
Note: the <<ListboxSelect>> event is triggered both when the selection is set or it is unset, so the code in your question could fail if the items are deselected by some means.
Finally, once you have the index of the selected item (eg: selection[0]), you can use that numerical index to get the text of the selected item. That is what the line value = widget.get(selection[0]) is doing.
I'm writing a little program that populates values that come from an API every second into Entry components. But also I need for the user to be able to change any of the values anytime by themselves.
So what I have right now is:
from tkinter import *
root = Tk()
sv = StringVar()
def callback():
print(sv.get())
return True
e = Entry(root, textvariable=sv, validate="key", validatecommand=callback)
e.grid()
e = Entry(root)
e.grid()
root.mainloop()
This way I can activate my callback function whenever they press a key. However, I need it to happen also when the value is changed by the API ticker that changes the Entry components. I need my function to be called whenever any Entry text/value is changed on any Entry.
I used to code in Delphi and there we had an onChage event for edits, but in Python I'm a little lost.
You can use the trace method on your StringVar:
def trace_method(*args):
#do your thing...
sv.trace("w",trace_method)
If you need to pass a parameter, you can use lambda:
def trace_method(*args,parameter=None):
if parameter:
print (parameter)
sv.trace("w",lambda *args: trace_method(parameter="Something"))
I bound the event <Return> to a Button, thinking that this would cause the command to be run after hitting Enter:
Button(self.f, text="Print", command=self.Printer).pack(side=RIGHT, padx=10, pady=10)
self.button1 = Button(self.f, text="search", command=self.search)
self.button1.bind('<Return>', self.search)
self.button1.pack(side=RIGHT, padx=10, pady=10)
But it doesn't do anything. What must I do for self.search to be run when Enter is pressed?
Your code looks fine, but note that the focus must be on the button if you want Return to call self.search(). You can change the focus from widget to widget by pressing Tab. The widget in focus is outlined with a thin black line. You may have to press Tab one or more times to move the focus to the button before pressing Return.
If you want Return to work anywhere in the GUI window, then change
self.button1.bind('<Return>', self.search)
to
root.bind('<Return>', self.search)
where root = tk.Tk().
For example, compare button.bind with master.bind in the code below:
import Tkinter as tk
class SimpleApp(object):
def __init__(self, master, **kwargs):
title = kwargs.pop('title')
frame = tk.Frame(master, **kwargs)
frame.grid()
button = tk.Button(frame, text = 'search', command = self.search)
button.grid()
button.bind('<Return>', self.search)
# uncomment if you want `<Return>` bound everywhere.
# master.bind('<Return>', self.search)
def search(self,*args):
print('searching...')
def basic():
root = tk.Tk()
app = SimpleApp(root, title = 'Hello, world')
root.mainloop()
basic()
Alternatively, you could use
button.bind('<Return>', self.search)
button.focus()
Doing it this way, pressing Return calls self.search() only when the button has the focus, but the button gets the focus when the app begins.
Regarding the use of *args and **kwargs:
**kwargs allows arbitrary keyword arguments to be passed to __init__.
When **kwargs is used in a function definition like this:
def __init__(self, master, **kwargs):
and we instantiate SimpleApp like this:
app = SimpleApp(root, title = 'Hello, world')
then Python sets kwargs to a dict containing the keyword arguments, e.g. {'title':'Hello, world'}. Note that **kwargs is Python syntax which can only be used in function definitions and function calls (see below), but kwargs itself is just a dict.
kwargs is then passed to Frame:
frame = tk.Frame(master, **kwargs)
Now, when **kwargs is used in a function call, the key-value pairs in the kwargs dict get expanded so that the above function call is equivalent to
frame = tk.Frame(master, title = 'Hello, world')
Since Frame can take numerous keyword arguments, and I don't know them all and don't wish to enumerate them, it is advantageous to use **kwargs.
Note also that even if new keyword arguments were added to Frame at some later date, my code will still work, whereas if I spelled out the keywords explicitly then my code would not automatically "upgrade" if Frame's allowable keywords were to change.
*args, similarly, allows you to include arbitrary positional arguments to search:
def search(self,*args):
Python sets args to a list containing all the positional arguments sent to search when search is called.
I used *args here because self.search is called with no arguments or one argument.
When you say
button = tk.Button(frame, text = 'search', command = self.search)
self.search() is called with no argumens when the button is clicked. But when you say
button.bind('<Return>', self.search)
self.search(event) is called with one argument when the Return key is pressed. event is a Tkinter.Event which has attributes (time, state, type, widget, x, y, etc) which allow you to learn more about what event took place.
Another, perhaps better, way to define search would have been
def search(self, event = None):
...
This would have made it clear that search may be passed 0 or 1 arguments, and not simply and arbitary number of arguments. It also would have provided easier access to event if an event were passed to search.
Reference:
saltycrane's
explanation.