How to access param values of widgets inside a dynamically defined WidgetBox?

Here is a simple demo setup of what I am trying to do:

import panel as pn
pn.extension()

widget_definer = pn.widgets.MultiSelect(options=['a', 'b', 'c'])

@pn.depends(widget_definer.param.value)
def get_widgetBox(values):
    selects = []
    for v in values:
        selects.append(pn.widgets.MultiSelect(name=v, options=[1,2,3,4,5]))

    return pn.WidgetBox(*selects, horizontal=True)

pn.Column(widget_definer, get_widgetBox).servable()

The WidgetBox returned by get_widgetBox is dynamic, depending on the value of widget_definer. However, with this setup, I don’t know how to access the state of the MultiSelect widgets living within the dynamic widget box. Any suggestions would be greatly appreciated!

This is where you’d want to use a parameterized class. Refer to the docs: https://panel.holoviz.org/user_guide/Param.html specifically the GoogleMapViewer example where they create dependencies between parameters. Once you have a parameterized class, you should be able to access any values from any of the widgets.

I do not know how to acces to the param function. But using pre defined containers to the widgets you want to acces, you can obtain its values.

I added a button to show the values of the select widgets. Specifically, I added a Column element called col which is simply a list. You can acces with indexing to its elements.

import panel as pn
pn.extension()

wBox = pn.WidgetBox(horizontal=True)
widget_definer = pn.widgets.MultiSelect(options=['a', 'b', 'c'])

@pn.depends(widget_definer.param.value, watch=True)
def get_widgetBox(values):
    selects = []
    for v in values:
        selects.append(pn.widgets.MultiSelect(name=v, options=[1,2,3,4,5]))
    wBox[:] = [*selects]

btn = pn.widgets.Button(name='show selects')
def show(event):
    for select in col[1]:
         print (select.value)
btn.on_click(show)

col = pn.Column(widget_definer, wBox)
pn.Row(col, btn).servable()

Thank you! This is great; I hadn’t thought of pre-defining the widget. I figured there must be a way to do it with a parameterized class too @jbogaardt, so I might try that also.

I’m still trying to understand how to use param.Parameterized to reproduce something like this. I see how the GoogleMapViewer example allows the available objects in the selectors to be dynamic, but I don’t yet understand how to use this model to create and refer to new, or variable numbers of widgets. The example I currently have working (following @nghenzi2019’s suggestion), is

import panel as pn
pn.extension()

wBox = pn.WidgetBox(horizontal=True)
widget_definer = pn.widgets.MultiSelect(options=['a', 'b', 'c'])
values = pn.widgets.StaticText(value='')

@pn.depends(widget_definer.param.value, watch=True)
def get_widgetBox(values):
    selects = []
    for v in values:
        selects.append(pn.widgets.MultiSelect(name=v, options=[1,2,3,4,5]))
    wBox[:] = [*selects]

btn = pn.widgets.Button(name='show selects')
def show(event):
    s = ''
    for select in wBox:
         s += f'{select.name}: {select.value}' + '\n'
    values.value = s
    
btn.on_click(show)

col = pn.Column(widget_definer, wBox, values)
pn.Row(col, btn).servable()

This is accomplished by predefining the wBox as empty, treating it as a global, and then having the callback redefine its objects. I don’t yet see how to do this all within a single parameterized class; it’s almost as if wBox itself would need to be a parameter, and I don’t know how to do that.

Edit: For example, the following gives close to the idea I want, but I don’t know how to access the values selected in the dynamic widgetbox in a param-ish way:

import panel as pn
import param

pn.extension()

class DynamicWidgetBox(param.Parameterized):
    options = param.ListSelector(default=[], objects=['a', 'b', 'c'])
    
    @param.depends('options')
    def widgets(self):
        selects = []
        for v in self.options:
            selects.append(pn.widgets.MultiSelect(name=v, options=[1,2,3,4,5]))

        return pn.WidgetBox(*selects, horizontal=True)
    
    @param.depends('widgets')  # how do I depend on the *values* of the result of the above?
    def values(self):
        s = ''
        for select in self.widgets():
            s += f'{select.name}: {select.value}; '
        return pn.widgets.StaticText(value=s)
    
dwb = DynamicWidgetBox()

pn.Column(dwb.param, dwb.widgets, dwb.values)

Further edit:

A sort of “hybrid” approach might look something like this. I’d be curious for any tips on how to do this more cleanly using only param.Parameterized objects and depends decorators.

import panel as pn
import param

pn.extension()

class DynamicWidgetBox(param.Parameterized):
    options = param.ListSelector(default=[], objects=['a', 'b', 'c'])
    
    wBox = pn.WidgetBox(horizontal=True)
    
    @param.depends('options')
    def widgets(self):
        selects = []
        for v in self.options:
            selects.append(pn.widgets.MultiSelect(name=v, options=[1,2,3,4,5]))

        self.wBox[:] = [*selects]
        return self.wBox
        
dwb = DynamicWidgetBox()

values = pn.widgets.StaticText(value='')

button = pn.widgets.Button(name='Update Values')

def update_values(event):
    s = ''
    for select in dwb.wBox:
        s += f'{select.name}: {select.value}; '
    values.value = s

button.on_click(update_values)

pn.Column(dwb.param, dwb.widgets, button, values)
1 Like

maybe this link can help you.

With that help, i adapted to your example.

import panel as pn
import param
pn.extension()

class DynamicWidgetBox(param.Parameterized):
    options = param.ListSelector(objects= ['a','b','c','d'])

    def __init__(self, **param):
        super(DynamicWidgetBox, self).__init__(**param)
        self.col1 = pn.Column(*pn.panel(self.param), 
                                pn.Row())

    @param.depends('options', watch=True)
    def get_widgetBox(self):
        selects = []
        for v in self.options:
             selects.append(pn.widgets.MultiSelect(name=v, options=[1,2,3,4,5]))
        self.col1[2] = pn.WidgetBox(*selects, horizontal=True)

    def panel(self):
        return self.col1

DynamicWidgetBox().panel().servable()