pythontkinterwindow

Python tkinter multiple windows from separate files


I continue to struggle with multiple windows in tkinter. It's now developing into an application where each window is in it's own separate file.

I need to open the second window from the first because that's how the application works.

I also need to open the second window independently of the first, i.e. by itself, so I can do unit testing as it develops.

This question destroys the previous window as the second window opens. Not what I want: Python tkinter multiple windows

My own previous question here closes the first window as the second window opens. Not what I want. Although the comments point out a typo error (omitting ()), the accepted answer uses withdraw and deiconify, which will eventually be helpful but not the solution to this problem. Python tkinter cloase first window while opening second window

This is closest because it opens a second window from a first window, but it doesn't address both (i) opening the second window from a separate file and (ii) also being able to open the second window independently: Python tkinter class multiple windows

Here's the SECOND window in a file called Location.py and it opens fine independently:

import tkinter as tk
from tkinter import ttk

class Location(tk.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        root.title("Location")
        root.geometry('400x275')
        #                                               Frames           
        self.mainframe = ttk.Frame(self.master)
        self.mainframe.grid(column = 0, row=0)
        
        ttk.Label(self.mainframe, text = "Second Window").grid(column=1, row=1, sticky=(tk.W, tk.E))               

if __name__ == "__main__":
    root = tk.Tk()
    Location(root)
    root.mainloop()  

Here's the FIRST window, which also opens fine, the problem is when I press either button to call the file to open the second window:

import tkinter as tk
from tkinter import ttk
import Location

class Building_Info(tk.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        #                                               Frames    
        self.infoframe = ttk.Frame(self.master, height=400, width=200, padding="50 100 50 100", borderwidth=10)
        self.infoframe['relief'] = 'raised' 
        self.infoframe.grid(column = 0, row=0, sticky=(tk.E, tk.N))
        
        self.buttonframe = ttk.Frame(self.master, height=400, width=200, padding="50 100 50 100", borderwidth=10)
        self.buttonframe['relief'] = 'raised' 
        self.buttonframe.grid(column = 1, row=0, sticky=(tk.E, tk.N))

        #                                               BUTTONS
        confirm_button = ttk.Button(self.infoframe, text = 'Stakeholders', command = self.open_location)
        confirm_button.grid(column=0, row=2)
        
        confirm_button = ttk.Button(self.buttonframe, text = 'Location', command = self.open_location)
        confirm_button.grid(column=0, row=2)

        for child in self.infoframe.winfo_children():
            child.grid_configure(padx=5, pady=5)
        for child in self.buttonframe.winfo_children():
            child.grid_configure(padx=5, pady=5)
    #                                                   METHODS
    def open_location(self):
        Location.Location()
            

if __name__ == "__main__":
    root = tk.Tk()
    root.title("Building Information")
    root.geometry('600x400')
    
    Building_Info(root)
    root.mainloop()      

When I try to pass Location.Location() or Location.Location(root) or Location.Location(self) or Location.Location(self.master), I get this error:

Traceback (most recent call last):
  File "C:\Users\User\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1948, in __call__
    return self.func(*args)
           ^^^^^^^^^^^^^^^^
  File "c:\Users\User\Documents\Python\Tutorials\BuildingInfo_Stay_Open.py", line 32, in open_location
    Location_Stay_Open.Location()
  File "c:\Users\User\Documents\Python\Tutorials\Location_Stay_Open.py", line 9, in __init__
    root.title("Location")
    ^^^^
NameError: name 'root' is not defined

But when I try to pass Location.Location(self.root), I get asked if I meant root.

Traceback (most recent call last):
  File "C:\Users\User\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 1948, in __call__
    return self.func(*args)
           ^^^^^^^^^^^^^^^^
  File "c:\Users\User\Documents\Python\Tutorials\BuildingInfo_Stay_Open.py", line 32, in open_location
    Location_Stay_Open.Location(self.root)
                                ^^^^^^^^^
AttributeError: 'Building_Info' object has no attribute 'root'. Did you mean: '_root'?

Now if I go back to the second window class Location(tk.Frame) and try class Location(tk.Tk), then the second window doesn't open independently, and gives this error:

Traceback (most recent call last):
  File "c:\Users\User\Documents\Python\Tutorials\Location_Stay_Open.py", line 29, in <module>
    Location(root)
  File "c:\Users\User\Documents\Python\Tutorials\Location_Stay_Open.py", line 7, in __init__
    super().__init__(*args, **kwargs)
  File "C:\Users\User\AppData\Local\Programs\Python\Python312\Lib\tkinter\__init__.py", line 2326, in __init__
    self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: create() argument 1 must be str or None, not Tk

Trying class Location(tk.Toplevel) opens two windows: one titled Location and one titled tk. Closing one window closes both windows. I only want one of those windows.

What do I need to do to get the second window (Location) to (i) open independently and to (ii) open from the first window?


Solution

  • Note that when Location.py is executed directly, root inside Location.__init__() refers to the global window root created in the if __name__ == "__main__" block.

    However when Location.py is imported, the code in the if block will not be executed and so there is no root defined because the root window created in main script cannot be accessed by imported module directly.

    You need to add root argument to class Location explicity:

    class Location(tk.Frame):
        # added root argument
        def __init__(self, root, *args, **kwargs):
            super().__init__(root, *args, **kwargs)
            ...
    

    And then you need to create a Toplevel inside Building_Info.open_location() and pass it to Location:

    class Building_Info(tk.Frame):
        ...
    
        def open_location(self):
            # create a toplevel window
            win = tk.Toplevel()
            # and pass it to Location class
            Location.Location(win)