WPF DataGrid - Creating a new custom Column
data-binding, datagrid, wpf
Solution
OK. Here is the complete code for a custom CheckBox column. It seems that, in order to have a control like a checkbox as a display (not editing) element in a DataGrid you have to make it hit-test-invisible. Or you can simply use a TextBlock to display some character that resembles a checkmark:
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;
}
}
//--------------------------------------------------------------------------------------------
Problem
I am trying to create my own checkbox column (replacing the default one), in order to move to more complex data columns later-on, and I have the following code: ``` 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); } } ``` And my custom 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); } } } ``` Now, everything seems nice except for two things: - It seems that the only used method in in `MyCheckBoxColumn` is the `GenerateElement()`. All the other methods are not used. I have put breakpoints in them and they never get hit... - I use an `ObservableCollection` as a data source and, while the rest of the columns notify me when they get changed, this one doesn't. The odd thing is that the `bool` value gets changed when you check/uncheck the checkbox, but without notification and without passing through `CommitCellEdit()`. Does anyone know what is going wrong here? EDIT : It seems that if I return a `TextBlock` from inside `GenerateElement()` it makes the other methods to be called (the notification problem doesn't get fixed though). But why doesn't this work with with CheckBoxes? How does the default check box column work???