Search code examples
pythontkinteroptionmenu

Tkinter: OptionMenu causing 'NoneType' trace error


So I'm trying to use an OptionMenu to switch between a list of items that are drawn on a window. I'm able to do this by tracing the OptionMenu's variable and handling the drawing in the trace callback function. This all works beautifully when the code first executes with a default value for the variable. However, as soon as I make a selection with the OptionMenu, I receive this error:

    Exception in Tkinter callback
    Traceback (most recent call last):
  File "C:\Users\NotActuallyMyRealName\AppData\Local\Programs\Python\Python37-32\lib\tkinter\__init__.py", line 1705, in __call__
    return self.func(*args)
TypeError: 'NoneType' object is not callable

I've narrowed the problem down to the OptionMenu and the trace callback. I actually stripped the script down to the point where it's basically the OptionMenu example in Tkinter's documentation, and I'm still getting the same error.

from tkinter import *
root = Tk()

def callback():
    print("var:" + var.get())

var = StringVar()
var.set("A")
var.trace("w", callback())
op = OptionMenu(root, var, "A", "B", "C")
op.pack()

root.mainloop()

Again here, the callback function DOES execute once with the default var set at "A", but changing var with the OptionMenu throws a TypeError, even if I try to change it back to "A". I can't see what could be causing this with the code stripped down to this point. Is the OptionMenu function in Tkinter simply no longer working?


Solution

  • Try this.

    When you call a function in trace, you should call the function by just its name. However, in trace, it passes three arguments to the function (you don't need them for your purpose: see What are the arguments to Tkinter variable trace method callbacks? for details). So you can define a lambda function to have 3 dummy arguments and call your callback function.

    from tkinter import *
    root = Tk()
    
    def callback():
        print("var:" + var.get())
    
    var = StringVar()
    var.set("A")
    var.trace("w", lambda x,y,z: callback())
    op = OptionMenu(root, var, "A", "B", "C")
    op.pack()
    
    root.mainloop()
    

    Or you can also define your callback function with three dummy arguments:

    from tkinter import *
    root = Tk()
    
    def callback(a,b,c):
        print("var:" + var.get())
    
    var = StringVar()
    var.set("A")
    var.trace("w", callback)
    op = OptionMenu(root, var, "A", "B", "C")
    op.pack()
    
    root.mainloop()
    

    Both approaches should work.