-1

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)

schwartz721
  • 767
  • 7
  • 19
  • ***"without using Toplevel?"***: It's unclear what you mean by this, you don't use a `Toplevel` widget? ***"getting circular dependency"***: Your example didn't use any custom import, [edit] your question and provide a [mcve]. Relevant [Switch between frames:`create=>use=>destroy()`](https://stackoverflow.com/a/49325719/7414759) – stovfl Jan 30 '20 at 08:43
  • @stovfl: The OP says they prefer using the code shown **instead** of using `Toplevel`, so that's why there's no use of it — and the question being asked: "Is there a way to easily modularize a structure like this without using Toplevel?" – martineau Jan 30 '20 at 09:24
  • 1
    @martineau: The answer about this is: **Yes** – stovfl Jan 30 '20 at 10:05
  • @stovfl: Agreed. `;¬)` – martineau Jan 30 '20 at 10:42
  • All you have to do is move `a` and `b` into separate files and then import them. Is that what you're asking how to do? – Bryan Oakley Jan 30 '20 at 13:15
  • I updated the question with an example that raises the error I initially mentioned – schwartz721 Jan 30 '20 at 20:55

1 Answers1

1

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()
martineau
  • 119,623
  • 25
  • 170
  • 301