How do I trigger an event after a widget is displayed?

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

PR up: Handle `null` _view_count by bollwyvl · Pull Request #3721 · jupyter-widgets/ipywidgets · GitHub

But the above hack won’t break if that was merged.

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