Il codice proposto non funziona come dovrebbe alla riga 105:
#self.p_bar.grid_remove() # not work!!
Si tratta di una finestra che mostra una etichetta e una progress bar con l'avanzamento di una certa operazione eseguita da una classe apposita ("Job").
L'etichetta che mostra lo stato dei lavori e la progress bar funzionano egregiamente ma alla riga incriminata, a lavoro ultimato, dovrebbe essere "nascosta" la progress bar.
Il comando "grid_remove()" lascia il programma in stato "bloccato", anche se posto in un blocco try-except. Stesso malfunzionamento con "grid_forget()".
import thread
import threading
import time
import Tkinter
from Tkinter import *
import ttk
import tkMessageBox
class Job():
def __init__(self, number):
self.stop_requested = False
self.is_running = False
self.number = number
self.current = 0
def do_work(self):
self.stop_requested = False
self.is_running = True
for x in range(1, self.number+1):
if self.stop_requested:
break
time.sleep(0.7)
self.current = x
print 'job %s' % self.current
self.is_running = False
def stop(self):
self.stop_requested = True
self.is_running = False
class Application(Frame):
def __init__(self, master=None):
#Frame.__init__(self, master, width=400, height=250, padx=20, pady=20, background='#F0F0FA')
Frame.__init__(self, master, width=400, height=250)
self.grid(padx=10, pady=10)
self.createWidgets()
#create a job and run it in a thread to leave __init__ else window is not drawn
job_1 = Job(10)
t = threading.Thread(target=self._start, args=(job_1,) )
t.start()
def createWidgets(self):
print 'createWidgets'
control_padding = {'padx':5, 'pady':3}
self.label_job1 = ttk.Label(self, text='Job 1')
self.label_job1.grid(control_padding, sticky=W)
self.label_job1.columnconfigure(0, weight=1)
self.label_job1_text = ttk.Label(self, text='...')
self.label_job1_text.grid(control_padding, row=0, column=1, sticky=W)
self.label_job1_text.columnconfigure(1, weight=3)
self.label_job2 = ttk.Label(self, text='Job 2')
self.label_job2.grid(control_padding, row=2, column=0, sticky=W)
self.label_job2_text = ttk.Label(self, text='...')
self.label_job2_text.grid(control_padding, row=2, column=1, sticky=W)
self.p_bar = ttk.Progressbar(self, orient='horizontal', length=self['width'], mode='determinate')
self.p_bar.grid(control_padding, columnspan=2)
self.button_quit = ttk.Button(self, text='ABANDON', command=self.stop)
self.button_quit.grid(control_padding, columnspan=2, sticky=SE)
def stop(self):
if self.job:
self.job.stop()
time.sleep(0.5)
self.quit()
def _start(self, job_1, job_2=None):
print 'start'
if job_2:
self.label_job2_text['text'] = "wait job 1 finish"
else:
self.label_job2_text['text'] = "not requested"
self.job = job_1
self.p_bar['maximum'] = self.job.number
self.p_bar['value'] = 0
#thread for job 1
t = threading.Thread(target=self.job.do_work )
t.start()
while self.job.is_running:
self.label_job1_text['text'] = 'running, ({current}/{total})'.format(
current=self.job.current, total=self.job.number
)
self.p_bar['value'] = self.job.current
time.sleep(0.5) #update GUI
print 'end'
self.label_job1_text['text'] = 'done!'
#self.p_bar.grid_remove() # not work!!
#self.p_bar['length'] = 0
self.button_quit['text'] = "CLOSE"
#self.stop() # automatically close at end
root = Tk()
app = Application(master=root)
def quit_handler():
if tkMessageBox.askokcancel("Quit?", "Are you sure you want to quit?"):
app.stop()
#root.quit()
root.protocol("WM_DELETE_WINDOW", quit_handler)
app.master.title('Progress bar test')
app.mainloop()
#if root.state == root.state.
try:
root.destroy()
except:
print 'fail to destroy root'
pass
print '### FINISH ###'
Per mia comodità il codice è presente anche qui:
pastebinNon so perché non sia possibile tale operazione.
Ho provato a far eseguire blocchi di codice in altri thread ed altre modifiche al codice ma questa resta la forma più lineare e logica e vorrei lasciarla così o migliorarla.
Per ora la cosa più importante è capire il perché, poi da li trovare il modo giusto per ottenere il risultato.
Accetto suggerimenti anche su una eventuale diversa impostazione del codice.
Di fatto volevo esporre la funzione "start()" pubblica e richiamarla da fuori della classe Application, obbligatoriamente prima di "mainloop()" ma questo comportava il dover usare un ulteriore "livello" di thread per poter far partire la "renderizzazione" della finestra (e i suoi widget) immediatamente e non dopo che il "lavoro" di Job fosse terminato, alla fine ho ceduto ed ho spostato la creazione di Job all'interno di Application. Lo scopo era tenerlo "fuori" e renderlo indipendente dalla Application che in realtà è un'alternativa alla nuda e semplice console.
La mia priorità comunque è quella di capire perché "grid_remove()" non funzioni come mi aspetto.
Sospetto sia per via del thread; al di fuori del thread in cui è eseguita la funzione "start" infatti funziona (ma ovviamente a me serve che sia chiamata li dove l'ho messa).
Alessandro