MVVM:使用SelectionMode = Extended的Listbox绑定,如果没有SelectionChanged事件

时间:2015-07-12 15:50:19

标签: wpf xaml mvvm listbox

我将在下面发布一些代码,但这里是我正在尝试做的总结:

我有2个视图模型和1个模型:

  • ModelData - 简单的Model Class,只保存一个字符串
  • ModelDataViewModel - 带有ObservableCollection的ViewModel类
  • MultipleCollectionsViewModel ,带有ObservableCollection的ViewModel类。

我有一个'parent'列表框,绑定到MultipleCollectionsViewModel。此列表框具有SelectionMode = Extended。每个项目代表一个ModelDataViewModel,可以选择任意数量的这些。

我有第二个'孩子'列表框。此列表框应显示父列表框中所有选定项的所有ModelData对象。基本上,它显示了集合(SelectedItems)的集合(ChildItems)。

我得到了这样做我想要的。但是有很多问题。首先,在绑定到SelectedItem时,SelectionMode = Extended just plain不能正常工作。选中的第一个项目将触发该属性,后续选择则不会。有很多关于这个的帖子,我通过向ListBoxItem添加一个IsSelected样式来解决这个问题。这将应用于'ModelDataViewModel'对象。

问题是父视图模型MultipleCollectionsViewModel没有任何关于其子viewModel之一的属性设置的线索。应该从MultipleCollectionsViewModel而不是ModelDataViewModel触发正确的PropertyChanged。

我通过添加活动解决了这个问题。我对MVVM的理解是它应该解决这些事件,并且只依赖于绑定。但是我一直在努力弯腰向后试图通过绑定/ xaml单独工作。所以,我发布这个是为了看看是否有人对我要做的事情有任何建议或不同的方法。

以下是包含多个选项的屏幕截图:

Binding with SelectionMode=Extended

代码如下:

ModelData 类非常简单:

    public class ModelData
{
    public ModelData(string id)
    {
        this.Identifier = id;
    }

    public string Identifier
    {
        get;
        set;
    }
}

这是 ModelDataViewModel

    public class ModelDataViewModel : ViewModelBase
{
    private ObservableCollection<ModelData> _aClassInstances;
    private string name;

    public ModelDataViewModel() : this("No-Name") { }

    public ModelDataViewModel(string name)
    {
        this.name = name;

        this.ChildItems = new ObservableCollection<ModelData>();
        this.ChildItems.Add(new ModelData(Name + "-1"));
        this.ChildItems.Add(new ModelData(Name + "-2"));
        this.ChildItems.Add(new ModelData(Name + "-3"));

        this.AVMIsSelected = false;
    }

    public ObservableCollection<ModelData> ChildItems
    {
        get
        {
            return this._aClassInstances;
        }
        set
        {
            this._aClassInstances = value;
            this.OnPropertyChanged("ChildItems");
        }
    }

    public string Name
    {
        get
        {
            return name;
        }

        set
        {
            name = value;
            this.OnPropertyChanged("Name");
        }
    }

    private bool isSelected;

    public bool AVMIsSelected
    {
        get
        {
            return this.isSelected;
        }
        set
        {
            this.isSelected = value;
            this.OnPropertyChanged("AVMIsSelected");         
        }
    }
}    

MultipleCollectionsViewModel

    public class MultipleCollectionsViewModel : ViewModelBase
{
    ObservableCollection<ModelDataViewModel> firstItems;
    ObservableCollection<ModelDataViewModel> _selectedItems;     

    public MultipleCollectionsViewModel()
    {
        this.firstItems = new ObservableCollection<ModelDataViewModel>();            
        this._selectedItems = new ObservableCollection<ModelDataViewModel>();

        this.FirstItems.Add(new ModelDataViewModel("First-1"));
        this.FirstItems.Add(new ModelDataViewModel("First-2"));
        this.FirstItems.Add(new ModelDataViewModel("First-3"));
    }

    public ObservableCollection<ModelDataViewModel> FirstItems
    {
        get
        {
            return firstItems;
        }

        set
        {
            firstItems = value;
        }
    }

