【问题标题】:How to get progressbar start() info from one window (class) to other?如何从一个窗口(类)到另一个窗口(类)获取进度条开始()信息?
【发布时间】:2013-05-01 11:10:17
【问题描述】:

有一个带有菜单和进度条的主窗口。带有 OK 按钮的对应窗口在菜单命令时打开,并且 OK 按钮启动该过程(此处:3 秒睡眠)。 通信窗口是通过继承我未在此处提供的类创建的(如果需要回答,请告诉我)。方法applyok 覆盖了母类中的现有方法。

现在我的问题:由于进度条位于主窗口(App 类)中,而progressbar(start)progressbar(stop) 在通信窗口中,我不知何故必须通过母类 tkSimpleDialog.Dialog 传递(开始)和(停止)到类 App。所以我想我也重写了__init__(self..)方法,提供self.到进度条。

我怎样才能做到这一点?

import Tkinter, ttk, tkFileDialog, tkSimpleDialog, time, threading

class App:
  def __init__(self, master, progressbar):
    self.progress_line(master)

  def progress_line (self, master):
    self.progressbar = ttk.Progressbar(master, mode='indeterminate')
    self.progressbar.place(anchor = 'ne', height = "20", width = "150", x = "175", y = "30")

class AppMenu(object):

  def __init__(self, master, progressbar):
    self.master = master
    self.menu_bar()

  def menu_bar(self):
    menu_bar = Tkinter.Menu(self.master)
    self.menu_bar = Tkinter.Menu(self.master)
    self.master.config(menu=self.menu_bar)
    self.create_menu = Tkinter.Menu(self.menu_bar, tearoff = False)
    self.create_menu.add_command(label = "do", command = self.do)
    self.menu_bar.add_cascade(label = "now", menu = self.create_menu)

  def do(self):
    do1 = Dialog(self.master, progressbar)    

class Dialog(tkSimpleDialog.Dialog):

  def __init__(self, parent, progressbar):

    tkSimpleDialog.Dialog.__init__(self, parent, progressbar)
    self.transient(parent)

    self.parent = parent
    self.result = None

    self.progressbar = progressbar

    body = Frame(self)
    self.initial_focus = self.body(body)
    body.pack(padx=5, pady=5)

    self.buttonbox()
    self.grab_set()

    if not self.initial_focus:
        self.initial_focus = self

    self.protocol("WM_DELETE_WINDOW", self.cancel)
    self.geometry("+%d+%d" % (parent.winfo_rootx()+50, parent.winfo_rooty()+50))
    self.initial_focus.focus_set()
    self.wait_window(self)

  def ok(self, event=None):
    self.withdraw()
    self.start_foo_thread()
    self.cancel()
  def apply(self):
    time.sleep(5)

  def start_foo_thread(self):
    global foo_thread
    self.foo_thread = threading.Thread(target=self.apply)
    self.foo_thread.daemon = True
    self.progressbar.start()
    self.foo_thread.start()
    master.after(20, check_foo_thread)

  def check_foo_thread(self):
    if self.foo_thread.is_alive():
        root.after(20, self.check_foo_thread)
    else:
        self.progressbar.stop()    

master = Tkinter.Tk()
progressbar = None
app = App(master, progressbar)
appmenu = AppMenu(master, progressbar)
master.mainloop()

错误信息: 先点击ok后:

Exception in Tkinter callback
Traceback (most recent call last):
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1410, in __call__
  File "ask-progressbar.py", line 57, in ok
    self.start_foo_thread()
  File "ask-progressbar.py", line 66, in start_foo_thread
    self.progressbar.start()
AttributeError: Dialog2 instance has no attribute 'progressbar'

秒:关闭应用后

Exception in Tkinter callback
Traceback (most recent call last):
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1410, in __call__
  File "ask-progressbar.py", line 26, in do
    do1 = Dialog2(self.master, progressbar)
  File "ask-progressbar.py", line 33, in __init__
    self.transient(parent)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1652, in wm_transient
TclError: can't invoke "wm" command:  application has been destroyed

【问题讨论】:

  • 请不仅仅包含您收到的错误消息——带有违规行的整个 Traceback 会很有帮助。
  • 这不是您特别要询问的问题,但您不应该先调用self.foo_thread.start(),然后再调用self.apply(),因为后者会在第一个被调用时发生,因为它是给定的当target 创建Thread 实例时。
  • 好的,我已经更正了

标签: python python-2.7 class progress-bar ttk


【解决方案1】:

以下是您的代码的工作版本。我必须解决许多问题,因为您没有更改代码中的许多内容,而我对您的其他 question 的回答是关于进度条的。

您的主要问题的答案基本上是您必须传递实例并在必要时在所涉及的各种类实例中记住它,以便他们的方法在需要时可以通过self 参数使用它。此外,您尝试派生和覆盖tkSimpleDialog.Dialog 基类方法的方式既过于复杂又不正确。

通常最好(也是最简单)的做法是提供您自己的validate()apply() 方法,因为它就是这样设计的。如果您还需要自己的__init__() 构造函数,那么只将参数传递给它从子类中的方法中理解的基类方法很重要。如果您需要更多功能,通常可以通过其他仅派生类方法提供,只有它或您创建的其他类知道。

不管怎样,这就是我最终得到的结果:

import Tkinter, ttk, tkFileDialog, tkSimpleDialog, time, threading

class App:
    def __init__(self, master):
        self.progress_line(master)

    def progress_line(self, master):
        # the value of "maximum" determines how fast progressbar moves
        self._progressbar = ttk.Progressbar(master, mode='indeterminate', 
                                            maximum=4) # speed of progressbar
        self._progressbar.place(anchor='ne', height="20", width="150", 
                                x="175", y="30")
    @property
    def progressbar(self):
        return self._progressbar # return value of private member

