Call the same function when clicking the Button and pressing enter - python

I have a GUI that has Entry widget and a submit Button.
I am basically trying to use get() and print the values that are inside the Entry widget. I wanted to do this by clicking the submit Button or by pressing enter or return on keyboard.
I tried to bind the "<Return>" event with the same function that is called when I press the submit Button:
self.bind("<Return>", self.enterSubmit)
But I got an error:
needs 2 arguments
But self.enterSubmit function only accepts one, since for the command option of the Button is required just one.
To solve this, I tried to create 2 functions with identical functionalities, they just have different number of arguments.
Is there a more efficient way of solving this?

You can create a function that takes any number of arguments like this:
def clickOrEnterSubmit(self, *args):
#code goes here
This is called an arbitrary argument list. The caller is free to pass in as many arguments as they wish, and they will all be packed into the args tuple. The Enter binding may pass in its 1 event object, and the click command may pass in no arguments.
Here is a minimal Tkinter example:
from tkinter import *
def on_click(*args):
print("frob called with {} arguments".format(len(args)))
root = Tk()
root.bind("<Return>", on_click)
b = Button(root, text="Click Me", command=on_click)
b.pack()
root.mainloop()
Result, after pressing Enter and clicking the button:
frob called with 1 arguments
frob called with 0 arguments
If you're unwilling to change the signature of the callback function, you can wrap the function you want to bind in a lambda expression, and discard the unused variable:
from tkinter import *
def on_click():
print("on_click was called!")
root = Tk()
# The callback will pass in the Event variable,
# but we won't send it to `on_click`
root.bind("<Return>", lambda event: on_click())
b = Button(root, text="Click Me", command=frob)
b.pack()
root.mainloop()

You could also assign a default value (for example None) for the parameter event. For example:
import tkinter as tk
def on_click(event=None):
if event is None:
print("You clicked the button")
else:
print("You pressed enter")
root = tk.Tk()
root.bind("<Return>", on_click)
b = tk.Button(root, text='Click Me!', command=on_click)
b.pack()
root.mainloop()

Related

tkinter bindo to a button or change the command of a bind

Ive made a game where at the end of the game, I change the command that is called by the button.
I was wondering if there was a way to link the bind to the button rather than the command, or if I could change the command called by the bind.
Here is my code for the button:
# create the button
self.but = Button(win, text="Submit", command=lambda: self.check_input(win))
self.but.grid(row=10, column=2, columnspan=3, sticky="NEWS")
win.bind("<enter>", self.check_input(win))
# change the button command
self.but["command"] = lambda: self.next(win, self.game_init)
self.but["text"] = "PLAY AGAIN!"
If your function does not use Event passed by tkinter when a widget is bind-ed, then it is fairly simple:
def check_input(self,win,event=None):
....
....
self.but = Button(win, text="Submit", command=lambda: self.check_input(win))
....
win.bind("<enter>", lambda e: self.check_input(win,e))
Though an easier and dynamic way to always follow the buttons command is(like jasonharper said) but you cannot use e at all, even if triggered by the given event:
win.bind("<enter>", lambda e: self.but.invoke())
An example to account for using same function with bind and command of a button:
from tkinter import *
root = Tk()
def func(win,e=None):
if e is not None:
print(f'Triggered by the hitting the {e.keysym} key')
else:
print('Triggered by the pressing button')
but = Button(root,text='Click me',command=lambda: func(root))
but.pack()
root.bind('<Return>',lambda e: func(root,e))
root.mainloop()
Also a good time to mention, your event is wrong, either you meant '<Return>'(enter key) or '<Enter>'(when you enter the bounds of a widget with cursor)

Properly assigning a function with arguments to a button in Tkinter

I have two files:
functions.py
import tkinter as tk
class funcs():
def func1(entry):
entry.delete(0, tk.END)
main.py
import tkinter as tk
import functions as f
root = tk.Tk()
entrybox = tk.Entry(master=root).grid()
button = tk.Button(master=root, command=f.funcs.func1(entrybox)).grid()
root.mainloop()
In main.py, I have assigned the command func1 with the argument entrybox to the widget button.
My intent is to have the entry argument represent an entry widget I want to manipulate.
This line of code is broken:
button = tk.Button(master=root, command=f.funcs.func1(entrybox)).grid()
The problem is that when I run the program, the function is called immediately and does not get assigned to the button.
I am looking for a way to assign a function with arguments to a button in tkinter.
You can use an anonymous function:
tk.Button(
master=root,
command=lambda: f.funcs.func1(entrybox)
)
Python recognizes the broken line of code as an immediate call, so you have to do lambda:
button = tk.Button(master=root, command=lambda: f.funcs.func1(entrybox)).grid()
and as far as I know that will get rid of the problem.

