我使用tkinter条目小部件创建了一个由行和列组成的小部件。我希望在每一行旁边放一个“ - ”按钮,它只会删除该行。
我的对象名为DisplayTable
,它是tk.Frame
的一个实例,它由一系列条目组成,每行包含一个按钮,其上有一个-
,应该允许轻松删除该行。
我会尝试跳过代码中更详细的部分,以便我能够达到目的。 DisplayTable
对象在self.rows
中创建行列表__init__
。随着行的添加,此列表将增长。例如,具有三行和两列的表格如下所示:
[
[<tkinter.Entry object>, <tkinter.Entry object>, <tkinter.Button object>],
[<tkinter.Entry object>, <tkinter.Entry object>, <tkinter.Button object>],
[<tkinter.Entry object>, <tkinter.Entry object>, <tkinter.Button object>]
]
在DisplayTable.add_row()
中,我有这段代码片段(删除了很多额外的代码以达到目的):
def add_row(self):
row = []
for i in range(self.columns):
cell = tk.Entry(self)
cell.grid(row=offset, column=i)
row.append(cell)
row_to_delete = len(self.rows)
btn = tk.Button(self, image=self.minus, command=lambda: self.delete_row(row_to_delete))
btn.grid(row=offset, column=self.columns)
row.append(btn)
self.rows.append(row)
上面的代码片段非常适合在数组末尾添加行。它适用于行删除一次。
def delete_row(self, row_number):
for widget in self.rows[row_number]:
widget.destroy()
self.rows.pop(row_number)
# re-assign the delete row buttons
for i, row in enumerate(self.rows):
button = row[-1]
button.configure(command=lambda: self.delete_row(i))
我对delete_row的意图是重新分配删除按钮,因为self.rows
的行索引刚刚为每行更改。执行此函数后,我得到的行为是始终删除最后row_number
。例如,如果有10行,则始终删除索引为9
的行。
使用更直接的引用替换删除按钮的重新分配似乎不会改变行为:
def delete_row(self, row_number):
for widget in self.rows[row_number]:
widget.destroy()
self.rows.pop(row_number)
# re-assign the delete row buttons
for i, row in enumerate(self.rows):
self.rows[i][-1].configure(command=lambda: self.delete_row(i))
使用print
命令,我已经验证变量i
在每一轮中肯定是不同的,并且每次引用的button
对象都是不同的按钮,因此{{ 1}}分配也应该是不同的。不知何故,似乎只有最后一次循环才会粘到所有按钮上,而不仅仅是它所针对的按钮。
答案 0 :(得分:1)
for i, row in enumerate(self.rows):
button = row[-1]
button.configure(command=lambda: self.delete_row(i))
单击该按钮时,将删除该时刻变量i
的值指定的行。由于此循环将在您可能单击按钮之前完成,i
将始终引用最后一行。这是循环中lambdas的常见问题,解决方案是捕获默认参数中所有已使用变量的值:command=lambda i=i: self.delete_row(i))
我认为更好的方法是将row
变量传递给delete_row()而不是索引 - 这样,删除行时不需要更改任何内容。您可以迭代for widget in row:
以删除要删除的小部件,并通过self.rows.remove(row)
删除行本身。
答案 1 :(得分:1)
jasonharper已经解释了问题的原因:lambda
中的变量在创建时并未“冻结”,它们是动态的。
有关详细信息,请参阅SO Python Wiki中的Why do my lambda functions or nested functions created in a loop all use the last loop value when called?。
这是一种避免这个问题的方法。我们可以在回调函数中使用默认参数。定义函数时会计算默认值,而不是在调用函数时计算默认值(函数是lambda
还是完整def
函数无关紧要)。
我们可以使用行对象本身作为delete_row
方法的参数,而不是存储行索引号,这需要在行删除后进行杂乱的重新赋值。
import tkinter as tk
class Grid(tk.Frame):
def __init__(self, root):
super().__init__(root)
self.pack()
self.rows = []
self.numcols = 3
self.numrows = 4
for _ in range(self.numrows):
self.add_row()
def add_row(self):
rownum = len(self.rows)
row = []
for i in range(self.numcols):
cell = tk.Entry(self)
cell.grid(row=rownum, column=i)
row.append(cell)
btn = tk.Button(self, text='-', command=lambda r=row: self.delete_row(r))
btn.grid(row=rownum, column=self.numcols)
row.append(btn)
self.rows.append(row)
def delete_row(self, row):
for widget in row:
widget.destroy()
self.rows.remove(row)
root = tk.Tk()
gui = Grid(root)
root.mainloop()