Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Control for DataGridTemplateColumn

I'm currently attempting to create a custom control out of a DataGridTemplateColumn that will be reused across many of our applications. I'm running into some issues getting a dependency property on the custom control to bind and raise the property changed notification correctly.

I currently have the control inheriting from DataGridTemplateColumn the xaml looks like this:

<DataGridTemplateColumn x:Class="Controls.DataGridDateColumn"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding SelectedDate}"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <Grid FocusManager.FocusedElement="{Binding ElementName=DatePicker}">
                <DatePicker Name="DatePicker" HorizontalAlignment="Left" VerticalAlignment="Center" SelectedDate="{Binding SelectedDate}"/>
            </Grid>
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>

And the code behind looks like this

public partial class DataGridDateColumn : DataGridTemplateColumn
{

    public static readonly DependencyProperty SelectedDateProperty = 
        DependencyProperty.Register("SelectedDate", 
        typeof(DateTime?), 
        typeof(DataGridDateColumn),
        new FrameworkPropertyMetadata(null, OnSelectedDateChanged));

    private static void OnSelectedDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DataGridDateColumn col = (DataGridDateColumn)d;
        col.SelectedDate = (DateTime?)e.NewValue;
    }

    public DateTime? SelectedDate {
        get { 
            return (DateTime?)GetValue(SelectedDateProperty); 
        }
        set { 
            SetValue(SelectedDateProperty, value);                
        }
    }      

    public DataGridDateColumn()
    {
        InitializeComponent();            
    }

}

When I have the control inside of my data grid on the main page and attempt to bind to SelectedDate like this <Controls:DataGridDateColumn Header="Policy Date" SelectedDate="{Binding Path=PolicyDate}" SortMemberPath="PolicyDate" />

I'm getting a binding error in the output window that states that it can't find the dependency property I'm referring to

System.Windows.Data Error: 40 : BindingExpression path error: 'SelectedDate' property not found on 'object' ''TestData' (HashCode=32071430)'. BindingExpression:Path=SelectedDate; DataItem='TestData' (HashCode=32071430); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

My initial thought is that because this is an items control I need to register the dependency property differently than I am, but I can't find any additional information.

The reason I am attempting to create custom columns is because we are planning on having specific behaviors associated with a few different types of the columns in order to make the user experience more homogeneous across all of our apps. So I want to be able to handle the behavior inside of the custom control so that we don't have to constantly hook up the different events to the template on every data grid that uses it.

Any suggestions would be appreciated.

like image 637
Philter Avatar asked Feb 13 '23 13:02

Philter


1 Answers

After fighting with this issue for a good deal of time I ended up creating a custom control that inherits from the DataGridBoundColumn in the PresentationFramework assembly. This works much better than trying to get the template properties to bind correctly. I believe they were not binding because the column template is not part of the visual tree. Based on what I see in the framework code it looks like what happens is the binding is passed off on to the cell that is generated. So the only real way to propagate the binding would be to use some kind of proxy object that does get data bound and have it map that binding to the dependency property. Very hacky.

I would suggest to future users to check out the DataGridTextColumn code on Microsoft's Reference Source. and build something similar for your own uses.

I ended up inheriting from the DataGridBound column for many of my custom controls. The key methods to pay attention to are GenerateEditingElement, GenerateElement and PrepareCellForEdit. They are all event handlers and allow you to manipulate the presentation of the cell as well as attach bindings and event handlers.

As an example here is a chunk of code from my custom DataGridDateColumn, as there is no built-in one and I wanted a reusable version with specific behavior for my application:

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        DatePicker dp = new DatePicker();
        dp.Name = "datePicker";
        CustomControlHelper.ApplyBinding(dp, DatePicker.SelectedDateProperty, this.Binding);
        dp.PreviewKeyDown += DatePicker_OnPreviewKeyDown;
        return (FrameworkElement)dp;
    }

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        TextBlock tb = new TextBlock();
        CustomControlHelper.ApplyBinding(tb, TextBlock.TextProperty, this.Binding);            
        cell.TextInput += OnCellTextInput;            
        return (FrameworkElement)tb;
    }

    protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
    {
        DatePicker picker = editingElement as DatePicker;
        DateTime newValue = DateTime.Today;

        if (picker != null)
        {
            DateTime? dt = picker.SelectedDate;
            if (dt.HasValue)
            {
                newValue = dt.Value;
            }

        }            

        picker.Focus();
        return newValue;
    }       
like image 132
Philter Avatar answered Feb 15 '23 10:02

Philter