Reputation: 3885
I'm building an application using WPF and MVVM. I've come across a situation where I have a view containing a usercontrol (representing a Timer). This usercontrol has a property in it's code behind which performs some calculations before getting and setting data.
TimerControl.xaml.cs:
public DateTime? DateTimeValue
{
get
{
string hours = this.txtHours.Text;
string minutes = this.txtMinutes.Text;
string amPm = this.txtAmPm.Text;
if (!string.IsNullOrWhiteSpace(hours) && !string.IsNullOrWhiteSpace(minutes) && !string.IsNullOrWhiteSpace(amPm))
{
string value = string.Format("{0}:{1} {2}", this.txtHours.Text, this.txtMinutes.Text, this.txtAmPm.Text);
DateTime time = DateTime.Parse(value);
return time;
}
else
{
return null;
}
}
set
{
DateTime? time = value;
if (time.HasValue)
{
string timeString = time.Value.ToShortTimeString();
//9:54 AM
string[] values = timeString.Split(':', ' ');
if (values.Length == 3)
{
this.txtHours.Text = values[0];
this.txtMinutes.Text = values[1];
this.txtAmPm.Text = values[2];
}
}
}
}
Now I wanted to bind this property to a property present in view model of the view. Following is property in the VM:
public DateTime? StartTime
{
get
{
return _StartTime;
}
set
{
_StartTime = value;
RaisePropertyChanged("StartTime");
}
}
This is how I am performing binding in the xaml of View.
MyView.xaml:
<my:TimeControl Background="White" Grid.Column="1" Grid.Row="2" Margin="3" x:Name="StartTimeControl" DateTimeValue="{Binding StartTime}" Width="150" Height="26" HorizontalAlignment="Left">
But it is giving me an error that: A 'Binding' cannot be set on the 'DateTimeValue' property of type 'TimeControl'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
I've been struggling for hours trying to figure out a way to make this binding work. I have even tried to make a dependency property in the TimeControl's code behind for the DateTimeValue property, which has resolved the above exception, but the binding still doesn't work. Whenever I access StartTime property in the VM's code behind, it is showing null. Although it should show a valid value by getting the DateTimeValue property.
Kindly suggest me a way to make this work. Thanks.
Upvotes: 2
Views: 1600
Reputation: 3407
Your implementation of DateTimeValue property shown in this question is certainly wrong and leads to exception, because DateTimeValue should be dependency property.
But you mentioned that you have tried to use dependency property with no success. I suppose the reason is in collision of DataContexts and your XAML looks like this:
<UserControl x:Class="Test.SomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:self="clr-namespace:Test"
Name="Root">
<WrapPanel>
<self:TimerControl Time="{Binding StartTime}"/>
</WrapPanel>
</UserControl>
This code doesn't work. Why? DataContext of TimerControl is inherited (or maybe you replace it at all), meanwhile when you address to StartTime you have in mind ViewModel as DataContext. So you should clearly point to correct DataContext:
<self:Timer Time="{Binding DataContext.StartTime, ElementName=Root}"/>
===UPDATE===
The whole code of my Timer control (as you can see my Timer has textbox, when you input some text, textbox raises appropriate event, which we handle and set Time property):
public partial class Timer : UserControl
{
public Timer()
{
InitializeComponent();
}
public DateTime? Time
{
get
{
return (DateTime?)this.GetValue(Timer.TimeProperty);
}
set
{
this.SetValue(Timer.TimeProperty, value);
}
}
public static readonly DependencyProperty TimeProperty =
DependencyProperty.Register("Time",
typeof(DateTime?),
typeof(Timer),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (d, e) => { }));
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (DateTime.Now.Ticks % 2 == 0)
{
this.Time = DateTime.Now;
}
else
{
this.Time = null;
}
}
}
And XAML:
<UserControl x:Class="Test.Timer">
<Grid>
<TextBox TextChanged="TextBox_TextChanged"/>
</Grid>
</UserControl>
Usage of Time control in XAML:
<UserControl x:Class="Test.StartupView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:self="clr-namespace:Test"
Name="Root">
<WrapPanel>
<self:Timer Time="{Binding DataContext.StartTime, ElementName=Root}"/>
</WrapPanel>
</UserControl>
Code behind of StartupView:
public StartupView()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
Property in ViewModel remains the same. During debugging setter of StartTime property fires every time when I change text in Timer.
Upvotes: 2
Reputation: 2773
What excatly do you want to do?
You can't bind to a standard property. If you want to bind you should use a dependency property.
public DateTime? DateTimeValue
{
get { return (DateTime?)GetValue(DateTimeValueProperty); }
set { SetValue(DateTimeValueProperty, value); }
}
// Using a DependencyProperty as the backing store for DateTimeValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DateTimeValueProperty =
DependencyProperty.Register("DateTimeValue", typeof(DateTime?), typeof(TimeControl), new UIPropertyMetadata(null));
Inside the UserControl:
<TextBox Text="{Binding DateTimeValue,RelativeSource={RelativeSource AncestorLevel=1, Mode=FindAncestor,AncestorType=UserControl}, Converter=...}" />
To bind directly to a DateTimeValue is not possible because there is no converter available for string->DateTime so you have to write an IValueConverter and specify this in your binding.
From outside of course you should be able to bind the value directly.
Upvotes: 2