Search code examples
c#wpfmvvmlistboxdatatemplate

Items.Count = 0 in SelectionChanged when there are items


I want to display some custom content (using datatemplate) on button click:

<ContentControl x:Name="content" />
<Button Content="Test" Click="button_Click" />

Button shows/hides content like this

VM _vm = new VM();
void button_Click(object sender, RoutedEventArgs e) =>
     content.Content = content.Content == null ? _vm : null;

Here is datatemplate:

<DataTemplate DataType="{x:Type local:VM}">
    <ListBox ItemsSource="{Binding Items}" SelectionChanged="listBox_SelectionChanged">
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="IsSelected" Value="{Binding IsSelected}" />
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
</DataTemplate>

Event handler:

void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) =>
    Title = "Items: " + ((ListBox)sender).Items.Count;

Viewmodel:

public class VM
{
    public List<Item> Items { get; } = new List<Item> { new Item(), new Item(), new Item() };
}
public class Item
{
    public bool IsSelected { get; set; }
}

The problem: when datatemplate is unloaded then SelectionChanged for ListBox event is rised with no items.

I do not want this event. I don't want to see "Items: 0" after selecting something and unloading datatemplate.

Question: what is happening and how can I prevent this from happening?


Note: this is very short and simplified MCVE, i.e. not everything is pretty, though there are key points: datatemplate with ListBox inside, which uses IsSelected binding and I need to get rid from that SelectionChanged event at unloading.

Call stack:


Solution

  • This is working exactly as designed. You made a selection by clicking an item in the list box. When the template is unloaded, the ItemsSource binding is disconnected, and the items source becomes empty. At that point, the current selection is no longer valid (the item doesn't exist in the items source), so the selection is cleared. That's a selection change: the selection went from something to nothing. The event is expected to be raised under these circumstances.

    It's rarely necessary to subscribe to SelectionChanged. It's usually better to bind the SelectedItem to a property on your view model. Whenever the selection changes, that property will be updated. Instead of responding to the SelectionChanged event, you can respond to that property changing.

    This approach nicely avoids the issue you're seeing. Once the template is unloaded, the SelectedItem binding will be disconnected, so your view model won't be updated anymore. Consequently, you won't see that final change when the selection is cleared.


    Alternate solution for multiple selections

    If your ListBox supports multiple selections, you can continue subscribing to SelectionChanged. However, don't query listBoxItems; instead, scan through _vm.Items and see which items have IsSelected set to true. That should tell you the actual selection, and the results should not be affected by the template being unloaded.

    You can also determine that the template was unloaded by checking whether (sender as ListBox)?.ItemsSource is null in your handler. However, this should not be necessary.