本文介绍了Tkinter启动画面和主循环外的多处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经实现了启动屏幕,该屏幕在我的应用程序启动时从远程云存储加载数据库时显示.初始屏幕通过调用.update()保持活动状态(上面有进度条),并在单独的加载过程结束后销毁.此后,将启动mainloop并运行该应用程序.

I have implemented a splash screen that is shown while my application loads the database from remote cloud storage on startup. The splash screen is kept alive (there's a progressbar on it) with calls to .update() and is destroyed once the separate loading process ends. After this, the mainloop is started and the app runs normally.

下面的代码曾经在我的Mac上使用python 3.6和tcl/tk 8.5.9正常工作.但是,在更新到Sierra之后,我被迫将tk更新到ActiveTcl 8.5.18.现在,在单独的过程完成之前,不会显示启动画面,但是会出现并与根窗口一起显示并停留在屏幕上(即使调用了.destroy()方法).

The code below used to work fine on my Mac with python 3.6 and tcl/tk 8.5.9. However, after the update to Sierra I was forced to update tk to ActiveTcl 8.5.18. Now, the splash screen is not displayed until the separate process finishes, but then appears and stays on screen together with the root window (even though 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 do not understand why this is happening and how to solve it. Can anybody help? Thanks!

如果您删除了self.overrideredirect(True)行,则将正确显示启动屏幕.但是,我不希望窗口装饰,并且脚本结尾处仍保留在屏幕上.但是,它在内部被破坏,对self.splash的任何其他方法调用(例如.winfo_...-方法)都会导致_tkinter.TclError: bad window path name ".!splashscreen".

The splash screen is displayed correctly if you outcomment 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 is being destroyed internally though, any further method calls on self.splash (e.g. .winfo_...-methods) result in _tkinter.TclError: bad window path name ".!splashscreen".

此外,此代码在Windows和tcl/tk 8.6下也能正常工作.这是Mac上tcl/tk 8.5.18的窗口管理的错误/问题吗?

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

推荐答案

显然,这是由于在调用overrideredirect(True)后窗口没有由窗口管理器装饰时,窗口堆叠顺序出现问题.它似乎也发生在其他平台上.

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.

在具有Python 3.6.1和tcl/tk 8.5.18的macOS 10.12.5上运行以下代码,单击打开"按钮后不会出现顶级窗口:

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 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()

取消注释行self.after_idle(self.lift)可以解决此问题(只需调用self.lift()也可以.但是使用after_idle()可以防止窗口在移动到其位置并调整大小之前闪烁几分之一秒,这是我在tkinter上反复遇到的另一个问题,让我想知道我应该继续学习PyQT还是PySide2....)

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...).

关于我最初的问题中关闭未装饰窗口的问题:调用after_idle(window.destroy())而不是window.destroy()似乎也可以解决此问题.我不明白为什么.

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.

这篇关于Tkinter启动画面和主循环外的多处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-05 11:26
查看更多