| | | 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 | | } |