Share via


WPF/MVVM: Binding the DatePickerTextBox in WPF

Introduction

The default control template of the built-in DatePicker control in WPF and Silverlight consists of, among some other visual elements and panels that defines its appearance and internal layout, a Calendar control that lets the user select a date by using a visual calendar and an editable DatePickerTextBox where the currently selected date is displayed.

When you bind the SelectedDate property of a DatePicker to a DateTime source property and entering a new date in the TextBox using the keyboard, the source property don’t actually get set until the TextBox loses focus. This behaviour happens even if you set the UpdateSourceTrigger property of the Binding to PropertyChanged and may cause some issues in your application.

If you for example are performing some validations based on the value of the source property and also are enabling or disabling controls or presenting some visual feedback to the user as a result of this validation logic, you probably want to perform the very same logic regardless of whether the user enters the new date using the calendar – the source property does get set as expected when a date is selected in the calendar – or by entering it directly in the TextBox.

Custom DatePicker

A solution to this issue is to define your own custom DatePicker class that extends the built-in one and handles the TextChanged event of the DatePickerTextBox. You can get a reference to the DatePickerTextBox in the DatePicker’s control template and hook up an event handler for the event by overriding the base class’ OnApplyTemplate() method:

public class  CustomDatePicker : DatePicker
{
    protected DatePickerTextBox _datePickerTextBox;
    public override  void OnApplyTemplate()
    {
        base.OnApplyTemplate();
  
        _datePickerTextBox = this.Template.FindName("PART_TextBox", this) as  DatePickerTextBox;
        if (_datePickerTextBox != null)
        {
            _datePickerTextBox.TextChanged += dptb_TextChanged;
        }
    }
  
    private void  dptb_TextChanged(object sender, TextChangedEventArgs e)
    {
       /* implementation presented below */
    }
}

In the TextChanged event handler you can then parse the value of the DatePicker’s Text property using the System.DateTime.TryParseExact method and, providing that the Text property actually contains a valid date, then set the SelectedDate property of the DatePicker:

public class  CustomDatePicker : DatePicker
{
    protected DatePickerTextBox _datePickerTextBox;
    protected readonly  string _shortDatePattern;
  
    public CustomDatePicker()
        : base()
    {
        _shortDatePattern = Thread.CurrentThread.CurrentCulture.DateTimeFormat.ShortDatePattern;
    }
  
    public override  void OnApplyTemplate()
    {
        ...
    }
  
  
    private void  dptb_TextChanged(object sender, TextChangedEventArgs e)
    {
        DateTime dt;
        if (DateTime.TryParseExact(_datePickerTextBox.Text, _shortDatePattern, Thread.CurrentThread.CurrentCulture, 
            System.Globalization.DateTimeStyles.None, out  dt))
        {
                this.SelectedDate = dt;
        }
  
    }
}

An important thing to notice in the above sample code is that the DateTime.TryParseExact method will only return true when the Text property contains a valid and completely entered date that matches the date format of the DatePicker. This format is the same that Windows uses to display dates, unless of course you have explicitly set the Thread.CurrentThread.CurrentCulture property to some other culture somewhere in your application. The default format is set under the Region and Language settings in the ControlPanel:

http://magnusmontin.files.wordpress.com/2014/09/cp_region.png?w=498&h=551

This means that a British user has to enter both the month and day part of the new date using two digits for the source property to get set immediately while an American user can choose to enter between 1 or 2 digits for both the day and the month part since Window’s default short date formats for the United Kingdom and United States regions are “dd/MM/yyyy” and “M/d/yyyy” respectively.

Also, for the parsing of the Text property in the TextChanged event handler to work as expected, the SelectedDateFormat of the DatePicker must be set to its default enumeration value of DatePickerFormat.Long.

Sample application

There is a sample project that uses the custom DatePicker class and demonstrates how the source property get set when you enter a date in the TextBox that can be downloaded from the MSDN code gallery here.

The sample WPF application consists of a single window with a CustomDatePicker control and TextBlock element that displays the current value of the same DateTime source property of a view model that is bound to the SelectedDate property of the CustomDatePicker control:

public class  ViewModel : INotifyPropertyChanged
{
    private DateTime? _date;
    public DateTime? Date
    {
        get { return _date; }
        set { _date = value; NotifyPropertyChanged(); }
    }
  
  
    public event  PropertyChangedEventHandler PropertyChanged;
    private void  NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new  PropertyChangedEventArgs(propertyName));
        }
    }
}
<Window x:Class="Mm.CustomDatePicker.WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Mm.CustomDatePicker.WpfApplication"
        xmlns:controls="clr-namespace:Mm.CustomDatePicker.Controls;assembly=Mm.CustomDatePicker.Controls"
        Title="blog.magnusmontin.net" Height="150" Width="525">
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <DockPanel Margin="20">
        <controls:CustomDatePicker DockPanel.Dock="Top" SelectedDate="{Binding Date}"/>
          
        <StackPanel Orientation="Horizontal" Margin="0 10 0 0">
            <TextBlock Text="Selected Date: " FontSize="16" FontWeight="Bold"/>
            <TextBlock Text="{Binding Date, StringFormat=dd/MM/yyyy}" FontSize="16"/>
        </StackPanel>
    </DockPanel>
</Window>

http://magnusmontin.files.wordpress.com/2014/09/customdatepicker.png?w=525&h=150

Additional Resources

DatePicker Class: http://msdn.microsoft.com/en-us/library/system.windows.controls.datepicker(v=vs.110).aspx (MSDN)
Binding.UpdateSourceTrigger Property: http://msdn.microsoft.com/en-us/library/system.windows.data.binding.updatesourcetrigger(v=vs.110).aspx (MSDN)
DateTime.TryParseExact Method: http://msdn.microsoft.com/en-us/library/system.datetime.tryparseexact(v=vs.110).aspx (MSDN)