How to get the value of a runtime property back into a design time property?
See the question and my original answer on StackOverflowAssuming you know how to dynamically add a TypeConverter to a property, as seen in How to get a runtime list into a PropertyGrid?, you need to build a a converter with properties that remember the initial instance that's being worked on.
Here is some sample code you can test like this:
public class A
{
[TypeConverter(typeof(FullNameConverter))] // for quick demo purpose
public string FullName { get; set; } = "John Doe";
}
// a class that knows how to parse and format a full name
public class FullName
{
public string Forename { get; set; }
public string LastName { get; set; }
public override string ToString() => $"{Forename} {LastName}";
public static FullName Parse(string? fullName)
{
if (string.IsNullOrWhiteSpace(fullName))
return new FullName();
var split = fullName.Split(' ');
return new FullName { Forename = split[0], LastName = split[1] };
}
}
// A <=> FullName property descriptors (they remember the A instance)
public class FullNamePropertyDescriptor : PropertyDescriptor
{
// we use initial value to be able to reset (feature sometimes used by property grids)
public FullNamePropertyDescriptor(A instance, object initialValue, string name, Type propertyType, Attribute[]? attrs)
: base(name, attrs)
{
Instance = instance;
PropertyType = propertyType;
InitialValue = GetValue(initialValue);
}
public A Instance { get; }
public object? InitialValue { get; }
public override Type ComponentType => Instance.GetType();
// honor ReadOnly attributes on FullName properties
public override bool IsReadOnly => Attributes.OfType<ReadOnlyAttribute>().FirstOrDefault()?.IsReadOnly ?? false;
public override Type PropertyType { get; }
public override bool CanResetValue(object component) => true;
public override bool ShouldSerializeValue(object component) => false;
public override void ResetValue(object component) => SetValue(component, InitialValue);
// component is a string here
public override object? GetValue(object? component)
{
var fn = FullName.Parse(component as string);
// defer to FullName properties
return TypeDescriptor.GetProperties(typeof(FullName))[Name].GetValue(fn);
}
// component is a string here
public override void SetValue(object? component, object? value)
{
var fn = FullName.Parse(component as string);
// defer to FullName properties
TypeDescriptor.GetProperties(typeof(FullName))[Name].SetValue(fn, value);
// update original object
Instance.FullName = fn.ToString();
}
}
// the A <=> FullName type converter
public class FullNameConverter : TypeConverter
{
public override bool GetPropertiesSupported(ITypeDescriptorContext? context) => true;
public override PropertyDescriptorCollection? GetProperties(ITypeDescriptorContext? context, object value, Attribute[]? attributes)
=> new([.. TypeDescriptor.GetProperties(typeof(FullName))
.OfType<PropertyDescriptor>()
.Select(pd => new FullNamePropertyDescriptor(
(A)context.Instance,
value,
pd.Name,
pd.PropertyType,
pd.Attributes.Cast<Attribute>().ToArray()))]
);
// we can convert to and from string (the underlying type)
public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) => typeof(string) == destinationType || base.CanConvertTo(context, destinationType);
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) => typeof(string) == sourceType || base.CanConvertFrom(context, sourceType);
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) => value;
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) => value;
}
Here's the result: