2014-09-26 1 views
0

Пожалуйста, запустите следующий пример.Python Tkinter: Неисправность индикатора хода при использовании многопоточности

Я создал индикатор выполнения для своего приложения, и нажав кнопку «Открыть», отобразится индикатор выполнения. Тем не менее, прогресс бар не заполняется и представляется, что сценарий привал на

bar.set(i) 

когда функция ProgressBarLoop называется.

from Tkinter import Tk, Frame, BOTH, Label, Toplevel, Canvas, Button 
import thread 
import time 

class ProgressBar: 
    def __init__(self, parent, width, height): 
     master = Toplevel(parent) 
     master.protocol('WM_DELETE_WINDOW', self.hide) 
     self.master = master 
     self.master.overrideredirect(True) 
     ws = self.master.winfo_screenwidth() 
     hs = self.master.winfo_screenheight() 
     w = (True and ws*0.2) or 0.2 
     h = (True and ws*0.15) or 0.15 
     x = (ws/2) - (w/2) 
     y = (hs/2) - (h/2) 
     self.master.geometry('%dx%d+%d+%d' % (width, height * 2.5, x, y)) 

     self.mode = 'percent' 
     self.ONOFF = 'on' 
     self.width = width 
     self.height = height 
     self.frame = None 
     self.canvas = None 
     self.progressBar = None 
     self.backgroundBar = None 
     self.progressformat = 'percent' 
     self.label = None 
     self.progress = 0 

     self.createWidget() 
     self.frame.pack() 
     self.set(0.0)     # initialize to 0% 
    def createWidget(self): 
     self.frame = Frame(self.master, borderwidth = 1, relief = 'sunken') 
     self.canvas = Canvas(self.frame) 
     self.backgroundBar = self.canvas.create_rectangle(0, 0, self.width, self.height, fill = 'darkblue') 
     self.progressBar = self.canvas.create_rectangle(0, 0, self.width, self.height, fill = 'blue') 
     self.setWidth() 
     self.setHeight() 
     self.label = Label(self.frame, text = 'Loading...', width = 20) 
     self.label.pack(side = 'top') # where text label should be packed 
     self.canvas.pack() 
    def setWidth(self, width = None): 
     if width is not None: 
     self.width = width 
     self.canvas.configure(width = self.width) 
     self.canvas.coords(self.backgroundBar, 0, 0, self.width, self.height) 
     self.setBar() # update progress bar 
    def setHeight(self, height = None): 
     if height is not None: 
     self.height = height 
     self.canvas.configure(height = self.height) 
     self.canvas.coords(self.backgroundBar, 0, 0, self.width, self.height) 
     self.setBar() # update progress bar 
    def set(self, value): 
     if self.ONOFF == 'off': # no need to set and redraw if hidden 
     return 
     if self.mode == 'percent': 
     self.progress = value 
     self.setBar() 
     return 
    def setBar(self): 
     self.canvas.coords(self.progressBar,0, 0, self.width * self.progress/100.0, self.height) 
     self.canvas.update_idletasks() 
    def hide(self): 
     if isinstance(self.master, Toplevel): 
     self.master.withdraw() 
     else: 
     self.frame.forget() 
     self.ONOFF = 'off' 
    def configure(self, **kw): 
     mode = None 
     for key,value in kw.items(): 
     if key=='mode': 
      mode = value 
     elif key=='progressformat': 
      self.progressformat = value 
     if mode: 
     self.mode = mode 
def ProgressBarLoop(window, bar): 
    bar.configure(mode = 'percent', progressformat = 'ratio') 
    while(True): 
     if not window.loading: 
     break 
     for i in range(101): 
     bar.set(i) 
     print "refreshed bar" 
     time.sleep(0.001) 
    bar.hide() 

class Application(Frame): 
    def __init__(self, parent): 
     Frame.__init__(self, parent) 
     self.pack(fill = BOTH, expand = True) 
     parent.geometry('%dx%d+%d+%d' % (100, 100, 0, 0)) 
     Button(parent, text = "Open", command = self.onOpen).pack() 
    def onOpen(self, event = None): 
     self.loading = True 
     bar = ProgressBar(self, width=150, height=18) 
     thread.start_new_thread(ProgressBarLoop, (self, bar)) 
     while(True): 
     pass 
