Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you use NodaTime Classes in a PropertyGrid?

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; }
}
like image 522
scott Avatar asked Oct 06 '22 19:10

scott


1 Answers

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))]
like image 63
scott Avatar answered Oct 10 '22 03:10

scott