8

I have the same issue as TheInterestedOne asked here.

I need to create two buttons for the user, and suggest that the user clicks one of the two buttons in the loop; so that the next iteration of the loop occurs only after the user's choice.

I read this source, but I can't make it work for buttons. I don't understand, how the widget attribute changes in the case of using buttons.

from functools import wraps
def yield_for_change(widget, attribute):
    def f(iterator):
        @wraps(iterator)
        def inner():
            i = iterator()
            def next_i(change):
                try:
                    i.send(change.new)
                except StopIteration as e:
                    widget.unobserve(next_i, attribute)
            widget.observe(next_i, attribute) //**button.on_click(on_button_clicked) 
                                                                may be?**
            # start the generator
            next(i)
        return inner
    return f


from ipywidgets import Button
button=Button()


def on_button_clicked():
    print("Button clicked.")


@yield_for_change(button, 'value')
def f():
    for i in range(10):
        print('did work %s'%i)
        x = yield
        button.on_click(on_button_clicked)
David Buck
  • 3,752
  • 35
  • 31
  • 35
  • Hi, and welcome to stackoverflow. You'll have a better chance of getting an answer from someone if you show a specific example of the code you've tried, and where it failed. See [mcve]. – Chris Olsen Mar 19 '19 at 17:00

3 Answers3

7

This version uses awaitio and is modified for buttons.

from ipywidgets import Button
import asyncio

def wait_for_change(widget):
    future = asyncio.Future()
    def getvalue(change):
        future.set_result(change.description)
        widget.on_click(getvalue, remove=True) 
        # we need to free up the binding to getvalue to avoid an InvalidState error
        # buttons don't support unobserve
        # so use `remove=True` 
    widget.on_click(getvalue)
    return future

button = Button(description="wow")

list_to_tag = ["one", "two", "three", "four"]

async def f():
    for i in list_to_tag:
        print("going to tag {}".format(i))
        x = await wait_for_change(button)
        print("tagged {} with {}".format(i, x))
        print()

asyncio.create_task(f())
button
Nikita Popov
  • 896
  • 10
  • 19
IanMulvany
  • 199
  • 2
  • 7
4

To have multiple buttons, you can add additional widgets to one of the previous answers by @IanMulvany.

See <------ references in code below for where this differs from the previous answer.

from ipywidgets import Button, HBox #<----- Add HBox for displaying multiple buttons
import asyncio

def wait_for_change(widget1, widget2): #<------ Rename to widget1, and add widget2
    future = asyncio.Future()
    def getvalue(change):
        future.set_result(change.description)
        widget1.on_click(getvalue, remove=True) #<------ Rename to widget1
        widget2.on_click(getvalue, remove=True) #<------ New widget2
        # we need to free up the binding to getvalue to avoid an IvalidState error
        # buttons don't support unobserve
        # so use `remove=True` 
    widget1.on_click(getvalue) #<------ Rename to widget1
    widget2.on_click(getvalue) #<------ New widget2
    return future

button1=Button(description="wow") #<------ Rename to button1
button2=Button(description="wow2") #<------ New button2 and description

list_to_tag = ["one", "two", "three", "four"]

async def f():
    for i in list_to_tag:
        print('going to tag ', i)
        x = await wait_for_change(button1,button2) #<---- Pass both buttons into the function
        if x == "wow": #<--- use if statement to trigger different events for the two buttons
            print("tagged ", i, "with  %s"%x)
        else:
            print(i, "tagged with %s"%x)
        print("")
        print("")

asyncio.create_task(f())
HBox([button1,button2]) #<----Display both buttons in an HBox
1

Here's the example adapted for Button. Main changes are in the decorator, swapping observe for an on_click, which is sort of equivalent to observe for a Button.

from functools import wraps
def yield_for_change(widget):
    def f(iterator):
        @wraps(iterator)
        def inner():
            i = iterator()
            def next_i(change):
                try:
                    i.send(change)
                except StopIteration as e:
                    widget.unobserve(next_i, attribute)
            widget.on_click(next_i)
            # start the generator
            next(i)
        return inner
    return f


from ipywidgets import Button
button=Button()

def on_button_clicked():
    print("Button clicked.")


@yield_for_change(button)
def f():
    for i in range(10):
        print('did work %s'%i)
        x = yield

f()

button
ac24
  • 5,325
  • 1
  • 16
  • 31