I'm converting some application code to use NodaTime classes instead of System.DateTime. Part of my application uses the PropertyGrid control to allow a user to edit a class containing both a LocalDate and an Instant. Without changing anything, the PropertyGrid displays the properties okay, but they are no longer editable. What's the best way of allowing the user to edit these fields.
For the sake of exposition, we can use this class as a representative of the type of thing I'd like to display and edit:
public class User
{
public string Name { get; set; }
public LocalDate BirthDate { get; set; }
public Instant NextAppointment { get; set; }
}
Best I've come up with so far:
Step 1: Create TypeConverter's so that the Noda classes are editable
public class ToAndFromStringTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
else
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
return true;
else
return base.CanConvertTo(context, destinationType);
}
}
public class LocalDateTypeConverter : ToAndFromStringTypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
DateTime parsed;
if (!DateTime.TryParse((string)value, out parsed))
throw new ArgumentException("Cannot convert '" + (string)value + "' to LocalDate.");
else
return new LocalDate(parsed.Year, parsed.Month, parsed.Day);
}
else
{
return base.ConvertFrom(context, culture, value);
}
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
var tvalue = (LocalDate)value;
try
{
var x = tvalue.ToString("yyyy-MM-dd");
return x;
}
catch (NullReferenceException)
{
return "1900-1-1";
}
catch
{
throw new ArgumentException("Could not convert '" + value.ToString() + "' to LocalDate.");
}
}
else
return base.ConvertTo(context, culture, value, destinationType);
}
public class InstantTypeConverter : ToAndFromStringTypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
try
{
DateTime parsed = DateTime.Parse((string)value);
LocalDateTime dt = LocalDateTime.FromDateTime(parsed);
Instant i = dt.InZoneLeniently(DateTimeZoneProviders.Default.GetSystemDefault()).ToInstant();
return i;
}
catch
{
throw new ArgumentException("Cannot convert '" + (string)value + "' to Instant.");
}
}
else
{
return base.ConvertFrom(context, culture, value);
}
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
try
{
Instant tvalue = (Instant)value;
LocalDateTime local = tvalue.InZone(DateTimeZoneProviders.Default.GetSystemDefault()).LocalDateTime;
string output = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss.FFFFFF").Format(local);
return output;
}
catch
{
throw new ArgumentException("Could not convert '" + value.ToString() + "' to LocalDate.");
}
}
else
return base.ConvertTo(context, culture, value, destinationType);
}
}
Step 2: Register TypeConverters
Put this code at the top of your app:
TypeDescriptor.AddAttributes(typeof(LocalDate), new TypeConverterAttribute(typeof(LocalDateTypeConverter)));
TypeDescriptor.AddAttributes(typeof(Instant), new TypeConverterAttribute(typeof(InstantTypeConverter)));
Step 3: Use custom collection editor to handle things like List
public class NodaCollectionEditor : System.ComponentModel.Design.CollectionEditor
{
public NodaCollectionEditor(Type collection_type) : base(collection_type) { }
protected override object CreateInstance(Type itemType)
{
if (itemType == typeof(LocalDate))
return LocalDateHelper.MinValue;
else
return base.CreateInstance(itemType);
}
}
This can be registered by adding this attribute to any appropriate properties:
[System.ComponentModel.Editor(typeof(NodaCollectionEditor),typeof(System.Drawing.Design.UITypeEditor))]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With