WPF互斥列表框

5

我有一个应用程序,其中有一个ListBox的ListBox。 我想使InnerList框互斥。 我的ViewModel有一个集合Foos,其中包含描述、IsSelected属性和集合Bars,其中有一个名称和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。这个ListBox的ItemsSource属性使用ItemsSource={Binding Path=Foos}进行绑定,在这个ListBox的模板中有一个内部ListBox,它使用ItemsSource="{Binding Path=Bars}"进行绑定。A、B和C是Foos的描述。它们包含的项目是Bar的名称。

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

我需要让用户只能从任何一个“Bars”中选择单个项目。因此,如果用户从Foo A选择Item 1,然后从Foo B选择Item X,则应取消选择Item 1。
我还需要将所选项目绑定到窗口上其他位置的TextBox控件,但我想先一步一步来。
在代码和选择更改事件中完成这些操作不是一个选项。我希望仅使用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个回答

9
最简单的方法可能是在MyViewModel类中添加一个SelectedBar属性,并将列表框的SelectedItem属性绑定到它。这样只允许选择一个项目,并提供了一个稍后绑定文本框的东西。
然后,您可以在每个ListBoxItem的IsSelected属性上设置一个绑定(OneWayToSource),以更新每个bar的IsSelected属性(可能通过ItemContainerStyle)。要更新Foo对象的IsSelected属性,请将绑定设置为listbox的SelectedItem,并使用valueconverter检查是否为空。
编辑:
SelectedItem属性(实现Dan的修复):
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. The reason items weren't appearing to select was that you'd re-templated the ListBoxItems populated with Bar objects, so there was no highlight style when an item was selected - fixed this by setting the ItemTemplate instead, which templates the content of the item rather than overriding the whole template.

  2. Instead of binding the SelectedItem of one of the nested ListBoxes to the SelectedItem index of the parent and then binding that to the viewmodel, I changed the binding to bind directly to the viewmodel, which fixed the multiple-selection issue.

    <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属性的setter来解决这个问题,以便在更新之间脉冲通知属性更改为“null”。这将正确地导致列表框取消选择。 - Dan Bryant
@Dan - 很好的发现,也是一个有趣的解决方案。 - Moonshield
@Adam - 我更新了我的答案,并加入了一些例子。 - Moonshield
@Moonshield 谢谢,还是有一些问题。我已经更新了我的XAML,请看一下。 - Adam
@Moonshield 太棒了,你真是太厉害了。谢谢。 - Adam
显示剩余2条评论

1
如果您只想在任何时候选择一个项目,则拥有一个IsSelected属性是无意义的。相反,您应该有一个容器属性,它保存当前选定的项目(根据Moonshield的建议)。这个模型意味着只能选择一个,而您现有的模型意味着可以选择多个。最终,Foo的各个实例可能根本不需要知道它们已被选择。

0

尝试绑定到SelectedValue而不是SelectedItem。我在ViewModel中有两个DataGrid绑定到两个不同的ICollectionView属性的ItemsSource,这些属性使用相同的ObservableCollection作为它们的原始源,并通过使用MyType的属性进行互斥过滤。(即一个过滤属性的一个值,另一个ICollectionView过滤同一属性的不同值。)

我在ViewModel中有一个名为SelectedMyType的属性,类型为MyType,它绑定到每个DataGrid的SelectedValue属性。当从任何一个DataGrid中选择项目时,先前选择的项目将被取消选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接