Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop multiple windows from opening with one button

Have a button to open a new window in my code, and have been trying to make the button open a new window called newest_release_window with two things in mind:

  • If newest_release_window is not open, open the window.
  • If newest_release_window is open, set focus on the said window but do not open a new window.

Unfortunately, it has been getting too complicated and I cannot figure out how to do it. The issue is that I cannot make the code detect whether newest_release_window is open or not, and change the variable according to that.

welcome_window = Tk()
welcome_window.title("Games R Us")
welcome_window.geometry("360x350")
welcome_window.configure(bg = "gold")
currentDisplay = 10

newest_release_windowtracker = 0

gui_font_5 = ("Helvetica", 5, "bold")
gui_font_10 = ("Helvetica", 10, "bold")
gui_font_15 = ("Helvetica", 15, "bold")
gui_font_20 = ("Helvetica", 20, "bold")
space_between = (5)
button_variable = IntVar()

def newwindow_newest_release():
    global newest_release_windowtracker
    newest_release_window = Tk()
    newest_release_window.title("Games R Us")

    newest_release_window.geometry("360x350")
    newest_release_window.configure(bg = "greenyellow")
    currentDisplay = 10
    
    display = Label(newest_release_window, text="Humm, see a new window !", 
    bg ="limegreen")
    display.pack()
    
    newest_release_window.withdraw()
    
    if newest_release_windowtracker == 0:
        newest_release_window.deiconify()
        newest_release_windowtracker = 1
    elif newest_release_windowtracker == 1:
        newest_release_window.focus_set()
    elif newest_release_window.winfo_exists == 0:
        newest_release_window = Tk()

ww_newest_release = Button(welcome_window,
            text = "Newest Release", bg = "goldenrod", font = "Helvetica 10", 
            width = 12, command = newwindow_newest_release)

This isn't the full code, I just grabbed the most important parts to give context to what the problem might be.

like image 982
Simone Rizzuto Avatar asked Aug 13 '26 18:08

Simone Rizzuto


2 Answers

The way I do it is by having one variable to store the Toplevel instance into. I initialize that variable to None first, so I can only set it once (by testing if the variable is None)

The variable is reset back to None when the Toplevel is destroyed (by binding the WM_DELETE_WINDOW protocol).

I also noticed that in your code, you're instanciating a new Tk() object. The Tk() object should only be instanciated once, and be the root of your program. To open new windows, your should use the Toplevel object.

Now for an example :

import tkinter as tk


class OptionsWindow(tk.Frame):
    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)
        pass


class MainWindow(tk.Frame):
    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)
        self.options_toplevel = None
        tk.Button(self, text='open toplevel', command=self._open_toplevel).pack()

    def _open_toplevel(self, *args):
        if self.options_toplevel is None:
            self.options_toplevel = tk.Toplevel(self.master)
            self.options_toplevel.protocol('WM_DELETE_WINDOW', self.on_tl_close)
            gui = OptionsWindow(self.options_toplevel, width=300, height=300)
            gui.pack()

    def on_tl_close(self, *args):
        self.options_toplevel.destroy()
        self.options_toplevel = None


root = tk.Tk()
gui = MainWindow(root)
gui.pack()
root.mainloop()
like image 145
Dogeek Avatar answered Aug 16 '26 20:08

Dogeek


What you want to do is spawn a TopLevel window.

To prevent multiple instances of the top level from spawning, store its reference and check against it when the button is clicked.

    ...
    
    self.options_toplevel = None

    def _open_toplevel(self):
        if self.options_toplevel is None:
            self.options_toplevel = OptionsWindow(self.master)

If you want to return the user to the toplevel on a click action you can lift() the window back to the top and then call focus_set() to direct keyboard & mouse events to the toplevel window again.

        else:
            self.options_toplevel.lift()
            self.options_toplevel.focus_set()

It's also worth mentioning that you can bind an event to the window destroy protocol. This allows you to define additional actions when a window is destroyed, so you could use it to achieve a similar effect, if your use case required that for some reason.

    ...    

    self.protocol("WM_DELETE_WINDOW", self.on_close)
    
    def on_close(self):
        # Any cleanup you need to do.
        self.grab_release()
        self.destroy()

Now we can put all of that together in a workable example.

import tkinter as tk


class OptionsWindow(tk.Toplevel):
    def __init__(self, root=None, **kwargs):
        super().__init__(root, **kwargs)
        self.title("Options Window")
        self.geometry("300x300")

        # Bind action to the tk close protocol
        self.protocol("WM_DELETE_WINDOW", self.on_close)

        tk.Label(self, text="This is the options window").pack(pady=20)
        tk.Button(self, text="Close", command=self.destroy).pack(pady=10)

        self.focus_set()  # Move cursor & keyboard focus to the toplevel window

    def on_close(self):
        # Any cleanup you need to do.
        self.grab_release()
        self.destroy()


class MainWindow(tk.Frame):
    def __init__(self, root=None, **kwargs):
        super().__init__(root, **kwargs)

        self.options_toplevel = None

        tk.Button(self, text='Open Options', command=self._open_toplevel).pack()
        tk.Button(self, text='Open Options', command=self.do_stuff).pack()

    def _open_toplevel(self, *args):
        if self.options_toplevel is None or not tk.Toplevel.winfo_exists(self.options_toplevel):
            self.options_toplevel = OptionsWindow(self.master)
        else:
            self.options_toplevel.lift()
            self.options_toplevel.focus_set()

    def on_toplevel_close(self, *args):
        if self.options_toplevel:
            self.options_toplevel = None

    def do_stuff(self):
        print("Doing stuff!!1")


root = tk.Tk()
root.title("Main Window")
gui = MainWindow(root)
gui.pack(pady=20, padx=20)
root.mainloop()

Modal Example:

If the goal is to design more of a modal, where the user must respond to the TopLevel before they can interact with the base application then you can achieve that with grab_set() and setting the '-topmost' attribute.

        ...

        self.attributes("-topmost", True)
        self.focus_set()
        self.grab_set()

The example below is enough to prevent more windows from spawning, but you should probably include the safeguards written in the primary example.

import tkinter as tk


class OptionsWindow(tk.Toplevel):
    def __init__(self, root=None, **kwargs):
        super().__init__(root, **kwargs)
        self.attributes("-topmost", True)

        tk.Label(self, text="This is the options window").pack(pady=20)
        tk.Button(self, text="Close", command=self.destroy).pack(pady=10)

        self.focus_set()  # Move focus to the toplevel window
        self.grab_set()   # Restrict user interaction to the top level until its closed

class MainWindow(tk.Frame):
    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)

        tk.Button(self, text='Open Options', command=self._open_toplevel).pack()

    def _open_toplevel(self):
        OptionsWindow(self.master)

root = tk.Tk()
root.title("Main Window")
gui = MainWindow(root)
gui.pack(pady=20, padx=20)
root.mainloop()

I recommend checking out the documentation for Tk: Toplevel if you want to know more.

like image 35
Stephanie Burns Avatar answered Aug 16 '26 19:08

Stephanie Burns



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!