Saturday, July 21, 2007

WPF and Date Entry

As most people with some WPF experience will know, V1.0 does not ship with an equivalent to Windows Forms' DateTimePicker class. If you need to allow date entry, your choices are:

In the past I have tried to use Kevin's implementation but came up against some frustrating bugs. I therefore switched to hosting the Windows Forms control inside my WPF application. However, this comes with problems of its own.

I finally got a chance to look at Kevin's implementation in some detail and am going to document bugs and fixes here for future reference and for others to benefit from. So here are the issues and resolution, in order of importance.

Problem 1: Value must be of Unspecified type and date component only

If you assign (or bind to) a DateTime whose Kind is not DateTimeKind.Unspecified, the control does not work. When you try to select a new date, nothing happens. This is due to assumptions throughout the code base that Value.Kind == DateTimeKind.Unspecified. Rather than fix the entire code base to work with any DateTime kind, I "fixed" this problem by altering the coercion logic for the Value property as follows (my changes are enlarged):

private static object CoerceValue(DependencyObject d, object value)
{
DatePicker datepicker = (DatePicker)d;

if (value != null)
{
DateTime newValue = (DateTime)value;
newValue = DateTime.SpecifyKind(newValue, DateTimeKind.Unspecified).Date;

DateTime min = datepicker.MinDate;
if (newValue < min)
{
return min;
}

DateTime max = datepicker.MaxDate;
if (newValue > max)
{
return max;
}

return newValue;
}
return value;
}

All I'm doing here is changing the type of the DateTime to Unspecified and extracting its Date component. Note that any value you get out of the DatePicker will have a Kind of Unspecified. If you need your DateTimes as a specific kind, you'll need to convert them to that kind using a value converter. I'll soon be releasing a set of WPF converters, one of which will make this very simple.


Problem 2: Formatting does not use correct culture information


Next up is the somewhat perplexing use of the XML language to format DateTimes, rather than the use of CultureInfo.CurrentCulture. This fix was a one-liner in the DoFormat method:

CultureInfo cultureInfo = CultureInfo.CurrentCulture;// Language != null ? Language.GetSpecificCulture() : null;

As pointed out by Chris in the comments, this fix should also be applied to the DoParse method.

Problem 3: Template sets minimum width


For some reason, the default template for the DatePicker control sets a minimum width of 150. I don't think a minimum width is necessary at all, but I reduced this to 50 because it was playing havoc with my layout. The fix is in DateControls.generic.xaml:

<Setter Property="MinWidth" Value="50"/>

Problem 4: No CLR->XML namespace mapping


This is just a minor inconvenience. There is no mapping between the CLR namespaces and an XML namespace. To rectify this, I just added this to AssemblyInfo.cs:

[assembly: System.Windows.Markup.XmlnsDefinition("http://www.microsoft.com/wpf/samples", "Microsoft.Samples.KMoore.WPFSamples.DateControls")]

9 comments:

Anonymous said...

Thanks for the info, Kent. You're probably aware, but some of your fixed format text (i.e. code samples) are being truncated.

- Ben

Chris Peacock said...

That's great, thanks for that. However, you've missed another correction which affects the date picker's interpretation of the date when you enter it into the text portion of the control. In 'DoParse', the culture info fix you used elsewhere should be applied, i.e.:

CultureInfo cultureInfo = Language != null ? Language.GetSpecificCulture() : null;

should be replaced with:

CultureInfo cultureInfo = CultureInfo.CurrentCulture;

Hope this helps.

Kent Boogaart said...

Excellent, thanks for the info Chris. I've updated the post accordingly.

shmiddy said...

I'm glad you folks are on the subject. Call me a rookie, but I want to let a user update a Due Date in a WPF app.

The idea is to display the existing due date value in the datepicker. the user then selects the new due date and saves.

I haven't been able to find a way to pass the existing due date value into Kevin's datepicker.

In another wpf datepicker I tried earlier, there was a CurrentlySelectedDate dependency property. I'm looking for something similar here and can't find one. Any ideas?

Thanks!

Kent Boogaart said...

Hi scmiddy,

There is a Value property on the DatePicker class that you can use for this.

Willie D. said...

Pardon my ignorance about Coersion but is the CoerceMaxDate() method not Coercing the MinDate and a CoerceMinDate() method missing all together?

Scott said...
This comment has been removed by the author.
Scott said...

Actually, one other question. Has anybody managed to come up with a method to allow the year be quickly changed in the month calendar?

If the user is trying to enter say a birth date, and needs to get back to 1954, scrolling is definitely not going to please the user.

shubham said...

HI Kent,

I am also facing one issue while using the DatePicker Control. I found one more bug.

Since in the Datepicker control we can edit the date using CanEdit property , and accepts only format of MM/DD/YYYY.What the user did it changes the date from 21/12/2007 to 21/12/20 and tries to save it since it is wrong.But the datepicker control is changing the above date to 21/12/2020. Could u please through suggestions for the same