GreySage
GreySage

Reputation: 1182

How can I make a script wait for a button press?

I apologize if the answer to this question is obvious, but I am unfamiliar with the inner workings of Python.

Basic Scenario: I have a Python script that does stuff. Periodically, when certain conditions are met (like a flag variable being set), I want the script to pause and wait for the user to press a GUI button.

How can I implement this?

To clarify, I want it to work like input() does, in that it pauses everything and waits for the function call to be resolved, except that I don't want it tied to text, but rather a GUI button. I plan on using Tkinter to make the button.

My initial thoughts are making a while loop something like this:

x = 2
while (x > 1):
    #do nothing

And then the button will call a function that sets x = 0

Is this the proper way to do this? Is there a better way? Am i missing something obvious?

Sample Code:

class Displayable(object):
    max_display_level = 1   # can be overridden in subclasses
    manual_step = False     # can be overridden in subclasses

    def display(self,level,*args,**kwargs):
        -Do stuff unrelated to the question-

        if (self.manual_step):
           if level <= self.max_display_level:
               input("Waiting for input: ")

The idea behind this is objects will extend the class Displayable and set their own max_display_level and manual_step values. A higher max_display_level means more messages will be displayed (ie. if I call display() with levels = 1,2,3, and 4, if my max_display_level ==2, only the first 2 calls will have all the logic executed. It is a way to allow a user to set the level of verbosity of the execution. It's there for a reason which I won't get into, but it should stay.) If a specific object has manual_step == true and the level is satisfied, it should wait for user input when display() is called. The trick is I want it to wait for a button press instead of text+enter.

Upvotes: 2

Views: 8951

Answers (1)

Delioth
Delioth

Reputation: 1554

If you're planning on using a GUI button (especially tkinter), you shouldn't need to do anything- GUIs tend to run their own infinite loop dealing with drawing and handling events. The moment you all a .mainloop() for tkinter, nothing after that point in your code will be executed except callbacks and tkinter events; i.e.:

root= Tk()
root.mainloop()
print("This won't be printed until root window is closed")

Best practice would be to tie the stuff that should happen when the button is pressed to that button as a command/callback. (Button(master, ... command=callback OR command=lambda *e: callback()))

To do what you would like, I'd suggest putting a Button.disable() at the beginning of longish-running code (so the button doesn't look clickable), and then a Button.enable() at the end so you can click it. Or create/show the button to do the next step at the end of the function (creating that button on-the-fly could let you pass results of this function directly into the callback for the next function via lambdas)

def cb_1(root_window, btn_to_disable):
    btn_to_disable.disable()
    foo = complex_algorithm()
    # Create a button for the next part
    Button(root_window, text='do cb_2 with foo',
           command=lambda e, arg=foo, r=root_window: cb_2(r, arg)).pack()

def cb_2(root, argument):
    print("This is foo, from cb_1:",argument) # prints foo

root = Tk()
btn = Button(root, text="do cb_1",
             command=lambda *e: cb_1(root, btn))
btn.pack()
root.mainloop()
print("This isn't printed until the GUI is closed!")

EDIT: As per your newest edit, it seems that after() will be your friend - it lets a function schedule itself again through tkinter's mainloop, without blocking the GUI's actions. i.e. in Displayable:

def display(self, level, *args, **kwargs):
    #unrelated#
    self.wait_for_next(level, *args, **kwargs)

def wait_for_next(self, level, *args, **kwargs)
    if self.manual_step:
       if (level <= self.max_display_level):
           if self.button_set_value:
               # Do something with button-set value
               print('FOO')
           else:
               # set ourselves to re-check after 1000 ms
               self.root.after(1000,
                               lambda *e:self.wait_for_next(level, *args, **kwargs))

Presumably, wherever your buttons are, they set some value (or just set a flag). When that value is truthy, this would print "FOO". You will probably call display() again at this point.

Another way to do it would be to simply have display() as the callback for the button, so when the button is pressed, display is called.

Upvotes: 1

Related Questions