class AppMenu(object):
  def __init__(self, master, progressbar):
      self.master = master
      self.menu_bar()
      self.progressbar = progressbar

  def menu_bar(self):
      self.menu_bar = Tkinter.Menu(self.master)
      self.master.config(menu=self.menu_bar)
      self.create_menu = Tkinter.Menu(self.menu_bar, tearoff=False)
      self.create_menu.add_command(label="do", command=self.do)
      self.menu_bar.add_cascade(label="now", menu=self.create_menu)

  def do(self):
      Dialog(self.master, self.progressbar) # display the dialog box

class Dialog(tkSimpleDialog.Dialog):
    def __init__(self, parent, progressbar):
        self.progressbar = progressbar
        tkSimpleDialog.Dialog.__init__(self, parent, title="Do foo?")

    def apply(self):
        self.start_foo_thread()

    # added dialog methods...
    def start_foo_thread(self):
        self.foo_thread = threading.Thread(target=self.foo)
        self.foo_thread.daemon = True
        self.progressbar.start()
        self.foo_thread.start()
        master.after(20, self.check_foo_thread)

    def check_foo_thread(self):
        if self.foo_thread.is_alive():
            master.after(20, self.check_foo_thread)
        else:
            self.progressbar.stop()

    def foo(self): # some time-consuming function...
        time.sleep(3)


master = Tkinter.Tk()
master.title("Foo runner")
app = App(master)
appmenu = AppMenu(master, app.progressbar)
master.mainloop()

希望这会有所帮助。

【讨论】:

  • 太棒了,非常感谢,它有效。我现在必须研究你的答案和你给出的代码。
  • @solarisman:不客气,很高兴听到。我还建议阅读(并关注)PEP 8 Style Guide for Python Code,因为它会让你的代码更容易——至少对其他人来说——阅读和修改;-)。
  • 也感谢您。事实上,我打算在 Stackoverflow(几天前我发现它并发现它对学习非常感兴趣)询问代码风格......如何做到最好......因为我基本上刚开始编程和 Python,我不确定我是否朝着正确的方向前进。所以我会彻底研究PEP8。谢谢!
  • @martineau 我尝试将您的两个解决方案都调整为我的problem,但不知何故它不起作用; UI 元素仅在用户完成提示后更新。请你看一下好吗?谢谢!
  • @mashedpotatoes:很可能是因为您使用的是线程,而 tkinter 并不真正支持。您可以创建多个线程,但只有其中一个(通常是主线程)可以与其及其小部件交互。解决方法是使用线程安全Queue 在线程之间传递信息。主线程可以使用通用小部件after() 方法定期检查队列的内容并根据需要更新GUI。我已经发布了几个答案。
【解决方案2】:

这是另一个更简单的解决方案,不需要使用线程——因此在您的情况下可能更容易使用/适应。它在耗时的foo() 函数期间多次调用进度条小部件的update_idletasks() 方法。同样,它说明了如何将进度条传递给需要它的代码的各个部分。

import Tkinter, ttk, tkFileDialog, tkSimpleDialog, time

class App:
    def __init__(self, master):
        self.progress_line(master)

    def progress_line(self, master):
        self._progressbar = ttk.Progressbar(master, mode='indeterminate')
        self._progressbar.place(anchor='ne', height="20", width="150", 
                                x="175", y="30")
    @property
    def progressbar(self):
        return self._progressbar # return value of private member

class AppMenu(object):
    def __init__(self, master, progressbar):
        self.master = master
        self.menu_bar()
        self.progressbar = progressbar

    def menu_bar(self):
        self.menu_bar = Tkinter.Menu(self.master)
        self.master.config(menu=self.menu_bar)
        self.create_menu = Tkinter.Menu(self.menu_bar, tearoff=False)
        self.create_menu.add_command(label="do foo", command=self.do_foo)
        self.menu_bar.add_cascade(label="now", menu=self.create_menu)

    def do_foo(self):
        confirm = ConfirmationDialog(self.master, title="Do foo?")
        self.master.update() # needed to completely remove conf dialog
        if confirm.choice:
            foo(self.progressbar)

class ConfirmationDialog(tkSimpleDialog.Dialog):
    def __init__(self, parent, title=None):
        self.choice = False
        tkSimpleDialog.Dialog.__init__(self, parent, title=title)

    def apply(self):
        self.choice = True

def foo(progressbar):
    progressbar.start()
    for _ in range(50):
        time.sleep(.1) # simulate some work
        progressbar.step(10)
        progressbar.update_idletasks()
    progressbar.stop()

master = Tkinter.Tk()
master.title("Foo runner")
app = App(master)
appmenu = AppMenu(master, app.progressbar)
master.mainloop()

【讨论】:

  • 致@martineau:我现在使用标准的线程处理方式完成了我的程序工作。非常感谢您的帮助。
  • @solarisman:这是个好消息——但是你为什么把你接受的答案改为这个非线程版本?
  • 致@martineau:我已经纠正了这一点。为什么最近的答案不在底部? :) 为什么在此评论字段的开头不接受(at)martineau?如果我之前使用“to”之类的东西,它就可以工作,一旦我点击“save”,它就会在开始时被删除
  • @solarisman:我认为除非有公认的答案,否则stackoverflow会首先列出最新的答案。顺便说一句,您可能想更新您的问题并提及您如何解决self.input.get() 遇到的问题。
  • 感谢@martineau 的提示。我在原始帖子中删除了第二个问题。老实说,我不记得出了什么问题。我从头开始构建所有东西,然后它工作了。
猜你喜欢
  • 2021-04-08
  • 1970-01-01
  • 1970-01-01
  • 2018-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多