Refactor PropertyGrid UI, converters, and solution setup

- Improved PropertyGrid.axaml formatting and control styles; ensured spinner buttons are always visible and ColorPicker layouts are flexible; disabled horizontal scrolling in property list.
- Reformatted property editors and group/category display for clarity.
- Made all value converters sealed, added XML docs, and updated ConvertBack to throw NotImplementedException where unsupported.
- Enhanced search logic to use StringComparison.OrdinalIgnoreCase.
- Minor code style fixes (e.g., char literals for string checks).
- Removed default value assignment for _useProxy in AdvancedConfiguration.cs.
- Updated Program.cs in examples to use sealed Program classes.
- Added PropertyGrid.slnx to organize solution and projects.
This commit is contained in:
Matthias Heil
2026-08-13 09:49:27 +02:00
parent 750721b549
commit 37720f103b
13 changed files with 551 additions and 378 deletions
@@ -8,8 +8,16 @@ namespace Avalonia.PropertyGrid.Controls;
/// <summary>
/// Converts an Avalonia.Media.Color to a SolidColorBrush
/// </summary>
public class ColorToBrushConverter : IValueConverter
public sealed class ColorToBrushConverter : IValueConverter
{
/// <summary>
/// Converts a Color to a SolidColorBrush.
/// </summary>
/// <param name="value">The Color value to convert.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional parameter.</param>
/// <param name="culture">Culture information.</param>
/// <returns>A SolidColorBrush with the specified color.</returns>
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Color color)
@@ -21,9 +29,17 @@ public class ColorToBrushConverter : IValueConverter
return new SolidColorBrush(Colors.Transparent);
}
/// <summary>
/// Converts a SolidColorBrush back to a Color.
/// </summary>
/// <param name="value">The SolidColorBrush value to convert back.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional parameter.</param>
/// <param name="culture">Culture information.</param>
/// <returns>The Color from the brush.</returns>
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is SolidColorBrush brush && brush.Color != null)
if (value is SolidColorBrush brush)
{
return brush.Color;
}
@@ -7,8 +7,16 @@ namespace Avalonia.PropertyGrid.Controls;
/// <summary>
/// Converts boolean value to its negation for two-way binding
/// </summary>
public class NegateBoolConverter : IValueConverter
public sealed class NegateBoolConverter : IValueConverter
{
/// <summary>
/// Converts a boolean value to its negation.
/// </summary>
/// <param name="value">The boolean value to convert.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional parameter.</param>
/// <param name="culture">Culture information.</param>
/// <returns>The negated boolean value.</returns>
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool boolValue)
@@ -18,6 +26,14 @@ public class NegateBoolConverter : IValueConverter
return false;
}
/// <summary>
/// Converts a boolean value back to its negation (two-way binding).
/// </summary>
/// <param name="value">The boolean value to convert back.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional parameter.</param>
/// <param name="culture">Culture information.</param>
/// <returns>The negated boolean value.</returns>
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool boolValue)
@@ -7,8 +7,16 @@ namespace Avalonia.PropertyGrid.Controls;
/// <summary>
/// Converts PropertyType to appropriate numeric format string
/// </summary>
public class NumericFormatConverter : IValueConverter
public sealed class NumericFormatConverter : IValueConverter
{
/// <summary>
/// Converts a Type to an appropriate numeric format string.
/// </summary>
/// <param name="value">The Type to convert.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional parameter.</param>
/// <param name="culture">Culture information.</param>
/// <returns>A format string like "N2" for floating point or "N0" for integers.</returns>
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Type type)
@@ -26,6 +34,15 @@ public class NumericFormatConverter : IValueConverter
return "N0";
}
/// <summary>
/// Not supported for this converter.
/// </summary>
/// <param name="value">The value to convert back.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional parameter.</param>
/// <param name="culture">Culture information.</param>
/// <returns>Throws NotImplementedException.</returns>
/// <exception cref="NotImplementedException">This converter does not support ConvertBack.</exception>
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
@@ -7,7 +7,7 @@ namespace Avalonia.PropertyGrid.Controls;
/// <summary>
/// Converts indent level to left margin
/// </summary>
public class IndentToMarginConverter : IValueConverter
public sealed class IndentToMarginConverter : IValueConverter
{
/// <summary>
/// Converts indent level to left margin.
@@ -33,7 +33,7 @@ public class IndentToMarginConverter : IValueConverter
/// <summary>
/// Converts enum type to list of enum values
/// </summary>
public class EnumValuesConverter : IValueConverter
public sealed class EnumValuesConverter : IValueConverter
{
/// <summary>
/// Converts an enum type to an array of its values.
@@ -59,8 +59,16 @@ public class EnumValuesConverter : IValueConverter
/// <summary>
/// Checks if a type is an enum
/// </summary>
public class IsEnumConverter : IValueConverter
public sealed class IsEnumConverter : IValueConverter
{
/// <summary>
/// Checks if the provided type is an enum.
/// </summary>
/// <param name="value">The Type to check.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional parameter.</param>
/// <param name="culture">Culture information.</param>
/// <returns>True if the type is an enum, otherwise false.</returns>
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Type type)
@@ -70,6 +78,10 @@ public class IsEnumConverter : IValueConverter
return false;
}
/// <summary>
/// Not supported for this converter.
/// </summary>
/// <exception cref="NotImplementedException">This converter does not support ConvertBack.</exception>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
@@ -79,8 +91,16 @@ public class IsEnumConverter : IValueConverter
/// <summary>
/// Checks if a type is a simple type (string, number, bool, etc.)
/// </summary>
public class IsSimpleTypeConverter : IValueConverter
public sealed class IsSimpleTypeConverter : IValueConverter
{
/// <summary>
/// Checks if the provided type is a simple type like string, number, DateTime, etc.
/// </summary>
/// <param name="value">The Type to check.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional parameter.</param>
/// <param name="culture">Culture information.</param>
/// <returns>True if the type is a simple type, otherwise false.</returns>
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Type type)
@@ -96,6 +116,10 @@ public class IsSimpleTypeConverter : IValueConverter
return false;
}
/// <summary>
/// Not supported for this converter.
/// </summary>
/// <exception cref="NotImplementedException">This converter does not support ConvertBack.</exception>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
@@ -105,8 +129,16 @@ public class IsSimpleTypeConverter : IValueConverter
/// <summary>
/// Checks if a type matches the specified type name
/// </summary>
public class TypeToBoolConverter : IValueConverter
public sealed class TypeToBoolConverter : IValueConverter
{
/// <summary>
/// Checks if the type's name matches the specified parameter string.
/// </summary>
/// <param name="value">The Type to check.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">The type name to match against.</param>
/// <param name="culture">Culture information.</param>
/// <returns>True if the type name matches, otherwise false.</returns>
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Type type && parameter is string typeName)
@@ -116,6 +148,10 @@ public class TypeToBoolConverter : IValueConverter
return false;
}
/// <summary>
/// Not supported for this converter.
/// </summary>
/// <exception cref="NotImplementedException">This converter does not support ConvertBack.</exception>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
@@ -125,8 +161,16 @@ public class TypeToBoolConverter : IValueConverter
/// <summary>
/// Converts Color to/from hex string representation
/// </summary>
public class ColorToStringConverter : IValueConverter
public sealed class ColorToStringConverter : IValueConverter
{
/// <summary>
/// Converts a Color to a hex string representation.
/// </summary>
/// <param name="value">The Color to convert.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional parameter.</param>
/// <param name="culture">Culture information.</param>
/// <returns>A hex string representation of the color (e.g., "#AARRGGBB").</returns>
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Media.Color color)
@@ -136,6 +180,14 @@ public class ColorToStringConverter : IValueConverter
return value?.ToString();
}
/// <summary>
/// Converts a hex string back to a Color.
/// </summary>
/// <param name="value">The hex string to convert.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional parameter.</param>
/// <param name="culture">Culture information.</param>
/// <returns>The parsed Color, or Black if parsing fails.</returns>
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is string hexString && !string.IsNullOrWhiteSpace(hexString))
@@ -143,7 +195,7 @@ public class ColorToStringConverter : IValueConverter
try
{
hexString = hexString.Trim();
if (!hexString.StartsWith("#"))
if (!hexString.StartsWith('#'))
hexString = "#" + hexString;
return Media.Color.Parse(hexString);
@@ -161,8 +213,16 @@ public class ColorToStringConverter : IValueConverter
/// <summary>
/// Checks if a type is Color
/// </summary>
public class IsColorTypeConverter : IValueConverter
public sealed class IsColorTypeConverter : IValueConverter
{
/// <summary>
/// Checks if the provided type is Avalonia.Media.Color.
/// </summary>
/// <param name="value">The Type to check.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional "Invert" parameter to invert the result.</param>
/// <param name="culture">Culture information.</param>
/// <returns>True if the type is Color (or inverted if parameter is "Invert"), otherwise false.</returns>
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Type type)
@@ -187,6 +247,10 @@ public class IsColorTypeConverter : IValueConverter
return false;
}
/// <summary>
/// Not supported for this converter.
/// </summary>
/// <exception cref="NotImplementedException">This converter does not support ConvertBack.</exception>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
@@ -196,8 +260,16 @@ public class IsColorTypeConverter : IValueConverter
/// <summary>
/// Checks if a type is numeric
/// </summary>
public class IsNumericTypeConverter : IValueConverter
public sealed class IsNumericTypeConverter : IValueConverter
{
/// <summary>
/// Checks if the provided type is a numeric type (int, long, float, double, decimal, etc.).
/// </summary>
/// <param name="value">The Type to check.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional "Invert" parameter to invert the result.</param>
/// <param name="culture">Culture information.</param>
/// <returns>True if the type is numeric (or inverted if parameter is "Invert"), otherwise false.</returns>
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Type type)
@@ -225,6 +297,10 @@ public class IsNumericTypeConverter : IValueConverter
return false;
}
/// <summary>
/// Not supported for this converter.
/// </summary>
/// <exception cref="NotImplementedException">This converter does not support ConvertBack.</exception>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
@@ -234,8 +310,16 @@ public class IsNumericTypeConverter : IValueConverter
/// <summary>
/// Checks if a type is neither Color nor Numeric nor Boolean nor Enum (for fallback TextBox)
/// </summary>
public class IsOtherTypeConverter : IValueConverter
public sealed class IsOtherTypeConverter : IValueConverter
{
/// <summary>
/// Checks if the type is not Color, Numeric, Boolean, or Enum (used for fallback TextBox editor).
/// </summary>
/// <param name="value">The Type to check.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional parameter.</param>
/// <param name="culture">Culture information.</param>
/// <returns>True if the type is not a special type, otherwise false.</returns>
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Type type)
@@ -255,6 +339,10 @@ public class IsOtherTypeConverter : IValueConverter
return true; // Default to showing TextBox
}
/// <summary>
/// Not supported for this converter.
/// </summary>
/// <exception cref="NotImplementedException">This converter does not support ConvertBack.</exception>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
@@ -264,8 +352,16 @@ public class IsOtherTypeConverter : IValueConverter
/// <summary>
/// Checks if a type is boolean
/// </summary>
public class IsBooleanTypeConverter : IValueConverter
public sealed class IsBooleanTypeConverter : IValueConverter
{
/// <summary>
/// Checks if the provided type is boolean.
/// </summary>
/// <param name="value">The Type to check.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Optional "Invert" parameter to invert the result.</param>
/// <param name="culture">Culture information.</param>
/// <returns>True if the type is boolean (or inverted if parameter is "Invert"), otherwise false.</returns>
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Type type)
@@ -290,6 +386,10 @@ public class IsBooleanTypeConverter : IValueConverter
return false;
}
/// <summary>
/// Not supported for this converter.
/// </summary>
/// <exception cref="NotImplementedException">This converter does not support ConvertBack.</exception>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();