I was musing today on Microsoft’s obsession with OnSelectionChanged events on list controls in general and particularily in Silverlight, which is what I’m working with at the moment.In one of the apps I’m mucking around with I have a ton of sidebar menu selections that get displayed and the easiest way to drive these is with ListBoxes.

They look something like this:

SilverlightMenuList

As you can see this ‘menu’ is a collection of controls – a StackPanel with a border element at the top:

<StackPanel x:Name="SidebarMenu2" Margin="10,40,10,10">
    <Border x:Name="SidebarMenu2Header" Style="{StaticResource MenuHeaderStyle}" >
        <TextBlock Text="Update" Style="{StaticResource MenuHeaderTextStyle}"></TextBlock>
    </Border>

    <Border BorderBrush="Black" BorderThickness="1" >
        <StackPanel Background="White" >
            <ListBox x:Name="SidebarMenu2MenuItems" BorderThickness="0" Padding="5" 
                     SelectionChanged="SidebarMenu2MenuItems_SelectionChanged"
                     >
                <ListBox.Items>
                    <ListBoxItem Tag="Save" Content="Save" ></ListBoxItem>
                    <ListBoxItem Tag="Close" Content="Close"></ListBoxItem>
                    <ListBoxItem Tag="Delete" Content="Delete"></ListBoxItem>
                </ListBox.Items>
            </ListBox>
        </StackPanel>
    </Border>
</StackPanel>

I thought to use a listbox for this because it provides the default behavior of clickability as well highlighting the menu selection. The alternative would be having to create all of this behavior manually which is decidedly non-trivial. Additionally listboxes can also be data bound, which isn’t the example above,  but if menus get more complex using dictionary data sure would be easier to use than individual control markup for each item.

Ok, so using a listbox is handy, but as shown above with the SelectionChanged event the behavior is not working all that well for a menu. The selection change event is implemented like this:

private void SidebarMenu2MenuItems_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    object item = this.SidebarMenu2MenuItems.SelectedItem;
    if (item == null)
        return;

    ListBoxItem listItem = item as ListBoxItem;
    string tag = (string)listItem.Tag;

    if (tag == "Save")            
        this.SaveItem();            
    else if (tag == "Close")
        NavigatorPanel.Navigate(new ItemListPage());
    
}

So far so good. This actually works to make a single selection or another selection that is different which is the most common thing you might do.

But in this scenario SelectionChanged becomes a problem when you try to save more than once in a row. This is a common problem with SelectionChanged events. As the name implies SelectionChanged fires only – well when the selection changes. But what if I saved to accept my changes to commit the changes, then make some more edits and save again? I’m not  re-selecting the same selection and I’m expecting the save to fire again. Only it doesn’t…

This behavior is also prominent in list controls in ASP.NET and even Windows Forms and frankly the absence of an easy way to trap ItemClicks as opposed to SelectionChanged operations are annoying. I’d argue 90% the behavior that is expected is an ItemClick not a SelectionChange.

Ok, so how can this be fixed in the Silverlight code above? My first thought was to hook up a MouseLeftButtonDown event:

 

<ListBox x:Name="SidebarMenu2MenuItems" BorderThickness="0" 
         Padding="5" 
         MouseLeftButtonDown="MenuClickHandler"    
         >
    <ListBox.Items>
        <ListBoxItem Tag="Save" Content="Save" MouseLeftButtonDown="MenuClickHandler"></ListBoxItem>
        <ListBoxItem Tag="Close" Content="Close"></ListBoxItem>
        <ListBoxItem Tag="Delete" Content="Delete"></ListBoxItem>
    </ListBox.Items>
</ListBox>

But I was surprised that this didn’t work. Either by handling the event on the listbox itself or on each of the items. Apparently the listbox eats those events.

What did work in the end was the MouseLeftButtonUp event on the listbox:

<ListBox x:Name="SidebarMenu2MenuItems" BorderThickness="0" Padding="5" 
         MouseLeftButtonUp="MenuClickHandler">

which did handle the click. I now end up with a generic menu click handler that all the listbox ‘menus’ are hooked up to on the form:

private void MenuClickHandler(object sender, MouseButtonEventArgs e)
{
    ListBox list = sender as ListBox;
    object item = list.SelectedItem;
    if (item == null)
        return;

    ListBoxItem listItem = item as ListBoxItem;
    string tag = (string)listItem.Tag;

    if (tag == "Save")
        this.SaveItem();
    else if (tag == "Close")
        NavigatorPanel.Navigate(new ItemListPage());

    this.SidebarMenu2MenuItems.SelectedItem = null;
}

One thing to keep in mind with this approach is that it doesn’t account for keyboard navigation – so if you move the up and down arrows manually the menu will move, but nothing actually handles the selection. Incidentally this is another example of the shortcoming of SelectedChanged events as a mechanism for selection – when you use keyboard navigation you probably don’t want to fire the operations in the menu – only when you click or press space/enter should anything happen. But this is a pain in the ass to handle. Handling selection reliably on a control is not trivial as you have a host of events to capture (Mouse, Keyboard, plus navigation and possibly hotkeys – not trivial).

You ever run into these types of scenarios or am I making a mountain out of a molehill? I have the same issues in ASP.NET in many situations where you have to respond to SelectedIndexChanged events which often don’t handle the behavior I want to implement. This is why I more often than not implement my own list views these days. In Silverlight this is easy as well, but for basic behavior like the above this is overkill.

Not rocket science but searching around for workarounds earlier I saw several other posts on forums with similar issues, so I’d thought I post this. I’m struggling slowly through  the Silverlight UI learning curve at the moment so expect a few more of these type of basic WTF posts <s>…