Salvatore
Salvatore

Reputation: 93

Why does ttk.Entry widget not update textvariable if called inside a function

I am trying to understand a strange behavior displayed by the ttk.Entry widget. If I declare the Entry widget textvariable inside a function and call that function from main (where I create a root object and run my mainloop), for some reason the textvariable is not displayed. However if I run root.mainloop() within my function as well then the textvariable is displayed properly. Why is that? Why do I need to run mainloop() again inside my function as well when its already being run in main?

In the below code first run as is and observe Entry widget remains empty and then uncomment the root.mainloop() line within the function and then run it again - this time Entry widget textvariable will be displayed. Thank You!

from tkinter import Tk, Toplevel, StringVar, Label
from tkinter import ttk

nameVariable = 'EntryBoxTextValue'

def frameWindow(root, nameVariable):
    mainFrame = Toplevel(root)
    mainFrame.grid()
    nameLbl = Label(mainFrame, text="Name:", bg="white")
    nameLbl.grid(row=0, column=0, sticky='w', pady=(10,5))
    nameSV = StringVar()
    nameSV.set(nameVariable)
    nameEntry = ttk.Entry(mainFrame, textvariable=nameSV)
    nameEntry.grid(row=0, column=1, columnspan=4, sticky='ew', pady=(10,5))
    print(nameSV)
    # root.mainloop()

if __name__ == '__main__':
    root = Tk()
    frameWindow(root, nameVariable)
    root.mainloop()

Note that other widgets like ttk.Treeview and Text update without needing to run root.mainloop() inside the function call.

Upvotes: 0

Views: 970

Answers (1)

Delrius Euphoria
Delrius Euphoria

Reputation: 15088

It might seem a little weird at first, but I think it is the same reason as to why tkinter image does not show up when made inside a function, without keeping any reference.

So the problem might be that garbage collector sweeps nameSV once the function finishes executing because it is no longer used anywhere, but the tk widget is still using it.

The solution is simple, just like the image issue, make the variable global or keep some other reference to it.

def frameWindow(root, nameVariable):
    global nameSV
    ...
    ...

Or define it in the global scope itself:

if __name__ == '__main__':
    root = Tk()
    
    nameSV = StringVar() # Remove this line from inside the function
    frameWindow(root, nameVariable)
    
    root.mainloop()

If you wish to see the problem yourself, you can freeze the script before the function ends and force the events to be processed and see for yourself:

import time
...
...

def frameWindow(root, nameVariable):
    mainFrame = Toplevel(root)
    root.withdraw() # Hide root, to properly see the mainFrame window without root covering it

    nameLbl = Label(mainFrame, text="Name:", bg="white")
    nameLbl.grid(row=0, column=0, sticky='w', pady=(10,5))
    
    nameSV = StringVar()
    nameSV.set(nameVariable)

    nameEntry = ttk.Entry(mainFrame, textvariable=nameSV)
    nameEntry.grid(row=0, column=1, columnspan=4, sticky='ew', pady=(10,5))

    root.update() # To draw the widget to the screen
    time.sleep(5)
    root.deiconify() # Bring the window back to close the root window and end the process
...
...

For the first 5 seconds you can notice the entry filled with the correct data, but as soon that is over and root pops up, the entry goes to become blank.

Explanation in the case of image made inside a function, can be also used to explain the situation here(from effbot.org):

The problem is that the Tkinter/Tk interface doesn’t handle references to Image objects properly; the Tk widget will hold a reference to the internal object, but Tkinter does not. When Python’s garbage collector discards the Tkinter object, Tkinter tells Tk to release the image. But since the image is in use by a widget, Tk doesn’t destroy it. Not completely. It just blanks the image, making it completely transparent…

Upvotes: 4

Related Questions