This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
How to pass arguments to a Button command in Tkinter?
(18 answers)
Closed 6 months ago.
I currently have a Tkinter that displays multiple names as label.
The right side of every labels has a button named "Foo" and when clicked,
it will invoke a function that needs the name of the label on the left of the button that was clicked.
This is how I created the button and the label:
from Tkinter import *
class display():
def __init__(self):
self.namelist = ["Mike","Rachael","Mark","Miguel","Peter","Lyn"]
def showlist(self):
self.controlframe = Frame()
self.controlframe.pack()
self.frame = Frame(self.controlframe,height=1)
self.frame.pack()
row = 0
for x in self.namelist:
label = Label(self.frame,text="%s "%x,width=17,anchor="w") #limit the name to 17 characters
fooButton = Button(self.frame,text="Foo")
label.grid(row=row, column=0, sticky="W")
fooButton.grid(row=row, column=1)
row = row + 1
mainloop()
D = display()
D.showlist()
How do I do something like if I click the Foo button next to Mark then the button will return the name of the label, Mark. The same goes for other Foo buttons next to other labels.
Thanks!
Here's how you can do it:
define a command for every button in the loop
pass a current loop index into the callback (see How to pass arguments to a Button command in Tkinter?)
in the button click callback, get the item from the namelist by the index passed into the callback
Here's the code:
from Tkinter import *
class display():
def __init__(self, controlframe):
self.controlframe = controlframe
self.namelist = ["Mike", "Rachael", "Mark", "Miguel", "Peter", "Lyn"]
def callback(self, index):
print self.namelist[index]
def showlist(self):
self.frame = Frame(self.controlframe, height=1)
self.frame.pack()
row = 0
for index, x in enumerate(self.namelist):
label = Label(self.frame, text="%s " % x, width=17, anchor="w") #limit the name to 17 characters
fooButton = Button(self.frame, text="Foo",
command=lambda index=index: self.callback(index))
label.grid(row=row, column=0, sticky="W")
fooButton.grid(row=row, column=1)
row = row + 1
tk = Tk()
D = display(tk)
D.showlist()
tk.mainloop()
Note how the index is passed to the lambda, this is so called "lambda closure scoping" problem, see Python lambda closure scoping.
Hope that helps.
Related
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 3 years ago.
I'm using a dictionary to store my buttons as you can see in the main method. The 'self.tables' is a list with the names of tables. Therefore 'i' in the for loop is the name of a table, which is shown on the button as text. Each button should have a different command as you can see below, self.edit(i). But when you press a specific button instead of running self.edit(i), what is always run is the most recent iteration of i instead of the i that was used when the specific button was created.
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
self.title = Label(self, text="", font=("arial", 20))
self.title.grid(row=0, column=0)
self.table = ""
self.widgetdata = {}
def main(self):
for x in self.widgetdata:
self.widgetdata[x].grid_forget()
self.tables = GuiSetup.tbls
self.title["text"] = "Select table to edit:"
for j,i in enumerate(self.tables):
self.widgetdata[i] = Button(self, text=i, command=lambda: self.edit(i)) # When any of these buttons are pressed the most recent button command is run
self.widgetdata[i].grid(row=j+1, column=0)
self.controller.show_frame("Edit Menu")
# Sets up the Editmenu gui for editing the specified table
def edit(self, table, id="new"):
print(table)
self.table = table
self.id = id
The code above is a section of a class and its methods.
I have no idea why this is happening because the text on all of the buttons is unique but the command of each button is not what it was set to. Any suggestions would be appreciated. Thanks
Replace
self.widgetdata[i] = Button(self, text=i, command=lambda: self.edit(i))
by
self.widgetdata[i] = Button(self, text=i, command=lambda i=i: self.edit(i))
Explanation: the body of the lambda function is executed when clicking the Button, so it uses the current value of i at execution time (i.e. the index of the last created Button), not at definition time. Creating an aliased argument, forces the creation of a local variable for each loop step, each having a different value, so refering to a different Button.
I am trying to use lambda to create callbacks for tkinter buttons.
There are multiple buttons and each callback needs to pass an object inside it. Following code is what I am doing and is running fine
var0 = tk.StringVar()
label = tk.Label(top, bg = "White",height = 2, width = 12,textvariable=var0, justify="right")
def b0Callback(var):
var.set(var.get()+"0")
return
# creating a label which will print value of the any of the 0-9 button pressed
# creating a button 0
b0 = tk.Button(numFrame0, height = 1, width = 4, bg = "grey", text =
"0",command = lambda: b0Callback(var0))
#there are more buttons like that
var0 is used to update a label. Above code is working fine but I have to create callback for 0 to 9 and I have to just repeat above definition. So I tried using following example from this tutorial
def myfunc(n):
return lambda a : a * n
mydoubler = myfunc(2)
mytripler = myfunc(3)
print(mydoubler(11))
print(mytripler(11))
Using it I did following
def Callback(n):
return lambda var.set(var.get()+n)
b0Callback = Callback("0")
This shows error invalid index in the return line at var.set
Is there any way to pass var0 in this case to avoid this error?
Maybe its only me, but I don't see a reason for using lambda if you just want to add a number to the label text.
Lets make a function for it that gets your StringVar() as a variable and adds some number to it:
def button_callback(str_var, number):
str_var.set(str_var.get() + str(number))
To run this command we simply put it in the code as a lambda function, otherwise it will run upon initialization (because we are providing a function instead of a reference).
So to run it within a button we declare it like this:
my_button = Button(root, text='Some text here', command=lambda: button_callback(my_string_var, 5))
The '5' could be potentially changed to any other number.
I have now solved the problem, here is the final code:
I have also changed the number of buttons to 300 and added code to arrange them all in a nice grid, just for fun. (You can change this to however many you want by changing for number in range(1, whatever).
import tkinter as tk
class Window(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.var0 = tk.StringVar()
self.var0.set('0')
# creating buttons and adding them to dictionary
self.buttons = {}
for number in range(1, 301):
self.buttons.update({'button' + str(number): tk.Button(self, height=1, width=4, bg="grey", text=number)})
label = tk.Label(self, textvariable=self.var0, font='none 50')
label.grid(column=0, row=0)
for button in self.buttons:
self.buttons[button].bind('<Button-1>', lambda event, num=button[6:]: self.Callback(event, num))
self.buttons[button].grid(column=(int(button[6:]) % 10), row=(int(button[6:]) / 10) + 1)
def Callback(self, event, num):
self.var0.set(num)
self.update()
I have an entry, a listbox(dropdown) and another listbox. Whenever more than 3 characters are typed inside the entry. A completion list is looked up and inserted to the dropdown and the dropdown is shown. If an item is selected from the dropdown. It's value should fill the entry and the entry should get the focus again and the cursor should go to the end of the entry. And then, when Enter key is pressed the value of the entry should be inserted to the other listbox.
I've developed a code for that with much help from this utility and the code works perfectly fine. Except, I realized that whenever I select an option from the dropdown the corresponding method is called twice(I get two prints in the console from the same thing). But if I select the first option of the dropdown, it's called once(which is what should have actually happened in the other case) but the focus does not go to the entry (which is a problem).
Here is my code:
from tkinter import *
class Autocomplete(Frame, object):
def __init__(self, width, height, entries, *args, **kwargs):
super(Autocomplete, self).__init__(*args, **kwargs)
self._entries = entries
self.listbox_height = height
self.entry_width = width
self.text = StringVar()
self.entry = Entry(
self,
textvariable=self.text,
width=self.entry_width
)
self.frame = Frame(self)
self.listbox = Listbox(
self.frame,
height=self.listbox_height,
width=self.entry_width
)
self.dropdown = Listbox(
self.frame,
height=self.listbox_height,
width=self.entry_width,
background="#cfeff9"
)
def build(self):
self.text.trace("w", lambda name, index, mode, text=self.text: self._update_autocomplete())
self.entry.bind("<Return>", lambda event,: self._add_course())
self.entry.focus_set()
self.entry.pack()
self.frame.pack()
self.listbox.grid(column=0, row=0, sticky=N)
self.dropdown.bind("<<ListboxSelect>>", self._select_entry)
self.dropdown.grid(column=0, row=0, sticky=N)
self.dropdown.grid_forget()
return self
def _update_autocomplete(self):
self.dropdown["height"] = self.listbox_height
self.dropdown.delete(0, END)
text = self.text.get()
if len(text) < 3:
self.dropdown.grid_forget()
return
else:
for entry in self._entries:
if text.lower() in entry.strip().lower():
self.dropdown.insert(END, entry)
listbox_size = self.dropdown.size()
if not listbox_size:
self.dropdown.insert(END, "No results found for '{}'")
self.dropdown["height"] = 1
else:
if listbox_size <= self.dropdown["height"]:
self.dropdown["height"] = listbox_size
self.dropdown.grid(column=0, row=0, sticky=N)
def _select_entry(self, event):
widget = event.widget
value = widget.get(int(widget.curselection()[0]))
print(value)
self.text.set(value)
self.entry.focus_set()
self.entry.icursor(END)
def _add_course(self):
self.listbox.insert(END, self.text.get())
So what am I missing here?
By the way any general improvement to the code will also be much appreciated.
And here is how I call it:
from tkinter import *
from autocomplete import Autocomplete
from main import *
courses = load_courses_from_file("courses.txt")
root = Tk()
autocomplete_frame = Autocomplete(
60,
10,
list(set(course.name + ", " + course.instructor for course in courses))
).build().pack()
mainloop()
The selection of the listbox changes when you click on the item -- this is the default behavior of the listbox. This causes the entry widget value to change, which triggers a call to _update_autocomplete. That function deletes everything in the listbox, causing the selection to change again.
This question already has answers here:
Why is my Button's command executed immediately when I create the Button, and not when I click it? [duplicate]
(5 answers)
What is the purpose of the `self` parameter? Why is it needed?
(26 answers)
Closed 6 months ago.
I'm trying to create a grid of buttons that change colour with Tkinter.
from tkinter import *
class App():
def __init__(self, root):
self.root = root
buttonQ = Button(self.root, text = "Quit", command = endProgam())
buttonS = Button(self.root, text = "Save", command = saveToFile())
def Function(self):
self.grid = []
for i in range(5):
row = []
for j in range(5):
row.append(Button(self.root,width=6,height=3,command=lambda i=i, j=j: self.Click1(i, j),background='gray'))
row[-1].grid(row=i,column=j)
self.grid.append(row)
def Click1(self, i, j):
orig_color = self.grid[i][j].cget('bg')
#print(orig_color)
if orig_color=="red":
self.grid[i][j]["bg"]="gray"
else:
self.grid[i][j]["bg"]="red"
#self.grid[i][j]["bg"]="red"
#self.grid[i][j].configure(background="blue")
def endProgam(self):
# top.quit()
top.destroy()
def saveToFile(self):
# save matrix to file
top.destroy()
root = Tk()
app = App(root)
app.Function()
root.mainloop()
My problem is that I cannot add 2 buttons below the grid, one to quit and one to save into a file values based on the button colours (0-grey and 1-red as a matrix) and then quit.
File "--", line 37, in <module>
app = App(root)
File "--", line 6, in __init__
buttonQ = Button(self.root, text = "Quit", command = endProgam())
TypeError: endProgam() missing 1 required positional argument: 'self'
It's my first time coding in Python with Tkinter, so please be gentle :)
First, your indentation levels for your Class are off. The methods need to be indented another level or you'll get a TypeError for each method.
Second, for buttonQ and buttonS, make sure you are referencing the instance of the class, i.e.:
buttonQ = Button(self.root, text = "Quit", command = endProgam)
buttonS = Button(self.root, text = "Save", command = saveToFile)
should be:
buttonQ = Button(self.root, text = "Quit", command = self.endProgam)
buttonS = Button(self.root, text = "Save", command = self.saveToFile)
(Note the use of self)
As far as actually placing the buttons, I would recommend creating an additional frame to manage the layouts separately. You can create and place these just like widgets and it helps make managing the layouts much simpler.
For example:
class App():
def __init__(self, root):
self.root = root
self.TopFrame = Frame(root) # Create a top frame to place the original grid
self.BottomFrame = Frame(root) # Create a frame for the additional buttons
self.TopFrame.grid(row=0) # Place the Frame itself
self.BottomFrame.grid(row=6) # Place the new Frame directly below the first
# Changed to an instance variable to reference in Function method
buttonQ = Button(self.BottomFrame, text="Quit", command=self.endProgam)
buttonS = Button(self.BottomFrame, text="Save", command=self.saveToFile)
buttonS.grid(row=0, column=0, padx=10)
buttonQ.grid(row=0, column=1, padx=10)
def Function(self):
self.grid = []
for i in range(5):
row = []
for j in range(5):
row.append(Button(self.TopFrame,width=6,height=3,command=lambda i=i, j=j: self.Click1(i, j),background='gray'))
row[-1].grid(row=i,column=j)
self.grid.append(row)
Notice the new TopFrame and BottomFrame. The grid buttons are now sitting on the TopFrame while the BottomFrame contains the two new button widgets.
You'll find that placing separate layout objects in its own frame will make managing more complex layouts much simpler.
I'm creating a GUI where I need to create certain number of entries and buttons in Tkinter. I'd like to create all these in a for loop. As actions, when I press any of the button, it should transfer the value of the Entry to the callback of the button next to it.
This is what I've done so far but it's not working yet.
n=0
self.button = []
self.entFreq = []
for calVal in calibration:
lbl = Label(self.calFrame)
lbl.configure(text = "Set amplitud to " + calVal)
lbl.configure(background=self.bg_App, fg = "white")
lbl.grid(row=n, column=0)
self.entFreq.append(Entry(self.calFrame, width=10))
self.entFreq[n].grid(row=n, column=1, padx = 10)
#Construction Button send frequency
self.button.append(Button(self.calFrame, text="Cal", borderwidth=0, relief="groove", command = lambda n=self.entFreq[n].get(): self.get_val(n)))
self.button[n].configure(bg="#FFF3E0")
self.button[n].grid(row=n, column=2)
n+=1
def get_val(self, var):
print "Got this:", str(var)
I'm just getting blank in the var function. How to link those two?
You're putting too much code into your lambdas. You only need to pass in n, and get_val can do the rest of the work:
self.button.append(Button(..., command=lambda n=n: self.get_val(n)))
...
def get_val(self, n):
value = self.entFreq[n].get()
print "Got this:", value
You might want to consider defining a class for this set of label, entry and button, since they are designed to work together and you're making several sets.
You could, for example,pass in a label and a function to call when the user clicks the button. For example:
class LabelEntry(object):
def __init__(self, parent, text, command):
self.command = command
self.label = Label(parent, text=text)
self.entry = Entry(parent)
self.button = Button(parent, text="Cal", command=self.call_command)
def call_command(self):
value = self.entry.get()
self.command(value)
You would use it something like this:
def some_function(self, value):
print "the value is", value
...
for calVal in calibration:
le = LabelEntry(frame,
text="Set aplitud to " + calVal,
command=self.some_function)
le.label.grid(...)
le.entry.grid(...)
le.button.grid(...)