Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make tkinter toplevel window that doesn't close with parent

Tags:

python

tkinter

How should I spawn child Toplevel() windows in tkinter that don't close when the parent closes?

Do I need to have the parent keep a 'reference count' of child windows, intercept WM_DELETE_WINDOW and only call root.destroy() when all the children are gone?

Or is it acceptable practice to spawn another thread process with its own tk mainloop?

Or is there a more elegant way?

EDIT

I am currently doing things this way

root = Tk()
app = App(root) # doesn't call Toplevel()
root.mainloop()

where App.__init__() adds widgets to root without calling Toplevel(), and at some point spawns a new window with this function:

def new_window():
    root = Tk()
    window = App2(root) # doesn't call Toplevel() either

Note that root in new_window() is a different variable to the original root, obtained by another call to Tk().

All this seems to Do The Right Thing i.e. the child window lives independently of the parent and the python process dies after both are closed.

So my question becomes, does that make sense or am I doing something horribly wrong here?

like image 426
Sideshow Bob Avatar asked Nov 12 '22 03:11

Sideshow Bob


1 Answers

Instead of having to keep track of which Toplevels are alive, you could use a weakref on a sentinel -- just some object that is passed to each Toplevel and saved in a reference. When each Toplevel dies (is closed), let it delete its reference to the sentinel. When the last reference to the sentinel is deleted, the weakref callback, self.no_sentinel will be called automatically, in turn calling root.destroy for you.

import Tkinter as tk
import weakref


class Sentinel(object):
    pass


class Window(tk.Toplevel):
    def __init__(self, master, sentinel, **kwargs):
        title = kwargs.pop('title')
        self.sentinel = sentinel
        tk.Toplevel.__init__(self, master, **kwargs)
        self.protocol("WM_DELETE_WINDOW", self.ondelete)
        self.label = tk.Label(self, text=title)
        self.label.pack(padx=10, pady=10)

    def ondelete(self):
        self.destroy()
        del self.sentinel


class App(object):
    def __init__(self, master, **kwargs):
        self.master = master
        sentinel = Sentinel()
        parent = Window(master, sentinel, title='Parent')
        child = Window(master, sentinel, title='Child')
        self._ref = weakref.ref(sentinel, self.no_sentinel)            
        # When we exit `__init__` only two strong references to sentinal
        # remain -- in parent and child. When both strong references are
        # deleted, `self.no_sentinel` gets called.
    def no_sentinel(self, *args):
        self.master.destroy()

root = tk.Tk()
root.withdraw()
app = App(root)
root.mainloop()

Alternatively, you could use the multiprocessing module to spawn another process to make another Tkinter window and mainloop, but would be consumer more memory than the solution above, and would require you to setup some form of interprocess communication if you wanted the separate processes to share information.

like image 141
unutbu Avatar answered Nov 14 '22 22:11

unutbu