Python Tkinter:使用多线程时进度条出现故障

时间:2014-09-26 03:38:44

标签: python multithreading canvas tkinter progress-bar

请运行以下示例。

我为我的应用程序创建了一个进度条,然后按下"打开"按钮弹出一个进度条。但是,进度条没有填满,并且似乎脚本停止在

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

编辑:

尝试过dano的答案之后,它可以正常运行,但我收到以下错误:

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

1 个答案:

答案 0 :(得分: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中所示:

  

注意{3}中的thread模块已重命名为_thread   2to3工具将在转换时自动调整导入   Python 3的源代码;但是,您应该考虑使用高级别   而是threading模块。

完全放在一起,这是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不会在任何明显的时间内被阻止。