Tkinter rivet shield and multiprocessing outside of mainloop

I have implemented a splash screen that is displayed while my application is loading a database from remote cloud storage on startup. The splash screen stays alive (there is a progress bar) with calls to .update () and is destroyed after a separate boot process finishes. After that, mainloop starts and the application works fine.

The code below works on my Mac using python 3.6 and tcl / tk 8.5.9. However, after upgrading to Sierra, I was forced to upgrade tk to ActiveTcl 8.5.18. Now the splash screen is not displayed until the individual process exits, and then appears and remains on the screen along with the root window (even if its .destroy () method is called).

import tkinter as tk
import tkinter.ttk as ttk
import multiprocessing
import time


class SplashScreen(tk.Toplevel):
    def __init__(self, root):
        tk.Toplevel.__init__(self, root)
        self.geometry('375x375')
        self.overrideredirect(True)

        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)

        self.label = ttk.Label(self, text='My Splashscreen', anchor='center')
        self.label.grid(column=0, row=0, sticky='nswe')

        self.center_splash_screen()
        print('initialized splash')

    def center_splash_screen(self):
        w = self.winfo_screenwidth()
        h = self.winfo_screenheight()
        x = w / 2 - 375 / 2
        y = h / 2 - 375 / 2
        self.geometry("%dx%d+%d+%d" % ((375, 375) + (x, y)))

    def destroy_splash_screen(self):
        self.destroy()
        print('destroyed splash')


class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)

        self.start_up_app()

        self.title("MyApp")
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)

        self.application_frame = ttk.Label(self, text='Rest of my app here', anchor='center')
        self.application_frame.grid(column=0, row=0, sticky='nswe')

        self.mainloop()

    def start_up_app(self):
        self.show_splash_screen()

        # load db in separate process
        process_startup = multiprocessing.Process(target=App.startup_process)
        process_startup.start()

        while process_startup.is_alive():
            # print('updating')
            self.splash.update()

        self.remove_splash_screen()

    def show_splash_screen(self):
        self.withdraw()
        self.splash = SplashScreen(self)

    @staticmethod
    def startup_process():
        # simulate delay while implementation is loading db
        time.sleep(5)

    def remove_splash_screen(self):
        self.splash.destroy_splash_screen()
        del self.splash
        self.deiconify()

if __name__ == '__main__':
    App()

      

I don't understand why this is happening and how to solve it. Can anyone please help? Thank!

Update:

The splash screen displays correctly if you beat the line self.overrideredirect(True)

. However, I don't want window decorations and it still stays on screen at the end of the script. It gets destroyed internally, although any further method calls self.splash

(e.g. .winfo_...

-methods) results in _tkinter.TclError: bad window path name ".!splashscreen"

.

Also, this code works fine under windows and tcl / tk 8.6. Is this a bug / issue with tcl / tk 8.5.18 window management on Mac?

+2


source to share


1 answer


This appears to be due to a window stacking order issue where windows are not rendered by the window manager after being called overrideredirect(True)

. It looks like it happened on other platforms as well.

By running the following code on macOS 10.12.5 with Python 3.6.1 and tcl / tk 8.5.18, the top-level windows will not appear after clicking the open button:

import tkinter as tk

class 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 (just triggers a call self.lift()

). But usage after_idle()

prevents the window from blinking for a second before it is moved to its position and changed, which is another problem I have encountered with tkinter on numerous occasions and makes me wonder if I should move on to learning PyQT or PySide2 .. .).

Regarding the problem of closing an unselected window in my original question: calling after_idle (window.destroy ()) instead of window.destroy () seems to fix this too. I do not understand why.

In case other people reproduce this and someone tells me where to report this as a bug, I'm happy to do so.

0


source







All Articles