Modularizing larger tkinter program

Question:

I have a GUI that I’m designing using tkinter. I have it built inside a class. The structure that I usually use is to create a frame and pack all my widgets into it. Then when I need to show a different screen, I destroy that frame and call a function that creates a new parent frame and new widgets to pack into it. Here’s a simple example to clarify this structure.

import tkinter as tk

class Window():
    def __init__(self, master):
        self.master = master
        self.master.geometry('300x300')
        frame = tk.Frame(self.master)
        frame.pack()
        self.main(frame)

    def goto(self, destination, frame):
        frame.destroy()
        frame = tk.Frame(self.master)
        frame.pack()
        goto = {
            'main': self.main,
            'a': self.a,
            'b': self.b
        }
        goto[destination](frame)

    def main(self, frame):
        tk.Label(frame, text='Main').pack()
        tk.Button(frame, text='Goto A', command=lambda: self.goto('a', frame)).pack()
        tk.Button(frame, text='Goto B', command=lambda: self.goto('b', frame)).pack()

    def a(self, frame):
        tk.Label(frame, text='A').pack()
        tk.Button(frame, text='Back to Main', command=lambda: self.goto('main', frame)).pack()

    def b(self, frame):
        tk.Label(frame, text='B').pack()
        tk.Button(frame, text='Back to Main', command=lambda: self.goto('main', frame)).pack()

root = tk.Tk()
Window(root)
root.mainloop()

I prefer this structure to Toplevel windows because I’ve had problems working with them in the past (windows dropping behind other open windows, focus issues, etc). But I really miss how easy Toplevel windows make it to build modules, instead of having all the code in a single script. Is there a way to easily modularize a structure like this without using Toplevel? It would be great for organization and readability. I’ve tried taking the different ‘screen creation’ functions and putting them in modules, but I’m getting circular dependency issues.

main.py

import tkinter as tk
import module

class Window():
    def __init__(self, master):
        self.master = master
        self.master.geometry('300x300')
        frame = tk.Frame(self.master)
        frame.pack()
        self.main(frame)

    def goto(self, destination, frame):
        frame.destroy()
        frame = tk.Frame(self.master)
        frame.pack()
        goto = {
            'main': self.main,
            'a': module.a,
        }
        goto[destination](frame)

    def main(self, frame):
        tk.Label(frame, text='Main').pack()
        tk.Button(frame, text='Goto A', command=lambda: self.goto('a', frame)).pack()

root = tk.Tk()
window = Window(root)
root.mainloop()

module.py

import tkinter as tk
import main

def a(frame):
    tk.Label(frame, text='A').pack()
    tk.Button(frame, text='Back to Main', command=lambda: main.window.goto('main', frame)).pack()

When I click on the button that should take me to the frame built in the module, I get:

AttributeError: partially initialized module ‘module’ has no attribute ‘a’ (most likely due to a circular import)

Asked By: schwartz721

||

Answers:

You can avoid the partially initialized module error due to the circular import by simply adding an if __name__ == '__main__': guard around the code near the end of the main script as shown below (which prevents the statements following it from executing when it’s imported by module.py).

main.py

import tkinter as tk
import module

class Window():
    def __init__(self, master):
        self.master = master
        self.master.geometry('300x300')
        frame = tk.Frame(self.master)
        frame.pack()
        self.main(frame)

    def goto(self, destination, frame):
        frame.destroy()
        frame = tk.Frame(self.master)
        frame.pack()
        goto = {
            'main': self.main,
            'a': module.a,
        }
        goto[destination](frame)

    def main(self, frame):
        tk.Label(frame, text='Main').pack()
        tk.Button(frame, text='Goto A', command=lambda: self.goto('a', frame)).pack()


if __name__ == '__main__':  # ADDED
    root = tk.Tk()
    window = Window(root)
    root.mainloop()

module.py (no significant change)

import tkinter as tk
import main


def a(frame):
    tk.Label(frame, text='A').pack()
    tk.Button(frame, text='Back to Main',
              command=lambda: main.window.goto('main', frame)).pack()
Answered By: martineau

this code gives me this error
-Exception in Tkinter callback
-Traceback (most recent call last):

  • File -"C:Users19417AppDataLocalProgramsPythonPython310libtkinter_init_.py", -line 1921, in call
  • return self.func(*args)
  • File "C:CodeATE APASmodule.py", line 6, in
  • tk.Button(frame, text=’Back to Main’, command=lambda: main.window.goto(‘main’, -frame)).pack()
    -AttributeError: module ‘main’ has no attribute ‘window’
Answered By: julio lopez
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.