WPF MVVM Light Multiple ListBoxItems bound to same

2019-09-11 15:56发布

问题:

I have UserControl containing a procedurally generated ItemsControl. Each item in the ItemsControl contains a ListBox and there is no consistent number of how many items will be generated. The selected item in the listbox is bound to am object (SelectedClass) in the ViewModel. The initial value of the SelectedClass object is null.

The scenario I am running into is this:

  1. User selects ListBoxItemA from ItemsControlItemA, PropertyChanged fires, SelectedClass object is set to the proper value.
  2. User then selects ListBoxItemA from ItemsControlItemB, PropertyChanged fires, SelectedClass object is set to the proper value.
  3. User then selects ListBoxItemA from ItemsControlItemA, but since the selection in that list is still considered to be the same item from step 1, PropertyChanged does not fire, and the SelectedClass object remainsListBoxItemA from ItemsControlItemB.

So my question is, how do i get the UpdateSourceTrigger event to fire OnClick rather than on PropertyChanged, and is that even the best way to approach it? I'm using the MVVM Light framework.

Thanks

<ItemsControl ItemsSource="{Binding AllUpcomingClasses}" >
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding classDescription}" />                    
                <ListBox Name="availableClasses"
                    ItemsSource="{Binding ClassInstances}" 
                    SelectedItem="{Binding
                                       DataContext.SelectedClass, 
                                       Mode=TwoWay}
                                       RelativeSource={RelativeSource 
                                           FindAncestor, 
                                           AncestorType={x:Type UserControl}}">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel>
                                <Grid>
                                    <TextBlock Text="{Binding ClassDate}" />
                                </Grid>
                            </StackPanel>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>                                    
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Edit: Cleaned up the example a bit for readability.

回答1:

You could handle the PreviewMouseLeftButtonDown event of the ListBoxItem container and "manually" set the SelectedItem property of your view model if the clicked item is the one that is already selected:

<ListBox SelectedItem="{Binding SelectedItem}" xmlns:s="clr-namespace:System;assembly=mscorlib">
  <ListBox.ItemContainerStyle>
     <Style TargetType="ListBoxItem">
        <EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnMouseLeftButtonDown"/>
     </Style>
  </ListBox.ItemContainerStyle>
  <s:String>A</s:String>
  <s:String>B</s:String>
  <s:String>C</s:String>
</ListBox>



private void OnMouseLeftButtonDown(object sender, MouseEventArgs e)
{
  ListBoxItem lbi = sender as ListBoxItem;
  if (lbi != null)
  {
    YourViewModel vm = DataContext as YourViewModel;
    if (vm != null)
    {
        var selectedItem = lbi.DataContext as YourObjectType;
        if (vm.SelectedItem == selectedItem)
        {
            vm.SelectedItem = selectedItem;
            e.Handled = false;
        }
    }
  }
}

If you don't want to handle this in the code-behind of the view you could wrap the same functionality in an attached behaviour: https://www.codeproject.com/articles/28959/introduction-to-attached-behaviors-in-wpf. The former approach doesn't really break the MVVM pattern though since you are just kind of "extending" the ListBox control functionality to be able to set the same view model source property that the ListBox control sets for you when you select a new item. This functionality belongs to the view or the control.