20

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

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

不知道如何解决这个问题

谢谢迈克尔

4

10 回答 10

40

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

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" />

问题是 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);
    }
}

这种方法会起作用,但它真的很难看。我首选的方法是将这种行为提取到“附加行为”中。如果您这样做,您可以完全消除您的代码隐藏并在 XAML 中进行设置。好处是这个“附加行为”现在可以在任何 ListBox 中重复使用:

<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);
        }
    }
}
于 2009-08-19T12:16:00.920 回答
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;
      }
    }
  }
于 2011-05-19T17:19:37.087 回答
3

谢谢你!我添加了一个小更新来支持初始加载和 DataContext 更改。

干杯,

亚历山德罗·皮洛蒂 [MVP / IIS]

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);
        }
    }
}
于 2009-10-18T12:02:21.017 回答
3

通过选择 Collection Changed & Rebinded 上的项目更新了现有行为

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

于 2011-04-14T09:12:43.277 回答
1

如果您记得首先创建可观察集合的实例,则上述原始解决方案有效!此外,您需要确保 Observable 集合内容类型与您的 ListBox ItemSource 的内容类型匹配(如果您与上面提到的确切示例不同)。

于 2010-06-15T11:21:22.343 回答
0

这是一个包含此问题解决方案的博客,包括一个示例应用程序,以便您可以准确了解如何使其工作: http ://alexshed.spaces.live.com/blog/cns!71C72270309CE838!149.entry

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

于 2010-10-15T20:07:52.967 回答
0

我的解决方案是将 Alessandro Pilotti 更新与 Brian Genisio 附加行为结合起来。但是删除 DataContext 更改 Silverlight 4 的代码不支持这一点。

如果您将列表框绑定到ObservableCollection<string> 上面的工作正常,但如果您ObservableCollection<Person> SelectedItems { get; private set; } 通过 DataTemplate 绑定到复杂的对象,它似乎不起作用。这是由于集合使用的Equals方法的默认实现。您可以通过告诉您的 Person 对象在确定对象是否相等时比较哪些字段来解决此问题,这是通过IEquatable<T>在您的对象上实现接口来完成的。

之后 IndexOf(item) 代码将起作用,并且能够比较对象是否相等并选择列表中的项目

// 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

于 2011-02-18T21:19:08.783 回答
0

我在 XAML 中的选择更改事件上使用 EventToCommand 对象并将 ListBox 作为参数传递给那里。比 MMVM 中的命令管理所选项目的 ObservableCollection。它简单快捷;)

于 2011-08-04T14:40:36.433 回答
0

Brian Genisio 和 Samuel Jack 的解决方案很棒。我已经成功实施了。但我也遇到过这样的情况,因为我不是 WPF 或 .Net 专家,所以我无法调试它。我仍然不确定问题是什么,但在适当的时候,我找到了多选绑定的解决方法。在这个解决方案中,我不必访问 DataContext。

此解决方案适用于无法使上述 2 个解决方案起作用的人。我猜这个解决方案不会被视为 MVVM。它是这样的。假设您在 ViewModel 中有 2 个集合:

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

您需要一个列表框:

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

现在添加另一个 ListBox 并将其绑定到 SelectedItems 并设置 Visibility:

<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 中使用。

于 2012-05-05T09:53:01.553 回答
0

对于那些仍然无法使 candritzky 回答工作的人,请确保您没有像我一样修改您的 Windows 主题颜色。事实证明,当 ListBox 失焦时,我的 ListBox 背景颜色与选择颜色匹配,因此看起来好像没有选择任何内容。

将您的 ListBox 背景画笔更改为红色,以检查您是否遇到了这种情况。它让我花了 2 个小时,直到我意识到......

于 2013-03-09T03:03:50.633 回答