DateGrid: Binding to my own UserControl doesn't work

Nov 15, 2008 at 12:07 PM
Ok, first off, I'm new to WPF, but I have search alot for an answer to this problem and can't seem to figure out what to do about this.

Here the case.
I have a DataGrid where I bind the object TaskItem. This class is simple and have 3 properties:
class TaskItem
    {
        public TimeSpan From { get; set; }
        public TimeSpan To { get; set; }
        public string Note { get; set; }
    }

I bind a list of these object to my DataGrid in the Loaded event:
protected void SimpleWindow_Loaded(object sender, RoutedEventArgs e)
        {
            //Test databind
            TaskDataGrid.ItemsSource = TaskGenerator.GetTaskItems();
        }

The XAML for my DataGrid looks lige this:
<tk:DataGrid x:Name="TaskDataGrid"
                          Grid.Row="1"
                          CanUserAddRows="True"
                          CanUserDeleteRows="True"
                          AutoGenerateColumns="False"
                          SelectionUnit="Cell"
                          >
            <tk:DataGrid.Columns>
                <tk:DataGridTemplateColumn Header="To" Width="50" >
                    <tk:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <tw:TimePicker Time="{Binding Path=To }" />
                        </DataTemplate>
                    </tk:DataGridTemplateColumn.CellTemplate>
                </tk:DataGridTemplateColumn>
            </tk:DataGrid.Columns>
        </tk:DataGrid>



 I have my own UserControl, TimePicker, that I would like to bind the "To" property of my TaskItem class to. I would like to bind the "To" property to my "Time" property on my TimePicker class. I have testet the "Time" peroperty on my TimePicker class and this property seems to work when I'm not in a "binding" scenario.

When I run the code above I only see the "default" time set in my TimePicker, so the Time property is not ever set. If I change the code so I have a <Label /> instead of my TimePicker, I see the correct time from the TaskItem object.

I see these errors in my console:
System.Windows.Data Error: 39 : BindingExpression path error: 'To' property not found on 'object' ''Object' (HashCode=58891391)'. BindingExpression:Path=To; DataItem='Object' (HashCode=58891391); target element is 'TimePicker' (Name=''); target property is 'Time' (type 'TimeSpan')

I have googled this error message, but can't seem to find an solution for it that fits case.

What am I doing wrong?

Thanks in advance,
Qbus

Coordinator
Nov 17, 2008 at 1:56 PM
It looks that you are not bound to a TaskItem object for some reason.  Does it work if you replace TimePicker with TextBox?
Nov 17, 2008 at 3:28 PM
Yep it works fine with a Textbox, thats the odd thing.

Just tried this:
<tk:DataGridTemplateColumn Header="To" Width="50" >
                    <tk:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding Path=From, Mode=TwoWay}" />
                        </DataTemplate>
                    </tk:DataGridTemplateColumn.CellTemplate>
                </tk:DataGridTemplateColumn>

Works perfectly. What am I doing wrong in my UserControl?


Nov 17, 2008 at 3:32 PM
By the way, my TimePicker has this property, think I forgot that:

 public TimeSpan Time
        {
            get
            {
                return (TimeSpan)GetValue(TimeProperty);
            }
            set
            {
                time = value;
                PickerTextbox.Text = time.Hours.ToString("d2") + ":" + time.Minutes.ToString("d2");
                SetValue(TimeProperty, value);
                NotifyPropertyChanged("Time");
            }
        }
Nov 17, 2008 at 10:16 PM
Ok I've added a "Date" property (of type DateTime) to my TaskItem class.

I try to bind to the WPF Toolkit Calender:
<tk:DataGridTemplateColumn Header="Test" Width="50" >
                    <tk:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <tk:Calendar DisplayDate="{Binding Path=Date}" />
                        </DataTemplate>
                    </tk:DataGridTemplateColumn.CellTemplate>
                </tk:DataGridTemplateColumn>

This doesn't work either. The calender just shows me the current date.


Nov 19, 2008 at 7:28 PM
Any suggestions here?

Would you like to see the intire source? I could provide you with a link if needed, then you could see it for your self?

Thanks in advance,
Qbus :)
Coordinator
Nov 19, 2008 at 10:57 PM
Edited Nov 19, 2008 at 11:00 PM
A couple of things which may help....

         public TimeSpan Time
        {
            get
            {
                return (TimeSpan)GetValue(TimeProperty);
            }
            set
            {
                time = value;
                PickerTextbox.Text = time.Hours.ToString("d2") + ":" + time.Minutes.ToString("d2");
                SetValue(TimeProperty, value);
                NotifyPropertyChanged("Time");
            }
        }

It looks like the TimePicker.Time property is backed by a dependency property TimePicker.TimeProperty and there is some custom logic in its setter. Whenever a property is backed by a dependency property and one hooks a binding to it, WPF's binding engine may use the underlying dependency property directly without calling your custom logic in the setter. Hence it is not recommended to put custom logic in the setter of a property which is backed by dependency property. So the code should have been....

         public TimeSpan Time
        {
            get
            {
                return (TimeSpan)GetValue(TimeProperty);
            }
            set
            {
                SetValue(TimeProperty, value);
            }
        }

Now speaking about your custom logic, Dependency property provides you to hook a property changed call back while registering it, which gets called whenever the property value changes. Following could be an example of your Time dependency property definition.

public static readonly TimeProperty = DependencyProperty.Register("Time", 
                                                                                                        typeof(TimeSpan), 
                                                                                                        typeof(TimePicker), 
                                                                                                        new FrameworkPropertyMetaData(new PropertyChangedCallback(OnTimeChanged)));

private static void OnTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    TimePicker tp = (TimePicker)d;
    TimeSpan time = (TimeSpan)e.NewValue;
    tp.PickerTextbox.Text = time.Hours.ToString("d2") + ":" + time.Minutes.ToString("d2");
}

Or you can simply bind PickerTextbox.Text property to TimePicker.Time property using a converter if needed.

Also for your calender in the other template you might want to try settin Mode=TwoWay in the binding.

Let know how it goes!!
Nov 20, 2008 at 8:01 PM
Thanks!

It worked. I guess I missed that point about dependencyproperties...

Thanks again :)

//Qbus