2

I have the following custom datagrid column, for passing in the value of the DatePart dependency property as the ConverterParameter to the editing element's converter:

Public Class DataGridTimeColumn
    Inherits DataGridTextColumn

    Shared ReadOnly DatePartProperty = DependencyProperty.Register("DatePart", GetType(DateTime?), GetType(DataGridTimeColumn), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, AddressOf RefreshBinding))
    Property DatePart As DateTime?
        Get
            Return GetValue(DatePartProperty)
        End Get
        Set(value As DateTime?)
            SetValue(DatePartProperty, value)
        End Set
    End Property

    Private Shared Sub RefreshBinding(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim tc As DataGridTimeColumn = d
        tc.Binding = tc.Binding
    End Sub

    Public Overrides Property Binding As BindingBase
        Get
            Return MyBase.Binding
        End Get
        Set(value As BindingBase)
            Dim b As Data.Binding = value
            With b
                .Converter = New TimeConverter
                .ConverterParameter = DatePart
            End With
            MyBase.Binding = b
        End Set
    End Property
End Class

With the following XAML:

<my:DataGridTimeColumn Header="From" Binding="{Binding FromDate}" DatePart="{Binding FromDate}" />
<my:DataGridTimeColumn Header="Until" Binding="{Binding TillDate}" DatePart="{Binding TillDate}" />

But RefreshBinding is never called (I've set a breakpoint and it's never triggered), and thus DatePart is always Nothing(null) when the ConverterParameter is set. How can I fix this?

Edit

In C#:

public class DataGridTimeColumn : DataGridTextColumn
{
    static readonly DependencyProperty DatePartProperty = DependencyProperty.Register(
        "DatePart", typeof(DateTime?), typeof(DataGridTimeColumn),
        new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, RefreshBinding)
    );

    public DateTime? DatePart
    {
        get { return (DateTime?)GetValue(DatePartProperty); }
        set { SetValue(DatePartProperty, value); }
    }

    private static void RefreshBinding(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    {
        var tc = (DataGridTimeColumn)d;
        tc.Binding = tc.Binding;
    }

    public override System.Windows.Data.BindingBase Binding
    {
        get { return base.Binding; }
        set
        { 
            var b = (Binding)value;
            b.Converter = new TimeConverter();
            b.ConverterParameter = DatePart;
            base.Binding = b;
        }
    }
}
Zev Spitz
  • 13,950
  • 6
  • 64
  • 136
  • Did you implement INotifyPropertyChanged for the `FromDate` and `TillDate` properties? – Clemens Jun 10 '13 at 12:42
  • The columns from are not part of the visual tree so they don't have the DataContex connected. If you look in output window while debugging you should see an error like "Cannot find governing FrameworkElement or FrameworkContentElement for target element..." – Adrian Fâciu Jun 10 '13 at 13:12
  • Have a look here for a possible solution: http://stackoverflow.com/questions/7660967/wpf-error-cannot-find-govering-frameworkelement-for-target-element – Adrian Fâciu Jun 10 '13 at 13:17
  • @Clemens - No I didn't implement INPC; I'll try it and see. – Zev Spitz Jun 10 '13 at 15:32
  • @AdrianFaciu I know I can't bind just any property in the generated elements by binding a property in the DataGridColumn. I am trying to bind a property of the DataGridColumn, and any change to the binding's source should cause the ConverterParameter of the binding to be set. Assuming the datagrid's elements have not been generated yet, they should use the new binding with the ConverterParameter, because the binding is copied/cloned to the generated elements. – Zev Spitz Jun 10 '13 at 15:48
  • @Clemens Implementing INPC doesn't help. – Zev Spitz Jun 11 '13 at 11:01
  • @AdrianFaciu Please write your comment as an answer for the bounty. If you can describe how to implement this for a `DateTime` value (as opposed to the link which uses the `Text` string property), that would also help. – Zev Spitz Jun 15 '13 at 21:07

4 Answers4

0

I might not understand what you're exactly trying to do, but this seems like a lot of work to just view/edit the DatePart. Would using a formatter and a DataGridTextColumn be easier? WPF Binding StringFormat Short Date String

Community
  • 1
  • 1
Darlene
  • 808
  • 5
  • 16
  • Unless this has been fixed in .NET 4.5, using `StringFormat` and a `DataGridTextColumn` produces the same results -- when editing the time-only column the date part is set to the current date, and not the original date part. – Zev Spitz Jun 15 '13 at 19:47
0

First, please use debug to check your binding path whether it is ok. Second, check your FromDate property whether is triggered. Third, please use Snoop to monitor this DP whether is updated.

Last, i remember DataGrid has dynamic binding bug, so i am not sure above steps can solve your problem. If it is not ok , please let me know.

  • 1) I'm getting the following error in Debug: `Cannot find governing FrameworkElement or FrameworkContentElement for target element.` 2) The `DatePart` setter is never triggered. – Zev Spitz Jun 15 '13 at 20:34
  • it said that your binding elementName is error, please check your binding according error information. – muzizongheng Jun 17 '13 at 01:44
