如何安全地退出线程上的Toplevel()窗口

时间:2018-03-02 08:29:10

标签: multithreading python-3.x tkinter thread-safety

我希望在我的程序中包含一个微调器,以显示一些指示,当正在进行一些繁重的背景计算时,程序正在执行某些操作。这是我提出的旋转器,但我很难安全地退出它。我不知道如何让我的想法发挥作用,这是我最新的代码。任何人都有关于如何实现此功能的任何提示?

import tkinter as tk
from threading import Thread
from time import sleep


class Spinner_top(tk.Toplevel):
    def __init__(self, master, text = None, barsize = 10, speed = 0.10, spinnerSize = 50):
        super().__init__(master = master)
        self.barsize = barsize
        self.speed = speed
        self.size = spinnerSize
        self.done = False
        if text is None:
            self.text = 'Program is thinking, thus progress is moving'
        else:
            self.text = text
        self.title(self.text)
        self.out = tk.Label(master = self,
                            height = 1,
                            borderwidth = 0,
                            width = 80,
                            bg = self['background'])
        self.out.pack()
        self.update()
        self.thread = Thread(target = self.fill_self)
        self.thread.start()

    def fill_self(self):
        print('start called')
#        print(self)
#        print('update2')
        forward = True
        progress = 0
        print('entered while loop')
        while True:
            msg = self.spinnerCalc(progress)
            print('message changed')
            self.out.configure(text = msg)
            print('message updated')
            self.update()
            print('updated self')
            if forward:
                if progress == self.size - self.barsize:
                    forward = False
                    progress -= 1
                else:
                    progress += 1
            else:
                if progress == 0:
                    forward = True
                    progress += 1
                else:
                    progress -= 1
            print(self.done)
            if self.done:
                break
            sleep(self.speed)
        return

    def spinnerCalc(self, progress):
        bar = '|'
        barsize = self.barsize
        size = self.size
        for i in range (progress):
            bar += '-'
        for i in range (barsize):
            bar += '\u2588'
        for i in range (size-barsize-progress):
            bar += '-'
        bar += '|'
        return bar

    def stop(self):
        print('stop called')
        self.done = True
        self.thread.join()
        print('got pass join()')
        self.quit()
        self.destroy()

root = tk.Tk()
spinner = [None]
def start():
    if spinner[0] is None:
        spinner[0] = Spinner_top(root,'Program is thinking')
def stop():
    if spinner[0] is not None:
        spinner[0].stop()
        spinner[0] = None
def experiment():
    if spinner[0] is None:
        spinner[0] = Spinner_top(root,'Try something')
        spinner[0].stop()
tk.Button(root,
          text = 'start spinner',
          command = start).pack()
tk.Button(root,
          text = 'stop spinner',
          command = stop).pack()
tk.Button(root,
          text='experiment',
          command = experiment).pack()
tk.Button(root,
          text = 'quit',
          command = root.destroy).pack()
root.mainloop()

我遇到了两个问题: 1.当使用“开始微调器”按钮开始时,程序会冻结。 2.当使用“实验”按钮开始时,代码无法打印(“消息更新”)行。

#trying to use the spinner using after as per @Nae suggestion (resulted in more problems :/
class Spinner_top(tk.Toplevel):
    def __init__(self, master, text = None, barsize = 10, speed = 100, spinnerSize = 50):
        super().__init__(master = master)
        self.barsize = barsize
        self.speed = speed
        self.size = spinnerSize
        self.progress = 0
        self.done = False
        if text is None:
            self.text = 'Program is thinking, thus progress is moving'
        else:
            self.text = text
        self.title(self.text)
        self.out = tk.Label(master = self,
                            height = 1,
                            borderwidth = 0,
                            width = 80,
                            bg = self['background'])
        self.out.pack()
        self.fill_self()

    def fill_self(self):
        print('start called')
#        print(self)
#        print('update2')
        self.forward = True
        progress = 0
        print('entered while loop')
        def foo():
            msg = self.spinnerCalc(progress)
            print('message changed')
            self.out.configure(text = msg)
            print('message updated')
            self.update()
            print('updated self')
            if self.forward:
                if self.progress == self.size - self.barsize:
                    self.forward = False
                    self.progress -= 1
                else:
                    self.progress += 1
            else:
                if progress == 0:
                    self.forward = True
                    self.progress += 1
                else:
                    self.progress -= 1
#            print(self.done)
            if not self.done:
                self.after(self.speed, func = foo)
        foo()
        return

    def spinnerCalc(self, progress):
        bar = '|'
        barsize = self.barsize
        size = self.size
        for i in range (progress):
            bar += '-'
        for i in range (barsize):
            bar += '\u2588'
        for i in range (size-barsize-progress):
            bar += '-'
        bar += '|'
        return bar

    def stop(self):
        print('stop called')
        self.done = True
        self.quit()
        self.destroy()

在试图解决这个问题之后,我只使用了简单的微调器。总比没有好,技术上做得好。如果有人找到方法让这个微调器的想法与更新gui一起工作,我会很感激。

class Simple_Spinner(tk.Toplevel):
    def __init__(self,master,text=None):
        super().__init__(master)
        self.grab_set()
        self.protocol("WM_DELETE_WINDOW", self.do_nothing)
        if text is None:
            text = 'Program is processing'
        tk.Label(self,text=text).pack()
        self.update()

    def do_nothing(self):
        return

    def stop(self):
        self.grab_release()
        self.destroy()

1 个答案:

答案 0 :(得分:0)

如果您无法衡量进度并且只是想显示它很忙,并且用户不应该同时做其他事情,只需将窗口鼠标光标更改为手表:

>>> import tkinter
>>> root = tkinter.Tk()
>>> root.configure(cursor='watch')

并以

结束
>>> root.configure(cursor='arrow')

你可以根据你的代码进行调整,这就像我想的那样简单。