root = Tk() 
Application(root) 
root.mainloop() 

EDIT:

После попытки ответа Дано, он работает, но я получаю следующее сообщение об ошибке:

Exception in thread Thread-1: 
Traceback (most recent call last): 
    File "/mnt/sdev/tools/lib/python2.7/threading.py", line 551, in __bootstrap_inner 
    self.run() 
    File "/mnt/sdev/tools/lib/python2.7/threading.py", line 504, in run 
    self.__target(*self.__args, **self.__kwargs) 
    File "/home/jun/eclipse/connScript/src/root/nested/test.py", line 88, in ProgressBarLoop 
    bar.set(i) 
    File "/home/jun/eclipse/connScript/src/root/nested/test.py", line 61, in set 
    self.setBar() 
    File "/home/jun/eclipse/connScript/src/root/nested/test.py", line 64, in setBar 
    self.canvas.coords(self.progressBar,0, 0, self.width * self.progress/100.0, self.height) 
    File "/mnt/sdev/tools/lib/python2.7/lib-tk/Tkinter.py", line 2178, in coords 
    self.tk.call((self._w, 'coords') + args))) 
RuntimeError: main thread is not in main loop 

ответ

0

Проблема заключается в том, что вы запускаете бесконечный цикл сразу же после того, как вы начинаете резьба:

def onOpen(self, event = None): 
    self.loading = True 
    bar = ProgressBar(self, width=150, height=18) 
    thread.start_new_thread(ProgressBarLoop, (self, bar)) 
    while(True): # Infinite loop 
     pass 

Из-за этого управление никогда не возвращается к Tk цикл событий, поэтому индикатор выполнения никогда не может быть обновлен. Код должен работать нормально, если вы удалите цикл.

Кроме того, вы должны использовать threading модуль вместо thread модуля, как это было предложено в Python docs:

Note The thread module has been renamed to _thread in Python 3. The 2to3 tool will automatically adapt imports when converting your sources to Python 3; however, you should consider using the high-level threading module instead.

Собираем в целом, вот что onOpen должен выглядеть следующим образом:

def onOpen(self, event = None): 
    self.loading = True 
    bar = ProgressBar(self, width=150, height=18) 
    t = threading.Thread(target=ProgressBarLoop, args=(self, bar)) 
    t.start() 

Редактировать:

Попытка Обновление виджетов tkinter из нескольких потоков несколько утомительно. Когда я пробовал этот код на трех разных системах, я получал три разных результата. Избежание петли в резьбовом метод помогает избежать ошибок:

def ProgressBarLoop(window, bar, i=0): 
    bar.configure(mode = 'percent', progressformat = 'ratio') 
    if not window.loading: 
     bar.hide() 
     return 
    bar.set(i) 
    print "refreshed bar" 
    i+=1 
    if i == 101: 
     # Reset the counter 
     i = 0 
    window.root.after(1, ProgressBarLoop, window, bar, i) 

Но на самом деле, если мы не используем бесконечный цикл в ProgressBarLoop во всяком случае, на самом деле нет необходимости использовать отдельный поток на всех. Эта версия ProgressBarLoop может быть вызвана в основном потоке, и GUI не будет заблокирован за какое-либо заметное время.

+0

Хм, я получаю сообщение об ошибке при закрытии окна RuntimeError: основной поток не находится в основном цикле –

+0

@JamestheGreat Вы используете точный пример кода, который вы указали выше, за исключением моего метода onOpen? Я не смог воспроизвести эту ошибку. Использование 'Tk' с потоками несколько сложно сделать безопасно, но пока ваш основной поток находится в' root.mainloop() ', он ожидал, что он будет вести себя нормально. – dano

+0

Я работаю на Python 2.7 и в Linux. –

Смежные вопросы