以前寫過幾個方法實現這個功能,但最終還是選擇了不繼承DataGrid,所以再重新發布出來。
先顯示最終結果,其中左邊是錯誤的例子,右邊才是正確的
在DataGrid中使用CheckBox選擇行時典型的錯誤就是CheckBox沒有Binding到任何屬性上,這樣的話當拖動滾動條時CheckBox.IsChecked就會亂掉,如Demo中左邊那個DataGrid所示。最直觀的解決方法是禁用DataGrid的滾動條,或者在綁定的數據上添加一個用於綁定CheckBox的bool屬性。
其實只要在DataGrid.LoadingRow事件中將CheckBox的DataContext設定為另外一個Object,就不需要犧牲DataGrid的高效能,也不需要改變原有數據的結構。最終的實現方法是自定義一個包含DataGrid的DataGridTemplateColumn,而不需要重寫DataGrid,這樣的壞處就是每次調用需要多寫一局代碼,如果不想這樣就繼承DataGrid然后把這句代碼寫進去。在DataGrid.Columns中加入自定義的Column,然后在代碼中關聯DataGrid和這個Colum,就完成了:
AutoGenerateColumns ="False"
Grid.Column ="1"
x:Name ="TestDataGrid" >
< sdk:DataGrid.Columns >
< local:DataGridSelectColumn />
< sdk:DataGridTextColumn Header ="Name"
Binding =" {Binding Name} "
Width ="*" />
</ sdk:DataGrid.Columns >
</ sdk:DataGrid >
下面是這個DataGridSelectColumn的具體實現。創建一個UserControl然后把它改成DataGridTemplateColumn,這樣好處是DataTemplate和Style中的FrameworkElement都可以輕松地獲取。
x:Class ="DataGridSelectSample.DataGridSelectColumn"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d ="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable ="d"
d:DesignHeight ="300"
d:DesignWidth ="400" >
< sdk:DataGridTemplateColumn.HeaderStyle >
< Style TargetType ="ContentControl" >
< Setter Property ="ContentTemplate" >
< Setter.Value >
< DataTemplate >
< CheckBox Content ="Select All"
VerticalContentAlignment ="Center"
VerticalAlignment ="Center"
Loaded ="OnHeaderCheckBoxLoaded" />
</ DataTemplate >
</ Setter.Value >
</ Setter >
</ Style >
</ sdk:DataGridTemplateColumn.HeaderStyle >
< sdk:DataGridTemplateColumn.CellTemplate >
< DataTemplate >
< CheckBox IsChecked =" {Binding IsSelected,Mode=TwoWay} "
VerticalAlignment ="Center"
HorizontalAlignment ="Center" />
</ DataTemplate >
</ sdk:DataGridTemplateColumn.CellTemplate >
</ sdk:DataGridTemplateColumn >
{
private DataGrid _ownerDataGrid;
private CheckBox _headerCheckBox;
private Dictionary< object, MarkObject> _markObjects;
public event EventHandler SelectedItemChanged;
public DataGridSelectColumn()
{
InitializeComponent();
IsReadOnly = true;
_markObjects = new Dictionary< object, MarkObject>();
}
public DataGrid OwnerDataGrid
{
get { return _ownerDataGrid; }
set
{
_ownerDataGrid = value;
_ownerDataGrid.LoadingRow += OnLoadingRow;
}
}
public void SelecteAll()
{
if (_headerCheckBox != null)
_headerCheckBox.IsChecked = true;
SetAllSelectedStates( true);
}
public void UnselectAll()
{
if (_headerCheckBox != null)
_headerCheckBox.IsChecked = false;
SetAllSelectedStates( false);
}
public List<T> GetSelectedItems<T>()
{
List<T> result = new List<T>();
if (_ownerDataGrid.ItemsSource != null)
{
var enu = _ownerDataGrid.ItemsSource.GetEnumerator();
while (enu.MoveNext())
{
if (GetMarkObject(enu.Current).IsSelected)
result.Add((T)enu.Current);
}
}
ClearItems();
return result;
}
public void SetSelectedItems(IList items)
{
if (_ownerDataGrid.ItemsSource == null)
return;
var enu = _ownerDataGrid.ItemsSource.GetEnumerator();
while (enu.MoveNext())
{
GetMarkObject(enu.Current).IsSelected = items.Contains(enu.Current);
}
}
private void ClearItems()
{
var enu = _ownerDataGrid.ItemsSource.GetEnumerator();
List< object> list = new List< object>();
while (enu.MoveNext())
{
list.Add(enu.Current);
}
List< object> removableObjects = new List< object>();
foreach ( var item in _markObjects)
{
if (list.Contains(item.Key) == false)
{
removableObjects.Add(item.Key);
}
}
for ( int i = 0; i < removableObjects.Count; i++)
{
_markObjects.Remove(removableObjects[i]);
}
}
private void OnHeaderCheckBoxLoaded( object sender, RoutedEventArgs e)
{
_headerCheckBox = sender as CheckBox;
_headerCheckBox.Loaded -= OnHeaderCheckBoxLoaded;
_headerCheckBox.Checked += (s2, e2) => SetAllSelectedStates( true);
_headerCheckBox.Unchecked += (s2, e2) => SetAllSelectedStates( false);
}
private void OnLoadingRow( object sender, DataGridRowEventArgs e)
{
object dataContext = e.Row.DataContext;
FrameworkElement element = this.GetCellContent(e.Row);
element.DataContext = GetMarkObject(dataContext);
}
private void SetAllSelectedStates( bool value)
{
if (_ownerDataGrid.ItemsSource == null)
return;
var enu = _ownerDataGrid.ItemsSource.GetEnumerator();
while (enu.MoveNext())
{
GetMarkObject(enu.Current).IsSelected = value;
}
ClearItems();
}
private MarkObject GetMarkObject(Object obj)
{
if (_markObjects.ContainsKey(obj) == false)
{
MarkObject markObject;
markObject = new MarkObject();
_markObjects.Add(obj, markObject);
markObject.PropertyChanged += (s, e) =>
{
if (e.PropertyName == " IsSelected ")
{
if (SelectedItemChanged != null)
{
SelectedItemChanged( this, EventArgs.Empty);
}
}
};
}
return _markObjects[obj];
}
}
其中MarkObject是一個繼承INotifyPropertyChanged的類,包含Selected屬性,這樣更改IsSelected時可以更新UI。
最后加上DataGridExtensions 是為了方便調用。
View Code
{
internal static DataGridSelectColumn GetSelectColumn( this DataGrid dataGrid)
{
DataGridSelectColumn result = null;
for ( int i = 0; i < dataGrid.Columns.Count; i++)
{
result = dataGrid.Columns[i] as DataGridSelectColumn;
if (result != null)
break;
}
return result;
}
public static void EnableSelect( this DataGrid dataGird)
{
var column = GetSelectColumn(dataGird);
column.OwnerDataGrid = dataGird;
}
public static void SelectAll( this DataGrid dataGrid)
{
DataGridSelectColumn column = GetSelectColumn(dataGrid);
if (column == null)
throw new Exception( " No Select Column ");
column.SelecteAll();
}
public static void UnselectAll( this DataGrid dataGrid)
{
DataGridSelectColumn column = GetSelectColumn(dataGrid);
if (column == null)
throw new Exception( " No Select Column ");
column.UnselectAll();
}
public static List<T> GetSelectedItems<T>( this DataGrid dataGrid)
{
DataGridSelectColumn column = GetSelectColumn(dataGrid);
if (column == null)
throw new Exception( " No Select Column ");
return column.GetSelectedItems<T>();
}
public static void SetSelectedItems( this DataGrid dataGrid, IList items)
{
DataGridSelectColumn column = GetSelectColumn(dataGrid);
if (column == null)
throw new Exception( " No Select Column ");
column.SetSelectedItems(items);
}
}
附上完整源碼,歡迎指正。
