【问题标题】:Python Tkinter: Progress bar malfunction when using multi-threadingPython Tkinter:使用多线程时进度条故障
【发布时间】:2014-09-26 03:38:43
【问题描述】:

请运行以下示例。

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

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

【问题讨论】:

    标签: python multithreading canvas tkinter progress-bar


    【解决方案1】:

    问题是您在启动线程后立即运行无限循环:

    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 中所建议的那样:

    注意 thread 模块在 Python 3 中已重命名为 _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 不会在任何明显的时间内被阻塞。

    【讨论】:

    • 嗯,关闭窗口时出现错误 RuntimeError: main thread is not in main loop
    • @JamestheGreat 除了我的onOpen 方法外,您是否正在运行上面包含的确切示例代码?我无法重现该错误。将Tk 与线程一起使用在安全上有些棘手,但只要您的主线程在root.mainloop() 中,它就会预期它的行为正常。
    • 我在 Python 2.7 和 Linux 上运行。
    • 我在进度条运行时按下了应用程序上的“x”关闭按钮退出。
    • 请查看编辑版本以获取确切的错误消息
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-03
    • 1970-01-01
    相关资源
    最近更新 更多