diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 0b5e25fa9f..38d207a31d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs index fa9d364fc0..e09d9bfd17 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs @@ -51,6 +51,14 @@ namespace Avalonia.Markup.Xaml /// The type converter. public static Type GetTypeConverter(Type type) { + if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + var inner = GetTypeConverter(type.GetGenericArguments()[0]); + if (inner == null) + return null; + return typeof(NullableTypeConverter<>).MakeGenericType(inner); + } + if (_converters.TryGetValue(type, out var result)) { return result; diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs new file mode 100644 index 0000000000..5e7a31da56 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections; +using System.ComponentModel; +using System.Globalization; + +namespace Avalonia.Markup.Xaml.Converters +{ + public class NullableTypeConverter : TypeConverter where T : TypeConverter, new() + { + private TypeConverter _inner; + + public NullableTypeConverter() + { + _inner = new T(); + } + + public NullableTypeConverter(TypeConverter inner) + { + _inner = inner; + } + + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (value == null) + return null; + return _inner.ConvertTo(context, culture, value, destinationType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value == null) + return null; + if (value as string == "") + return null; + return _inner.ConvertFrom(context, culture, value); + } + + public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) + { + return _inner.CreateInstance(context, propertyValues); + } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return _inner.GetStandardValuesSupported(context); + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return _inner.GetStandardValuesExclusive(context); + } + + public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) + { + return _inner.GetCreateInstanceSupported(context); + } + + public override bool GetPropertiesSupported(ITypeDescriptorContext context) + { + return _inner.GetPropertiesSupported(context); + } + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + return _inner.GetStandardValues(context); + } + + public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) + { + return _inner.GetProperties(context, value, attributes); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return _inner.CanConvertTo(context, destinationType); + } + + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return _inner.CanConvertFrom(context, sourceType); + } + + public override bool IsValid(ITypeDescriptorContext context, object value) + { + return _inner.IsValid(context, value); + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs new file mode 100644 index 0000000000..abe6fa84b0 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs @@ -0,0 +1,31 @@ +using Avalonia.Controls; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Converters +{ + public class ClassWithNullableProperties + { + public Thickness? Thickness { get; set; } + public Orientation? Orientation { get; set; } + } + + public class NullableConverterTests + { + [Fact] + public void Nullable_Types_Should_Still_Be_Converted_Properly() + { + using (UnitTestApplication.Start(TestServices.MockPlatformWrapper)) + { + var xaml = @""; + var loader = new AvaloniaXamlLoader(); + var data = (ClassWithNullableProperties)loader.Load(xaml, typeof(ClassWithNullableProperties).Assembly); + Assert.Equal(new Thickness(5), data.Thickness); + Assert.Equal(Orientation.Vertical, data.Orientation); + } + } + } +}