Search code examples
pythoncomboboxtkinterttk

Python tkinter.ttk combobox throws exception on quit


In my Python 3.3 code I use some comboboxes from the ttk library, and they function fine, but if I use any of them, I got an exception when I close the window with the X button. This is an example:

from tkinter import Tk,Label,Button
from tkinter import ttk
from tkinter.ttk import Combobox

def cbox_do(event):
    'Used for cbox.'
    clabel.config(text=cbox.get())

a = Tk()
cbox = Combobox(a, value=('Luke','Biggs','Wedge'), takefocus=0)
cbox.bind("<<ComboboxSelected>>", cbox_do)
cbox.pack()
clabel = Label(a)
clabel.pack()
a.mainloop()

If you close it without selecting a value, everithing is fine, but try to close it after a value was selected, it exits but prints the following error into the python command line:

can't invoke "winfo" command:  application has been destroyed
    while executing
"winfo exists $w"
    (procedure "ttk::entry::AutoScroll" line 3)
    invoked from within
"ttk::entry::AutoScroll .41024560"
    (in namespace inscope "::" script line 1)
    invoked from within
"::namespace inscope :: {ttk::entry::AutoScroll .41024560}"
    ("uplevel" body line 1)
    invoked from within
"uplevel #0 $Repeat(script)"
    (procedure "ttk::Repeat" line 3)
    invoked from within
"ttk::Repeat"
    ("after" script)

How could I fix it? I would be grateful for any help you could provide.

Update 1: My Python version is v3.3, I use the bundled Tcl/Tk and Tkinter. I tried both the x86 and x64 versions.

Update 2: The exception is thrown only if I run my script from command line. It wont show up in Idle.


Solution

  • This is a problem with the Tcl/Tk binding code used in ttk.

    The problem is hinted at by a comment in tcl/tk8.5/ttk/entry.tcl file in a typical python Tkinter installation:

    ## AutoScroll
    #   Called repeatedly when the mouse is outside an entry window
    #   with Button 1 down.  Scroll the window left or right,
    #   depending on where the mouse is, and extend the selection
    #   according to the current selection mode.
    #
    # TODO: AutoScroll should repeat faster (50ms) than normal autorepeat.
    # TODO: Need a way for Repeat scripts to cancel themselves.
    

    Basically a deferred call with after isn't canceled and cannot complete anymore after the last window gets closed and Tk is finalized, because the procedure/function 'winfo' no longer exists. When you run IDLE, there still is a window, so Tk does not get finalized and the error does not show up.

    You can fix this with a binding on the WM_DELETE_WINDOW message, which stops the repeat timer. The code for that would be (in Tcl/Tk):

    proc shutdown_ttk_repeat {args} {
        ::ttk::CancelRepeat
    }
    wm protocol . WM_DELETE_WINDOW shutdown_ttk_repeat
    

    For Tkinter it should work in a similar way:

    from tkinter import Tk,Label,Button
    from tkinter import ttk
    from tkinter.ttk import Combobox
    
    def cbox_do(event):
        'Used for cbox.'
        clabel.config(text=cbox.get())
    
    a = Tk()
    cbox = Combobox(a, value=('Luke','Biggs','Wedge'), takefocus=0)
    cbox.bind("<<ComboboxSelected>>", cbox_do)
    cbox.pack()
    clabel = Label(a)
    clabel.pack()
    
    def shutdown_ttk_repeat():
        a.eval('::ttk::CancelRepeat')
        a.destroy()
    
    a.protocol("WM_DELETE_WINDOW", shutdown_ttk_repeat)
    a.mainloop()