39

I can register a handler to button.on_click in ipython notebook widgets, but I don't know how to do the same for a dropdown widget

import ipywidgets as widgets
from IPython.display import display

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

button = widgets.Button(description="Click Me!")
display(button)

button.on_click(on_button_clicked)

But for

choose_task = widgets.Dropdown(
    options=['Addition', 'Multiplication', 'Subtraction'],
    value='Addition',
    description='Task:',
)

there seems to be only

on_trait_change(...)

if I register a handler with this, can I use it to access the value of the widget? I have seen examples with the handler and the widget belong to a subclass, and the handler can use self to introspect. But if I don't want to use a subclass, how does the handler know what widget was the target of the event.?

Tim Richardson
  • 6,608
  • 6
  • 44
  • 71

6 Answers6

74

Between this link and the traitlet docs on github and just playing around, I finally figured this out:

w = widgets.Dropdown(
    options=['Addition', 'Multiplication', 'Subtraction', 'Division'],
    value='Addition',
    description='Task:',
)

def on_change(change):
    if change['type'] == 'change' and change['name'] == 'value':
        print("changed to %s" % change['new'])

w.observe(on_change)

display(w)

Overall this looks a lot richer than the deprecated interface, but it could definitely use more examples.

sfjac
  • 7,119
  • 5
  • 45
  • 69
  • 2
    any idea what to do if we want to pass an argument to the on_change function? – fabiob Apr 16 '20 at 16:59
  • 3
    @fabiob use `functools.partial`: https://github.com/jupyter-widgets/ipywidgets/issues/2103#issuecomment-545716743 – alex Aug 21 '20 at 08:33
  • @sfjac how would I convert the return from the dropdown into a str to filter a dataframe and pandas, such as: selection = change['new'] df[df['column' == selection]] – ReinholdN Feb 17 '21 at 23:45
  • is there any solution for this? https://stackoverflow.com/questions/67927358/ipywidgets-dropdown-widgets-how-to-populate-nested-widgets-based-on-selected-dr – jax Jun 10 '21 at 19:28
9

You can specify the change name in observe. This makes for cleaner code, and the handler is not called for changes you don't need:

from IPython.display import display
from ipywidgets import Dropdown

def dropdown_eventhandler(change):
    print(change.new)

option_list = (1, 2, 3)
dropdown = Dropdown(description="Choose one:", options=option_list)
dropdown.observe(dropdown_eventhandler, names='value')
display(dropdown)
Endre Both
  • 5,540
  • 1
  • 26
  • 31
  • any idea how to make the widget disappear/become inactive after one value has been selected? – fabiob Apr 14 '20 at 14:24
  • I am actually looking for a widget with two buttons. Clicking on one action A is performed, clicking on the other one action B is performed. – fabiob Apr 14 '20 at 14:25
  • @fabiob try `change.owner.disabled = True` inside the handler – gokul_uf Jul 06 '21 at 20:59
4

Put it all together

Inspired on previous answers and lambda expressions I use this:

def function(option):
    print(option)


w = widgets.Dropdown(
    options=['None', 'Option 1', 'Option 2', 'Option 3'],
    description='Option:',
    disabled=False
)

w.observe(
    lambda c: plot_content(c['new']) if (c['type'] == 'change' and c['name'] == 'value') else None
)

display(w)
Thomas Devoogdt
  • 816
  • 11
  • 16
  • Thanks for this. I've been looking for methods/examples to combine multiple widgets into selecting values for a function, slicing/dicing data, selecting type of plots from data etc. – CodingMatters Apr 05 '18 at 23:39
  • is there any solution for this? https://stackoverflow.com/questions/67927358/ipywidgets-dropdown-widgets-how-to-populate-nested-widgets-based-on-selected-dr – jax Jun 10 '21 at 19:28
3

I agree that event handling is not as thorough as would be desired: I have been filtering the events as you receive multiple events for a typical dropdown change as the index changes, the value changes, i.e., change['name'].

I am doing the following:

