在项目中,凡是涉及到表格的地方用的最多的控件,自然少不了DataGrid的身影,它明了的展示各种数据让人十分喜欢。现在要实现一个功能,使DataGrid具有全选和项选中的功能,如果在传统后台代码中完成这个事情可以说十分简单,但是换到MVVM模式下呢? 不得不面临一个很囧的情况,为了完成UI端CheckBox被选中后能在ViewModel中获取到选中的数据,不得不在在业务实体之外添加一个字段IsChecked 来与我们的数据交互,这样不仅影响美观还影响心情。
为了实现这一点,无疑需要设置DataGridTemplateColumn的CellTemplate,同时让每一个checkBox拥有这条数据的上下文引用,已方便在当我们选中或反选时能确定数据项,最好的设置时机当然是DataGrid的LoadingRow事件,可以捕获到我们所需要的一切。
当然,为了实现MVVM,我们需要添加一下附加属性
1 public static ObservableCollection<object> GetSelectedObjects(DependencyObject obj) 2 { 3 return (ObservableCollection<object>)obj.GetValue(SelectedObjectsProperty); 4 } 5 6 public static void SetSelectedObjects(DependencyObject obj, ObservableCollection<object> value) 7 { 8 obj.SetValue(SelectedObjectsProperty, value); 9 } 10 11 // 用于通知到ViewModel已经被选中的列 12 public static readonly DependencyProperty SelectedObjectsProperty = 13 DependencyProperty.RegisterAttached("SelectedObjects", typeof(ObservableCollection<object>), typeof(DataGrid), new PropertyMetadata(new ObservableCollection<object>())); 14 15 public static bool GetMonitor(DependencyObject obj) 16 { 17 return (bool)obj.GetValue(MonitorProperty); 18 } 19 20 public static void SetMonitor(DependencyObject obj, bool value) 21 { 22 obj.SetValue(MonitorProperty, value); 23 } 24 25 // 监视器,用于在DataGrid初始化的时候能有时机注册LoadingRow事件 26 public static readonly DependencyProperty MonitorProperty = 27 DependencyProperty.RegisterAttached("Monitor", typeof(bool), typeof(DataGrid), new PropertyMetadata(false, OnMonitorStateChanged)); 28 29 private static void OnMonitorStateChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) 30 { 31 var attachedDataGrid = dp as DataGrid; 32 _attachedDataGrid = attachedDataGrid; 33 _attachedDataGrid.LoadingRow += (s, ee) => 34 { 35 object dataContext = ee.Row.DataContext; 36 var elementControl = _attachedDataGrid.Columns[0] as DataGridSelectableColumn; 37 if (elementControl == null) 38 throw new InvalidCastException("请将DataGridSelectableColumn放在DataGrid的第一列"); 39 FrameworkElement element = elementControl.GetCellContent(ee.Row); 40 41 var elementContext = new SelectableColumnObject() {RowDataContext = new WeakReference( dataContext) };
//当CheckBox的IsChecked属性值变化之后发生 42 elementContext.OnChildItemStateChanged += elementContext_OnChildItemStateChanged; 43 element.DataContext = elementContext; 44 45 }; 46 } 47 48 static void elementContext_OnChildItemStateChanged(WeakReference rowDataContext, bool obj) 49 { 50 _currentColumn.UpdateChildItemSelectedState(rowDataContext, obj); 51 }
我将这一列的数据上下文保存到了一个名为RowDataContext的字段中,这样就满足了当选择状态更新时我能知道是那条数据,那么剩下的工作就是列选择和全选状态更新的事件了。Silverlight中DataGrid没有HeaderTemplate,也没有其他事件能够帮助我知道Header的加载,这时候需要借助于xaml,如下
1 <sdk:DataGridTemplateColumn.HeaderStyle> 2 <Style TargetType="sdk:DataGridColumnHeader"> 3 <Setter Property="Padding" Value="4" /> 4 <Setter Property="Template"> 5 <Setter.Value> 6 <ControlTemplate TargetType="sdk:DataGridColumnHeader"> 7 <CheckBox VerticalAlignment="Center" HorizontalAlignment="Center" VerticalContentAlignment="Center" Content="全选" Loaded="OnHeaderCheckBoxLoaded" /> 8 </ControlTemplate> 9 </Setter.Value> 10 </Setter> 11 </Style> 12 </sdk:DataGridTemplateColumn.HeaderStyle>
有了这里我们就可以直接在后台代码中定义事件进行处理
private void OnHeaderCheckBoxLoaded(object sender, RoutedEventArgs e) { var checkBox = sender as CheckBox; if (checkBox == null) return; checkBox.Loaded -= this.OnHeaderCheckBoxLoaded; checkBox.Checked += (s2, e2) => this.UpdateAllItemSelectedState(true); checkBox.Unchecked += (s2, e2) => this.UpdateAllItemSelectedState(false); }
到此为止,我们要做的工作基本已经完工,全选状态、行数据的选择状态更新我们做相应处理了。