Binding all keyboard keys to entry.get() tkinter

I'm trying to create a dynamically-updating searchbox and instead of polling the the entry widget every x milliseconds, I am trying to use keyboard bindings to get the contents of the entry widget whenever a key on the keyboard is pressed:
from tkinter import *
root = Tk()
def callback():
result = searchbox.get()
print(result)
#do stuff with result here
searchbox = Entry(root)
searchbox.pack()
searchbox.bind("<Key>", lambda event: callback())
root.mainloop()
my problem is that searchbox.get() is always executed before the key the user pressed is added to the searchbox, meaning the result is the content of the searchbox BEFORE the key was pressed and not after.
For example if I were to type 'hello' in the searchbox, I would see the following printed:
>>>
>>> h
>>> he
>>> hel
>>> hell
Thanks in advance.
There's no need to use bindings. You can associate a StringVar with the widget, and put a trace on the StringVar. The trace will call your function whenever the value changes, no matter if it changes from the keyboard or the mouse or anything else.
Here's your code, modified to use a variable with a trace:
from tkinter import *
root = Tk()
def callback(*args):
result = searchbox.get()
print(result)
#do stuff with result here
var = StringVar()
searchbox = Entry(root, textvariable=var)
searchbox.pack()
var.trace("w", callback)
root.mainloop()
For information about the arguments passed to the callback see What are the arguments to Tkinter variable trace method callbacks?
For a better understanding of why your bindings seem to always be one character behind, see the accepted answer to Basic query regarding bindtags in tkinter

Python - Simple tkinter menu

I need help from you.
I want to code a simple menu with tkinter, but i have problem with that.
What I want to do - In my menu,there are 2 items: "first", "second". When i click on the first, program must write 'The first' and then when I click on the second, it must write second, but the first one there will be not yet.
Can anybody help me? Thx.
I mean something like this
from tkinter import *
root = Tk()
def do_something():
# this function e.g. write 'The first'
pass
def do_something_other():
# this function e.g. write 'The second' (but 'The first' there will be not yet)
main_menu = Menu(root)
root["menu"] = main_menu
submenu1 = Menu(main_menu)
submenu1.add_command(label="Item1", command=do_something)
submenu1.add_command(label="Item2", command=do_something_other)
main_menu.add_cascade(label="Program", menu=submenu1)
My goal is, that the canvas will be changing after clicking on the Item1/Item2
You can use a Tkinter Label widget to display text in your GUI. The Label widget has a method called config that you can use to access options of the widget, to include its text attribute.
Here's a simple example. You'll have to modify it to work with your menus and specific needs:
root = Tk()
def callback(): # this function will access the
label.config(text='Updated Text') # config method of label to change it
label = Label(root, text='Original text') # make the label and set default text
btn = Button(root, text='Change text', command=callback) # make the button, which executes the callback func
label.pack()
btn.pack()
mainloop()

Why does Tkinter hang when I call tkSimpleDialog.askstring from a lambda?

