2010-11-27 2 views
5

У меня есть приложение, которое имеет ListBox ListBoxes. Я хотел бы сделать поля InnerList взаимоисключающими. My ViewModel имеет коллекцию Foos, в которой есть описание, свойство IsSelected и коллекции Bars, у которых есть имя и свойство IsSelected.WPF взаимно эксклюзивные списки

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 */ } 
} 

Ниже приведена часть моего MainWindow, чей DataContext установлен в MyViewModel. Это свойство ItemsSource ListBox связано с использованием ItemsSource={Binding Path=Foos}, а в шаблоне для этого списка ListBox используется внутренний список ListBox, связанный с использованием ItemsSource="{Binding Path=Bars}". A, B и C являются описаниями Foos. Элементы, содержащиеся в них, - это имена баров.

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

Мне нужно сделать так, чтобы пользователь мог выбрать только один элемент из любой из баров. Итак, если пользователь выбирает пункт 1 из Foo A, тогда выбирает элемент X из Foo B, тогда элемент 1 следует отменить.

Мне также нужно привязать выбранный элемент к элементу управления TextBox в другом месте окна, но, по-моему, одно событие.

Выполнение этого изменения кода и выбора событий не является вариантом. Я бы предпочел сохранить это только с помощью XAML.

Заранее спасибо.

UPDATE
Следуя совету Moonshield, я придумал это, но он все еще не полностью работает.

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> 

ответ

8

Самый простой способ сделать это, вероятно, чтобы добавить свойство SelectedBar к классу MyViewModel и связать SelectedItem свойство ListBoxes до этого. Это позволяет сразу выбрать один элемент и предоставить вам что-то, чтобы связать ваше текстовое поле позже.

Затем вы можете установить привязку (OneWayToSource) в свойстве IsSelected каждого объекта ListBoxItem (возможно, через ItemContainerStyle), чтобы обновить свойство IsSelected для каждого бара. Чтобы обновить свойство IsSelected объектов Foo, установите привязку к элементу SelectedItem списка с помощью valueconverter, чтобы проверить, является ли оно нулевым.

Edit:

SelectedItem собственности (реализации затруднительное Дэна):

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

     selectedItem = value; 
     NotifyPropertyChanged("SelectedItem"); 
    } 

ListBoxItem с окантовкой (при условии, ListBoxItem DataContext является бар ViewModel):

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

Edit - исправления в коде :

Мне удалось заставить ваш код работать. Два вопроса, я нашел:

  1. Причина элементы не появлялись, чтобы выбрать то, что вы вновь шаблонные в ListBoxItems заселенный с объектами Бара, так что не было никакого стиля изюминки, когда элемент был выбран - это исправил вместо этого вместо этого установите ItemTemplate, который замаскирует содержимое элемента, а не переопределяет весь шаблон.

  2. Вместо привязки SelectedItem одного из вложенных ListBoxes к индексу SelectedItem родителя, а затем привязывая его к viewmodel, я изменил привязку для привязки непосредственно к viewmodel, которая исправила проблему с множественным выбором.

    <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> 
    
+2

Одно предостережение, с которым я столкнулся, заключается в том, что если свойство SelectedItem в окне списка привязано к свойству, а свойство принимает значение, отсутствующее в списке, это не приведет к визуальному отмене выбранного элемента. Я в конечном итоге взломал это, изменив настройщик свойства SelectedObject ViewModel, чтобы он исказил свойство уведомления, измененное «null» между обновлениями. Это приведет к тому, что блокировки списка будут отменены. – 2010-11-27 20:01:06

1

Если вы хотите только один элемент должен быть выбран в любое время, то, имеющий IsSelected свойство не имеет смысла. Вместо этого вы должны иметь свойство контейнера, которое содержит выбранный элемент (в соответствии с предложением Moonshield). Эта модель подразумевает, что можно выбрать только один, тогда как ваша существующая модель предполагает, что многие могут быть выбраны. В конечном счете, отдельные экземпляры Foo, вероятно, не должны знать, что они были выбраны в любом случае.

0

Вместо привязки к SelectedItem вместо этого попробуйте привязку к SelectedValue. У меня аналогичная ситуация с двумя DataGrids, которые связывают ItemsSource с двумя разными свойствами ICollectionView в моей ViewModel. Эти свойства ICollectionView используют тот же ObservableCollection, что и исходный источник, и являются взаимоисключающими с помощью фильтра с использованием свойства MyType. (Ie One фильтрует одно значение свойства и другие фильтры ICollectionView при другом значении того же свойства.)

У меня есть свойство в моей модели ViewModel под названием SelectedMyType типа MyType, которая привязана к свойству SelectedValue каждого DataGrid. Когда элемент выбран из DataGrid, любой ранее выбранный элемент отменяется.

Смежные вопросы