I have a custom widget that I’m updating for compatibility with ipywidgets 8.
The widget has a callback that needs to be made after each time the widget is displayed. Previously I used the on_displayed
method for this, but that method seems to have been removed in the new version of the library.
What alternatives are there to on_displayed
? What is the recommended way to implement a similar callback?
All widgets have a (slightly broken, undocumented) ._view_count
member.
from ipywidgets import IntSlider
x = IntSlider(_view_count=0) # by default `None`, this is the "broken"
vc = IntSlider(description="view count")
def on_displayed(change):
if not change.old and change.new:
vc.value = change.new
x.observe(on_displayed, ["_view_count"])
vc
This would give a single slider, at 0.
x
This would show another slider, and bump the first up to 1
.
1 Like
Thank you. That partially works for me, but for some reason I am unable to display()
objects in the callback.
Below is a simplified example of what I am attempting to achieve. Essentially, when my custom widget is displayed, I want to automatically display an Output
below it, which captures the output of the events.
from ipywidgets import Button, Output
from IPython.display import display
class CustomButton(Button):
"""Button that prints events to an Output widget that gets
displayed below each instance"""
def __init__(self, **kwargs):
Button.__init__(self, _view_count=0,
description='Click Me', **kwargs)
# Create an Output() widget to display the text
self.out = Output()
# When clicked, write to output
def click_callback(event):
with self.out:
print('Clicked')
self.on_click(click_callback)
# Automatically insert the output beneath the button when displayed
# PROBLEM: The event is called, but the output is not displayed
self.observe(lambda event: display(self.out), names="_view_count")
# In ipywidgets 7.x this works
# self.on_displayed(lambda widget: display(widget.out))
cb = CustomButton()
cb
For the general case, It’s undefined where it would display, as that message is now coming from something other than the results of execution, but rather execution plus 1-10 other computers doing something.
An alternative would be to pre-create:
-
future_display = display("", display_id=True)
-
future_widget = Box()
- update its
.children
with your output
As an aside: Output
is kind of a hot mess, with a lot of edge cases: I recommend trying to use other, less peril-fraught widgets when possible, especially when getting started.
For the sake of people searching and finding this thread in the future:
The above suggestion technically works but displays the output above where the widget renders, rather than below it. You can see my modified toy code here:
from ipywidgets import Button, Output
from IPython.display import display
class CustomButton(Button):
"""Button that prints events to an Output widget that gets
displayed below each instance (actually above)"""
def __init__(self, **kwargs):
Button.__init__(self, _view_count=0,
description='Click Me', **kwargs)
# Create an Output() widget to display the text
self.out = Output()
future_display = display(self.out, display_id=True)
# When clicked, write to output
def click_callback(event):
with self.out:
print('Clicked')
self.on_click(click_callback)
# Automatically insert the output beneath the button when displayed
# PROBLEM: The event is called, but the output is not displayed
self.observe(lambda event: future_display.update(self.out), names="_view_count")
# In ipywidgets 7.x this works
# self.on_displayed(lambda widget: display(widget.out))
cb = CustomButton()
cb
This is undesirable for my use case and I have not been able to find a workaround that uses observe()
.
Instead, I have restructured my widget so that the Output
is a child of the top-level widget, rather than sibling. This allows me to side-step using observe()
events entirely.
Thank you, bollwyvl, for your help.
2 Likes