A Nullable DateTime IValueConverter and ValidationRule
Hey guys,
So I'm designing my issue editing form in the WPF version of Comicster, and one of the fields you can edit is the release date of the issue. Thing is, if you don't know (or don't care) what the release date is, you can leave it blank. The property itself is a Nullable DateTime, so it doesn't store any value if you don't supply one.
The problem is that the default binding between a DateTime (even a nullable one) and a TextBox in WPF doesn't allow you to leave the text empty! It wants you to enter a valid DateTime value.
So the first step was to create an IValueConverter which could convert between DateTime and String, but return null if the string is empty. No sweat.
The next problem, as outlined by living legend Douglas Stockwell here, is that any exceptions raised from a custom IValueConverter aren't caught by the built-in ExceptionValidationRule. So if you enter "fred" in my TextBox, the whole program crashes and burns. Not ideal. The only solution Douglas found (and it's a good one IMHO) is to use your IValueConverter as a ValidationRule too.
With that working, I added a few little niceties to the code. So now you can type "today", "now", "yesterday" and "tomorrow" into the TextBox and they'll parse correctly. I'm still considering allowing values like "Monday" and returning the date of the most recent Monday, but I want that to be culture-dependent so I'm not doing it just yet.
Anyway, here's the code! Enjoy!
class NullableDateTimeConverter : ValidationRule, IValueConverter { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { if (value == null || value.ToString().Trim().Length == 0) return null; return new ValidationResult( ConvertBack(value, typeof(DateTime?), null, cultureInfo) != DependencyProperty.UnsetValue, "Please enter a valid date, or leave this value blank"); } #region IValueConverter Members public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value == null) return ""; DateTime? dt = value as DateTime?; if (dt.HasValue) { return parameter == null ? dt.Value.ToString() : dt.Value.ToString(parameter.ToString()); } return ""; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (value == null || value.ToString().Trim().Length == 0) return null; string s = value.ToString(); if (s.CompareTo("today") == 0) return DateTime.Today; if (s.CompareTo("now") == 0) return DateTime.Now; if (s.CompareTo("yesterday") == 0) return DateTime.Today.AddDays(-1); if (s.CompareTo("tomorrow") == 0) return DateTime.Today.AddDays(1); DateTime dt; if (DateTime.TryParse(value.ToString(), out dt)) return dt; return DependencyProperty.UnsetValue; } #endregion }
Comments
# Jim
29/10/2008 11:50 AM
Hi,
I'm trying to do a similar thing and I'm finding that while returning UnsetValue will leave a null if there's one currently in there, it won't set my property to null if it already has a value. It just leaves the existing value.
Have you encountered this problem? I wonder if it's something specific to my type; it's an object and can be assigned a null value, but is not Nullable<T>.
# mabster
29/10/2008 12:08 PM
Interesting one, Jim. I can't say I've struck that particular problem, but the behaviour you describe makes sense. Perhaps instead of UnsetValue I should be explicitly returning null from ConvertBack.
# Jim
29/10/2008 12:50 PM
Got it - I was getting an error returning null from ConvertBack(), and thought that the binding couldn't assign this to my generic property. But it can; the exception was elsewhere.
So yeah, the trick it to return null; if the type is nullable it will either keep it null or set it to null.
# Jeff
5/12/2008 5:21 AM
Thanks for sharing this. I was having problems with the first line in ValidationResult.
"if (value == null || value.ToString().Trim().Length == 0) return null;"
Shouldn't it return "new ValidationResult( true, null ) instead of null?
# mabster
5/12/2008 5:57 AM
You could be right, Jeff! I've not had any trouble with the code as-is, but what you're saying makes sense!
# mo
28/08/2013 2:46 PM
find this quite useful, many thanks