Reputation: 167
I am new to Python's tkinter
module so I would be grateful for any help, even an explanation as to why this is simply not possible (if that is the case).
I have a list of 4-tuples of the form (foo, bar, x, y)
; something like this:
TUPLE_LIST = [('Hello', 'World', 0, 0),
('Hovercraft', 'Eels', 50, 100),
etc.]
and a for
loop later which ideally instantiates the variable foo
as a button with the text bar
, with each button having a function to add its respective bar
to an already defined Entry
widget, then places it at the co-ordinates x, y
:
for foo, bar, x, y in TUPLE_LIST:
exec("{0} = Button(self, text='{1}', command=lambda: self.update_text('{1}'))".format(foo, bar))
eval(name).place(x=x, y=y)
The buttons place perfectly, but if I go to click on one of the them, I get the following error:
Traceback (most recent call last):
File "...\lib\tkinter\__init__.py", line 1550, in __call__
return self.func(*args)
File "<string>", line 1, in <lambda>
NameError: name 'self' is not defined
I'm guessing this has something to do with the fact that the command is defined with a lambda, and thus isn't a defined 'function', per se, and yet I have seen other people define their buttons' commands with lambdas. So is it something to do with using exec
as well? Also, here's the code for update_text
(if it matters):
def update_text(self, new_char):
old_str = self.text_box_str.get()
# Above is a StringVar() defined in __init__ and used as textvariable in Entry widget creation.
self.text_box_str.set(old_str + new_char)
Any help would be much appreciated. Thanks in advance.
Upvotes: 0
Views: 920
Reputation: 385980
This is a very bad idea. There's simply no reason to dry to dynamically create variable names. It makes your code more complex, harder to maintain, and harder to read.
If you want to reference widgets by name, use a dictionary:
buttons = {}
for foo, bar, x, y in TUPLE_LIST:
buttons[foo] = Button(self, text=bar, ...)
buttons[foo].place(...)
As for the problem with self
, there's not enough code to say what the problem is. If you're using classes, the code looks ok. If you aren't, you shouldn't be using self
.
To create a function that updates a widget, you can simply pass that widget or that widget's name to the function. It's not clear what widget you're wanting to update. It appears to be an entry widget. I can't tell if you have one entry widget that all buttons must update, or one entry per button. I'll assume the former.
In the following example I'll show how to pass to a function the variable to be changed along with the text to add. This solution doesn't use the textvariable
attribute, though you can if you want. Just pass it rather than the widget.
buttons[foo] = Button(..., command=lambda widget=self.text_box, value=bar: self.update_text(widget, value))
...
def update_text(self, widget, value):
old_value = widget.get()
new_value = old_value + value
widget.delete(0, "end")
widget.insert(0, new_value)
Of course, if all you're doing is appending the string, you can replace those four lines with widget.insert("end", value)
Upvotes: 2