使用ViewModel中的集合在muliselect列表框中同步SelectedItems

MIa*_*sca 20 prism mvvm silverlight-3.0

我在使用棱镜的SL3应用程序中有一个多选列表框,我需要在我的viewmodel中包含列表框中当前所选项目的集合.

viewmodel对视图一无所知,因此它无权访问列表框控件.此外,我需要能够从viewmodel清除列表框中的选定项目.

不知道如何解决这个问题

谢谢迈克尔

Bri*_*sio 40

因此,假设您有一个具有以下属性的ViewModel:

public ObservableCollection<string> AllItems { get; private set; }
public ObservableCollection<string> SelectedItems { get; private set; }
Run Code Online (Sandbox Code Playgroud)

首先将AllItems集合绑定到ListBox:

<ListBox x:Name="MyListBox" ItemsSource="{Binding AllItems}" SelectionMode="Multiple" />
Run Code Online (Sandbox Code Playgroud)

问题是ListBox上的SelectedItems属性不是DependencyProperty.这非常糟糕,因为您无法将其绑定到ViewModel中的某些内容.

第一种方法是将此逻辑放在代码隐藏中,以调整ViewModel:

public MainPage()
{
    InitializeComponent();

    MyListBox.SelectionChanged += ListBoxSelectionChanged;
}

private static void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var listBox = sender as ListBox;
    if(listBox == null) return;

    var viewModel = listBox.DataContext as MainVM;
    if(viewModel == null) return;

    viewModel.SelectedItems.Clear();

    foreach (string item in listBox.SelectedItems)
    {
        viewModel.SelectedItems.Add(item);
    }
}
Run Code Online (Sandbox Code Playgroud)

这种方法可行,但实在太难看了.我首选的方法是将此行为提取为"附加行为".如果这样做,您可以完全消除代码隐藏并在XAML中进行设置.奖励是这个"附加行为"现在可以在任何ListBox中重复使用:

<ListBox ItemsSource="{Binding AllItems}" Demo:SelectedItems.Items="{Binding SelectedItems}" SelectionMode="Multiple" />
Run Code Online (Sandbox Code Playgroud)

以下是附加行为的代码:

public static class SelectedItems
{
    private static readonly DependencyProperty SelectedItemsBehaviorProperty =
        DependencyProperty.RegisterAttached(
            "SelectedItemsBehavior",
            typeof(SelectedItemsBehavior),
            typeof(ListBox),
            null);

    public static readonly DependencyProperty ItemsProperty = DependencyProperty.RegisterAttached(
            "Items",
            typeof(IList),
            typeof(SelectedItems),
            new PropertyMetadata(null, ItemsPropertyChanged));

    public static void SetItems(ListBox listBox, IList list) { listBox.SetValue(ItemsProperty, list); }
    public static IList GetItems(ListBox listBox) { return listBox.GetValue(ItemsProperty) as IList; }

    private static void ItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = d as ListBox;
        if (target != null)
        {
            GetOrCreateBehavior(target, e.NewValue as IList);
        }
    }

    private static SelectedItemsBehavior GetOrCreateBehavior(ListBox target, IList list)
    {
        var behavior = target.GetValue(SelectedItemsBehaviorProperty) as SelectedItemsBehavior;
        if (behavior == null)
        {
            behavior = new SelectedItemsBehavior(target, list);
            target.SetValue(SelectedItemsBehaviorProperty, behavior);
        }

        return behavior;
    }
}

public class SelectedItemsBehavior
{
    private readonly ListBox _listBox;
    private readonly IList _boundList;

    public SelectedItemsBehavior(ListBox listBox, IList boundList)
    {
        _boundList = boundList;
        _listBox = listBox;
        _listBox.SelectionChanged += OnSelectionChanged;
    }

    private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        _boundList.Clear();

