WPF DataGrid - 创建新的自定义列

时间:2013-06-29 15:25:52

标签: wpf data-binding datagrid

我正在尝试创建自己的复选框列(替换默认列),以便稍后移动到更复杂的数据列,并且我有以下代码:

public class MyCheckBoxColumn : DataGridBoundColumn
{
    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        var cb = new CheckBox();
        var bb = this.Binding as Binding;
        var b = new Binding { Path = bb.Path, Source = cell.DataContext };
        cb.SetBinding(ToggleButton.IsCheckedProperty, b);
        return cb;
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        var cb = new CheckBox();
        var bb = this.Binding as Binding;
        var b = new Binding { Path = bb.Path, Source = ToggleButton.IsCheckedProperty };
        cb.SetBinding(ToggleButton.IsCheckedProperty, b);
        return cb;
    }

    protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
    {
        var cb = editingElement as CheckBox;
        return cb.IsChecked;
    }

    protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue)
    {
        var cb = editingElement as CheckBox;
        if (cb != null) cb.IsChecked = (bool)uneditedValue;
    }

    protected override bool CommitCellEdit(FrameworkElement editingElement)
    {
        var cb = editingElement as CheckBox;
        BindingExpression binding = editingElement.GetBindingExpression(ToggleButton.IsCheckedProperty);
        if (binding != null) binding.UpdateSource();
        return true;// base.CommitCellEdit(editingElement);
    }
}

我的自定义DataGrid:

public class MyDataGrid : DataGrid
{
    protected override void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e)
    {
        try
        {
            var type = e.PropertyType;
            if (type == typeof(bool))
            {
                var col = new MyCheckBoxColumn();
                col.Binding = new Binding(e.PropertyName) {Mode = BindingMode.TwoWay};
                e.Column = col;
            }
            else
            {
                base.OnAutoGeneratingColumn(e);
            }
            var propDescr = e.PropertyDescriptor as System.ComponentModel.PropertyDescriptor;
            e.Column.Header = propDescr.Description;
        }
        catch (Exception ex)
        {
            Utils.ReportException(ex);
        }
    }
}

现在,除了两件事之外,一切似乎都很好:

  • 似乎MyCheckBoxColumn中唯一使用的方法是GenerateElement()。不使用所有其他方法。我已经在它们中加入了断点,它们永远不会被击中......
  • 我使用ObservableCollection作为数据源,而其余的列在更改时通知我,但这个列没有。

奇怪的是,当您选中/取消选中复选框时,bool值会更改,但不会通知CommitCellEdit()。 有谁知道这里出了什么问题?

编辑:

似乎如果我从TextBlock内部返回GenerateElement(),则会调用其他方法(虽然通知问题不会得到修复)。但是为什么这不适用于CheckBoxes?默认复选框列如何工作???

1 个答案:

答案 0 :(得分:4)

行。以下是自定义CheckBox列的完整代码。看来,为了让像复选框这样的控件作为DataGrid中的显示(不是编辑)元素,你必须使它成为命中测试不可见的。或者您可以简单地使用TextBlock来显示一个类似于复选标记的字符:

public class MyCheckBoxColumn : DataGridBoundColumn
{
    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        var cb = new CheckBox() { IsHitTestVisible = false, HorizontalAlignment = HorizontalAlignment.Center, HorizontalContentAlignment = HorizontalAlignment.Center };
        var bb = this.Binding as Binding;
        var b = new Binding { Path = bb.Path, Source = dataItem, Mode = BindingMode.TwoWay };
        cb.SetBinding(ToggleButton.IsCheckedProperty, b);
        return cb;
//          var cb = new TextBlock() { TextAlignment = TextAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center };
//          var bb = this.Binding as Binding;
//          var b = new Binding { Path = bb.Path, Source = dataItem, Mode = BindingMode.TwoWay, Converter = new MyBoolToMarkConverter() };
//          cb.SetBinding(TextBlock.TextProperty, b);
//          return cb;
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        var cb = new CheckBox() { HorizontalAlignment = HorizontalAlignment.Center, HorizontalContentAlignment = HorizontalAlignment.Center };
        var bb = this.Binding as Binding;
        var b = new Binding { Path = bb.Path, Source = dataItem, Mode = BindingMode.TwoWay };
        cb.SetBinding(ToggleButton.IsCheckedProperty, b);
        return cb;
    }

    protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
    {
        var cb = editingElement as CheckBox;
        if (cb != null) return cb.IsChecked;
        return false;
    }

    protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue)
    {
        var cb = editingElement as CheckBox;
        if (cb != null) cb.IsChecked = (bool)uneditedValue;
    }

    protected override bool CommitCellEdit(FrameworkElement editingElement)
    {
        // The following 2 lines seem to help when sometimes the commit doesn't happen (for unknown to me reasons).
        //var cb = editingElement as CheckBox;
        //cb.IsChecked = cb.IsChecked;
        BindingExpression binding = editingElement.GetBindingExpression(ToggleButton.IsCheckedProperty);
        if (binding != null) binding.UpdateSource();
        return true;// base.CommitCellEdit(editingElement);
    }
}
//--------------------------------------------------------------------------------------------
public class MyBoolToMarkConverter : IValueConverter
{
    const string cTick = "■";

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value.GetType() != typeof(bool)) return "";
        bool val = (bool)value;
        return val ? cTick : "";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value.GetType() != typeof(string)) return false;
        string val = (string)value;
        return val == cTick;
    }
}
//--------------------------------------------------------------------------------------------