rod rod - 3 months ago 10
Python Question

Tkinter unintentional recursion with menu bar command...cause?

I'm trying to make a Python GUI using

tkinter
, and I need a menu item that opens another copy of the main window. I tried to do the following code, and when I ran the program, it froze for a bit, then opened a large number of windows. The last error message printed is below.

I have two questions.


  1. How can I accomplish the task of making the "New" button open a new window and instance of the
    TheThing
    class? (In IDLE,
    File > New File
    has the behavior I'm seeking.)

  2. Why is this error happening?

    RecursionError: maximum recursion depth exceeded while calling a Python object



My code:

import tkinter as tk

class TheThing:
def __init__(self, root):
root.option_add('*tearOff', False)

menubar = tk.Menu(root)
root.config(menu = menubar)

file = tk.Menu(menubar)
menubar.add_cascade(menu = file, label = "File")
file.add_command(label = 'New', command = doathing())

def doathing():
thing1 = tk.Tk()
thing2 = TheThing(thing1)

def main():
win = tk.Tk()
do = TheThing(win)
win.mainloop()

if __name__ == '__main__': main()


Places I've already looked for answers:


  • This question seemed like it was having a very similar problem. I may be able to study that and find a solution, but I still won't understand the problem.

  • This question was about recursion, python, and tkinter, but seemed to be about more the
    after
    thing.


Answer

The problem is in this line:

    file.add_command(label = 'New', command = doathing())

Here, you execute the doathing callback and then try to bind its result (which is None) to the command. In this specific case, this also leads to an infinite recursion, as the callback will create a new instance of the frame, which will again execute the callback, which will create another frame, and so on. Instead of calling the function, you have to bind the function itself to the command.

    file.add_command(label = 'New', command = doathing)  # no ()

In case you need to pass parameters to that function (not the case here) you can use a lambda:

    file.add_command(label = 'New', command = lambda: doathing(params))

Also, instead of creating another Tk instance you should probably just create a Toplevel instance in the callback, i.e.

def doathing():
    thing1 = tk.Toplevel()
    thing2 = TheThing(thing1)