How to get progressbar start() info from one window (class) to other?
Asked Answered
J

2

1

There is a main window with menu and the progressbar. A correspondence window with OK button opens upon menu command and the OK button starts the process (here: 3 sec. sleep). The correspondence window is created via inheritance from a class I have not provided here (If required for answer, please let me know). The methods apply and ok override existing methods in the mother class.

Now my problem: Since the progressbar sits in the main window (class App) and progressbar(start) and progressbar(stop) in the correspondence window I somehow have to pass (start) and (stop) via the mother class tkSimpleDialog.Dialog to class App. So I thought I also override the __init__(self..) method, provide self. to progressbar.

How can I make this work?

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

error messages: first after clicking 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'

second: after closing app

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
Johnsten answered 7/5, 2013 at 13:14 Comment(3)
Please include more than just the error message you're getting -- the whole Traceback with the offending line would be helpful.Bengurion
It's not the problem you're specifically asking about, but you shouldn't be calling self.foo_thread.start() followed then by self.apply(), because the latter will happen when the first is called since it was given as the target the Thread instance was created.Bengurion
ok, I have corrected thatJohnsten
B
2

Below is a working version of your code. There were a number of issues I had to fix because you didn't change a number of things in the code from my answer to your other question about progressbars.

The answer to your main question here is basically that you have to pass the instance around and remember it when necessary in the various class instances involved so that their methods will have it available through theself argument when they need it. Also, the way you were trying to derive and override the tkSimpleDialog.Dialog base class methods was both over-complicated and incorrect as well.

Usually the best (and simplest) thing to do is just supply your own validate() and apply() methods since that's how it was designed to work. If you also need your own __init__() constructor, it's important to only pass parameters to the base class' method that it understands from within the one in the subclass. If you need more functionality, it can usually be provided via additional derived-class-only methods, that only it or other classes you've also created know about.

Anyway, here's what I ended-up with:

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

Hope this helps.

Bengurion answered 7/5, 2013 at 17:53 Comment(8)
fantastic, thanks a lot, it works. I now have to study your answer and the code you have given.Johnsten
@solarisman: You're welcome, that's good to hear. I would also recommend reading (and following) PEP 8 Style Guide for Python Code because it will make your code easier -- at least for others -- to read and modify ;-).Bengurion
thanks for this also. i was in fact going to ask here at Stackoverflow (which I discovered a couple of days ago and find absolutely fascinating for learning) about code style..how to best..since i basically just started with programming and Python and I am uncertain about whether I go into the right direction. So I will study PEP8 thoroughly. thanks!Johnsten
@Bengurion I tried adapting your both of your solutions to my problem but somehow it doesn't work; the UI elements only update after the prompt is completed by the user. Would you please take a look? Thank you!Faenza
@mashedpotatoes: It's most likely because you're using threading, which tkinter doesn't really support. You can create multiple threads, but only one of them — often the main thread — can interact with it and its widgets. The workaround is to use a threadsafe Queue to pass information between the threads. The main thread can use the universal widget after() method to periodically check the queue's contents and update the GUI as necessary. I've posted several answers that do it.Bengurion
I also tried your non-thread method, but I don't understand why mine doesn't work as intended. My problem is similar to this question, only that mine should run on button click, and the progress bar should pause when asking for user input, and continue after.. I'll look into your answers in the mean time, thanks!Faenza
@mashedpotatoes: Your threaded version is using tkinter in both the run_foo() thread and the main thread — which is likely why it doesn't work. I am referring to both the Progressbar updating as well as the calling of askdirectory(). I think you might find the information about event-driven programming in the accepted answer to the question Tkinter — executing functions over time helpful to understand.Bengurion
@Bengurion I finally understood it, thanks, but in the end I had to use the non-threaded solution; trying to solve tkinter and threading using Queue brought up another problem- it doesn't play well with tensorflow too. The non-threaded solution is the most unintrusive way to do these in my case.Faenza
B
2

Here's another, simpler, solution that doesn't require the use of threading -- so could be easier to use/adapt in your case. It calls the progressbar widget's update_idletasks() method multiple times during the time-consuming foo() function. Again, it illustrates how to pass the progressbar around to the various parts of the code that need it.

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()
Bengurion answered 11/5, 2013 at 21:29 Comment(7)
to @martineau: I got my programme work now using the standard way of Threading. Thanks a lot for your help.Johnsten
@solarisman: That's good news -- but why then did you change your accepted answer to this non-threading version?Bengurion
to @martineau: I have corrected that. Why is the most recent answer not at the bottom? :) and why isn't (at)martineau not accepted at the beginning of this comment field? if i use something like "to" before, it works, right at the beginning it gets deleted once i hit "save"Johnsten
@solarisman: I think unless there's an accepted answer, stackoverflow lists the most recent answer first. BTW, you might want to update your question and mention how you solved the problem you were having with the self.input.get().Bengurion
Thanks @Bengurion for that hint. I deleted that second question in my original post. To be honest, I can't recall what was wrong. I built everything up from scratch and then it worked.Johnsten
hello @martineau, if you look at this question you might think that I still have not understood threading sufficiently :(Johnsten
@solarisman: Multi-threading can be tricky, especially when coupled with Tkinter. Maybe you should reconsider using it and switch to the single-threaded approach in this answer (which is the main reason I added it).Bengurion

© 2022 - 2024 — McMap. All rights reserved.