Search code examples
python-2.7tkinterttk

How do I keep dynamic Entry fields populated after the function that created them terminates?


I'm working on a GUI to automate part of my project's pipeline using Tkinter and ttk in Python 2.7. I have a main Tk() window that generates a Toplevel() window upon clicking "Auto-Detect", then creates a dynamic series of readonly Entry widgets on button click based on a list of molecular species defined elsewhere in the code.

My issue is that, while the Entry boxes do appear correctly based on the species detected, they do not stay populated with the species names once the function that created them terminates. I suspect this is because the widgets aren't global [or even within the scope of the Tk() window], but rather are defined only within the function. However, I can't create them within the block of code where the Tk() window is defined since the number of Entry boxes needed is unknown until the button is pressed (thus calling the function that creates them).

I've included an abstracted block of code below that shows the problem I'm having. Apologies if it is long. I tried to cut it down as much as possible. The comments I included in the code show my thoughts and guesses for what's going on. It should be ready to run in Python 2.7; my hope is that the only Python 3.x changes that are necessary are import modifications.

My question is, after I have dynamically created Entry widgets within a function that is called by the main Tk() window and populated them with text, how can I prevent them from depopulating when the function ends? Disclaimer: I'm not a programmer (or even in the field of computer science) by trade, so I will do my best to hang on to all technical details, but I may have to ask some dumb questions.

from Tkinter import *
import ttk
from  time import sleep

def update_list(manager, rows):
    species_list = ['A','B','C','D']
    del rows[1:]

    for widget in manager.children.values():
        widget.grid_forget()
    if not species_list == ['']:
        species_elem_list = []
        for i, each in enumerate(species_list):

            ## Here I attempt to create a dynamic list of StringVars to attach to the Entry fields below, based on the contents of the species list.
            species_elem_list.append(StringVar())

            ## Here I initialize the values of the elements of the Entry fields by setting the StringVar of each.
            species_elem_list[i].set(each)

            ## I tried to attach the value of the StringVar (from the species list) to the Entry below, but when the program is run, the Entry does not stay populated.
            temp_name = ttk.Entry(manager, textvariable=species_elem_list[i], state='readonly')
            temp_color = ttk.Label(manager, text='data')
            temp_row = [temp_name, temp_color]
            rows.append(temp_row)

        for row_number in range(len(rows)):
            for column_number, each in enumerate(rows[row_number]):
                each.grid(column=column_number, row=row_number)
            each.grid()

        manager.update()
        sleep(3) ## Included so that the population of the fields can be observed before they depopulate.

        ## After this point, the update_list function terminates.
        ## When that happens, the Entry fields depopulate. How can I get them to persist after the function terminates?

root = Tk()

manager = ttk.Frame(root, padding='4 5 4 4')
manager.grid(column=0, row=0, sticky=NSEW)

name_label = ttk.Label(manager, text='Name')
color_label = ttk.Label(manager, text='RGB')

rows = [[name_label, color_label]]

options = ttk.Frame(root)
options.grid(sticky=NSEW)

detect_button = ttk.Button(options, text='Auto-Detect', command=lambda: update_list(manager,rows))
done_button = ttk.Button(options, text='Done', command=root.destroy)

detect_button.grid(column=0, row=0)
done_button.grid(column=1, row=0)

root.mainloop()

Ideally, the Entry widgets will remain (and remain populated!) after the function update_list terminates. I also want to be able to interact with the contents of these widgets from outside the function.

Currently, the Entry fields populate during the course of the function update_list, then depopulate immediately once it ends. I suspect this is because the widgets and their contents are not global in scope.


Solution

  • In textvariable=species_elem_list you use local variable species_elem_list which stop exist when you exit update_list()

    You have to create species_elem_list outside update_list() and use global species_elem_list in update_list() to use global variable instead of local one when you do species_elem_list = []

    from Tkinter import *
    import ttk
    from  time import sleep
    
    species_elem_list = [] # <--- put here or below of update_list
    
    def update_list(manager, rows):
        global species_elem_list  # <-- use global variable instead of local one
    
        species_list = ['A','B','C','D']
    
        del rows[1:]
    
        for widget in manager.children.values():
            widget.grid_forget()
    
        if not species_list == ['']:
            species_elem_list = []
            for i, each in enumerate(species_list):
    
                ## Here I attempt to create a dynamic list of StringVars to attach to the Entry fields below, based on the contents of the species list.
                species_elem_list.append(StringVar())
    
                ## Here I initialize the values of the elements of the Entry fields by setting the StringVar of each.
                species_elem_list[i].set(each)
    
                ## I tried to attach the value of the StringVar (from the species list) to the Entry below, but when the program is run, the Entry does not stay populated.
                temp_name = ttk.Entry(manager, textvariable=species_elem_list[i], state='readonly')
                temp_color = ttk.Label(manager, text='data')
                temp_row = [temp_name, temp_color]
                rows.append(temp_row)
    
            for row_number in range(len(rows)):
                for column_number, each in enumerate(rows[row_number]):
                    each.grid(column=column_number, row=row_number)
                each.grid()
    
            manager.update()
            sleep(3) ## Included so that the population of the fields can be observed before they depopulate.
    
            ## After this point, the update_list function terminates.
            ## When that happens, the Entry fields depopulate. How can I get them to persist after the function terminates?
    
    root = Tk()
    
    manager = ttk.Frame(root, padding='4 5 4 4')
    manager.grid(column=0, row=0, sticky=NSEW)
    
    name_label = ttk.Label(manager, text='Name')
    color_label = ttk.Label(manager, text='RGB')
    
    rows = [[name_label, color_label]]
    
    options = ttk.Frame(root)
    options.grid(sticky=NSEW)
    
    detect_button = ttk.Button(options, text='Auto-Detect', command=lambda: update_list(manager,rows))
    done_button = ttk.Button(options, text='Done', command=root.destroy)
    
    detect_button.grid(column=0, row=0)
    done_button.grid(column=1, row=0)
    
    root.mainloop()