Silverlight: MVVM hates RadioButton in ListBox

Silverlight uses this event bubbling model where it “bubbles” events raised in a child control up to all of the parent controls. This is (probably) why they call it “routed events”. Take the example of this Windows Phone 7 application:
Windows Phone 7 Sample Application
In this application, if you left mouse down on one of the red square, the mouse down event will be automatically passed to the list box which contains all of the red square items, then passed to the overall grid that contains the list box (i.e. where the background image is). You can easily stop this behavior by capturing the event and set the Handled property to true:

ListBox.MouseLeftButtonDown += (s, e) =>  e.Handled = true;

In this case, the event will be intercepted at the list box level and won’t keep bubbling up.

The world would be beautiful if this is always the case, but it seems that the world is not beautiful enough. If you put a button in the list box, the button will swallow the Click event and it’s parent control will never be notified. What this means is that even if you keep clicking on different buttons in the list box, the SelectionChanged event of the list box will never fire. In fact, this is due to that not all Silverlight UI elements support the Click event, such as ListBox. So the button simply has no parent event that it can pass on.

So what’s the big deal? It’s very usual that you want to create a list of radio button items, and want to do something after the user has made there selection. If you use MVVM, then you are probably generating this list of radio button items using a list box that binds to a view model that contains a list of corresponding data items.

<ListBox ItemsSource="{Binding Items}" SelectionChanged="ListBox_SelectionChanged">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <RadioButton Content="{Binding Title}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Now, the bad news is that RadioButton extends ButtonBase. Anything that extends ButtonBase detects the Click event and swallows it. So when you click on the radio button, the selection of the list box never changes and you won’t be able to get the newly selected data item in the ListBox_SelectionChanged event handler. Well the good news is that there are more than 1 simple method that you can get around with this.

Method 1: still elegantly sticking to MVVM
Create an extra property in the view model that binds to each radio button like this:

private bool _isSelected;
public bool IsSelected
{
    get { return _isSelected; }
    set
    {
        _isSelected = value;
        NotifyPropertyChanged("IsSelected");
    }
}

Then bind this property to the IsChecked property of your radio button. Also remember to use two way binding and turn hit test visibility off:

<ListBox ItemsSource="{Binding Items}" SelectionChanged="ListBox_SelectionChanged">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <RadioButton Content="{Binding Title}"
                         IsChecked="{Binding IsSelected,Mode=TwoWay}"
                         IsHitTestVisible="False"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

By turning off hit test visibility, you can click “through” the radio button onto the list box, thus triggering the SelectionChanged event. However, since the radio button is not been clicked, it will never visually appeared to be “checked”. That’s why we need to bind the IsChecked property to IsSelected, because you’ll set the IsSelected property to true in the SelectionChanged event:

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    ((DataItem)e.AddedItems[0]).IsSelected = true;
}

This is it! The 2 way data binding of the IsChecked property will visually check the radio button automatically when you set IsSelected to true.
Windows Phone 7 Sample Application

Method 2: a hack, but less effort
Method 2 is relatively easier. Instead of listening to the SelectionChanged event of the list box, you listen to the Checked event of the radio button:

<ListBox ItemsSource="{Binding Items}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <RadioButton Content="{Binding Title}"
                         Checked="RadioButton_Checked"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Then you store the data context of the radio button in some global variable and access it whereever you need:

public static DataItem CurrentDataItem;

private void ListBox_SelectionChanged(object sender, RoutedEventArgs e)
{
    CurrentDataItem = (DataItem)((RadioButton)sender).DataContext;
}

Well the downside of this method is, aside from not using MVVM, the selection of your list box won’t change. This is mostly OK for Windows Phone 7 applications as you don’t visually see the selection anyway.

Advertisements

2 thoughts on “Silverlight: MVVM hates RadioButton in ListBox

  1. Hi,

    I’m facing the same issue and your post was a great help in understanding the problem. However, I’m not able to get the code working.

    You told to add the property “IsSelected” at viewmodel class. However, radiobutton has its datacontext as an “item” in “items” collection. So, i cannot pass the selected radio button back to view model using above code. Please correct me or suggest an alternative.

    1. The IsSelected property needs to be created in each Item that the radio button is bound to. When the selection changed event happens, you are setting the IsSelected property in each item instead of in the VM that has the items collection.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s