WPF:将所有选项列表和选定选项列表绑定(bind)到带有选择复选框的列表框

标签 wpf mvvm data-binding observablecollection

假设我有一些选项的列表(可编辑)。我想要一个用户选择的项目的可维护列表。我想要以下 UI 来挑选项目:ListBox 对“选项名称”,“ispicked”。我希望下面的 ASCII 艺术能让事情变得更清楚:

 All cars            Select!          Selected cars
____________    _________________    ____________
|BMW       |    |BMW       | [x] |   |BMW       | 
|Audi      |    |Audi      | [x] |   |Audi      | 
|Volkswagen|    |Volkswagen| [x] |   |Volkswagen| 
|Honda     |    |Honda     | [ ] |   |          | 
|Toyota    |    |Toyota    | [ ] |   |          | 
|Nissan    |    |Nissan    | [ ] |   |          | 
|Ford      |    |Ford      | [ ] |   |          | 
|__________|    |__________|_____|   |__________|

如果用户标记“Nissan”复选框,一切都应该是:
 All cars            Select!          Selected cars
____________    _________________    ____________
|BMW       |    |BMW       | [x] |   |BMW       | 
|Audi      |    |Audi      | [x] |   |Audi      | 
|Volkswagen|    |Volkswagen| [x] |   |Volkswagen| 
|Honda     |    |Honda     | [ ] |   |Hond      | 
|Toyota    |    |Toyota    | [ ] |   |Nissan    | 
|Nissan    |    |Nissan    | [x] |   |          | 
|Ford      |    |Ford      | [ ] |   |          | 
|__________|    |__________|_____|   |__________|

将“Nissan”添加到所选汽车的集合应在中间的 ListBox 中标记相应的复选框。从所有汽车的集合中删除一个项目应该从第二个和第三个 ListBoxes 中删除它的名称。例如。从第二张图片中删除大众汽车应该会导致:
 All cars            Select!          Selected cars
____________    _________________    ____________
|BMW       |    |BMW       | [x] |   |BMW       | 
|Audi      |    |Audi      | [x] |   |Audi      | 
|Honda     |    |Honda     | [ ] |   |Hond      | 
|Toyota    |    |Toyota    | [ ] |   |Nissan    | 
|Nissan    |    |Nissan    | [x] |   |          | 
|Ford      |    |Ford      | [ ] |   |          | 
|__________|    |__________|_____|   |__________|

这是一些 View 模型代码:
[ImplementPropertyChanged]
public class ChoiceViewModel : ViewModelBase
{
    public string Title { get; set; }
    public bool IsChecked { get; set; }
}

[ImplementPropertyChanged]
public class TestViewModel : ViewModelBase
{
    public ObservableCollection<string> AllOptions { get; set; }
    public ObservableCollection<string> SelectedOptions { get; set; }

    public ObservableCollection<ChoiceViewModel> Choices { get; set; }

    public TestViewModel()
    {
        AllOptions = new ObservableCollection<string>();
        SelectedOptions = new ObservableCollection<string>();
        Choices = new ObservableCollection<ChoiceViewModel>();

        if(IsInDesignMode)
        {
            AllOptions.Add("BMW");
            AllOptions.Add("Audi");
            AllOptions.Add("Volkswagen");
            AllOptions.Add("Honda");
            AllOptions.Add("Toyota");
            AllOptions.Add("Nissan");
            AllOptions.Add("Ford");

            // German cars ftw!
            SelectedOptions.Add("BMW");
            SelectedOptions.Add("Audi");
            SelectedOptions.Add("Volkswagen");
        }
    }
}