        foreach (var item in _listBox.SelectedItems)
        {
            _boundList.Add(item);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 6

我希望有真正的双向绑定,以便ListBox选择反映底层ViewModel的SelectedItems集合中包含的项目.这允许我通过ViewModel层中的逻辑来控制选择.

以下是我对SelectedItemsBehavior类的修改.如果ViewModel属性实现INotifyCollectionChanged(例如,由ObservableCollection <T>类型实现),它们将ListBox.SelectedItems集合与基础ViewModel属性同步.

  public static class SelectedItems
  {
    private static readonly DependencyProperty SelectedItemsBehaviorProperty =
        DependencyProperty.RegisterAttached(
            "SelectedItemsBehavior",
            typeof(SelectedItemsBehavior),
            typeof(ListBox),
            null);

    public static readonly DependencyProperty ItemsProperty = DependencyProperty.RegisterAttached(
            "Items",
            typeof(IList),
            typeof(SelectedItems),
            new PropertyMetadata(null, ItemsPropertyChanged));

    public static void SetItems(ListBox listBox, IList list) { listBox.SetValue(ItemsProperty, list); }
    public static IList GetItems(ListBox listBox) { return listBox.GetValue(ItemsProperty) as IList; }

    private static void ItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      var target = d as ListBox;
      if (target != null)
      {
        AttachBehavior(target, e.NewValue as IList);
      }
    }

    private static void AttachBehavior(ListBox target, IList list)
    {
      var behavior = target.GetValue(SelectedItemsBehaviorProperty) as SelectedItemsBehavior;
      if (behavior == null)
      {
        behavior = new SelectedItemsBehavior(target, list);
        target.SetValue(SelectedItemsBehaviorProperty, behavior);
      }
    }
  }

  public class SelectedItemsBehavior
  {
    private readonly ListBox _listBox;
    private readonly IList _boundList;

    public SelectedItemsBehavior(ListBox listBox, IList boundList)
    {
      _boundList = boundList;
      _listBox = listBox;
      _listBox.Loaded += OnLoaded;
      _listBox.DataContextChanged += OnDataContextChanged;
      _listBox.SelectionChanged += OnSelectionChanged;

      // Try to attach to INotifyCollectionChanged.CollectionChanged event.
      var notifyCollectionChanged = boundList as INotifyCollectionChanged;
      if (notifyCollectionChanged != null)
      {
        notifyCollectionChanged.CollectionChanged += OnCollectionChanged;
      }
    }

    void UpdateListBoxSelection()
    {
      // Temporarily detach from ListBox.SelectionChanged event
      _listBox.SelectionChanged -= OnSelectionChanged;

      // Synchronize selected ListBox items with bound list
      _listBox.SelectedItems.Clear();
      foreach (var item in _boundList)
      {
        // References in _boundList might not be the same as in _listBox.Items
        var i = _listBox.Items.IndexOf(item);
        if (i >= 0)
        {
          _listBox.SelectedItems.Add(_listBox.Items[i]);
        }
      }

      // Re-attach to ListBox.SelectionChanged event
      _listBox.SelectionChanged += OnSelectionChanged;
    }

    void OnLoaded(object sender, RoutedEventArgs e)
    {
      // Init ListBox selection
      UpdateListBoxSelection();
    }

    void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
      // Update ListBox selection
      UpdateListBoxSelection();
    }

    void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
      // Update ListBox selection
      UpdateListBoxSelection();
    }

    void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
      // Temporarily deattach from INotifyCollectionChanged.CollectionChanged event.
      var notifyCollectionChanged = _boundList as INotifyCollectionChanged;
      if (notifyCollectionChanged != null)
      {
        notifyCollectionChanged.CollectionChanged -= OnCollectionChanged;
      }

      // Synchronize bound list with selected ListBox items
      _boundList.Clear();
      foreach (var item in _listBox.SelectedItems)
      {
        _boundList.Add(item);
      }

      // Re-attach to INotifyCollectionChanged.CollectionChanged event.
      if (notifyCollectionChanged != null)
      {
        notifyCollectionChanged.CollectionChanged += OnCollectionChanged;
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)