How do I pass parameters to a function from a guizero object? - python

I am unable to pass any parameter to a function called by a guizero widget either with the attribute "command" at initialization or by invoking an event.
This works as expected (no parameters passed):
from guizero import App, PushButton
def go():
print (10)
app = App(title="Test")
button = PushButton(app, text = "Click Here", command = go)
app.display()
but the following prints the number 10 once, before the button is clicked and then when I click, nothing happens
from guizero import App, PushButton
def go(n):
print (n)
app = App(title="Test")
button = PushButton(app, text = "Click Here", command = go(10))
app.display()
The same result I get with this:
from guizero import App, PushButton
def go(n):
print (n)
app = App(title="Test")
button = PushButton(app, text = "Click Here")
button.when_clicked = go(10)
app.display()
What am I missing?
Thanks in advance!

from guizero import App, PushButton
def go(n):
print (n)
app = App(title="Test")
button = PushButton(app, text = "Click Here", command = lambda: go(10))
app.display()
Whenever you write go(10) anywhere, you are invoking the function go. You might think you are passing go with arguments, but you aren't because the parentheses next to go() invoke the function right then and there. If you want to pass the function go to another function, and go should also be passed with some arguments, then you need to wrap the function go and pass the wrapped function as the argument "command". Using a lambda function to wrap go(10) is one such way of doing this.
The reason this works is that the lambda function is NOT invoked right then and there. You are saying that that command() should invoke the declared anonymous lambda function eventually, and when that lambda function is called it will itself call go(10). You are declaring an anonymous lambda function, NOT invoking it. The lambda function will be invoked later on as command().

In all GUI frameworks command= (or similar) expects callback - it means function's name without () and without parameters.
If you use go(10) then it does
result = go(10)
PushButton( ...., command=result)
so it executes go(10) before you even click button - and it assign result to command=. Because go() returns None so you get PushButton( ...., command=None) and when you click button then you get nothing.
You have to create new function which you can run without ()
def go_10():
go(10)
PushButton( ...., command=go_10)
Or you can use lambda to create function directly in command=
PushButton( ...., command=lambda:go(10))

I found the solution!
Using a lambda function is a clever way to work around the callback limitation but I found a more proper way to pass arguments to a function by using the widget properties (pun not intended). As it turns out I missed a very brief reference to the property args that can be set at instantiation and is used to pass a list to the function called by command
So here is my solution:
from guizero import App, PushButton
def go(n):
print (n)
app = App(title="Test")
button = PushButton(app, text = "Click Here", args = [10], command = go)
app.display()
As expected, it prints '10' every time you click the button and not before.
Thanks for all the other answers!

Related

in TKInter Why am I getting an error - takes 1 positional argument but 2 were given

Just learning Python and TKInter and have come across this error in my code. Don't know what I'm missing and hoping someone can help. I've included the button code and the function to show you what I have.
def change_font(self):
self.label_name['font'] = "Sawasdee"
self.button1 = Button(self.myframe2, text="Change font")
self.button1.bind("<Button-1>", self.change_font)
When you bind a function to an event, tkinter will call that function with an argument which represents the event which triggered the function to be called. That is why the error says it expected one argument (self) but got two (self, event).
You need to account for that event parameter even if you don't need it. The easiest way is to make it an optional named parameter:
def change_font(self, event=None):
self.label_name["font"] = "Sawasdee"
It's usually incorrect to use bind on a button. The Button widget accepts an attribute named command which can be used to tie the button to a function. In this case, the function will not get the event parameter:
def change_font(self):
self.label_name["font"] = "Sawasdee"
self.button1 = Button(self.myframe2, text="ChangeFont", command=change_font)
The advantage to using command is that it automatically supports not just clicking with the mouse, but also interacting with the button using the keyboard.

