diff --git a/src/Controls/src/Core.Design/AttributeTableBuilder.cs b/src/Controls/src/Core.Design/AttributeTableBuilder.cs
index 3d5a7b536274..666a1f782fbe 100644
--- a/src/Controls/src/Core.Design/AttributeTableBuilder.cs
+++ b/src/Controls/src/Core.Design/AttributeTableBuilder.cs
@@ -43,8 +43,7 @@ public AttributeTableBuilder()
new TypeConverterAttribute(typeof(ItemsLayoutDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.InputView", "Keyboard",
- new TypeConverterAttribute(typeof(KeyboardDesignTypeConverter)),
- new TypeConverterAttribute(typeof(StringConverter)));
+ new TypeConverterAttribute(typeof(KeyboardDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.EntryCell", "Keyboard",
new TypeConverterAttribute(typeof(KeyboardDesignTypeConverter)));
@@ -67,37 +66,51 @@ public AttributeTableBuilder()
//new System.Windows.Markup.MarkupExtensionReturnTypeAttribute (),
);
+ // Type level attributes
AddTypeAttributes("Microsoft.Maui.Controls.Brush", new TypeConverterAttribute(typeof(ColorDesignTypeConverter)));
- AddTypeAttributes("Microsoft.Maui.Graphics.Color", new TypeConverterAttribute(typeof(ColorDesignTypeConverter)));
- AddTypeAttributes("Microsoft.Maui.GridLength", new TypeConverterAttribute(typeof(GridLengthDesignTypeConverter)));
-
+ AddTypeAttributes("Microsoft.Maui.Controls.Button+ButtonContentLayout", new TypeConverterAttribute(typeof(ButtonContentDesignTypeConverter)));
+ AddTypeAttributes("Microsoft.Maui.Controls.Compatibility.Constraint", new TypeConverterAttribute(typeof(ConstraintDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Controls.ConstraintExpression", new MarkupExtensionReturnTypeAttribute());
+ AddTypeAttributes("Microsoft.Maui.Controls.ImageSource", new TypeConverterAttribute(typeof(ImageSourceDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Controls.LayoutOptions", new TypeConverterAttribute(typeof(LayoutOptionsDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Controls.LinearItemsLayout", new TypeConverterAttribute(typeof(LinearItemsLayoutDesignTypeConverter)));
AddTypeAttributes("Microsoft.Maui.Controls.ResourcesChangedEventArgs", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
+ AddTypeAttributes("Microsoft.Maui.CornerRadius", new TypeConverterAttribute(typeof(CornerRadiusDesignTypeConverter)));
+ AddTypeAttributes("Microsoft.Maui.Graphics.Color", new TypeConverterAttribute(typeof(ColorDesignTypeConverter)));
+ AddTypeAttributes("Microsoft.Maui.Graphics.Point", new TypeConverterAttribute(typeof(PointTypeDesignConverter)));
+ AddTypeAttributes("Microsoft.Maui.Graphics.Rect", new TypeConverterAttribute(typeof(RectTypeDesignConverter)));
+ AddTypeAttributes("Microsoft.Maui.GridLength", new TypeConverterAttribute(typeof(GridLengthDesignTypeConverter)));
+ AddTypeAttributes("Microsoft.Maui.Layouts.FlexAlignContent", new TypeConverterAttribute(typeof(FlexAlignContentDesignTypeConverter)));
+ AddTypeAttributes("Microsoft.Maui.Layouts.FlexAlignItems", new TypeConverterAttribute(typeof(FlexAlignItemsDesignTypeConverter)));
+ AddTypeAttributes("Microsoft.Maui.Layouts.FlexAlignSelf", new TypeConverterAttribute(typeof(FlexAlignSelfDesignTypeConverter)));
+ AddTypeAttributes("Microsoft.Maui.Layouts.FlexBasis", new TypeConverterAttribute(typeof(FlexBasisDesignTypeConverter)));
+ AddTypeAttributes("Microsoft.Maui.Layouts.FlexDirection", new TypeConverterAttribute(typeof(FlexDirectionDesignTypeConverter)));
+ AddTypeAttributes("Microsoft.Maui.Layouts.FlexJustify", new TypeConverterAttribute(typeof(FlexJustifyDesignTypeConverter)));
+ AddTypeAttributes("Microsoft.Maui.Layouts.FlexWrap", new TypeConverterAttribute(typeof(FlexWrapDesignTypeConverter)));
+ AddTypeAttributes("Microsoft.Maui.Thickness", new TypeConverterAttribute(typeof(ThicknessTypeDesignConverter)));
+ // Property level attributes
+ AddMemberAttributes("Microsoft.Maui.Controls.AbsoluteLayout", "LayoutBounds", new TypeConverterAttribute(typeof(BoundsDesignTypeConverter)));
+ AddMemberAttributes("Microsoft.Maui.Controls.Button", "ContentLayout", new TypeConverterAttribute(typeof(ButtonContentDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Button", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
+ AddMemberAttributes("Microsoft.Maui.Controls.Compatibility.AbsoluteLayout", "LayoutBounds", new TypeConverterAttribute(typeof(BoundsDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.DatePicker", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Editor", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Entry", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
+ AddMemberAttributes("Microsoft.Maui.Controls.Grid", "ColumnDefinitions", new TypeConverterAttribute(typeof(GridLengthCollectionDesignTypeConverter)));
+ AddMemberAttributes("Microsoft.Maui.Controls.Grid", "RowDefinitions", new TypeConverterAttribute(typeof(GridLengthCollectionDesignTypeConverter)));
+ AddMemberAttributes("Microsoft.Maui.Controls.Image", "Source", new TypeConverterAttribute(typeof(ImageSourceDesignTypeConverter)));
+ AddMemberAttributes("Microsoft.Maui.Controls.ImageButton", "Source", new TypeConverterAttribute(typeof(ImageSourceDesignTypeConverter)));
+ AddMemberAttributes("Microsoft.Maui.Controls.ImageCell", "ImageSource", new TypeConverterAttribute(typeof(ImageSourceDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Label", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Picker", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
- AddMemberAttributes("Microsoft.Maui.Controls.SearchBar", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
- AddMemberAttributes("Microsoft.Maui.Controls.TimePicker", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.RadioButton", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
+ AddMemberAttributes("Microsoft.Maui.Controls.SearchBar", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.SearchHandler", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
+ AddMemberAttributes("Microsoft.Maui.Controls.Shell", "FlyoutBackgroundImage", new TypeConverterAttribute(typeof(ImageSourceDesignTypeConverter)));
AddMemberAttributes("Microsoft.Maui.Controls.Span", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
-
- AddMemberAttributes("Microsoft.Maui.Controls.Grid", "RowDefinitions", new TypeConverterAttribute(typeof(GridLengthCollectionDesignTypeConverter)));
- AddMemberAttributes("Microsoft.Maui.Controls.Grid", "ColumnDefinitions", new TypeConverterAttribute(typeof(GridLengthCollectionDesignTypeConverter)));
-
- AddTypeAttributes("Microsoft.Maui.Layouts.FlexJustify", new TypeConverterAttribute(typeof(FlexJustifyDesignTypeConverter)));
- AddTypeAttributes("Microsoft.Maui.Layouts.FlexDirection", new TypeConverterAttribute(typeof(FlexDirectionDesignTypeConverter)));
- AddTypeAttributes("Microsoft.Maui.Layouts.FlexAlignContent", new TypeConverterAttribute(typeof(FlexAlignContentDesignTypeConverter)));
- AddTypeAttributes("Microsoft.Maui.Layouts.FlexAlignItems", new TypeConverterAttribute(typeof(FlexAlignItemsDesignTypeConverter)));
- AddTypeAttributes("Microsoft.Maui.Layouts.FlexAlignSelf", new TypeConverterAttribute(typeof(FlexAlignSelfDesignTypeConverter)));
- AddTypeAttributes("Microsoft.Maui.Layouts.FlexWrap", new TypeConverterAttribute(typeof(FlexWrapDesignTypeConverter)));
- AddTypeAttributes("Microsoft.Maui.Layouts.FlexBasis", new TypeConverterAttribute(typeof(FlexBasisDesignTypeConverter)));
+ AddMemberAttributes("Microsoft.Maui.Controls.TimePicker", "FontSize", new TypeConverterAttribute(typeof(FontSizeDesignTypeConverter)));
+ AddMemberAttributes("Microsoft.Maui.Controls.VisualElement", "IsVisible", new TypeConverterAttribute(typeof(VisibilityDesignTypeConverter)));
}
private void AddTypeAttributes(string typeName, params Attribute[] attribs)
diff --git a/src/Controls/src/Core.Design/BoundsDesignTypeConverter.cs b/src/Controls/src/Core.Design/BoundsDesignTypeConverter.cs
new file mode 100644
index 000000000000..1e0076d6feb8
--- /dev/null
+++ b/src/Controls/src/Core.Design/BoundsDesignTypeConverter.cs
@@ -0,0 +1,41 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Microsoft.Maui.Controls.Design
+{
+ public class BoundsDesignTypeConverter : StringConverter
+ {
+ public override bool IsValid(ITypeDescriptorContext context, object value)
+ {
+ // MUST MATCH BoundsTypeConverter.ConvertFrom
+ string strValue = value?.ToString();
+ if (string.IsNullOrEmpty(strValue))
+ return false;
+
+ string[] xywh = strValue.Split(',');
+ bool hasX, hasY, hasW, hasH;
+
+ hasX = (xywh.Length == 2 || xywh.Length == 4) && double.TryParse(xywh[0], NumberStyles.Number, CultureInfo.InvariantCulture, out _);
+ hasY = (xywh.Length == 2 || xywh.Length == 4) && double.TryParse(xywh[1], NumberStyles.Number, CultureInfo.InvariantCulture, out _);
+ hasW = xywh.Length == 4 && double.TryParse(xywh[2], NumberStyles.Number, CultureInfo.InvariantCulture, out _);
+ hasH = xywh.Length == 4 && double.TryParse(xywh[3], NumberStyles.Number, CultureInfo.InvariantCulture, out _);
+ if (!hasW && xywh.Length == 4 && string.Compare("AutoSize", xywh[2].Trim(), StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ hasW = true;
+ }
+
+ if (!hasH && xywh.Length == 4 && string.Compare("AutoSize", xywh[3].Trim(), StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ hasH = true;
+ }
+
+ if (hasX && hasY && xywh.Length == 2)
+ return true;
+ if (hasX && hasY && hasW && hasH && xywh.Length == 4)
+ return true;
+
+ return false;
+ }
+ }
+}
diff --git a/src/Controls/src/Core.Design/ButtonContentDesignTypeConverter.cs b/src/Controls/src/Core.Design/ButtonContentDesignTypeConverter.cs
new file mode 100644
index 000000000000..f4fd3bac69f8
--- /dev/null
+++ b/src/Controls/src/Core.Design/ButtonContentDesignTypeConverter.cs
@@ -0,0 +1,49 @@
+using System;
+using System.ComponentModel;
+
+namespace Microsoft.Maui.Controls.Design
+{
+ public class ButtonContentDesignTypeConverter : StringConverter
+ {
+ private static readonly char[] Separators = { ',' };
+
+ // MUST MATCH values of ButtonContentConverter.ImagePosition. NOTE that we use enum rather than strings
+ // to better match ButtonContentConverter. First, Enum.Parse will accept int values like 1 or 20. Second,
+ // values passed to converter contain numbers and enum values, i.e. there is a possibility that we might
+ // need to handle ints in place of enums. ButtonContentConverter does not properly handle corner cases.
+ // For example, it does not trim incoming strings, so " 15" will be converted as
+ // ButtonContentLayout((ImagePorition)15, DefaultSpacing/*10*/)
+ private enum ImagePosition { Left, Top, Right, Bottom };
+
+ public override bool IsValid(ITypeDescriptorContext context, object value)
+ {
+ // MUST MATCH ButtonContentConverter.ConvertFrom
+ string stringValue = value?.ToString();
+ if (string.IsNullOrEmpty(stringValue))
+ return false;
+
+ string[] parts = stringValue.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length > 2)
+ return false;
+
+ if (char.IsDigit(parts[0][0]))
+ {
+ // Examples: "5" or "10, Top"
+ if (!double.TryParse(parts[0], out _))
+ return false; // bogus number, e.g. 5a
+ if (parts.Length == 2 && !Enum.TryParse(parts[1], true, out ImagePosition _))
+ return false; // bogus position, e.g. "Hello"
+ }
+ else
+ {
+ // Examples: "Right" or "Bottom, 5"
+ if (!Enum.TryParse(parts[0], true, out ImagePosition _))
+ return false; // bogus position, e.g. "Hello"
+ if (parts.Length == 2 && !double.TryParse(parts[1], out _))
+ return false; // bogus number, e.g. 5a
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Controls/src/Core.Design/ConstraintDesignTypeConverter.cs b/src/Controls/src/Core.Design/ConstraintDesignTypeConverter.cs
new file mode 100644
index 000000000000..ff3ebd400354
--- /dev/null
+++ b/src/Controls/src/Core.Design/ConstraintDesignTypeConverter.cs
@@ -0,0 +1,15 @@
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Microsoft.Maui.Controls.Design
+{
+ public class ConstraintDesignTypeConverter : StringConverter
+ {
+ public override bool IsValid(ITypeDescriptorContext context, object value)
+ {
+ // MUST MATCH ConstraintTypeConverter.ConvertFrom
+ var strValue = value?.ToString();
+ return (strValue != null && double.TryParse(strValue, NumberStyles.Number, CultureInfo.InvariantCulture, out _));
+ }
+ }
+}
diff --git a/src/Controls/src/Core.Design/Controls.Core.Design.csproj b/src/Controls/src/Core.Design/Controls.Core.Design.csproj
index 4a0e896da57d..55684069f45f 100644
--- a/src/Controls/src/Core.Design/Controls.Core.Design.csproj
+++ b/src/Controls/src/Core.Design/Controls.Core.Design.csproj
@@ -8,25 +8,37 @@
<_MauiDesignDllBuild Condition=" '$(OS)' != 'Unix' ">True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Controls/src/Core.Design/CornerRadiusDesignTypeConverter.cs b/src/Controls/src/Core.Design/CornerRadiusDesignTypeConverter.cs
new file mode 100644
index 000000000000..02c001339bf8
--- /dev/null
+++ b/src/Controls/src/Core.Design/CornerRadiusDesignTypeConverter.cs
@@ -0,0 +1,49 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using Controls.Core.Design;
+
+namespace Microsoft.Maui.Controls.Design
+{
+ public class CornerRadiusDesignTypeConverter : StringConverter
+ {
+ public override bool IsValid(ITypeDescriptorContext context, object value)
+ {
+ // MUST MATCH CornerRadiusTypeConverter.ConvertFrom
+ var strValue = value?.ToString();
+ if (strValue != null)
+ {
+ if (strValue.IndexOf(",", StringComparison.Ordinal) != -1)
+ {
+ var parts = strValue.Split(',');
+
+ // Example: "1,2,3,4"
+ if (parts.Length == 4)
+ {
+ foreach (string part in parts)
+ {
+ if (!double.TryParse(part, NumberStyles.Number, CultureInfo.InvariantCulture, out _))
+ return false;
+ }
+
+ return true;
+ }
+
+ // Example: "1,a,b". CornerRadiusTypeConverter has unusual behavior
+ // for 2 or 3 token string. We match its behavior here
+ if (parts.Length < 4)
+ return double.TryParse(parts[0], NumberStyles.Number, CultureInfo.InvariantCulture, out _);
+ }
+ else
+ {
+ // Example: "1 2 3 4". Any count of numbers between 1 and 4 is valid.
+ // Note that CornerRadiusTypeConverter is sensitive to spaces, e.g.
+ // "1 2" is valid but "1 2" is not. We match its behavior here.
+ return DesignTypeConverterHelper.TryParseNumbers(strValue.Trim(), ' ', maxCount: 4) is int;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Controls/src/Core.Design/DesignTypeConverterHelper.cs b/src/Controls/src/Core.Design/DesignTypeConverterHelper.cs
new file mode 100644
index 000000000000..1c4f2120cddc
--- /dev/null
+++ b/src/Controls/src/Core.Design/DesignTypeConverterHelper.cs
@@ -0,0 +1,28 @@
+using System.Globalization;
+
+namespace Controls.Core.Design
+{
+ internal static class DesignTypeConverterHelper
+ {
+ ///
+ /// Returns count of numbers in the string. Returns null if some of the values are invalid or total count exceeds max count.
+ ///
+ public static int? TryParseNumbers(string numberCollection, char separator, int maxCount)
+ {
+ // Examples: "1,2" or "1 2 3 4".
+ if (string.IsNullOrEmpty(numberCollection))
+ return null;
+
+ string[] parts = numberCollection.Split(separator);
+ if (parts.Length > maxCount)
+ return null; // too many numbers
+
+ foreach (string part in parts)
+ {
+ if (!double.TryParse(part, NumberStyles.Number, CultureInfo.InvariantCulture, out _))
+ return null; // invalid number found
+ }
+ return parts.Length; // all numbers are valid
+ }
+ }
+}
diff --git a/src/Controls/src/Core.Design/ImageSourceDesignTypeConverter.cs b/src/Controls/src/Core.Design/ImageSourceDesignTypeConverter.cs
new file mode 100644
index 000000000000..58af35904691
--- /dev/null
+++ b/src/Controls/src/Core.Design/ImageSourceDesignTypeConverter.cs
@@ -0,0 +1,17 @@
+using System;
+using System.ComponentModel;
+
+namespace Microsoft.Maui.Controls.Design
+{
+ public class ImageSourceDesignTypeConverter : StringConverter
+ {
+ public override bool IsValid(ITypeDescriptorContext context, object value)
+ {
+ // MUST MATCH ImageSourceConverter.ConvertFrom
+ if (value?.ToString() is string strValue)
+ return Uri.TryCreate(strValue, UriKind.Absolute, out Uri _);
+
+ return false;
+ }
+ }
+}
diff --git a/src/Controls/src/Core.Design/PointTypeDesignConverter.cs b/src/Controls/src/Core.Design/PointTypeDesignConverter.cs
new file mode 100644
index 000000000000..a327b5236229
--- /dev/null
+++ b/src/Controls/src/Core.Design/PointTypeDesignConverter.cs
@@ -0,0 +1,15 @@
+using System.ComponentModel;
+using Controls.Core.Design;
+
+namespace Microsoft.Maui.Controls.Design
+{
+ public class PointTypeDesignConverter : StringConverter
+ {
+ public override bool IsValid(ITypeDescriptorContext context, object value)
+ {
+ // MUST MATCH Point.TryParse
+ int? count = DesignTypeConverterHelper.TryParseNumbers(value?.ToString(), ',', 2);
+ return count == 2;
+ }
+ }
+}
diff --git a/src/Controls/src/Core.Design/RectTypeDesignConverter.cs b/src/Controls/src/Core.Design/RectTypeDesignConverter.cs
new file mode 100644
index 000000000000..3cec67ef411c
--- /dev/null
+++ b/src/Controls/src/Core.Design/RectTypeDesignConverter.cs
@@ -0,0 +1,15 @@
+using System.ComponentModel;
+using Controls.Core.Design;
+
+namespace Microsoft.Maui.Controls.Design
+{
+ public class RectTypeDesignConverter : StringConverter
+ {
+ public override bool IsValid(ITypeDescriptorContext context, object value)
+ {
+ // MUST MATCH Rect.TryParse
+ int? count = DesignTypeConverterHelper.TryParseNumbers(value?.ToString(), ',', 4);
+ return count == 4;
+ }
+ }
+}
diff --git a/src/Controls/src/Core.Design/ThicknessTypeDesignConverter.cs b/src/Controls/src/Core.Design/ThicknessTypeDesignConverter.cs
new file mode 100644
index 000000000000..1da72e03f2b2
--- /dev/null
+++ b/src/Controls/src/Core.Design/ThicknessTypeDesignConverter.cs
@@ -0,0 +1,30 @@
+using System;
+using System.ComponentModel;
+using Controls.Core.Design;
+
+namespace Microsoft.Maui.Controls.Design
+{
+ public class ThicknessTypeDesignConverter : StringConverter
+ {
+ public override bool IsValid(ITypeDescriptorContext context, object value)
+ {
+ // MUST MATCH ThicknessTypeConverter.ConvertFrom
+ string strValue = value?.ToString()?.Trim();
+ if (string.IsNullOrEmpty(strValue))
+ return false;
+
+ if (strValue.IndexOf(",", StringComparison.Ordinal) != -1)
+ {
+ int? count = DesignTypeConverterHelper.TryParseNumbers(value?.ToString(), ',', maxCount: 4);
+ return count == 2 || count == 4;
+ }
+ else
+ {
+ // Example: "1 2 3 4". Any count of numbers between 1 and 4 is valid.
+ // Note that ThicknessTypeConverter is sensitive to spaces, e.g.
+ // "1 2" is valid but "1 2" is not. We match its behavior here.
+ return DesignTypeConverterHelper.TryParseNumbers(strValue.Trim(), ' ', maxCount: 4) is int;
+ }
+ }
+ }
+}
diff --git a/src/Controls/src/Core.Design/VisibilityDesignTypeConverter.cs b/src/Controls/src/Core.Design/VisibilityDesignTypeConverter.cs
new file mode 100644
index 000000000000..af9305c3e59f
--- /dev/null
+++ b/src/Controls/src/Core.Design/VisibilityDesignTypeConverter.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace Microsoft.Maui.Controls.Design
+{
+ public class VisibilityDesignTypeConverter : StringConverter
+ {
+ private static readonly string[] validValues = ["Collapse", "Hidden", bool.FalseString, bool.TrueString, "Visible"];
+ private static readonly HashSet supportedValues = new HashSet(validValues, StringComparer.OrdinalIgnoreCase);
+ private static readonly StandardValuesCollection standardValues = new StandardValuesCollection(validValues);
+
+ public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) => true;
+ override public bool GetStandardValuesSupported(ITypeDescriptorContext context) => true;
+ override public StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) => standardValues;
+ public override bool IsValid(ITypeDescriptorContext context, object value)
+ {
+ // MUST MATCH VisibilityConverter.ConvertFrom
+ if (value?.ToString()?.Trim() is string strValue)
+ return supportedValues.Contains(strValue);
+
+ return false;
+ }
+ }
+}
diff --git a/src/Controls/src/Core/Button/Button.cs b/src/Controls/src/Core/Button/Button.cs
index 995c94b45a6f..8cab188ab4d2 100644
--- a/src/Controls/src/Core/Button/Button.cs
+++ b/src/Controls/src/Core/Button/Button.cs
@@ -577,6 +577,7 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
+ // IMPORTANT! Update ButtonContentDesignTypeConverter.IsValid if making changes here
var strValue = value?.ToString();
if (strValue == null)
throw new InvalidOperationException($"Cannot convert null into {typeof(ButtonContentLayout)}");
diff --git a/src/Controls/src/Core/ImageSourceConverter.cs b/src/Controls/src/Core/ImageSourceConverter.cs
index a450dafed0df..4fac2085f0d9 100644
--- a/src/Controls/src/Core/ImageSourceConverter.cs
+++ b/src/Controls/src/Core/ImageSourceConverter.cs
@@ -18,6 +18,7 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
+ // IMPORTANT! Update ImageSourceDesignTypeConverter.IsValid if making changes here
var strValue = value?.ToString();
if (strValue != null)
return Uri.TryCreate(strValue, UriKind.Absolute, out Uri uri) && uri.Scheme != "file" ? ImageSource.FromUri(uri) : ImageSource.FromFile(strValue);
diff --git a/src/Controls/src/Core/Layout/BoundsTypeConverter.cs b/src/Controls/src/Core/Layout/BoundsTypeConverter.cs
index b80bb1ce7716..5fe7ab443d08 100644
--- a/src/Controls/src/Core/Layout/BoundsTypeConverter.cs
+++ b/src/Controls/src/Core/Layout/BoundsTypeConverter.cs
@@ -18,6 +18,7 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
+ // IMPORTANT! Update BoundsDesignTypeConverter.IsValid if making changes here
var strValue = value?.ToString();
if (strValue != null)
diff --git a/src/Controls/src/Core/LegacyLayouts/ConstraintTypeConverter.cs b/src/Controls/src/Core/LegacyLayouts/ConstraintTypeConverter.cs
index fd778b5cae31..c0a58f1a80d5 100644
--- a/src/Controls/src/Core/LegacyLayouts/ConstraintTypeConverter.cs
+++ b/src/Controls/src/Core/LegacyLayouts/ConstraintTypeConverter.cs
@@ -16,6 +16,7 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
+ // IMPORTANT! Update ConstraintDesignTypeConverter.IsValid if making changes here
var strValue = value?.ToString();
if (strValue != null && double.TryParse(strValue, NumberStyles.Number, CultureInfo.InvariantCulture, out var size))
diff --git a/src/Controls/src/Core/VisualElement/VisualElement.cs b/src/Controls/src/Core/VisualElement/VisualElement.cs
index f7f331f2a944..3b86e02b0115 100644
--- a/src/Controls/src/Core/VisualElement/VisualElement.cs
+++ b/src/Controls/src/Core/VisualElement/VisualElement.cs
@@ -2263,6 +2263,7 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
+ // IMPORTANT! Update VisibilityDesignTypeConverter.IsValid if making changes here
var strValue = value?.ToString()?.Trim();
if (!string.IsNullOrEmpty(strValue))
diff --git a/src/Controls/tests/Core.Design.UnitTests/BoundsDesignTypeConverterTests.cs b/src/Controls/tests/Core.Design.UnitTests/BoundsDesignTypeConverterTests.cs
new file mode 100644
index 000000000000..c1a7d0d2791c
--- /dev/null
+++ b/src/Controls/tests/Core.Design.UnitTests/BoundsDesignTypeConverterTests.cs
@@ -0,0 +1,41 @@
+using Microsoft.Maui.Controls.Design;
+using Xunit;
+
+namespace Microsoft.Maui.Controls.Core.UnitTests
+{
+ public class BoundsDesignTypeConverterTests
+ {
+ [Theory]
+ [InlineData("1,2 ")]
+ [InlineData(" 3.1, -4.2, 5, -6")]
+ [InlineData(" 14,17, AutoSize, -20 ")]
+ [InlineData("5,6,7,AUTOSIZE")]
+ [InlineData("11,-12, autosize, AutoSize")]
+ public void BoundsDesignTypeConverter_Valid(string value)
+ {
+ BoundsDesignTypeConverter converter = new BoundsDesignTypeConverter();
+ Assert.True(converter.CanConvertFrom(typeof(string)));
+
+ bool actual = converter.IsValid(value);
+ Assert.True(actual);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData("1")]
+ [InlineData("2,3,4")]
+ [InlineData(",7,8")]
+ [InlineData("9,10,")]
+ [InlineData("11,12,13,14,15")]
+ [InlineData("AutoSize,AutoSize")]
+ [InlineData("AutoSize,AutoSize,AutoSize,AutoSize")]
+ public void BoundsDesignTypeConverter_Invalid(string value)
+ {
+ BoundsDesignTypeConverter converter = new BoundsDesignTypeConverter();
+ bool actual = converter.IsValid(value);
+ Assert.False(actual);
+ }
+ }
+}
diff --git a/src/Controls/tests/Core.Design.UnitTests/ButtonContentDesignTypeConverterTests.cs b/src/Controls/tests/Core.Design.UnitTests/ButtonContentDesignTypeConverterTests.cs
new file mode 100644
index 000000000000..f0c76216c2e3
--- /dev/null
+++ b/src/Controls/tests/Core.Design.UnitTests/ButtonContentDesignTypeConverterTests.cs
@@ -0,0 +1,51 @@
+using Microsoft.Maui.Controls.Design;
+using Xunit;
+
+namespace Microsoft.Maui.Controls.Core.UnitTests
+{
+ public class ButtonContentDesignTypeConverterTests
+ {
+ [Theory]
+ [InlineData("Left")]
+ [InlineData(" TOP ")]
+ [InlineData("2, right")]
+ [InlineData("Left, -25 ")]
+ [InlineData(" Bottom, .3 ")]
+ public void ButtonContentDesignTypeConverter_Valid_Common(string value)
+ {
+ ButtonContentDesignTypeConverter converter = new ButtonContentDesignTypeConverter();
+ Assert.True(converter.CanConvertFrom(typeof(string)));
+
+ bool actual = converter.IsValid(value);
+ Assert.True(actual);
+ }
+
+ [Theory]
+ [InlineData(",1")]
+ [InlineData("9,15")]
+ [InlineData(",10,,,Top,")]
+ [InlineData(" 4,,,,")]
+ [InlineData("Left,")]
+ [InlineData(" 15")]
+ public void ButtonContentDesignTypeConverter_Valid_Unusual(string value)
+ {
+ // ButtonContentConverter.ConvertFrom allows these cases
+ ButtonContentDesignTypeConverter converter = new ButtonContentDesignTypeConverter();
+ bool actual = converter.IsValid(value);
+ Assert.True(actual);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(".2,Top")]
+ [InlineData("Left,Right")]
+ [InlineData(".3,.4")]
+ public void ButtonContentDesignTypeConverter_Invalid(string value)
+ {
+ ButtonContentDesignTypeConverter converter = new ButtonContentDesignTypeConverter();
+ bool actual = converter.IsValid(value);
+ Assert.False(actual);
+ }
+ }
+}
diff --git a/src/Controls/tests/Core.Design.UnitTests/ConstraintDesignTypeConverterTests.cs b/src/Controls/tests/Core.Design.UnitTests/ConstraintDesignTypeConverterTests.cs
new file mode 100644
index 000000000000..0cbfbf2679de
--- /dev/null
+++ b/src/Controls/tests/Core.Design.UnitTests/ConstraintDesignTypeConverterTests.cs
@@ -0,0 +1,34 @@
+using Microsoft.Maui.Controls.Design;
+using Xunit;
+
+namespace Microsoft.Maui.Controls.Core.UnitTests
+{
+ public class ConstraintDesignTypeConverterTests
+ {
+ [Theory]
+ [InlineData("1")]
+ [InlineData(" -2.3")]
+ [InlineData(" NaN ")]
+ [InlineData("Infinity")]
+ public void ConstraintDesignTypeConverter_Valid(string value)
+ {
+ ConstraintDesignTypeConverter converter = new ConstraintDesignTypeConverter();
+ Assert.True(converter.CanConvertFrom(typeof(string)));
+
+ bool actual = converter.IsValid(value);
+ Assert.True(actual);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData("1a")]
+ public void ConstraintDesignTypeConverter_Invalid(string value)
+ {
+ ConstraintDesignTypeConverter converter = new ConstraintDesignTypeConverter();
+ bool actual = converter.IsValid(value);
+ Assert.False(actual);
+ }
+ }
+}
diff --git a/src/Controls/tests/Core.Design.UnitTests/Controls.Core.Design.UnitTests.csproj b/src/Controls/tests/Core.Design.UnitTests/Controls.Core.Design.UnitTests.csproj
index 0d4428eb873d..6554ac7bbda4 100644
--- a/src/Controls/tests/Core.Design.UnitTests/Controls.Core.Design.UnitTests.csproj
+++ b/src/Controls/tests/Core.Design.UnitTests/Controls.Core.Design.UnitTests.csproj
@@ -17,9 +17,18 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Controls/tests/Core.Design.UnitTests/CornerRadiusDesignTypeConverterTests.cs b/src/Controls/tests/Core.Design.UnitTests/CornerRadiusDesignTypeConverterTests.cs
new file mode 100644
index 000000000000..60f24c7ab306
--- /dev/null
+++ b/src/Controls/tests/Core.Design.UnitTests/CornerRadiusDesignTypeConverterTests.cs
@@ -0,0 +1,50 @@
+using Microsoft.Maui.Controls.Design;
+using Xunit;
+
+namespace Microsoft.Maui.Controls.Core.UnitTests
+{
+ public class CornerRadiusDesignTypeConverterTests
+ {
+ [Theory]
+ [InlineData("1.6")]
+ [InlineData("1, 2.7")]
+ [InlineData("1, 2, 3.8")]
+ [InlineData("1,2,3,4.9")]
+ [InlineData("1 2.7")]
+ [InlineData("1 2 3.8")]
+ [InlineData("1 2 3 4.9")]
+ public void CornerRadiusDesignTypeConverter_Valid_Common(string value)
+ {
+ CornerRadiusDesignTypeConverter converter = new CornerRadiusDesignTypeConverter();
+ Assert.True(converter.CanConvertFrom(typeof(string)));
+
+ bool actual = converter.IsValid(value);
+ Assert.True(actual);
+ }
+
+ [Theory] // CornerRadiusTypeConverter has unusual behavior for 2 or 3 comma separated tokens; design converter matches its behavior
+ [InlineData("1,")]
+ [InlineData("2,,")]
+ [InlineData("3,hello")]
+ [InlineData("4,hello,world")]
+ public void CornerRadiusDesignTypeConverter_Valid_Unusual(string value)
+ {
+ CornerRadiusDesignTypeConverter converter = new CornerRadiusDesignTypeConverter();
+ bool actual = converter.IsValid(value);
+ Assert.True(actual);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("1 2")] // CornerRadiusTypeConverter is sensitive to spaces; design converter matches its behavior
+ [InlineData("1,2,3,4,5")]
+ [InlineData("1 2 3 4 5")]
+ public void CornerRadiusDesignTypeConverter_Invalid(string value)
+ {
+ CornerRadiusDesignTypeConverter converter = new CornerRadiusDesignTypeConverter();
+ bool actual = converter.IsValid(value);
+ Assert.False(actual);
+ }
+ }
+}
diff --git a/src/Controls/tests/Core.Design.UnitTests/ImageSourceDesignTypeConverterTests.cs b/src/Controls/tests/Core.Design.UnitTests/ImageSourceDesignTypeConverterTests.cs
new file mode 100644
index 000000000000..301f6af0bb27
--- /dev/null
+++ b/src/Controls/tests/Core.Design.UnitTests/ImageSourceDesignTypeConverterTests.cs
@@ -0,0 +1,32 @@
+using Microsoft.Maui.Controls.Design;
+using Xunit;
+
+namespace Microsoft.Maui.Controls.Core.UnitTests
+{
+ public class ImageSourceDesignTypeConverterTests
+ {
+ [Theory]
+ [InlineData("http://consoto.com")]
+ [InlineData("file:///x:/logo.png")]
+ public void ImageSourceDesignTypeConverter_Valid(string value)
+ {
+ ImageSourceDesignTypeConverter converter = new ImageSourceDesignTypeConverter();
+ Assert.True(converter.CanConvertFrom(typeof(string)));
+
+ bool actual = converter.IsValid(value);
+ Assert.True(actual);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("consoto.com")]
+ [InlineData("/app/logo.png")]
+ public void ImageSourceDesignTypeConverter_Invalid(string value)
+ {
+ ImageSourceDesignTypeConverter converter = new ImageSourceDesignTypeConverter();
+ bool actual = converter.IsValid(value);
+ Assert.False(actual);
+ }
+ }
+}
diff --git a/src/Controls/tests/Core.Design.UnitTests/PointTypeDesignConverterTests.cs b/src/Controls/tests/Core.Design.UnitTests/PointTypeDesignConverterTests.cs
new file mode 100644
index 000000000000..ac5fe3296aff
--- /dev/null
+++ b/src/Controls/tests/Core.Design.UnitTests/PointTypeDesignConverterTests.cs
@@ -0,0 +1,33 @@
+using Microsoft.Maui.Controls.Design;
+using Xunit;
+
+namespace Microsoft.Maui.Controls.Core.UnitTests
+{
+ public class PointTypeDesignConverterTests
+ {
+ [Theory]
+ [InlineData("1,2")]
+ [InlineData(" -3.5, NaN")]
+ public void PointTypeDesignConverter_Valid(string value)
+ {
+ PointTypeDesignConverter converter = new PointTypeDesignConverter();
+ Assert.True(converter.CanConvertFrom(typeof(string)));
+
+ bool actual = converter.IsValid(value);
+ Assert.True(actual);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("1")]
+ [InlineData("1,2,3")]
+ [InlineData("a,b")]
+ public void PointTypeDesignConverter_Invalid(string value)
+ {
+ VisibilityDesignTypeConverter converter = new VisibilityDesignTypeConverter();
+ bool actual = converter.IsValid(value);
+ Assert.False(actual);
+ }
+ }
+}
diff --git a/src/Controls/tests/Core.Design.UnitTests/RectTypeDesignConverterTests.cs b/src/Controls/tests/Core.Design.UnitTests/RectTypeDesignConverterTests.cs
new file mode 100644
index 000000000000..15170add5b68
--- /dev/null
+++ b/src/Controls/tests/Core.Design.UnitTests/RectTypeDesignConverterTests.cs
@@ -0,0 +1,34 @@
+using Microsoft.Maui.Controls.Design;
+using Xunit;
+
+namespace Microsoft.Maui.Controls.Core.UnitTests
+{
+ public class RectTypeDesignConverterTests
+ {
+ [Theory]
+ [InlineData("1,2,3,4")]
+ [InlineData(" -3.5, NaN, 7, Infinity")]
+ public void RectTypeDesignConverter_Valid(string value)
+ {
+ RectTypeDesignConverter converter = new RectTypeDesignConverter();
+ Assert.True(converter.CanConvertFrom(typeof(string)));
+
+ bool actual = converter.IsValid(value);
+ Assert.True(actual);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("1")]
+ [InlineData("1,2")]
+ [InlineData("1,2,3,4,5")]
+ [InlineData("a,b")]
+ public void RectTypeDesignConverter_Invalid(string value)
+ {
+ RectTypeDesignConverter converter = new RectTypeDesignConverter();
+ bool actual = converter.IsValid(value);
+ Assert.False(actual);
+ }
+ }
+}
diff --git a/src/Controls/tests/Core.Design.UnitTests/ThicknessTypeDesignConverterTests.cs b/src/Controls/tests/Core.Design.UnitTests/ThicknessTypeDesignConverterTests.cs
new file mode 100644
index 000000000000..c0d151b4ec62
--- /dev/null
+++ b/src/Controls/tests/Core.Design.UnitTests/ThicknessTypeDesignConverterTests.cs
@@ -0,0 +1,40 @@
+using Microsoft.Maui.Controls.Design;
+using Xunit;
+
+namespace Microsoft.Maui.Controls.Core.UnitTests
+{
+ public class ThicknessTypeDesignConverterTests
+ {
+ [Theory]
+ [InlineData("-5")]
+ [InlineData("1,2")]
+ [InlineData("1,2, 3, 4 ")]
+ [InlineData("1 2")]
+ [InlineData("1 2 3")]
+ [InlineData("1 2 3 4")]
+ public void ThicknessTypeDesignConverter_Valid(string value)
+ {
+ ThicknessTypeDesignConverter converter = new ThicknessTypeDesignConverter();
+ Assert.True(converter.CanConvertFrom(typeof(string)));
+
+ bool actual = converter.IsValid(value);
+ Assert.True(actual);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData("1,2,3")]
+ [InlineData("1,2,3,4,5")]
+ [InlineData("1 2")] // ThicknessConverter is sensitive to spaces; design converter matches its behavior
+ [InlineData("1 2 3 4 5")]
+ [InlineData("a,b")]
+ public void ThicknessTypeDesignConverter_Invalid(string value)
+ {
+ ThicknessTypeDesignConverter converter = new ThicknessTypeDesignConverter();
+ bool actual = converter.IsValid(value);
+ Assert.False(actual);
+ }
+ }
+}
diff --git a/src/Controls/tests/Core.Design.UnitTests/VisibilityDesignTypeConverterTests.cs b/src/Controls/tests/Core.Design.UnitTests/VisibilityDesignTypeConverterTests.cs
new file mode 100644
index 000000000000..15f4ee63aab6
--- /dev/null
+++ b/src/Controls/tests/Core.Design.UnitTests/VisibilityDesignTypeConverterTests.cs
@@ -0,0 +1,50 @@
+using Microsoft.Maui.Controls.Design;
+using Xunit;
+
+namespace Microsoft.Maui.Controls.Core.UnitTests
+{
+ public class VisibilityDesignTypeConverterTests
+ {
+ [Fact]
+ public void VisibilityDesignTypeConverter_StandartValues()
+ {
+ VisibilityDesignTypeConverter converter = new VisibilityDesignTypeConverter();
+ Assert.True(converter.CanConvertFrom(typeof(string)));
+
+ bool actual = converter.GetStandardValuesSupported();
+ Assert.True(actual);
+
+ actual = converter.GetStandardValuesExclusive();
+ Assert.True(actual);
+
+ var values = converter.GetStandardValues();
+ Assert.Equal(5, values.Count);
+ }
+
+ [Theory]
+ [InlineData("true")]
+ [InlineData(" FALSE ")]
+ [InlineData("Collapse")]
+ [InlineData("hidden ")]
+ [InlineData(" VISIBLE")]
+ public void VisibilityDesignTypeConverter_Valid(string value)
+ {
+ VisibilityDesignTypeConverter converter = new VisibilityDesignTypeConverter();
+ bool actual = converter.IsValid(value);
+ Assert.True(actual);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData("Collapse Hidden")]
+ [InlineData("foo")]
+ public void VisibilityDesignTypeConverter_Invalid(string value)
+ {
+ VisibilityDesignTypeConverter converter = new VisibilityDesignTypeConverter();
+ bool actual = converter.IsValid(value);
+ Assert.False(actual);
+ }
+ }
+}
diff --git a/src/Core/src/Converters/CornerRadiusTypeConverter.cs b/src/Core/src/Converters/CornerRadiusTypeConverter.cs
index 4174217d1ca8..31e4fd34b879 100644
--- a/src/Core/src/Converters/CornerRadiusTypeConverter.cs
+++ b/src/Core/src/Converters/CornerRadiusTypeConverter.cs
@@ -15,6 +15,7 @@ public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destina
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object? value)
{
+ // IMPORTANT! Update CornerRadiusDesignTypeConverter.IsValid if making changes here
var strValue = value?.ToString();
if (strValue != null)
diff --git a/src/Core/src/Converters/ThicknessTypeConverter.cs b/src/Core/src/Converters/ThicknessTypeConverter.cs
index 881dd72f7750..1910c2cc0862 100644
--- a/src/Core/src/Converters/ThicknessTypeConverter.cs
+++ b/src/Core/src/Converters/ThicknessTypeConverter.cs
@@ -17,6 +17,7 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
+ // IMPORTANT! Update ThicknessTypeDesignConverter.IsValid if making changes here
var strValue = value?.ToString();
if (strValue != null)
diff --git a/src/Graphics/src/Graphics/Point.cs b/src/Graphics/src/Graphics/Point.cs
index 1ae71e280d88..895d5d8cc618 100644
--- a/src/Graphics/src/Graphics/Point.cs
+++ b/src/Graphics/src/Graphics/Point.cs
@@ -129,6 +129,7 @@ public void Deconstruct(out double x, out double y)
public static bool TryParse(string value, out Point point)
{
+ // IMPORTANT! Update RectTypeDesignConverter.IsValid if making changes here
if (!string.IsNullOrEmpty(value))
{
string[] xy = value.Split(',');
diff --git a/src/Graphics/src/Graphics/Rect.cs b/src/Graphics/src/Graphics/Rect.cs
index 9fe25894d6c0..78a6cf1d41a0 100644
--- a/src/Graphics/src/Graphics/Rect.cs
+++ b/src/Graphics/src/Graphics/Rect.cs
@@ -221,6 +221,7 @@ public void Deconstruct(out double x, out double y, out double width, out double
public static bool TryParse(string value, out Rect rectangle)
{
+ // IMPORTANT! Update RectTypeDesignConverter.IsValid if making changes here
if (!string.IsNullOrEmpty(value))
{
string[] xywh = value.Split(',');