Как остановить вложенные вычисления и закрыть окно верхнего уровня в tkinter

avatar
fishbacp
8 августа 2021 в 23:34
83
1
0

У меня есть окно Toplevel в сценарии tkinter, которое содержит два индикатора выполнения, соответствующие вложенному циклу. Я хочу добавить кнопку остановки в окно верхнего уровня, чтобы завершить выполнение и закрыть окно верхнего уровня, но не корень.

from tkinter import Tk, Toplevel,Button,Label,TOP,BOTH,DoubleVar
from tkinter.ttk import Progressbar
import time 

root = Tk()
root.configure(bg='lightgray')
root.wm_title("Main")


progress_window = Toplevel()

progress_window.attributes('-topmost', 'true')
progress_window.configure(bg='lightgrey')
progress_window.wm_title("Progress")
progress_window_label = Label(root, text="")
progress_window.geometry('600x300')
progress_window_label.pack()

M=4 # outer loop
N=5 # inner loop

progress1_var=DoubleVar()
progress1bar=Progressbar(master=progress_window,variable=progress1_var,length=M)    
progress1bar.pack(side=TOP,ipady=5,fill=BOTH,expand=True)
progress1_var.set(0)
progress1bar.update()
progress1bar_label=Label(master=progress_window,text='Bar1',bg='lightgray')
progress1bar_label.pack(side=TOP,pady=5,fill=BOTH, expand=True)

progress2_var = DoubleVar()
progress2bar=Progressbar(master=progress_window,variable=progress2_var,length=N)
progress2bar.pack(side=TOP,ipady=5,fill=BOTH, expand=True)
​progress2_var.set(0)
progress2bar.update()
progress2bar_label=Label(master=progress_window,text='Bar2',bg='lightgray')
progress2bar_label.pack(side=TOP,pady=5,fill=BOTH, expand=True)

def _stop():
    return

stop_button=Button(master=progress_window, text="Cancel",command=_stop)
stop_button.pack(side=TOP,pady=5,fill=BOTH, expand=True)


​for t in range(M):

       ​progress1_var.set(t/M)
       ​progress1bar_label.config(text='Bar 1:   '+str(round((t+1)/M*100,3))+'%')
       ​progress1bar.update()

       ​progress2_var.set(0)
       ​progress2bar.update()
   ​
       ​for i in range(N):
           ​progress2_var.set(i/N)
           ​time.sleep(1.0) #Sleep to slow down execution and view progress window
           ​progress2bar_label.config(text='Bar 2:  '+str(round((i+1)/N*100,3))+'%')
           ​progress2bar.update()


​progress_window.destroy()
root.mainloop()

Кнопка остановки появляется там, где должна быть, но не работает и останавливает выполнение. Должно быть, это очень фундаментальная ошибка, но я не вижу, как ее исправить.

Источник
Matiiss
9 августа 2021 в 00:47
0

Я надеюсь, вы знаете, что вызов _stop() абсолютно ничего не делает (кроме траты очень небольшого количества ресурсов), я бы предложил поднять какой-нибудь флаг и иметь несколько операторов if в этих циклах, которые их сломают.

Ответы (1)

avatar
Derek
10 августа 2021 в 14:20
1

Я внес несколько изменений, прежде всего добавил protocol для root и progress_window.

Я также создал флаг killed, который используется для разрыва циклов for. Я поместил их в функцию, вызываемую after.

.

Это останавливает TclError: Invalid command name .!toplevel.!label2

from tkinter import Tk, Toplevel, Button, Label, TOP, BOTH, DoubleVar
from tkinter.ttk import Progressbar
import time 

killed  =  False

root  =  Tk()
root.configure(bg = 'lightgray')
root.wm_title("Main")

root.update()

progress_window  =  Toplevel()

def stop():
    global killed
    killed = True
    progress_window.destroy()
    root.destroy()

# Added protocol controls for exit

root.protocol( "WM_DELETE_WINDOW", stop )
progress_window.protocol( "WM_DELETE_WINDOW", stop )

progress_window.attributes('-topmost', 'true')
progress_window.configure(bg = 'lightgrey')
progress_window.wm_title("Progress")
progress_window_label  =  Label(root, text = "")
progress_window.geometry('600x300')
progress_window_label.pack()

M = 4 # outer loop
N = 5 # inner loop

progress1_var = DoubleVar()
progress1bar = Progressbar(master = progress_window, variable = progress1_var, length = M)    
progress1bar.pack(side = TOP, ipady = 5, fill = BOTH, expand = True)
progress1_var.set(0)
progress1bar.update()
progress1bar_label = Label(master = progress_window, text = 'Bar1', bg = 'lightgray')
progress1bar_label.pack(side = TOP, pady = 5, fill = BOTH, expand = True)

progress2_var  =  DoubleVar()
progress2bar = Progressbar(master = progress_window, variable = progress2_var, length = N)
progress2bar.pack(side = TOP, ipady = 5, fill = BOTH, expand = True)

progress2_var.set(0)
progress2bar.update()

progress2bar_label = Label(master = progress_window, text = 'Bar2', bg = 'lightgray')
progress2bar_label.pack(side = TOP, pady = 5, fill = BOTH, expand = True)

def process():
    for t in range(M):
        if killed:
            break
        progress1_var.set(t/M)
        progress1bar_label.config(text = "Bar 1:    " + str( round( (t+1)/M*100, 3)) + "%")
        progress1bar.update()
        progress2_var.set(0)
        progress2bar.update()
        for i in range(N):
            progress2_var.set(i/N)
            time.sleep(1.0)
            if killed:
                break
            progress2bar_label.config(text = "Bar 2:    " + str( round( (i+1)/N*100, 3)) + "%")
            progress2bar.update()

stop_button = Button(master = progress_window, text = "Cancel", command = stop)
stop_button.pack(side = TOP, pady = 5, fill = BOTH, expand = True)

root.after( 1000, process )
root.mainloop()
fishbacp
10 августа 2021 в 14:58
0

Очень хорошо! Всего пара вопросов. Для моего реального, более сложного сценария индикаторы выполнения создаются внутри длинной функции, принимающей шесть аргументов. Когда progress_window закрыт, я хочу, чтобы root оставался открытым, так как он будет содержать холст рисунка. Я предполагаю, что root.destroy() будет удален из вышеперечисленного. Кроме того, root.after(1000,process) в сочетании с root.update() действительно просто заменяет таймер?

Derek
11 августа 2021 в 00:27
0

Я предлагаю отказаться от sleep и использовать after, хотя это потребует довольно радикальных изменений в вашей базовой программе. Я также заметил, что ваше главное окно root мало что делает, я предположил, что вы сможете продолжить разработку своего кода. Если вы не хотите закрывать все окна, создайте другую функцию специально для root, но идентичную stop (вам может понадобиться сделать еще один флаг killed и связать его с `protocol'.