I'm developing a GUI application that models an essay. Among other things, the user can create a new topic and then populate that topic with notes. At the moment, I have two ways of creating new topics: through a dropdown option in the menu (the menu command) and through a button on the main screen (the button command). The button starts life with the text "New Topic". When the user presses the button, the program makes a new topic, asks the user to name the topic using tkSimpleDialog.askstring, and then sets the button's text to be the name of the topic and the number of notes in that topic. The button's command then changes to be adding a note to that topic.
While developing the program, I first verified that the menu command worked. It calls askstring successfully, creating a new popup window that handles input in the way I wanted. However, as soon as I added the button command, the call to askstring failed, even when called via the menu command. The window that should have the askstring dialog is whited out and the program hangs. If I comment out the button command, it works again. If I comment out the menu command, it hangs.
Here's the code where I add the command to the menu:
TopicBtn.menu.add_command(label="New Topic", underline=0,
command=self.newTopic)
Here's the code for newTopic():
def newTopic(self, button=None):
""" Create a new topic. If a Button object is passed, associate that Button
with the new topic. Otherwise, create a new Button for the topic. """
topicPrompt = "What would you like to call your new topic?"
topicName = tkSimpleDialog.askstring("New Topic", topicPrompt)
if topicName in self.topics.keys():
print "Error: topic already exists"
else:
newTopic = {}
newTopic["name"] = topicName
newTopic["notes"] = []
newTopic["button"] = self.newTopicButton(newTopic, button)
self.topics[topicName] = newTopic
self.addToTopicLists(newTopic)
Here's the code for newTopicButton():
def newTopicButton(self, topic, button=None):
""" If a Button object is passed, change its text to display the topic name.
Otherwise, create and grid a new Button with the topic name. """
if button is None:
button = Button(self.topicFrame)
index = len(self.topics)
button.grid(row=index/self.TOPICS_PER_ROW, column=(index %
self.TOPICS_PER_ROW), sticky=NSEW, padx=10, pady=10)
else:
button.unbind("<Button-1>")
buttonText = "%s\n0 notes" % topic["name"]
button.config(text=buttonText)
button.config(command=(lambda s=self, t=topic: s.addNoteToTopic(t)))
return button
And, finally, here's the code for the button command:
for col in range(self.TOPICS_PER_ROW):
button = Button(self.topicFrame, text="New Topic")
button.bind("<Button-1>", (lambda e, s=self: s.newTopic(e.widget)))
button.grid(row=0, column=col, sticky=NSEW, padx=10, pady=10)
Anybody have any idea why binding the lambda expression to the button makes askstring hang?
Edit: Thanks for the comments. Here's a minimal example that exhibits the behavior:
from Tkinter import *
import tkSimpleDialog
class Min():
def __init__(self, master=None):
root = master
frame = Frame(root)
frame.pack()
button = Button(frame, text="askstring")
button.bind("<Button-1>", (lambda e, s=self: s.newLabel()))
button.grid()
def newLabel(self):
label = tkSimpleDialog.askstring("New Label", "What should the label be?")
print label
root = Tk()
m = Min(root)
root.mainloop()
Note that switching from button.bind("<Button-1>", (lambda e, s=self: s.newLabel())) to button = Button(frame, text="askstring", command=(lambda s=self: s.newLabel())) fixes the bug (but doesn't give me a reference to the button that was pressed). I think the problem has something to do with capturing the event as one of the inputs to the lambda.
The problem you encountered here is due to the call to wait_window in the dialog you are using (you never call it yourself, but the code that implement the dialog does). For instance, the following code replicates the problem after (likely) two button clicks:
import Tkinter
def test(event=None):
tl = Tkinter.Toplevel()
tl.wait_window(tl)
root = Tkinter.Tk()
btn = Tkinter.Button(text=u'hi')
btn.bind('<Button-1>', test)
btn.pack(padx=10, pady=10)
root.mainloop()
This call to wait_window effectively does what the update command does, and is a typical example of why calling update is a bad thing to do. It enters in conflict with the <Button-1> event being handled, and hangs. The problem is that you will have to live with wait_window being used, since it belongs to the dialog's code. Apparently, if you bind to <ButtonRelease-1> then this conflict never happens. You could also use the command parameter in the button, which works fine too.
Lastly, I suggest the following to create the buttons in a cleaner manner based on what you want to achieve:
for i in range(X):
btn = Tkinter.Button(text=u'%d' % i)
btn['command'] = lambda button=btn: some_callback(button)
I figured out a workaround. From the minimum-example testing, it appears that the problem comes from making a separate call to bind and thereby accepting the event as an input to the lambda. If anyone can explain why that might be happening, I'll accept their answer over mine, but I'll accept this one for now.
The workaround is not to use a separate bind function but to create an array of buttons and then pass the correct entry in the array as the parameter to the lambda function (you can't pass the button itself, since it's being created in the line that has the lambda function).
Here's the code:
from Tkinter import *
import tkSimpleDialog
class Min():
def __init__(self, master=None):
root = master
frame = Frame(root)
frame.pack()
buttons = [None] * 2
for i in range (2):
buttons[i] = Button(frame, text="askstring",
command=(lambda s=self, var=i: s.newLabel(buttons[var])))
buttons[i].grid()
def newLabel(self, button):
label = tkSimpleDialog.askstring("New Label", "What should the label be?")
button.config(text=label)
print label
root = Tk()
m = Min(root)
root.mainloop()

Categories