How do access the content of tk entry fields generated in a loop?

Viewed 72

Still trying to teach myself GUI development using tkinter, I decided to try to code a simple substitution cipher app as a learning exercise.

At the core of this is the alphabet where each letter in the plaintext is mapped to a fixed letter in the ciphertext alphabet.

I would like to generate the (26 * 2) widgets (one label and one entry box) in a loop.

Whilst I can do this (see example code), I cannot work out how to access the data typed in to the entry field. I have looked at examples where buttons are generated in a loop, and the "command" is linked using a lambda function - but I can't see how to do this with entry fields.

Of course I could do:

entryA = tk.Entry(self.input_frame, width=1).grid(row=1, column=0)
entryB = tk.Entry(self.input_frame, width=1).grid(row=1, column=1)
....
entryZ = tk.Entry(self.input_frame, width=1).grid(row=1, column=25)

but that seems cumbersome and inelegant.

What I would like to achieve, I guess, is some sort of event which, when the user types a letter into a particular box, the alphabet dictionary gets updated - but how do I know which box they have typed in? I have played around with the "name" field, but got nowhere so far...

Thanks in advance.

import tkinter as tk

class App(tk.Tk):
    def __init__(self, title="Sample App", *args, **kwargs):
        super().__init__()

        self.title(title)
        self.alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
        # (This will be a dictionary in the real implementation)

        # Create a frame:
        self.input_frame = tk.LabelFrame(self, text="The Alphabet")
        self.input_frame.grid(row=0, column=0, padx=5, pady=5, sticky=tk.NSEW)
        self.input_frame.columnconfigure(0, weight=1)
        self.input_frame.rowconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

        self.draw_alphabet()


    def draw_alphabet(self):
        for i in range(26): # Display each letter and an entry box to enter letter
            tk.Label(self.input_frame,
                    text=self.alphabet[i]
                    ).grid(row=0, column=i)
            tk.Entry(self.input_frame,
                 width = 1,
                 name = f"letter{i}").grid(row=1, column=i)
    
if __name__ == "__main__":
    App().mainloop()
2 Answers

I would like to generate the (26 * 2) widgets (one label and one entry box) in a loop. Whilst I can do this (see example code), I cannot work out how to access the data typed in to the entry field.

Tkinter objects are no different than any other python objects. You can store them in a list and then iterate over the list to access the objects.

def draw_alphabet():
    self.entries = []

    for i in range(26):
        ...
        entry = tk.Entry(self, input_frame, width=1, name=f"letter{i}")
        entry.grid(row=1, column=i)
        self.entries.append(entry)

def print_values():
    for entry in self.entries:
        print(entry.get())

how do I know which box they have typed in?

You can call focus_get() on any widget to get the widget that has the keyboard focus.

Or, if you have set up a binding to call a function when the user types something, you could pass in the index into the function. The correct solution depends on how you are calling that function.

With enormous thanks to the ever helpful @Bryan Oakley, I now have this working - see below (obviously not the whole implementation, but the bit I needed help with).

My follow-up question relates to the "handleOut" method which deals with updating the dictionary once the user has typed a letter into the field.

In particular, obtaining the letter in question by means of letter =str(event.widget); letter = letter[-1] feels like a bit of a kludge.

If there is a neater way to do this (or anything else in my not-very-good code) please do let me know. This is a learning exercise for me.

With thanks:

import tkinter as tk

class App(tk.Tk):
    def __init__(self, title="Sample App", *args, **kwargs):
        super().__init__()

        self.title(title)
        self.alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
        self.alphabet_dict = {'A':"*", 'B':"*", 'C':"*", 'D':"*", 'E':"*", 'F':"*", 'G':"*", 'H':"*", 'I':"*", 'J':"*", 'K':"*", 'L':"*", 'M':"*", 'N':"*", 'O':"*", 'P':"*", 'Q':"*", 'R':"*", 'S':"*", 'T':"*", 'U':"*", 'V':"*", 'W':"*", 'X':"*", 'Y':"*", 'Z':"*"}

        # Create a frame:
        self.input_frame = tk.LabelFrame(self, text="The Alphabet")
        self.input_frame.grid(row=0, column=0, padx=5, pady=5, sticky=tk.NSEW)
        self.input_frame.columnconfigure(0, weight=1)
        self.input_frame.rowconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

        self.draw_alphabet()

    def draw_alphabet(self):
        self.entries = []
        for i in range(26): # Display each letter and an entry box to enter letter
            tk.Label(self.input_frame,
                    text=self.alphabet[i]
                    ).grid(row=0, column=i)
            entry = tk.Entry(self.input_frame, width=1, name=f"letter{self.alphabet[i]}")
            entry.insert(0,self.alphabet_dict[self.alphabet[i]])
            entry.bind("<FocusIn>", self.handleIN)
            entry.bind("<FocusOut>", self.handleOut)
            entry.grid(row=1, column=i)
            self.entries.append(entry)

    def print_values(self):
        for entry in self.entries:
            print(entry, entry.get())

    def handleIN(self,event):
        event.widget.delete(0, tk.END)

    def handleOut(self, event):
        typed = event.widget.get()
        if typed.isalpha() and len(typed)==1:
            typed = typed.upper()
        else:
            typed = "*"
        event.widget.delete(0, tk.END)
        event.widget.insert(0, typed)
        letter = str(event.widget)
        letter = letter[-1]
        print(f"Letter {letter} will now be represented by {typed} in the alphabet.")
        self.alphabet_dict[letter] = typed
        #print("focus =", event.widget.focus_get())

if __name__ == "__main__":
     App().mainloop()
Related