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);
+ }
+ }
+ }
+}