我有一些数据的两个 View :一个 ListView (现在是 ListBox
,但我一直想切换到 ListView
)和 map 上的精美图形表示。在任一 View 中,用户都可以单击一个对象,它将在两个 View 中被选中。多选也是可能的,所以每个 ViewModel
实例有自己的IsSelected
属性(property)。
目前我正在绑定(bind)ListBoxItem.IsSelected
至ViewModel.IsSelected
,但这只有在 ListBox
不是虚拟化(see here)。不幸的是,禁用虚拟化会损害性能,并且我的应用程序变得太慢了。
所以我必须再次启用虚拟化。为了维护ViewModel.IsSelected
屏幕外项目的属性,我注意到 ListBox
和 ListView
有一个SelectionChanged
我可以(大概)用来从 ListBox/ListView
传播选择状态的事件到ViewModel
.
我的问题是,如何反向传播选择状态? SelectedItems
ListBox/ListView
的属性(property)是只读的!假设用户单击图形表示中的一个项目,但它在屏幕外 w.r.t.名单。如果我只是设置 ViewModel.IsSelected
然后 ListBox/ListView
将不知道新的选择,因此如果用户单击列表中的其他项目,它将无法取消选择该项目。我可以调用ListBox.ScrollIntoView
来自 ViewModel
,但有几个问题:
ListBox/ListView
中完全不同的位置。 . 那么,亲爱的 WPF 专家,有什么想法吗?
编辑:我最终切换到 Infragistics 控件并使用了一个丑陋且相当缓慢的解决方案。关键是,我不再需要答案了。
最佳答案
您可以创建 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
集合必须被初始化;行为不会设置它,它只会改变它的内容)
关于.net - 将多选 ListBox 与 MVVM 同步,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8088595/