1

In my tkinter project I've created an Entry, a Label and two Buttons for each of the line in my line list using a for loop. I've also saved them in a list when created.

Right now my problem is how can I access them? For example: if the edit button of the 12th line is clicked, then I want to be able to get the entry values of the 12th entry or if my user click the delete button of the 3rd line in the list, then, only the entry, the label and the two button of the selected line should be deleted.

This is my code:

self.line_loop = []

for line in self.line_list:
    self.row_count += 1

    self.n_entry = tk.Entry(self.list_frame, justify="center", width=4)
    self.n_entry.grid(row=self.row_count, column=0, pady=10, padx=10, ipady=3)

    self.text_label = tk.Label(self.list_frame, text=line, anchor="w", justify="left", 
                               wraplengt=701)
    self.text_label.grid(row=self.row_count, column=1, pady=10, padx=10, sticky="w")

    self.edit_button = tk.Button(self.list_frame, text="Edit", command=self.edit)
    self.edit_button.grid(row=self.row_count, column=2, pady=10, padx=10)

    self.delete_button = tk.Button(self.list_frame, text="Delete", command=self.edit)
    self.delete_button.grid(row=self.row_count, column=3, pady=10, padx=10)

    self.line_loop.append(self.n_entry)
    self.line_loop.append(self.text_label)
    self.line_loop.append(self.edit_button)
    self.line_loop.append(self.delete_button)

EDIT: This are examples of the functions. The code should work only on the clicked button and linked widgets

def delete(self):
    self.n_entry.destroy() 
    self.text_label.destroy() 
    self.edit_button.destroy() 
    self.delete_button.destroy() 
def edit(self): 
    for entry in self.line_loop:
       print(entry.get())

How can I do so?