Deciphering a lambda expression [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 months ago.
I have been following closely, a YouTube video in order to create an on-screen keyboard using tkinter.
I understand most of what is going on; create our buttons within a list and loop through those buttons, inserting rows at the best position to create a good looking keyboard.
The only problem I've been having with this task, is when I click a button, the text of the button gets inserted into an Entry box via tkinter.
The way in which we do this, is to assign a command to the button, and upon pressing it, calls an anonymous function, assigning the button that we pressed to the 'x' parameter. We then pass this parameter to another method and insert the value into the Entry widget.
I'm trying to understand, why can't we just pass in the button itself, but instead we have to assign the button to a parameter...
self.textbox = textbox
row=0
col=0
buttons = [
'1','2','3','4','5','6','7','8','9','0',
'q','w','e','r','t','y','u','i','o','p',
'a','s','d','f','g','h','j','k','l',
'z','x','c','v','b','n','m','end']
for button in buttons:
# why not the following command instead?
# command = lambda: self.command(button)
command = lambda x=button: self.callback(x)
if button != 'end':
tk.Button(self, text=button, width=5,
command=command).grid(row=row, column=col)
col+=1
if col > 9:
col=0
row+=1
def command(self, button):
x = button
self.callback(x)
def callback(self, value):
self.textbox.insert(tk.END, value)
With the above code, I can successfully insert the desired value into my entry widget. However, if we use the code I have commented out, it will instead insert 'end' to the entry widget.
I have tried to replicate the lambda function into a separate method, but I am still inserting an 'end' into my entry widget.
# using the commented code above and passing in button as the parameter
def command(self, button):
x = button
self.callback(x)
def callback(self, value):
self.textbox.insert(tk.END, value)
I thought If I was able to replicate the function into a non-anon function, then it would work, but clearly it is not.
What exactly is my lambda function doing it and is there a way to replicate it by using a non-anonymous function (ie. method)?
The problem with
for button in buttons:
command = lambda: self.command(button)
is that command is equal to lambda: self.command(button), which is passed to the Button constructor.
So the command of the Button instance is equal to lambda: self.command(button).
You would think that self.command(button) is evaluated, and replaced by the result of the following:
def command(self, button):
x = button
self.callback(x)
However, there is no reason to do so: since self.command(button) is inside of the lambda function, it will evaluated when the lambda will be called.
As a consequence, when executing a so created lambda function, button will be evaluated to the last value it was assigned, not to the value it had when the lambda was created.
Therefore, after that loop, all of those lamda functions will have the same behaviour, and the button inside of their body will point toward the same Button instance.
If you want to have a method instead of a lambda, you can wrap the command method inside of a command builder:
def build_command(self, button):
def command():
self.callback(button)
return command
Then just call that method to get the expected command:
command = self.build_command(button)
The reason why this works, is that button is then a variable local to build_command, and the inner command "inherits" this variable into its own local variable dictionary (given by locals()).
This local dictionary will stay unaffected by outer changes.
Here is a sample code that shows what Right leg is explaining. Python lazily evaluates number:
functions1 = []
functions2 = []
for number in range(0,5):
functions1.append(lambda x=number: print(x))
functions2.append(lambda : print(number))
for function in functions1:
function()
for function in functions2:
function()
Because of that the functions2 only prints 4. While lambda x=number evaluates number immediately, and set its value to x.

Python: button command isn't running?

For an A-level computing project, I am making a car data monitoring system. I have a button that opens the filedialog.askopenfilename method. When I pass this through a method like below, it doesn't work. However when I pass it straight into the button, it works fine. Any ideas as to why?
Doesn't work:
def get_data_file():
filedialog.askopenfilename
return
OpenfileButton=Button(master,text="Select File",width=20,command=get_data_file).grid(row=3, column=2)
works:
OpenfileButton=Button(master,text="Select File",width=20,command=filedialog.askopenfilename).grid(row=3, column=2)
You need to actually call the function
def get_data_file():
filedialog.askopenfilename()
When you pass the function to the button you should not call it but simply pass it to be called when the button is clicked, but as you have now wrapped it in another function it must be called by you.
The return is redundant and can be left out if you wish. All python functions return None by default.

on_press event in kivy button not working as expected

Below is small snippet from my code .
issue:- get function is call when a screen "X" is loaded and some params are passed. In get i am create buttons with some names and assigning on_press event. But issue is on_press event is called automatically without pressing button and next screen comes into picture.
def get(self, service):
""" some code"""
but = Button(size_hint=(1, None))
but.text = str(i['name'][0][:10])
but.bind(on_press = self.change_screen(dict))
print "adding widget to home " + str(but)
self.home_box.add_widget(but)
def change_screen(self, dict):
self.screen_manager.current = 'Per_settings'
Any idea whats happening ?
but.bind(on_press = self.change_screen(dict))
You're calling self.change_screen(dict) - this is normal python syntax for a function call, the bind method doesn't even know about it and is only passed the result of the call.
You must pass the function itself. You can use functools.partial to automatically include the argument, though note that bind also passes extra arguments.
from functools import partial
but.bind(on_press=partial(self.change_screen, dict))

Define pressed button of drop-down list in Tkinter Python

I created a drop-down list using Menubutton from Python Tkinter, but i can't detect which button was pressed ('button-1', 'button-2' or 'button-3')
from Tkinter import *
widget = Frame()
widget.pack()
btnMenu = Menubutton(widget, text='Select action')
contentMenu = Menu(btnMenu)
btnMenu.config(menu=contentMenu)
btnMenu.pack()
btnList = ['button-1', 'button-2', 'button-3']
for btn in btnList:
contentMenu.add_command(label=btn, command=???)
mainloop()
What should i use for "command=" in the string
contentMenu.add_command(label=btn, command=???)
in order to define particular button? Thank you!
What you're looking for is lambda. You can use lambda in your command call like such:
contentMenu.add_command(label=btn, command = lambda btn=btn: buttonClicked(btn))
Then make a method called buttonClicked which would take one argument which would reflect which button has been pressed. Here's a minimal example of what that would look like:
def buttonClicked(btn):
print btn
Ideally though if each button has an entirely different set of execution instructions then they should each get their own method and perhaps you change the list to a tuple of (name, method). This is usually the case for why you would use a menubutton instead of an optionmenu. If you're simply calling the same method for all of them then you might want to consider switching to an optionmenu instead.

Categories