同步SelectedItems在与视图模型集合的muliselect列表框(Sync Selecte

2019-06-18 14:22发布

我有一个SL3应用程序多选列表框使用棱镜,我需要一个集合在我的视图模型包含在列表框中当前选定的项目。

该视图模型不知道该视图的任何所以它没有访问列表框控件。 此外,我需要能够清除从视图模型列表框中选择的项目。

不知道如何解决这个问题

感谢迈克尔

Answer 1:

因此,假设你有以下属性的视图模型:

public ObservableCollection<string> AllItems { get; private set; }
public ObservableCollection<string> SelectedItems { get; private set; }

你会被你的AllItems收集绑定到ListBox开始:

<ListBox x:Name="MyListBox" ItemsSource="{Binding AllItems}" SelectionMode="Multiple" />

问题是,在列表框selectedItems属性不是一个DependencyProperty。 这是非常糟糕的,因为你不能把它绑定到你的视图模型的东西。

第一种方法是只把这个逻辑的代码隐藏,来调整视图模型:

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);
    }
}

这种方法将工作,但它实在是太丑了。 我首选的方法是提取这种行为为“附加的行为”。 如果你这样做,你可以完全消除您的代码隐藏,并在XAML设置。 奖金是,这个“附加的行为”,现在是可以重复使用的任何列表框:

<ListBox ItemsSource="{Binding AllItems}" Demo:SelectedItems.Items="{Binding SelectedItems}" SelectionMode="Multiple" />

这里是为附加的行为的代码:

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);
        }
    }
}


Answer 2:

我想有真正的双向绑定,以便列表框选择反映包含底层视图模型的SelectedItems集合中的项目。 这使我控制由逻辑视图模型层的选择。

这里是我的修改到SelectedItemsBehavior类。 如果视图模型属性实现INotifyCollectionChanged(例如,由的ObservableCollection <T>类型实现)它们同步ListBox.SelectedItems收集与底层视图模型属性。

  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;
      }
    }
  }


Answer 3:

感谢这个! 我添加了一个小更新,以支持初始加载和DataContext的变化。

干杯,

亚历山德罗飞行员[MVP / US]

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

    public ListBoxSelectedItemsBehavior(ListBox listBox, IList boundList)
    {
        _boundList = boundList;
        _listBox = listBox;

        SetSelectedItems();

        _listBox.SelectionChanged += OnSelectionChanged;
        _listBox.DataContextChanged += ODataContextChanged;
    }

    private void SetSelectedItems()
    {
        _listBox.SelectedItems.Clear();

        foreach (object item in _boundList)
        {
            // References in _boundList might not be the same as in _listBox.Items
            int i = _listBox.Items.IndexOf(item);
            if (i >= 0)
                _listBox.SelectedItems.Add(_listBox.Items[i]);
        }
    }

    private void ODataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        SetSelectedItems();
    }

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

        foreach (var item in _listBox.SelectedItems)
        {
            _boundList.Add(item);
        }
    }
}


Answer 4:

更新现有的行为与选择上的收集和改变的Rebinded项目

http://rnragu.blogspot.com/2011/04/multiselect-listbox-in-silverlight-use.html



Answer 5:

如果你还记得先创建观察集合的一个实例的原始解决上述作品! 此外,您还需要确保观察集合内容类型的内容类型匹配您的ListBox的ItemSource(如果你是从上面提到的确切例子偏离)。



Answer 6:

下面是与此问题的解决方案,其中包括一个示例应用程序,一个博客,所以你可以看到究竟是如何使其工作: http://alexshed.spaces.live.com/blog/cns!71C72270309CE838!149.entry

我只是在我的应用程序中实现这一点,它很好地解决了这个问题



Answer 7:

对我来说,解决办法是亚历山德罗皮洛蒂更新与布赖恩Genisio附加的行为结合起来。 但是,删除代码为DataContext的改变Silverlight 4中不支持这一点。

如果您在列表框绑定到ObservableCollection<string>上述工作正常,但如果你绑定到像复杂对象ObservableCollection<Person> SelectedItems { get; private set; } ObservableCollection<Person> SelectedItems { get; private set; } ObservableCollection<Person> SelectedItems { get; private set; }通过一个DataTemplate它似乎并没有工作。 这是由于equals方法集合使用的默认实现。 你可以告诉哪些字段确定的对象相等时比较您的Person对象解决这个问题,这是由implementating接口进行IEquatable<T>的对象。

在此之后,的IndexOf(项目)的代码将工作和能够比较如果对象是相等的,并选择项目列表

// References in _boundList might not be the same as in _listBox.Items
int i = _listBox.Items.IndexOf(item);
if (i >= 0)
  _listBox.SelectedItems.Add(_listBox.Items[i]);

见链接: http://msdn.microsoft.com/en-us/library/ms131190(VS.95).aspx



Answer 8:

即时通讯使用在XAML选择改变事件EventToCommand对象和通过那里列表框作为参数。 比MMVM命令是选择的项目管理的ObservableCollection。 它的方便,快捷;)



Answer 9:

由Brian Genisio和塞缪尔·杰克的解决方案在这里是巨大的。 我已经成功地实现了它。 但是,我也有这个地方没有工作的情况下,自,我与WPF或.Net的专家,我无法调试。 我仍然不能确定是什么问题了,但在适当的时候,我发现了一个解决方法多选结合。 而在这个解决方案,我没有得到到DataContext。

该解决方案是人谁也不能作出上述2个解决方案的工作。 我想这个解决方案将不被视为MVVM。 它是这样的。 假设你在视图模型2分集:

public ObservableCollection<string> AllItems { get; private set; }
public ObservableCollection<string> SelectedItems { get; private set; }

你需要一个列表框:

<ListBox x:Name="MyListBox" ItemsSource="{Binding AllItems}" SelectionMode="Multiple" />

现在添加一个列表框,并将其绑定到SelectedItems和设置可见:

<ListBox x:Name="MySelectedItemsListBox" ItemsSource="{Binding SelectedItems, Mode=OneWayToSource}" SelectionMode="Multiple" Visibility="Collapsed" />

现在,在WPF页面背后的代码,添加到构造后InitializeComponent()方法:

MyListBox.SelectionChanged += MyListBox_SelectionChanged;

并添加了一个方法:

private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    MySelectedItemsListBox.ItemsSource = MyListBox.SelectedItems;
}

和你做。 这将肯定工作。 我想这可以在Silverlight也可以使用,如果上述方案不能正常工作。



Answer 10:

对于那些谁仍然不能让candritzky答案工作,确保你没有修改Windows的主题颜色和我一样。 事实证明我的列表框的背景颜色选择匹配的颜色,当列表框是失焦,从而使它看起来像被什么也没有选择。

更改列表框的背景刷红检查,如果这是发生了什么事给你。 这让我花2个小时,直到我意识到......



文章来源: Sync SelectedItems in a muliselect listbox with a collection in ViewModel