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.
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.
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.
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))
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.