danjjl
danjjl

Reputation: 462

How to update a ipywidget called from the callback of an ipywidget?

I am having troubles understanding the functioning of update on an IPython display.

It seems that to be updated the widget needs to be instantiated at the top level and cannot be instantiated from the callback of an ipywidget.

Here is a non-working exampled of an IPython display called by a ipywidget Button:

import functools
import ipywidgets as widgets
from IPython.display import display
import random

def foo(b):
    label = display('Initial text', display_id = True)

    def on_button_click(b, label):
        label.update('New number {}'.format(random.random()))

    button = widgets.Button(description="New number")
    display(button)           
    button.on_click(functools.partial(on_button_click, label=label))


button = widgets.Button(description="Button")
button.on_click(foo)
display(button)

Binder

In this example clicking on Button displays a text label and a new button (New number). However, clicking on New number does not update the text label.

Calling foo(0) directly works correctly. When the New number button is clicked, the text label is updated.

Is something wrong with the code example? Why can't an ipywidget be updated if it is instantiated from the callback of another widget?

Upvotes: 2

Views: 7017

Answers (2)

Yuval
Yuval

Reputation: 3443

I think what you mean to achieve is this:

import ipywidgets as widgets
from IPython.display import display
import random

text = widgets.Text('Initial text')
out = widgets.HBox([text])

def foo(b):
  text.value = 'New number {}'.format(random.random())
  # alternatively: out.children = [widgets.Text('New number {}'.format(random.random()))]

button = widgets.Button(description="Button")
button.on_click(foo)
display(button)
display(out)

Upvotes: 0

ac24
ac24

Reputation: 5575

I have seen this issue before and it can be due with where the output is routed when using display. I can get the desired behaviour by wrapping your function with a context manager Output widget.

For anything more complex I would consider building a class deriving from HBox, containing an Output widget and various buttons.


import functools
import ipywidgets as widgets
from IPython.display import display
import random

out = widgets.Output()


def foo(b):
    with out:
        label = display('Initial text', display_id = True)

        def on_button_click(b, label):
            label.update('New number {}'.format(random.random()))

        button = widgets.Button(description="New number")
        display(button)           
        button.on_click(functools.partial(on_button_click, label=label))


button = widgets.Button(description="Button")
button.on_click(foo)
display(button)
display(out)

Upvotes: 3

Related Questions