Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF DataGrid - Creating a new custom Column

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???

like image 558
NoOne Avatar asked Jun 29 '13 15:06

NoOne


1 Answers

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;
    }
}
//--------------------------------------------------------------------------------------------
like image 129
NoOne Avatar answered Oct 08 '22 05:10

NoOne