WPF mutually exclusive listings

I have an application that has a ListBox ListBoxes. I would like to make the InnerList fields mutually exclusive. My ViewModel has a collection of Foos that have a description, IsSelected property, and collections. Bars that have the name and property IsSelected.

public class MyViewModel : INotifyPropertyChanged { public ObservableCollection<Foo> Foos { /* code removed for brevity */ } } public class Foo : INotifyPropertyChanged { public string Description { /* code removed for brevity */ } public ObservableCollection<Bar> Bars { /* code removed for brevity */ } public bool IsSelected { /* code removed for brevity */ } } public class Bar : INotifyPropertyChanged { public string Name { /* code removed for brevity */ } public bool IsSelected { /* code removed for brevity */ } } 

The following is part of my MainWindow whose DataContext is set to MyViewModel. This ListBox ItemsSource property is bound using ItemsSource={Binding Path=Foos} and in the template for this ListBox is an internal ListBox associated with using ItemsSource="{Binding Path=Bars}" . A, B and C are descriptions of Foos. The elements contained in them are the names of the bars.

 |--------------------------| | A |--------------------| | | | Item 1 | | | | Item 2 | | | | Item 3 | | | |--------------------| | | | | B |--------------------| | | | Item X | | | | Item Y | | | | Item Z | | | |--------------------| | | | | C |--------------------| | | | Item l | | | | Item m | | | |--------------------| | |--------------------------| 

I need to make the user select only one element from any of the bars. So, if the user selects item 1 from Foo A, then selects the element X from Foo B, then element 1 should be canceled.

I also need to bind the selected item to the TextBox control elsewhere in the window, but, in my opinion, 1 thing.

Doing this in code and selecting modified events is not an option. I would prefer to keep this only with XAML.

Thanks in advance.

UPDATE
Following Moonshield's advice, I came up with this, but it still doesn't work.

 public class MyViewModel { private Bar _selectedBar; public ObservableCollection<Foo> Foos { /* code removed for brevity */ } public Bar SelectedBar { get { return _selectedBar; } set { _selectedBar = null; NotifyPropertyChanged("SelectedBar"); _selectedBar = value; NotifyPropertyChanged("SelectedBar"); } } } 
 <ListBox x:Name="lbFoos" ItemsSource="{Binding Path=Foos}" SelectedItem="{Binding Path=SelectedBar}"> <ListBox.ItemContainerStyle> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <StackPanel> <TextBlock Text="Foo: " /> <TextBlock Text="{Binding Path=Description}" /> <ListBox ItemsSource="{Binding Path=Bars}" SelectedItem="{Binding Path=SelectedItem RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ListBox}}}"> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <TextBlock Text="{Binding Path=Name}" /> </ControlTemplate> </Setter.Value> </Setter> <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=OneWayToSource}" /> </Style> </ListBox.ItemContainerStyle> </ListBox> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </ListBox.ItemContainerStyle> </ListBox> 
+5
wpf binding xaml
source share
3 answers

The easiest way to do this is probably to add the SelectedBar property to your MyViewModel class and associate the SelectedItem of the list with it. This allows you to immediately select one item and provide you with something to bind your text box to later.

You can then set the binding (OneWayToSource) in the IsSelected property of each ListBoxItem (possibly via ItemContainerStyle) to update the IsSelected property for each bar. To update the IsSelected property of Foo objects, use the valueconverter to bind to the SelectedItem list item to check if it is null.

Edit:

SelectedItem Property (Dan fix implementation):

 protected Bar selectedItem; public Bar SelectedItem{ get { return selectedItem; } set { selectedItem = null; NotifyPropertyChanged("SelectedItem"); selectedItem = value; NotifyPropertyChanged("SelectedItem"); } 

Bind ListBoxItem (assuming ListBoxItem DataContext is the view mode in the bar):

 <ListBoxItem IsSelected="{Binding Path=IsSelected, Mode=OneWayToSource}" /> 

Edit - fix the code:

I managed to get your code to work. Two questions I found:

  • Reason elements did not appear to select that you reconfigured ListBoxItems filled with Bar objects, so when selecting an element there was no selection style - this was fixed by setting ItemTemplate instead, which templates the contents of the element, rather than redefining the entire template.

  • Instead of binding the SelectedItem of one of the nested ListBoxes to the parent's SelectedItem index and then binding it to the viewmodel, I changed the binding to bind directly to the viewmodel, which fixed the multiple-selection problem.

     <ListBox x:Name="lbFoos" ItemsSource="{Binding Path=Foos}"> <!--Removed SelectedItem binding.--> <ListBox.ItemContainerStyle> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <StackPanel> <TextBlock Text="Foo: " /> <TextBlock Text="{Binding Path=Description}" /> <ListBox ItemsSource="{Binding Path=Bars}" SelectionChanged="ListBox_SelectionChanged" SelectedItem="{Binding Path=DataContext.SelectedBar, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ListBox}}}"><!--Changed binding to bind directly to ViewModel--> <ListBox.ItemTemplate><!--Set ItemTemplated rather than ControlTemplate--> <DataTemplate> <TextBlock Text="{Binding Path=Name}" /> </DataTemplate> </ListBox.ItemTemplate> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=OneWayToSource}" /> </Style> </ListBox.ItemContainerStyle> </ListBox> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </ListBox.ItemContainerStyle> </ListBox> 
+8
source share

If you want only one item to be selected at any time, then the presence of the IsSelected property IsSelected meaningless. Instead, you should have a container property that contains the currently selected item (as suggested by Moonshield). This model implies that you can choose only one, while your existing model assumes that many can be selected. Ultimately, individual instances of Foo probably should not know that they were selected anyway.

+1
source share

Instead of binding to SelectedItem, try binding to SelectedValue instead. I have a similar situation with two DataGrids that bind ItemsSource with two different ICollectionView properties in my ViewModel. These ICollectionView properties use the same ObservableCollection as the original source, and are mutually exclusive using the filter using the MyType property. (Ie One filter for one property value and other ICollectionView filters for another value for the same property.)

I have a property in my ViewModel called SelectedMyType of type MyType that is bound to the SelectedValue property of each DataGrid. When an item is selected from a DataGrid, any previously selected item is not selected.

0
source share

All Articles