4

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.

Philter
  • 535
  • 1
  • 9
  • 24

2 Answers2

8

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;
    }       
Juan Pablo Gomez
  • 5,203
  • 11
  • 55
  • 101
Philter
  • 535
  • 1
  • 9
  • 24
  • I am running into the exact same issue you posted here. Would you be willing to share some of the code you used in your solution? – imdandman Aug 08 '14 at 15:31
  • Basically I was trying to add header filters to the data grid columns. So I just overrode the base types, DataGridTextColumn, DataGridDCheckBoxColumn and DataGridComboBoxColumn. I also built my own DataGridDateColumn becuase there is no built in datepicker column. If you look at the link provided in the post above you can see how MS has implemented it's column and you can simply override it and hook into the events that are exposed. The key methods to take note of are the `GenerateEditingElement`, `GenerateElement`, and `PrepareCellForEdit` event handlers. – Philter Aug 11 '14 at 16:56
0

Here as Message shows, This look like a type conversion issue and for that what u can do is you can create a one valueconverter and can apply manually as and when require which will convert binded value to specific type you want and will server your purpose.

Ashok Rathod
  • 840
  • 9
  • 24
  • What I'm wondering is why would I have to use a type converter at all? I'm specifying the path that I"m looking for in the SelectedDate, yet WPF is binding the entire object for that row to it. Why is it doing that at all? It should be simply supplying the PolicyDate off of the TestData object to the custom control shouldn't it? Here is the TestData object. public class TestData { public string PolicyNumber { get; set; } public bool IsValid { get; set; } public string Name { get; set; } public DateTime PolicyDate { get; set; } } – Philter Jul 09 '14 at 13:31
  • here as in error shows you had use textblock for your datatemplate.which has property text and text is expecting string type object you had to convert your type System.DateTime to System.String or System.string type. Please try this – Ashok Rathod Jul 10 '14 at 03:13
  • That totally defeats the purpose of the custom control if I have to have a converter for every type I'm binding to the column. I ended up forgoing this venture and just creating a custom control that inherits from the DataGridBoundColumn in the WPF framework. – Philter Jul 10 '14 at 21:30