0

I would recommend Darlene approach if you only need to format the value. If you really need a converter for some magic I would recommend to set it via your binding in the XAML (I don't really know why you are trying to set it in the property definition).

Your XAML will be something like this:

 <my:DataGridTimeColumn Header="From" Binding="{Binding FromDate, Converter={StaticResource myTimeConverter}} />

TimeConverter must implement IValueConverter and an instance must be defined in your window/datagrid resources. You can also define the TwoWay in that binding.

You can find a full example here.

In case you want to combine two or more pieces of data you can use a MultiBinding.

mikehc
  • 999
  • 8
  • 22
  • The problem is that when editing the value, `TimeConverter.ConvertBack` gets only the displayed value -- the time only (e.g. 17:00) and not the full date (5/7/12 17:00). There is no way to return the **original date** + the **edited time** using a converter alone. – Zev Spitz Jun 15 '13 at 20:12
  • If I am using a converter alone, I have no need to implement `DataGridTimeColumn`; I can use the standard `DataGridTextColumn`. – Zev Spitz Jun 15 '13 at 20:14
  • 1
    Ok tell me If follow, you want to combine two pieces of data: Original date and edited time, right? The simple way to do it (I think) is for you to have a new property where you combine this data in your model. But there's also a way to do it with binding using MultiBindings, it's very similar to the methods I told you before. [Check this example at MSDN](http://msdn.microsoft.com/en-us/library/system.windows.data.multibinding.aspx) And yes if you use the converter alone, you don't have to implement your own `DataGirdTextColumn` – mikehc Jun 15 '13 at 22:55
  • MultiBinding doesn't help; it has the same issue -- the `value` argument in `ConvertBack` only has the time part, and not the date part. – Zev Spitz Jun 16 '13 at 07:55
  • When the user edits the time and types 15:00, that is converted back to a DateTime in the converter. – Zev Spitz Jun 16 '13 at 17:35
0

Tried to understand what you want to achieve from the comments and if I got it right you can do it using only the view model and a converter with a small workaround:

In your converter ConvertBack method, you can try to figure out if the value entered by the user has only the time part and if so set the date to something that will not be used (for example DateTime.MinValue):

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
    var convertedDateTime = DateTime.Parse(value.ToString());
    var hasOnlyTime = value.ToString().Trim().Length <= 5; // This should be replaced with something more consistent

    if (hasOnlyTime)
        return new DateTime(DateTime.MinValue.Year, DateTime.MinValue.Month, DateTime.MinValue.Day, convertedDateTime.Hour, convertedDateTime.Minute, convertedDateTime.Second);

    return convertedDateTime;
}

Then in the object setter for the date you can check that and use the required value, if the date part is DateTime.MinValue change it back to the previous one, if not leave it as it is:

 private DateTime _fromDate;
 public DateTime FromDate
 {
    get { return _fromDate; }
    set 
    {
        if (value.Date != DateTime.MinValue)
            _fromDate = value;
        else
            _fromDate = new DateTime(_fromDate.Year, _fromDate.Month, _fromDate.Day, value.Hour, value.Minute, value.Second);

        OnPropertyChanged("FromDate");
    }
 }

Tested on .NET 4.5 and works as expected.

Adrian Fâciu
  • 12,414
  • 3
  • 53
  • 68
  • I'm not using MVVM (not something I can change), but the class is an EF-generated POCO, and I can accomplish the same thing by defining the POCO's property as private/internal, and an additional property on the partial class. – Zev Spitz Jun 19 '13 at 14:41
  • This works for existing instances. What can I do about new instances? I'm using this class in a DataGrid which allows entering new records. – Zev Spitz Jun 19 '13 at 14:45
  • Well, what is the problem with new instances ? What is not working as you'd expect ? – Adrian Fâciu Jun 19 '13 at 16:57
  • The DataGrid will automatically create new instances of the class. How can I specify the date part for new instances via binding? Each column in the DataGrid has a different date part. – Zev Spitz Jun 20 '13 at 07:10
  • Just use the constructor of your class to initialize the values to whatever you want, it should have nothing to do with binding. – Adrian Fâciu Jun 21 '13 at 07:04