先显示最终结果,其中左边是错误的例子,右边才是正确的

在DataGrid中使用CheckBox选择行时典型的错误就是CheckBox没有Binding到任何属性上,这样的话当拖动滚动条时CheckBox.IsChecked就会乱掉,如Demo中左边那个DataGrid所示。最直观的解决方法是禁用DataGrid的滚动条,或者在绑定的数据上添加一个用于绑定CheckBox的bool属性。
其实只要在DataGrid.LoadingRow事件中将CheckBox的DataContext设定为另外一个Object,就不需要牺牲DataGrid的高效能,也不需要改变原有数据的结构。具体实现如下:

MainPage.xaml
 <my:MyDataGrid ItemsSource="{Binding}"
                       AutoGenerateColumns
="False">
            
<my:MyDataGrid.Columns>
                
<sdk:DataGridTextColumn Header="Name"
                                        Binding
="{Binding Name}"
                                        Width
="*" />
                
<sdk:DataGridTextColumn Header="GUID"
                                        Binding
="{Binding GUID}"
                                        Width
="*" />
            
</my:MyDataGrid.Columns>
        
</my:MyDataGrid>
MyDataGrid.cs
public class MyDataGrid : DataGrid
    {
        
private const string HeaderCheckBoxName = "select_column_checkbox";

        
private Dictionary<object, MarkObject> _markObjects;
        
private DataGridTemplateColumn _selectColumn;
        
private CheckBox _selectCheckBox;

        
public MyDataGrid()
        {
            _markObjects 
= new Dictionary<object, MarkObject>();

            _selectColumn 
= new DataGridTemplateColumn();
            _selectColumn.HeaderStyle 
= GetHeaderStyle();
            _selectColumn.CellTemplate 
= GetCellTemplate();
            
this.Columns.Insert(0, _selectColumn);
            
this.SizeChanged += new SizeChangedEventHandler(OnSizeChanged);
        }

        
public void SelectAll()
        {
            
if (_selectCheckBox != null)
                _selectCheckBox.IsChecked 
= true;
            SetAllSelectedStates(
true);
        }

        
public void UnselectAll()
        {
            
if (_selectCheckBox != null)
                _selectCheckBox.IsChecked 
= false;
            SetAllSelectedStates(
false);
        }

        
public List<T> GetSelectedItems<T>()
        {
            List
<T> result = new List<T>();
            
if (ItemsSource != null)
            {
                var enu 
= ItemsSource.GetEnumerator();
                
while (enu.MoveNext())
                {
                    
if (GetMarkObject(enu.Current).Selected)
                        result.Add((T)enu.Current);
                }
            }
            
return result;
        }

        
protected override void OnLoadingRow(DataGridRowEventArgs e)
        {
            
base.OnLoadingRow(e);

            
object dataContext = e.Row.DataContext;
            FrameworkElement element 
= _selectColumn.GetCellContent(e.Row);
            element.DataContext 
= GetMarkObject(dataContext);
        }

        
private Style GetHeaderStyle()
        {
            Style style 
= new System.Windows.Style();
            style.TargetType 
= typeof(ContentControl);

            StringBuilder tmp 
= new StringBuilder();
            tmp.Append(
"<DataTemplate ");
            tmp.Append(
"xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' ");
            tmp.Append(
"xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' >");
            tmp.Append(
string.Format("<CheckBox  Content='Select All' x:Name='{0}' VerticalAlignment='Center' HorizontalAlignment='Center' />", HeaderCheckBoxName));
            tmp.Append(
"</DataTemplate>");
            DataTemplate contentTemplate 
= XamlReader.Load(tmp.ToString()) as DataTemplate;

            style.Setters.Add(
new Setter(ContentControl.ContentTemplateProperty, contentTemplate));
            
return style;
        }

        
private DataTemplate GetCellTemplate()
        {
            StringBuilder tmp 
= new StringBuilder();
            tmp.Append(
"<DataTemplate ");
            tmp.Append(
"xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' ");
            tmp.Append(
"xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' >");
            tmp.Append(
"<CheckBox IsChecked='{Binding Selected,Mode=TwoWay}' VerticalAlignment='Center' HorizontalAlignment='Center' />");
            tmp.Append(
"</DataTemplate>");
            
return XamlReader.Load(tmp.ToString()) as DataTemplate;
        }

        
private MarkObject GetMarkObject(Object obj)
        {
            
if (_markObjects.ContainsKey(obj) == false)
            {
                MarkObject markObject;
                markObject 
= new MarkObject();
                _markObjects.Add(obj, markObject);
            }

            
return _markObjects[obj];
        }

        
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            _selectCheckBox 
= this.GetChild<CheckBox>(HeaderCheckBoxName);
            
if (_selectCheckBox == null)
                
return;

            _selectCheckBox.Checked 
+= (sender2, e2) => SetAllSelectedStates(true);
            _selectCheckBox.Unchecked 
+= (sender2, e2) => SetAllSelectedStates(false);
        }

        
private void SetAllSelectedStates(bool value)
        {
            
if (ItemsSource == null)
                
return;

            var enu 
= ItemsSource.GetEnumerator();
            
while (enu.MoveNext())
            {
                GetMarkObject(enu.Current).Selected 
= value;
            }
        }




    }

其中MarkObject是一个继承INotifyPropertyChanged的类,包含Selected属性,这样更改Selected时可以更新UI。在构造函数中直接插入了个模板列,模板列使用XamlReader.Load(string str)方法直接在代码中创建模板。至于列头的CheckBox则在SizeChanged事件时才可以通过VisualTreeHelper.GetChild递归找到。

欢迎指正。

作者: dino623 发表于 2011-02-23 23:10 原文链接

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"