Tkinter splash screen & multiprocessing outside of mainloop Tkinter splash screen & multiprocessing outside of mainloop tkinter tkinter

Tkinter splash screen & multiprocessing outside of mainloop


Apparently this is due to a problem with the window stacking order when windows are not decorated by the window manager after calling overrideredirect(True). It seems to have occurred on other platforms as well.

Running the following code on macOS 10.12.5 with Python 3.6.1 and tcl/tk 8.5.18, toplevel windows do not appear after the button 'open' is clicked:

import tkinter as tkclass TL(tk.Toplevel):    def __init__(self):        tk.Toplevel.__init__(self)        self.overrideredirect(True)        # self.after_idle(self.lift)        tl_label = tk.Label(self, text='this is a undecorated\ntoplevel window')        tl_label.grid(row=0)        b_close = tk.Button(self, text='close', command=self.close)        b_close.grid(row=1)    def close(self):        self.destroy()def open():    TL()root = tk.Tk()label = tk.Label(root, text='This is the root')label.grid(row=0)b_open = tk.Button(root, text='open', command=open)b_open.grid(row=1)root.mainloop()

Uncommenting the line self.after_idle(self.lift) fixes the problem (simply calling self.lift() does too. But using after_idle()prevents the window from flashing up for a fraction of a second before it is moved to its position and resized, which is another problem I have experienced repeatedly with tkinter and keeps me wondering whether I should move on to learn PyQT or PySide2...).

As to the problem with closing an undecorated window in my original question: calling after_idle(window.destroy()) instead of window.destroy() seems to fix that too. I do not understand why.

In case other people reproduce this and somebody hints me towards where to report this as a bug, I am happy to do so.


I came across this while looking for an example on how to make a tkinter splash screen that wasn't time dependent (as most other examples are). Sam's version worked for me as is. I decided to make it an extensible stand-alone class that handles all the logic so it can just be dropped into an existing program:

# Original Stackoverflow thread:# https://stackoverflow.com/questions/44802456/tkinter-splash-screen-multiprocessing-outside-of-mainloopimport multiprocessingimport tkinter as tkimport functoolsclass SplashScreen(tk.Toplevel):    def __init__(self, root, **kwargs):        tk.Toplevel.__init__(self, root, **kwargs)        self.root = root        self.elements = {}        root.withdraw()        self.overrideredirect(True)        self.columnconfigure(0, weight=1)        self.rowconfigure(0, weight=1)        # Placeholder Vars that can be updated externally to change the status message        self.init_str = tk.StringVar()        self.init_str.set('Loading...')        self.init_int = tk.IntVar()        self.init_float = tk.DoubleVar()        self.init_bool = tk.BooleanVar()    def _position(self, x=.5,y=.5):        screen_w = self.winfo_screenwidth()        screen_h = self.winfo_screenheight()        splash_w = self.winfo_reqwidth()        splash_h = self.winfo_reqheight()        x_loc = (screen_w*x) - (splash_w/2)        y_loc = (screen_h*y) - (splash_h/2)        self.geometry("%dx%d+%d+%d" % ((splash_w, splash_h) + (x_loc, y_loc)))    def update(self, thread_queue=None):        super().update()        if thread_queue and not thread_queue.empty():            new_item = thread_queue.get_nowait()            if new_item and new_item != self.init_str.get():                self.init_str.set(new_item)    def _set_frame(self, frame_funct, slocx=.5, sloxy=.5, ):        """        Args:            frame_funct: The function that generates the frame            slocx: loction on the screen of the Splash popup            sloxy:            init_status_var: The variable that is connected to the initialization function that can be updated with statuses etc        Returns:        """        self._position(x=slocx,y=sloxy)        self.frame = frame_funct(self)        self.frame.grid(column=0, row=0, sticky='nswe')    def _start(self):        for e in self.elements:            if hasattr(self.elements[e],'start'):                self.elements[e].start()    @staticmethod    def show(root, frame_funct, function, callback=None, position=None, **kwargs):        """        Args:            root: The main class that created this SplashScreen            frame_funct: The function used to define the elements in the SplashScreen            function: The function when returns, causes the SplashScreen to self-destruct            callback: (optional) A function that can be called after the SplashScreen self-destructs            position: (optional) The position on the screen as defined by percent of screen coordinates                (.5,.5) = Center of the screen (50%,50%) This is the default if not provided            **kwargs: (optional) options as defined here: https://www.tutorialspoint.com/python/tk_toplevel.htm        Returns:            If there is a callback function, it returns the result of that. Otherwise None        """        manager = multiprocessing.Manager()        thread_queue = manager.Queue()        process_startup = multiprocessing.Process(target=functools.partial(function,thread_queue=thread_queue))        process_startup.start()        splash = SplashScreen(root=root, **kwargs)        splash._set_frame(frame_funct=frame_funct)        splash._start()        while process_startup.is_alive():            splash.update(thread_queue)        process_startup.terminate()        SplashScreen.remove_splash_screen(splash, root)        if callback: return callback()        return None    @staticmethod    def remove_splash_screen(splash, root):        splash.destroy()        del splash        root.deiconify()    class Screen(tk.Frame):        # Options screen constructor class        def __init__(self, parent):            tk.Frame.__init__(self, master=parent)            self.grid(column=0, row=0, sticky='nsew')            self.columnconfigure(0, weight=1)            self.rowconfigure(0, weight=1)### Demo ###import timedef splash_window_constructor(parent):    """        Function that takes a parent and returns a frame    """    screen = SplashScreen.Screen(parent)    label = tk.Label(screen, text='My Splashscreen', anchor='center')    label.grid(column=0, row=0, sticky='nswe')    # Connects to the tk.StringVar so we can updated while the startup process is running    label = tk.Label(screen, textvariable=parent.init_str, anchor='center')    label.grid(column=0, row=1, sticky='nswe')    return screendef startup_process(thread_queue):    # Just a fun method to simulate loading processes    startup_messages = ["Reticulating Splines","Calculating Llama Trajectory","Setting Universal Physical Constants","Updating [Redacted]","Perturbing Matrices","Gathering Particle Sources"]    r = 10    for n in range(r):        time.sleep(.2)        thread_queue.put_nowait(f"Loading database.{'.'*n}".ljust(27))    time.sleep(1)    for n in startup_messages:        thread_queue.put_nowait(n)        time.sleep(.2)    for n in range(r):        time.sleep(.2)        thread_queue.put_nowait(f"Almost Done.{'.'*n}".ljust(27))    for n in range(r):        time.sleep(.5)        thread_queue.put_nowait("Almost Done..........".ljust(27))        time.sleep(.5)        thread_queue.put_nowait("Almost Done......... ".ljust(27))def callback(text):    # To be run after the splash screen completes    print(text)class App(tk.Tk):    def __init__(self):        tk.Tk.__init__(self)        self.callback_return = SplashScreen.show(root=self,                                   frame_funct=splash_window_constructor,                                   function=startup_process,                                   callback=functools.partial(callback,"Callback Done"))        self.title("MyApp")        self.columnconfigure(0, weight=1)        self.rowconfigure(0, weight=1)        self.application_frame = tk.Label(self, text='Rest of my app here', anchor='center')        self.application_frame.grid(column=0, row=0, sticky='nswe')        self.mainloop()if __name__ == "__main__":    App()