    public ObservableCollection<ModelDataViewModel> SelectedItems
    {
        get
        {
            return this._selectedItems;
        }

        set
        {                
            this._selectedItems.Clear();

            foreach (ModelDataViewModel aViewModel in this.FirstItems.Where(n => n.AVMIsSelected))
            {
                this._selectedItems.Add(aViewModel);
            }

            this.OnPropertyChanged("SelectedItems");
            this.OnPropertyChanged("SelectedSubItems");
        }
    }   

    public ObservableCollection<Model.ModelData> SelectedSubItems
    {
        get
        {
            return new ObservableCollection<Model.ModelData>(this.SelectedItems.SelectMany(n => n.ChildItems).Where(m => 1 == 1));
        }
    }

最后,这是视图和背后的代码:

<Window x:Class="MVVM_Help_Needed.View.SelectedItemSync"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="SelectedItemSync" Height="500" Width="600">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>

    <ListBox Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" ItemsSource="{Binding Path=FirstItems}" SelectedItem="{Binding FirstSelectedItem, Mode=TwoWay}" SelectionMode="Extended" SelectionChanged="ListBox_SelectionChanged">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Path=Name}"></TextBlock>
            </DataTemplate>
        </ListBox.ItemTemplate>
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="IsSelected" Value="{Binding AVMIsSelected}"/>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>

    <Button Grid.Column="1" Grid.Row="0" Content="{Binding Path=SelectedItems.Count}"></Button>
    <ListBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding Path=SelectedSubItems}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Path=Identifier}"></TextBlock>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

    public partial class SelectedItemSync : Window
{
    public SelectedItemSync()
    {
        InitializeComponent();
        DataContext = new ViewModel.MultipleCollectionsViewModel();
    }

    private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        ViewModel.MultipleCollectionsViewModel vm = this.DataContext as ViewModel.MultipleCollectionsViewModel;
        vm.SelectedItems = null; // this fires the setter, doesn't actually modify anything. 
    }
}

1 个答案:

答案 0 :(得分:0)

实际上这是一个ListBox(和DataGrid)限制。基本上有两种解决方案:一种使用附加属性,另一种使用ListBox控件稍微增强并添加缺少的属性。这是一个品味问题,我更喜欢第二种方式,因为它给了我智能感知帮助。在您的XAML中,您使用MultiSelectionListBox而不是ListBox,您可以绑定到AllSelectedItems。请注意,此解决方案仅观察新列表的更改/分配(在视图模型中设置)。当选择发生变化时,它总是会创建一个新列表。在视图模型中设置类型的情况下,是否还需要观察ObservableCollection,这取决于您的需求。

public class MultiSelectionListBox : ListBox
{
  public static readonly DependencyProperty AllSelectedItemsProperty = DependencyProperty.Register(
    "AllSelectedItems",
    typeof(IList),
    typeof(MultiSelectionListBox),
    new FrameworkPropertyMetadata(null, OnAllSelectedItems));

  public MultiSelectionDataGrid()
  {
    this.SelectionChanged += this.MultiSelectionDataGridSelectionChanged;
  }

  public IList AllSelectedItems
  {
    get
    {
      return (IList)this.GetValue(AllSelectedItemsProperty);
    }

    set
    {
      this.SetValue(AllSelectedItemsProperty, value);
    }
  }

  private static void OnAllSelectedItems(object sender, DependencyPropertyChangedEventArgs e)
  {
    var me = (MultiSelectionListBox)sender;
    var newItemList = e.NewValue as IList;

    me.SetAllSelectedItems(newItemList);
  }

  private void MultiSelectionDataGridSelectionChanged(
    object sender, SelectionChangedEventArgs e)
  {
    IEnumerable<object> tempListSrc = this.SelectedItems.Cast<object>();
    var tempListDest = new List<object>();
    tempListDest.AddRange(tempListSrc.ToList());

    this.AllSelectedItems = tempListDest;
  }

  private void SetAllSelectedItems(IList list)
  {
    if (list == null)
    {
      return;
    }

    this.SelectedItems.Clear();

    foreach (object item in list)
    {
      this.SelectedItems.Add(item);
    }
  }
}