和窗口的 XAML:
<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300"
    DataContext="{Binding TestViewModel">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="1*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="22" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>
        <TextBlock Text="All cars" Grid.Row="0" Grid.Column="0" />
        <DataGrid ItemsSource="{Binding AllOptions}" Grid.Row="1" Grid.Column="0" Margin="10" 
                  AutoGenerateColumns="False"
                  CanUserAddRows="True" 
                  CanUserDeleteRows="True">
            <DataGrid.Columns>
                <DataGridTextColumn Width="1*" Binding="{Binding}"/>
            </DataGrid.Columns>
        </DataGrid>

        <TextBlock Text="Select!" Grid.Row="0" Grid.Column="1" />
        <ListBox ItemsSource="{Binding Choices}" Grid.Row="1" Grid.Column="1" Margin="10" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Title}" />
                        <CheckBox IsChecked="{Binding IsChecked}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <TextBlock Text="Selected cars" Grid.Row="0" Grid.Column="2" />
        <DataGrid ItemsSource="{Binding SelectedOptions}" Grid.Row="1" Grid.Column="2" Margin="10" 
                  AutoGenerateColumns="False"
                  CanUserAddRows="True" 
                  CanUserDeleteRows="True">
            <DataGrid.Columns>
                <DataGridTextColumn Width="1*" Binding="{Binding}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

所以问题是如何同步AllOptions , SelectedOptionsChoices集合 WPF\MVVM 样式?

编辑:
我最终得到了接受的答案。可以通过设置 ListBoxEx 来规避 SelectedItems 绑定(bind)的限制。带有附加 BindableSelectedItems 的包装器依赖属性暴露或使用附加属性。见 this询问详情

最佳答案

显然有多种方法可以解决这个问题,但我倾向于使用单个集合——这样你根本不需要同步。

  • 第一个列表将与您拥有的相同,绑定(bind)到“AllOptions”
  • 还将第二个列表绑定(bind)到“AllOptions”。要完成这项工作,您需要为 ListBox 使用“复选框列表”模板。
  • 然后简单地将第三个绑定(bind)到第二个列表的 SelectedItems属性(property)。

  • 棘手的部分是创建复选框样式列表,但我相信这比同步 2 或 3 个单独的列表和 subview 模型更简单。

    编辑

    下面是一个简单的例子。
    <Grid>
        <Grid.Resources>
            <Style TargetType="ListBox" x:Key="CheckBoxListStyle">
                <Setter Property="SelectionMode" Value="Multiple" />
                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                <Setter Property="ItemTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition />
                                    <ColumnDefinition />
                                </Grid.ColumnDefinitions>
                                <TextBlock Text="{Binding}" />
                                <CheckBox Grid.Column="1"
                                          IsChecked="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},Path=IsSelected,Mode=TwoWay}"
                                          />
                            </Grid>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
    
        <ListBox ItemsSource="{Binding AllOptions}" />
    
        <ListBox x:Name="selectionList"
                 Grid.Column="1"
                 ItemsSource="{Binding AllOptions}" Style="{StaticResource CheckBoxListStyle}"
                 />
    
        <ListBox Grid.Column="2"
                 ItemsSource="{Binding ElementName=selectionList,Path=SelectedItems}"
                 />
    </Grid>
    

    编辑#2

    现在我不喜欢这个解决方案了,因为我看到“SelectedItems”列表没有保留原始排序。

    关于WPF:将所有选项列表和选定选项列表绑定(bind)到带有选择复选框的列表框,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27377437/

    相关文章:

    WPF在DataGrid单元MouseOver上显示弹出窗口

    c# - 用户无法在已绑定(bind)到浮点值的文本框中键入 '.',而 UpdateSourceTrigger 在 WPF 中为 PropertyChanged

    javascript - 在 onclick url 中使用数据绑定(bind)

    c# - 如何添加动态控件而不是静态控件?

    Grails 数据绑定(bind)和 transient

    c# - 列字符串格式

    c# - 测量标签尺寸

    c# - WPF复选框IsChecked两种方式绑定(bind)不起作用

    c# - MVVM中的Canvas上的TouchEvents

    windows-phone-7 - 如何在Mvvm Light中清理ViewModel?