【问题标题】:WPF Datagrid: MVVM friendly way to bind SelectedCells to my ViewModelWPF Datagrid:将 SelectedCells 绑定到我的 ViewModel 的 MVVM 友好方式
【发布时间】:2011-06-10 12:41:14
【问题描述】:

我正在使用 WPF 数据网格,并且具有 SelectionUnit="Cell" 和 SelectionMode="Extended"。我也在尽量遵守 MVVM 原则。

我需要我的 ViewModel 来跟踪当前的 SelectedCells。

如果我可以将其 SelectedCells 属性绑定到我的 ViewModel,生活会很轻松。奇怪的是,SelectedCells 只引发一次 - 当我们第一次选择网格中的任何单元格时。

MS 在这里解释它:http://social.msdn.microsoft.com/Forums/en/wpf/thread/737117f4-6d20-4232-88cf-e52cc44d4431

谁能想到一种对 MVVM 友好的方法来解决它?

谢谢!

【问题讨论】:

    标签: wpf mvvm datagrid


    【解决方案1】:

    我意识到我的最后一个答案是针对 SelectedItems 而不是 SelectedCells,所以我编写了一个完整的附加属性类来为多个 SelectedCells 进行数据绑定,其工作原理如下:

    <controls:DataGrid ItemsSource="{StaticResource list}"
                       SelectionMode="Extended"
                     behaviors:DataGridSelectedCellsBehavior.SelectedCells="{Binding Path=SelectedGridCellCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>  
    

    我有一个工作源代码和它的演示项目here

    附加属性行为代码:

    public class DataGridSelectedCellsBehavior
    {
        // Source : https://archive.codeplex.com/?p=datagridthemesfromsl
        // Credit to : T. Webster, https://stackoverflow.com/users/266457/t-webster
    
        public static IList<DataGridCellInfo> GetSelectedCells(DependencyObject obj)
        {
            return (IList<DataGridCellInfo>)obj.GetValue(SelectedCellsProperty);
        }
        public static void SetSelectedCells(DependencyObject obj, IList<DataGridCellInfo> value)
        {
            obj.SetValue(SelectedCellsProperty, value);
        }
    
        public static readonly DependencyProperty SelectedCellsProperty = DependencyProperty.RegisterAttached("SelectedCells", typeof(IList<DataGridCellInfo>), typeof(DataGridSelectedCellsBehavior), new UIPropertyMetadata(null, OnSelectedCellsChanged));
    
        static SelectedCellsChangedEventHandler GetSelectionChangedHandler(DependencyObject obj)
        {
            return (SelectedCellsChangedEventHandler)obj.GetValue(SelectionChangedHandlerProperty);
        }
        static void SetSelectionChangedHandler(DependencyObject obj, SelectedCellsChangedEventHandler value)
        {
            obj.SetValue(SelectionChangedHandlerProperty, value);
        }
    
        static readonly DependencyProperty SelectionChangedHandlerProperty = DependencyProperty.RegisterAttached("SelectedCellsChangedEventHandler", typeof(SelectedCellsChangedEventHandler), typeof(DataGridSelectedCellsBehavior), new UIPropertyMetadata(null));
    
        //d is MultiSelector (d as ListBox not supported)
        static void OnSelectedCellsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
        {
            if (GetSelectionChangedHandler(d) != null)
                return;
    
            if (d is DataGrid)//DataGrid
            {
                DataGrid datagrid = d as DataGrid;
                SelectedCellsChangedEventHandler selectionchanged = null;
                foreach (var selected in GetSelectedCells(d) as IList<DataGridCellInfo>)
                    datagrid.SelectedCells.Add(selected);
    
                selectionchanged = (sender, e) =>
                {
                    SetSelectedCells(d, datagrid.SelectedCells);
                };
                SetSelectionChangedHandler(d, selectionchanged);
                datagrid.SelectedCellsChanged += GetSelectionChangedHandler(d);
            }
            //else if (d is ListBox)
            //{
            //    ListBox listbox = d as ListBox;
            //    SelectionChangedEventHandler selectionchanged = null;
    
            //    selectionchanged = (sender, e) =>
            //    {
            //        SetSelectedCells(d, listbox.SelectedCells);
            //    };
            //    SetSelectionChangedHandler(d, selectionchanged);
            //    listbox.SelectionChanged += GetSelectionChangedHandler(d);
            //}
        }
    
    }
    

    查看型号代码:

    class DemoViewModel : INotifyPropertyChanged
    {  
        private IList<DataGridCellInfo> selectedGridCellCollection = new List<DataGridCellInfo>();
        public IList<DataGridCellInfo> SelectedGridCellCollection
        {
            get { return selectedGridCellCollection; }
            set
            {
                selectedGridCellCollection = value;
                NotifyPropertyChanged();
            }
        }
    
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    }
    

    【讨论】:

    • T.Webster 的回答很好,如果您使用的是 .NET4。显然,如果您使用 .NET3.5 并从 WPF 工具包中获取 DataGrid,则可能没有简单的答案,您必须捕获 SelectedCellsChanged 事件并手动获取“myGrid.SelectedCells”并将其传递给您的 ViewModel。我知道这听起来很蹩脚,但可能没有其他办法......
    • 我检查了你的网格,它非常好,但如果我们有一个复选框一次选择/取消选择所有列,它会非常慢
    【解决方案2】:

    您需要 SelectedCells 不断地进行数据绑定,还是仅在用户点击 OK/Accept 按钮时才需要?例如,如果您只在用户所处的任何进程结束时才需要它,您可以将 SelectedCells 绑定到 Button 的 CommandParameter 属性。 SelectedCells 是一个 IList,您知道的足以对选择的实际对象类型进行强制转换。另一个选项更混乱,您可以使用附加属性,将事件处理排除在您的视图之外。此附加属性将处理 ListBox 或在您的情况下为 DataGrid (MultiSelector)。

    public class Attach 
    {
        public static IList GetSelectedItems(DependencyObject obj)
        {
            return (IList)obj.GetValue(SelectedItemsProperty);
        }
    
        public static void SetSelectedItems(DependencyObject obj, IList value)
        {
            obj.SetValue(SelectedItemsProperty, value);
        }
    
        /// <summary>
        /// Attach this property to expose the read-only SelectedItems property of a MultiSelector for data binding.
        /// </summary>
        public static readonly DependencyProperty SelectedItemsProperty =
            DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(Attach), new UIPropertyMetadata(new List<object>() as IList, OnSelectedItemsChanged));
    
    
    
        static SelectionChangedEventHandler GetSelectionChangedHandler(DependencyObject obj)
        {
            return (SelectionChangedEventHandler)obj.GetValue(SelectionChangedHandlerProperty);
        }
        static void SetSelectionChangedHandler(DependencyObject obj, SelectionChangedEventHandler value)
        {
            obj.SetValue(SelectionChangedHandlerProperty, value);
        }
        static readonly DependencyProperty SelectionChangedHandlerProperty =
            DependencyProperty.RegisterAttached("SelectionChangedHandler", typeof(SelectionChangedEventHandler), typeof(Attach), new UIPropertyMetadata(null));
    
    
        //d is MultiSelector (d as ListBox not supported)
        static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
        {
            if (GetSelectionChangedHandler(d) != null)
                return;
    
            if (d is MultiSelector)//DataGrid
            {
                MultiSelector multiselector = d as MultiSelector;
                SelectionChangedEventHandler selectionchanged = null;
                foreach (var selected in GetSelectedItems(d) as IList)
                    multiselector.SelectedItems.Add(selected);
    
                selectionchanged = (sender, e) =>
                {
                    SetSelectedItems(d, multiselector.SelectedItems);
                };
                SetSelectionChangedHandler(d, selectionchanged);
                multiselector.SelectionChanged += GetSelectionChangedHandler(d);
            }
            else if (d is ListBox)
            {
                ListBox listbox = d as ListBox;
                SelectionChangedEventHandler selectionchanged = null;
    
                selectionchanged = (sender, e) =>
                {
                    SetSelectedItems(d, listbox.SelectedItems);
                };
                SetSelectionChangedHandler(d, selectionchanged);
                listbox.SelectionChanged += GetSelectionChangedHandler(d);
            }}}
    

    在 XAML 中的用法:

    <DataGrid ItemsSource="{Binding Path=SourceList}"
                  myControls:Attach.SelectedItems="{Binding Path=myMvvmSelectedItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                  SelectionMode="Extended" />
    

    【讨论】:

      【解决方案3】:

      您可能对 WPF Application Framework (WAF)BookLibrary 示例应用程序感兴趣。它展示了如何将 DataGrid.SelectedItems 与 ViewModel 同步。这可能与 SelectedCells 非常相似。

      【讨论】:

      • 只是为其他读者添加一个小信息:该示例基于代码隐藏事件处理程序,它更新 SelectionChangedEvent 中的 ViewModel,而不是 ViewModel 属性和 @987654323 之间的数据绑定@ 财产。如果有人不想遵循过于严格的 MVVM 规则,这仍然很有用。
      • 模型-视图-视图模型 (MVVM) 是一种设计模式。设计模式描述了如何允许对象交互。但他们并不禁止像代码隐藏这样的方面。如果您查看 Binding 或 Behaviors 在内部是如何工作的,那么您会发现它们也调用 ViewModels 或 Models 上的方法。这不是 MVVM 禁止的。
      【解决方案4】:

      在完美的 MVVM 绑定和完整的事件处理程序代码隐藏之间的某个地方存在交互 EventTriggers 的灰色区域(请参阅 Blend SDK):)
      如果您将事件触发器放入数据网格,并设置为“SelectionChanged”并将事件参数传递给命令(使用 EventToCommand 动作触发器),您可以希望从事件参数中获取所选项目...
      或者使用MS线程中所说的多重绑定:)

      【讨论】:

      • TDaver: It sounds like a good idea but oddly enough SelectionChanges isn't fired when a cell is selected and the DataGrid is set to SelectionMode=Cell as it does when a row is selected when SelectionMode="全行”。这很奇怪但真实.....
      • SelectionChanges 还是 SelectionChanged?
      • 是的,还有一个...您需要 VM 中包含的 CELLS 或 ITEMS 吗?第一个不是很好的 MVVM,但第一个只需要 SelectedItems 属性而不是 SelectedCells,我相信每次选择不同行中的单元格时都会引发第二个(因为如果单元格在同一行中,它是同一个项目.. .)
      • 我使用了这种方法,它与 mvvm light 配合得非常好,而且它也是最快的方法。选择删除 10000 行没有问题!!
      猜你喜欢
      • 2021-03-01
      • 2018-02-07
      • 2012-02-23
      • 2023-04-02
      • 1970-01-01
      • 2016-08-28
      • 2011-04-13
      • 2013-03-24
      • 1970-01-01
      相关资源
      最近更新 更多