def on_dropdown_change(change):
    if change['name'] == 'value' and (change['new'] != change['old']):
        print('do something with the change')
dropdown =  ipywidgets.Dropdown({options=['one','two','three'],
                                 value='one'})
dropdown.observe(on_dropdown_change)
  • is there any solution for this? https://stackoverflow.com/questions/67927358/ipywidgets-dropdown-widgets-how-to-populate-nested-widgets-based-on-selected-dr – jax Jun 10 '21 at 19:28
2

I believe the idea is to use trait name, e.g. value. For example:

from ipywidgets import Dropdown

def handle_change():
    print type_sel.value

type_sel = Dropdown(description="Keypoint type", options=['surf', 'orb'])
type_sel.on_trait_change(handle_change, name="value")
display(type_sel)

SciPy 2015 Advanced Jupyter Video Tutorial

kpykc
  • 663
  • 6
  • 8
  • 9
    That is horribly documented – Brandon Kuczenski Apr 25 '16 at 22:51
  • 1
    Also, I get a warning when I try to use this: ```[...]/anaconda/lib/python2.7/site-packages/ipykernel/__main__.py:13: DeprecationWarning: on_trait_change is deprecated in traitlets 4.1: use observe instead``` but when I try using `observe` my handler is called three times. – sfjac Oct 20 '16 at 21:09
0

I had the same issue. This also begs the next question, how to interface button actions based on dropdown menu selections.

# Common Imports for Widgets
from __future__ import print_function
from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets

'''
    Precusor:

    <class 'traitlets.utils.bunch.Bunch'>  It is a dictionary-like object containing:

    {'name': 'value', 'old': 'what_ever_the_old_value_was', 'new': 'what_ever_the_new_value_is', 
    'owner': Dropdown(description='the_user_defined_label:', index=1, # I'm not sure what this is
    options=()#list of options passed, 
    value='value_kwarg_value'), 'type': 'change'} # type: action_or_event type

    For more information see:

    https://traitlets.readthedocs.io/en/stable/using_traitlets.html#default-values-and-checking-type-and-value

    or

    https://github.com/jupyter-widgets/tutorial/blob/master/notebooks/08.00-Widget_Events.ipynb

    or a long but well done SciPy talk on the use of widgets @

    https://www.youtube.com/watch?v=HaSpqsKaRbo
'''

foo = ['a','b','c'] # List to use

# Function to apply to drop box object
def bar(x):
    '''
        I am intentionally passing what it is made of so you can see the output.
    '''
    print(x,'\n') # Whole object
    print(x.new,'\n') # New value


# Function for the button to select user input and do work
def get_user_selection(a): # A default arg is needed here, I am guessing to pass self
    # Displays the current value of dropbox1 and dropbox two
    display(dropbox1.value,dropbox2.value)


# creation of a widget dropdown object called dropbox1
dropbox1 = widgets.Dropdown(
    options=foo, # Object to iterate over
    description='Letter:', # User defined 
    value=foo[1], # Default value selection
    rows=len(foo), # The number of rows to display when showing the box
    interactive=True, # This makes the box interactive, I believe this is true by default
);

# Drop box of k,v like pairs 
dropbox2 = widgets.Dropdown(
    options=[('One', 1), ('Two', 2), ('Three', 3)],
    value=2,
    description='Number:',
)

# Button to click
select_button = widgets.Button(
    description='Click', # User defined
    disabled=False
)

# Event Handlers
dropbox1.observe(bar,names='value')
dropbox2.observe(bar,names='value')
select_button.on_click(get_user_selection)


# I you need more help with commands try things like:
# interact_manual?
# display(arg.keys,arg.traits)
# print(widgets.widget_type_here.widget_function_or_attr.__doc__)

# Create a UI object to display things.  There are other ways of organizing them.
ui = widgets.HBox([dropbox1,dropbox2,select_button]) # pass an array of widgets to the ui

# display the UI
display(ui)

This will display the following after a couple of clicks.

Code output that has been run.