在項目中,凡是涉及到表格的地方用的最多的控件,自然少不了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);
}
到此為止,我們要做的工作基本已經完工,全選狀態、行數據的選擇狀態更新我們做相應處理了。