| | 1 | | using System; |
| | 2 | | using System.ComponentModel; |
| | 3 | | using System.Globalization; |
| | 4 | | using System.Reflection; |
| | 5 | | using System.Windows.Forms; |
| | 6 | |
|
| | 7 | | namespace PropertyGridHelpers.Converters |
| | 8 | | { |
| | 9 | | /// <summary> |
| | 10 | | /// A generic <see cref="ExpandableObjectConverter"/> that enables editing of |
| | 11 | | /// complex types with nested properties directly in a <see cref="PropertyGrid"/>. |
| | 12 | | /// </summary> |
| | 13 | | /// <typeparam name="T"> |
| | 14 | | /// The type to expand in the property grid. This type should expose public |
| | 15 | | /// properties to be displayed as editable sub-properties. |
| | 16 | | /// </typeparam> |
| | 17 | | /// <remarks> |
| | 18 | | /// This converter supports: |
| | 19 | | /// <list type="bullet"> |
| | 20 | | /// <item>Expanding nested objects in the property grid for inline editing.</item> |
| | 21 | | /// <item>Conversion of <typeparamref name="T"/> to and from string via <c>ToString()</c> |
| | 22 | | /// and optional parsing if <typeparamref name="T"/> implements <c>IParsable<T></c>.</item> |
| | 23 | | /// <item>Compatibility with .NET 7+ <c>IParsable<T></c> interface when present.</item> |
| | 24 | | /// </list> |
| | 25 | | /// </remarks> |
| | 26 | | /// <example> |
| | 27 | | /// <code> |
| | 28 | | /// public class Size |
| | 29 | | /// { |
| | 30 | | /// public int Width { get; set; } |
| | 31 | | /// public int Height { get; set; } |
| | 32 | | /// } |
| | 33 | | /// |
| | 34 | | /// [TypeConverter(typeof(TypeConverter<Size>))] |
| | 35 | | /// public Size MySize { get; set; } |
| | 36 | | /// </code> |
| | 37 | | /// This will allow <c>MySize</c> to expand in a property grid with separate |
| | 38 | | /// editors for <c>Width</c> and <c>Height</c>. |
| | 39 | | /// </example> |
| | 40 | | public partial class TypeConverter<T> : ExpandableObjectConverter |
| | 41 | | { |
| | 42 | | /// <inheritdoc/> |
| | 43 | | public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) => |
| 16 | 44 | | destinationType == typeof(T) || destinationType == typeof(string) || base.CanConvertTo(context, destinationT |
| | 45 | |
|
| | 46 | | /// <summary> |
| | 47 | | /// Converts an object of type <typeparamref name="T"/> to the requested destination type. |
| | 48 | | /// </summary> |
| | 49 | | /// <param name="context">Context information from the property grid.</param> |
| | 50 | | /// <param name="culture">The culture to use during conversion.</param> |
| | 51 | | /// <param name="value">The current value to convert.</param> |
| | 52 | | /// <param name="destinationType">The type to convert to (typically string).</param> |
| | 53 | | /// <returns> |
| | 54 | | /// A string representation of the value if <paramref name="destinationType"/> is <c>string</c>; |
| | 55 | | /// otherwise defers to the base implementation. |
| | 56 | | /// </returns> |
| | 57 | | public override object ConvertTo( |
| | 58 | | ITypeDescriptorContext context, |
| | 59 | | CultureInfo culture, |
| | 60 | | object value, |
| | 61 | | Type destinationType) => |
| 16 | 62 | | destinationType == typeof(string) && |
| 16 | 63 | | value is T t |
| 16 | 64 | | ? t.ToString() |
| 16 | 65 | | : base.ConvertTo(context, culture, value, destinationType); |
| | 66 | |
|
| | 67 | | /// <inheritdoc/> |
| | 68 | | public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) |
| 8 | 69 | | { |
| 16 | 70 | | if (sourceType == typeof(string)) |
| 8 | 71 | | { |
| 16 | 72 | | var tType = typeof(T); |
| | 73 | |
|
| | 74 | | #if NET7_0_OR_GREATER |
| | 75 | | try |
| | 76 | | { |
| | 77 | | var iParsableType = typeof(System.IParsable<>).MakeGenericType(tType); |
| | 78 | | return iParsableType.IsAssignableFrom(tType); |
| | 79 | | } |
| | 80 | | catch (ArgumentException) |
| | 81 | | { |
| | 82 | | // If the type does not implement IParsable<T>, we will catch the exception |
| | 83 | | // and fall back to the custom IParsable<T> interface. |
| | 84 | | } |
| | 85 | | #endif |
| 16 | 86 | | return typeof(IParsable<T>).IsAssignableFrom(tType); |
| | 87 | | } |
| | 88 | |
|
| 16 | 89 | | return base.CanConvertFrom(context, sourceType); |
| 8 | 90 | | } |
| | 91 | |
|
| | 92 | | /// <summary> |
| | 93 | | /// Converts from a string to an instance of <typeparamref name="T" />, if supported. |
| | 94 | | /// </summary> |
| | 95 | | /// <param name="context">Context information from the property grid.</param> |
| | 96 | | /// <param name="culture">Culture info to use for parsing.</param> |
| | 97 | | /// <param name="value">The string to convert from.</param> |
| | 98 | | /// <returns> |
| | 99 | | /// An instance of <typeparamref name="T" /> if conversion is successful; |
| | 100 | | /// otherwise defers to the base implementation. |
| | 101 | | /// </returns> |
| | 102 | | /// <remarks> |
| | 103 | | /// Supports parsing if: |
| | 104 | | /// <list type="bullet"> |
| | 105 | | /// <item><typeparamref name="T" /> implements .NET 7+ <c>IParsable<T></c></item> |
| | 106 | | /// <item>or the PropertyGridHelpers' custom <see cref="IParsable{T}" /> interface</item> |
| | 107 | | /// </list> |
| | 108 | | /// </remarks> |
| | 109 | | public override object ConvertFrom( |
| | 110 | | ITypeDescriptorContext context, |
| | 111 | | CultureInfo culture, |
| | 112 | | object value) |
| 8 | 113 | | { |
| 16 | 114 | | if (value is string s) |
| 8 | 115 | | { |
| 16 | 116 | | var tType = typeof(T); |
| | 117 | |
|
| | 118 | | #if NET7_0_OR_GREATER |
| | 119 | | // Attempts to locate a static Parse method if the type supports System.IParsable<T>. |
| | 120 | | MethodInfo parseMethod = null; |
| | 121 | |
|
| | 122 | | try |
| | 123 | | { |
| | 124 | | var iParsableType = typeof(System.IParsable<>).MakeGenericType(tType); |
| | 125 | | if (iParsableType.IsAssignableFrom(tType)) |
| | 126 | | { |
| | 127 | | parseMethod = tType.GetMethod("Parse", [typeof(string), typeof(IFormatProvider)]); |
| | 128 | | } |
| | 129 | | } |
| | 130 | | catch (ArgumentException) |
| | 131 | | { |
| | 132 | | // If the type does not implement IParsable<T>, we will catch the exception |
| | 133 | | // and fall back to the custom IParsable<T> interface. |
| | 134 | | } |
| | 135 | |
|
| | 136 | | if (parseMethod != null) |
| | 137 | | { |
| | 138 | | try |
| | 139 | | { |
| | 140 | | // let this throw if parsing fails |
| | 141 | | return parseMethod.Invoke(null, [s, culture]); |
| | 142 | | } |
| | 143 | | catch (TargetInvocationException tie) when (tie.InnerException != null) |
| | 144 | | { |
| | 145 | | // Rethrow the real exception from inside Parse |
| | 146 | | throw tie.InnerException; |
| | 147 | | } |
| | 148 | | } |
| | 149 | | #endif |
| | 150 | |
|
| | 151 | | // Attempts to locate a static Parse method if the type supports IParsable<T>. |
| 16 | 152 | | if (typeof(IParsable<T>).IsAssignableFrom(tType)) |
| 8 | 153 | | { |
| 16 | 154 | | var instance = Activator.CreateInstance<T>(); |
| 16 | 155 | | return ((IParsable<T>)instance).Parse(s, culture); |
| | 156 | | } |
| 8 | 157 | | } |
| | 158 | |
|
| 16 | 159 | | return base.ConvertFrom(context, culture, value); |
| 8 | 160 | | } |
| | 161 | | } |
| | 162 | | } |