WPF взаимоисключающие списки - PullRequest
5 голосов
/ 27 ноября 2010

У меня есть приложение, в котором есть ListBox of ListBoxes.Я хотел бы сделать коробки InnerList взаимоисключающими.Мой ViewModel имеет коллекцию Foos, у которой есть описание, свойство IsSelected и панели коллекций, которые имеют имя и свойство 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 */ }
}

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

|--------------------------|
| 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 в другом месте окна, но я полагаю, 1 вещь за раз.

Делать это в коде и выбирать измененные события нельзя.Я бы предпочел сохранить это, используя только XAML.

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

ОБНОВЛЕНИЕ Следуя совету 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>

Ответы [ 3 ]

8 голосов
/ 27 ноября 2010

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

Затем можно установить привязку (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 является моделью представления Bar):

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

Редактировать - исправления в вашем коде:

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

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

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

    <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>
    
1 голос
/ 27 ноября 2010

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

0 голосов
/ 28 октября 2016

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...