Diana Mele
  • 135
  • 8
  • Does this answer your question? [How do you find a unique and constant ID of a widget?](https://stackoverflow.com/questions/30004505/how-do-you-find-a-unique-and-constant-id-of-a-widget) – Faraaz Kurawle Apr 18 '22 at 11:43

3 Answers3

2

You don't give any details about your edit or delete functions, but I'm guessing that if you get the corresponding button to provide its index to that function, the function could index into self.line_list:

        self.line_loop = []

        for index, line in enumerate(self.line_list):
            self.row_count += 1

            n_entry = tk.Entry(self.list_frame, justify="center", width=4)
            n_entry.grid(row=self.row_count, column=0, pady=10, padx=10, ipady=3)

            text_label = tk.Label(self.list_frame, text=line, anchor="w", justify="left", wraplengt=701)
            text_label.grid(row=self.row_count, column=1, pady=10, padx=10, sticky="w")

            edit_button = tk.Button(self.list_frame, text="Edit", command=lambda x=index: self.edit(x))
            edit_button.grid(row=self.row_count, column=2, pady=10, padx=10)

            delete_button = tk.Button(self.list_frame, text="Delete", command=lambda x=index: self.delete(x))
            delete_button.grid(row=self.row_count, column=3, pady=10, padx=10)

            self.line_loop.append((n_entry, text_label, edit_button, delete_button))
    def edit(self, index):
        n_entry, text_label, edit_button, delete_button = self.line_loop[index]
        line = self.line_list[index]
        ...

I've used enumerate() to provide indexes into self.line_list and used the command=lambda idiom to provide an index parameter to the example edit() method.

quamrana
  • 37,849
  • 12
  • 53
  • 71
  • Thank you! I've edited my question. When trying using your code I get this error `"TypeError: 'list' object is not callable"` in this line `n_entry, text_label, edit_button, delete_button = self.line_loop(index)` – Diana Mele Apr 17 '22 at 17:17
  • 1
    Ok, sorry, my bad. I'll correct it. I meant: `self.line_loop[index]` – quamrana Apr 17 '22 at 17:19
2

Your question is a little sketchy on details, but here's how to do what you want. It takes advantage of the fact that you're using the tkinter grid geometry manager, which keeps track of what row and column location of all the widgets under its control. This means you can get the information you need from it instead of keeping track of it yourself. i.e. There's no need for something like the self.line_loop list and many of the other instance attributes you're creating manually (although generally speaking that's also a viable approach if done properly).

When creating widgets in a loop it's important to make sure that the value of any variables being passed to callback functions aren't in variables that change each iteration of the loop because it doesn't work (see tkinter creating buttons in for loop passing command arguments).

A common way to avoid the problem is to use a lambda function with arguments that have been given default values as a way to "capture" the loop variable's current value. In this case it also required setting the Button widget's options in two steps in order to pass the button itself to the associated command= callback function.

Here's a runnable example based on the code in your question of doing that:

import tkinter as tk

class ListEditor:
    def __init__(self, list_frame, lines):
        self.list_frame = list_frame
        self.line_list = lines.splitlines()
        self.create_widgets()

    def create_widgets(self):
        for row, line in enumerate(self.line_list):
            entry = tk.Entry(self.list_frame, justify="center", width=4)
            entry.grid(row=row, column=0, pady=10, padx=10, ipady=3)

            text_label = tk.Label(self.list_frame, text=line, anchor="w",
                                  justify="left", wraplength=701)
            text_label.grid(row=row, column=1, pady=10, padx=10, sticky="w")

            edit_button = tk.Button(self.list_frame, text="Edit")
            edit_button.config(command=lambda widget=edit_button: self.edit(widget))
            edit_button.grid(row=row, column=2, pady=10, padx=10)

            delete_button = tk.Button(self.list_frame, text="Delete")
            delete_button.config(command=lambda widget=delete_button: self.delete(widget))
            delete_button.grid(row=row, column=3, pady=10, padx=10)

    def _get_widgets_on_same_row(self, widget):
        """Return list of all widgets on same row as given one in column order."""
        row = widget.grid_info()['row']
        return sorted(widget.master.grid_slaves(row=row),
                      key=lambda w: w.grid_info()['column'])

    def delete(self, widget):
        row = widget.grid_info()['row']
        widgets = self._get_widgets_on_same_row(widget)
        for widget in widgets:  # Get rid of associated widgets.
            widget.grid_remove()
        self.line_list[row] = None  # Indicate line was deleted.

    def edit(self, widget):
        row = widget.grid_info()['row']
        entry,text_label,edit_button,delete_button = self._get_widgets_on_same_row(widget)
        pass  # Whatever you want to do with the linked widgets (and instance data)...


if __name__ == '__main__':
    from textwrap import dedent

    lines = dedent('''\
        Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        Vivamus et leo id felis faucibus varius quis et risus.
        Ut nec felis ut enim tincidunt maximus.
        Sed at nunc eleifend, vestibulum dolor nec, dictum tellus.
        Aliquam et lorem tincidunt, rhoncus libero sed, molestie massa.
        Duis quis nunc maximus, semper justo placerat, posuere turpis.
    ''')

    root = tk.Tk()
    list_frame = tk.Frame(root)
    list_frame.pack()
    editor = ListEditor(list_frame, lines)
    root.mainloop()
martineau
  • 119,623
  • 25
  • 170
  • 301
2

You have to use the name to create a name for the widget by which we can be later be access it.

You can use root.nametowidget to get the reference of a widget by its name specified while creating the widget.

Edit:

By the help of index you would be able keep a trace of which buttons and entry you have to remove on click of that particular button.

from tkinter import *

root=Tk()
for i in range(4):
    Entry(name=f'entry{str(i)}').pack()
    Button(text='get', command=lambda temp=i:edit(temp),name=f'e_button{str(i)}').pack()
    Button(text='delete', command=lambda temp=i:delete(temp),name=f'd_button{str(i)}').pack()
def edit(num):
    Label(text=root.nametowidget(f'.entry{num}').get()).pack()
def delete(num):
    root.nametowidget(f'entry{num}').destroy()
    root.nametowidget(f'.e_button{num}').destroy()
    root.nametowidget(f'.d_button{num}').destroy()
    print('deleted',num)
root.mainloop()
martineau
  • 119,623
  • 25
  • 170
  • 301
Faraaz Kurawle
  • 1,085
  • 6
  • 24