将多选 ListBox 与 MVVM 同步
Posted
技术标签:
【中文标题】将多选 ListBox 与 MVVM 同步【英文标题】:Synchronizing multi-select ListBox with MVVM 【发布时间】:2011-12-26 16:17:22 【问题描述】:我有一些数据的两种视图:一个列表视图(现在是ListBox
,但我一直想切换到ListView
)和地图上的精美图形表示。在任一视图中,用户都可以单击一个对象,它将在两个视图中被选中。多选也是可能的,因此每个ViewModel
实例都有自己的IsSelected
属性。
目前我将ListBoxItem.IsSelected
绑定到ViewModel.IsSelected
,但这只有在ListBox
没有虚拟化(see here)时才能正常工作。不幸的是,禁用虚拟化会影响性能,而且我的应用变得太慢了。
所以我必须再次启用虚拟化。为了维护屏幕外项目的ViewModel.IsSelected
属性,我注意到ListBox
和ListView
有一个SelectionChanged
事件,我可以(大概)使用它来将选择状态从ListBox/ListView
传播到ViewModel
.
我的问题是,如何反向传播选择状态? ListBox/ListView
的 SelectedItems
属性是只读的!假设用户单击图形表示中的一个项目,但它在屏幕外 w.r.t.名单。如果我只是设置ViewModel.IsSelected
,那么ListBox/ListView
将不知道新的选择,因此如果用户单击列表中的其他项目,它将无法取消选择该项目。我可以从ViewModel
拨打ListBox.ScrollIntoView
,但有几个问题:
ListBox/ListView
中完全不同的位置。
它破坏了 ViewModel 的隔离(我的 ViewModel 完全不知道 WPF,我想保持这种状态。)
那么,亲爱的 WPF 专家,有什么想法吗?
编辑:我最终切换到 Infragistics 控件并使用了一个丑陋且相当缓慢的解决方案。重点是,我不再需要答案了。
【问题讨论】:
这能回答你的问题吗? Bind to SelectedItems from DataGrid or ListBox in MVVM 【参考方案1】:您可以创建一个Behavior,将ListBox.SelectedItems
与您的 ViewModel 中的集合同步:
public class MultiSelectionBehavior : Behavior<ListBox>
protected override void OnAttached()
base.OnAttached();
if (SelectedItems != null)
AssociatedObject.SelectedItems.Clear();
foreach (var item in SelectedItems)
AssociatedObject.SelectedItems.Add(item);
public IList SelectedItems
get return (IList)GetValue(SelectedItemsProperty);
set SetValue(SelectedItemsProperty, value);
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiSelectionBehavior), new UIPropertyMetadata(null, SelectedItemsChanged));
private static void SelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
var behavior = o as MultiSelectionBehavior;
if (behavior == null)
return;
var oldValue = e.OldValue as INotifyCollectionChanged;
var newValue = e.NewValue as INotifyCollectionChanged;
if (oldValue != null)
oldValue.CollectionChanged -= behavior.SourceCollectionChanged;
behavior.AssociatedObject.SelectionChanged -= behavior.ListBoxSelectionChanged;
if (newValue != null)
behavior.AssociatedObject.SelectedItems.Clear();
foreach (var item in (IEnumerable)newValue)
behavior.AssociatedObject.SelectedItems.Add(item);
behavior.AssociatedObject.SelectionChanged += behavior.ListBoxSelectionChanged;
newValue.CollectionChanged += behavior.SourceCollectionChanged;
private bool _isUpdatingTarget;
private bool _isUpdatingSource;
void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
if (_isUpdatingSource)
return;
try
_isUpdatingTarget = true;
if (e.OldItems != null)
foreach (var item in e.OldItems)
AssociatedObject.SelectedItems.Remove(item);
if (e.NewItems != null)
foreach (var item in e.NewItems)
AssociatedObject.SelectedItems.Add(item);
if (e.Action == NotifyCollectionChangedAction.Reset)
AssociatedObject.SelectedItems.Clear();
finally
_isUpdatingTarget = false;
private void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
if (_isUpdatingTarget)
return;
var selectedItems = this.SelectedItems;
if (selectedItems == null)
return;
try
_isUpdatingSource = true;
foreach (var item in e.RemovedItems)
selectedItems.Remove(item);
foreach (var item in e.AddedItems)
selectedItems.Add(item);
finally
_isUpdatingSource = false;
这种行为可以如下所示使用:
<ListBox ItemsSource="Binding Items"
DisplayMemberPath="Name"
SelectionMode="Extended">
<i:Interaction.Behaviors>
<local:MultiSelectionBehavior SelectedItems="Binding SelectedItems" />
</i:Interaction.Behaviors>
</ListBox>
(请注意,您的 ViewModel 中的 SelectedItems
集合必须被初始化;行为不会设置它,它只会更改其内容)
【讨论】:
要完成这项工作,应该注意您必须找到 System.Windows.Interactivity.dll 的副本(它位于 Expression Blend SDK 中)并且您必须定义“i: " XAML 根元素中的前缀,如 xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"... 您还必须定义 xmlns:local 以指向包含多选行为。 奇怪的是,虽然这个类是从 Behavior 派生的,但我可以不加修改地将它附加到 ListView!原来 ListView 是从 ListBox 派生的。 PRISM MVVM Reference Implementation 也使用这种方法。它有一个称为 SynchronizeSelectedItems 的行为,在 Prism4\MVVM RI\MVVM.Client\Views\MultipleSelectionView.xaml 中使用,它将选中的项目与名为 Selections 的 ViewModel 属性同步 @Qwertie 请将这个很棒的答案标记为答案。谢谢托马斯 让它在我的应用程序中运行。诀窍:我必须在我的视图模型中为SelectedItems
使用ObservableCollection
,否则它的计数一直为0。以上是关于将多选 ListBox 与 MVVM 同步的主要内容,如果未能解决你的问题,请参考以下文章