.net - 将多选 ListBox 与 MVVM 同步

标签 .net wpf mvvm multi-select virtualizingstackpanel

我有一些数据的两个 View :一个 ListView (现在是 ListBox,但我一直想切换到 ListView)和 map 上的精美图形表示。在任一 View 中,用户都可以单击一个对象,它将在两个 View 中被选中。多选也是可能的,所以每个 ViewModel实例有自己的IsSelected属性(property)。

目前我正在绑定(bind)ListBoxItem.IsSelectedViewModel.IsSelected ,但这只有在 ListBox不是虚拟化(see here)。不幸的是,禁用虚拟化会损害性能,并且我的应用程序变得太慢了。

所以我必须再次启用虚拟化。为了维护ViewModel.IsSelected屏幕外项目的属性,我注意到 ListBoxListView有一个SelectionChanged我可以(大概)用来从 ListBox/ListView 传播选择状态的事件到ViewModel .

我的问题是,如何反向传播选择状态? SelectedItems ListBox/ListView 的属性(property)是只读的!假设用户单击图形表示中的一个项目,但它在屏幕外 w.r.t.名单。如果我只是设置 ViewModel.IsSelected然后 ListBox/ListView将不知道新的选择,因此如果用户单击列表中的其他项目,它将无法取消选择该项目。我可以调用ListBox.ScrollIntoView来自 ViewModel ,但有几个问题:

  • 在我的 UI 中,如果它们以图形方式位于同一位置,实际上可以一键选择两个项目,尽管它们可能位于 ListBox/ListView 中完全不同的位置。 .
  • 它打破了 ViewModel 的隔离(我的 ViewModel 完全不知道 WPF,我想保持这种状态。)

  • 那么,亲爱的 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/

    相关文章:

    design-patterns - 使用MVVM时,是否应该创建新的 View 模型以不同方式显示同一模型?

    c# - 替换文本文件中的文本 c#

    c# - 如何使用 Microsoft.Extensions.DependencyInjection 在 .net 框架中的 webapi 中注入(inject)依赖项?

    c# - 从 ASMX 调用静态方法的陷阱

    wpf - 更改文本时更改样式

    wpf - .net 4 中的 MVVM 有什么新功能?

    wpf - 使用子元素创建 WPF 用户控件

    c# - 测试DelegateCommand异步,无需公开处理程序

    c# - .NET 中的链接接缝

    android - BaseFragment中的LiveData使用者未从BaseViewModel接收更新