312 lines
8.5 KiB
C#
312 lines
8.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Globalization;
|
|
using System.Reflection;
|
|
|
|
namespace Avalonia.PropertyGrid.Models;
|
|
|
|
/// <summary>
|
|
/// Represents a single property item in the PropertyGrid
|
|
/// </summary>
|
|
public class PropertyItem : INotifyPropertyChanged
|
|
{
|
|
private object? _value;
|
|
private bool _isExpanded;
|
|
|
|
/// <summary>
|
|
/// Occurs when a property value changes.
|
|
/// </summary>
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
|
/// <summary>
|
|
/// The property name displayed in the grid
|
|
/// </summary>
|
|
public string Name { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// The display name (from DisplayAttribute or DisplayNameAttribute)
|
|
/// </summary>
|
|
public string DisplayName { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// The property description
|
|
/// </summary>
|
|
public string? Description { get; set; }
|
|
|
|
/// <summary>
|
|
/// The category this property belongs to
|
|
/// </summary>
|
|
public string Category { get; set; } = "Misc";
|
|
|
|
/// <summary>
|
|
/// The property value
|
|
/// </summary>
|
|
public object? Value
|
|
{
|
|
get => _value;
|
|
set
|
|
{
|
|
if (_value != value)
|
|
{
|
|
_value = value;
|
|
OnPropertyChanged(nameof(Value));
|
|
OnPropertyChanged(nameof(ValueString));
|
|
OnPropertyChanged(nameof(NumericValue));
|
|
OnPropertyChanged(nameof(ColorValue));
|
|
OnPropertyChanged(nameof(BooleanValue));
|
|
|
|
// Update the actual object property
|
|
if (PropertyInfo != null && Instance != null)
|
|
{
|
|
try
|
|
{
|
|
PropertyInfo.SetValue(Instance, value);
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors during setting
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Numeric value for NumericUpDown binding (converts to/from decimal)
|
|
/// </summary>
|
|
public decimal NumericValue
|
|
{
|
|
get
|
|
{
|
|
if (Value == null) return 0;
|
|
|
|
try
|
|
{
|
|
return Convert.ToDecimal(Value, CultureInfo.InvariantCulture);
|
|
}
|
|
catch
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
set
|
|
{
|
|
try
|
|
{
|
|
// Convert decimal to the actual property type
|
|
object? converted = null;
|
|
if (PropertyType == typeof(int)) converted = (int)value;
|
|
else if (PropertyType == typeof(long)) converted = (long)value;
|
|
else if (PropertyType == typeof(short)) converted = (short)value;
|
|
else if (PropertyType == typeof(byte)) converted = (byte)value;
|
|
else if (PropertyType == typeof(uint)) converted = (uint)value;
|
|
else if (PropertyType == typeof(ulong)) converted = (ulong)value;
|
|
else if (PropertyType == typeof(ushort)) converted = (ushort)value;
|
|
else if (PropertyType == typeof(sbyte)) converted = (sbyte)value;
|
|
else if (PropertyType == typeof(float)) converted = (float)value;
|
|
else if (PropertyType == typeof(double)) converted = (double)value;
|
|
else if (PropertyType == typeof(decimal)) converted = value;
|
|
|
|
if (converted != null)
|
|
{
|
|
Value = converted;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore conversion errors
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Color value for ColorPicker binding
|
|
/// </summary>
|
|
public Media.Color ColorValue
|
|
{
|
|
get
|
|
{
|
|
if (Value is Media.Color color)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"ColorValue getter: returning {color}");
|
|
return color;
|
|
}
|
|
System.Diagnostics.Debug.WriteLine($"ColorValue getter: Value is not Color (is {Value?.GetType().Name ?? "null"}), returning Black");
|
|
return Media.Colors.Black;
|
|
}
|
|
set
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"ColorValue setter: setting to {value}");
|
|
Value = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Boolean value for RadioButton binding
|
|
/// </summary>
|
|
public bool BooleanValue
|
|
{
|
|
get
|
|
{
|
|
if (Value is bool boolVal)
|
|
{
|
|
return boolVal;
|
|
}
|
|
return false;
|
|
}
|
|
set
|
|
{
|
|
Value = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// String representation of the value for display
|
|
/// </summary>
|
|
public string ValueString
|
|
{
|
|
get
|
|
{
|
|
if (Value is Media.Color color)
|
|
{
|
|
return $"#{color.A:X2}{color.R:X2}{color.G:X2}{color.B:X2}";
|
|
}
|
|
return Value?.ToString() ?? string.Empty;
|
|
}
|
|
set
|
|
{
|
|
// Try to parse as color if the property type is Color
|
|
if (PropertyType == typeof(Media.Color))
|
|
{
|
|
try
|
|
{
|
|
var hexString = value?.Trim() ?? "";
|
|
if (!hexString.StartsWith("#"))
|
|
hexString = "#" + hexString;
|
|
|
|
Value = Media.Color.Parse(hexString);
|
|
}
|
|
catch
|
|
{
|
|
// If parsing fails, ignore
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For other types, try to convert the string to the property type
|
|
try
|
|
{
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
Value = Convert.ChangeType(value, PropertyType, CultureInfo.InvariantCulture);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// If conversion fails, ignore
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The property type
|
|
/// </summary>
|
|
public Type PropertyType { get; set; } = typeof(object);
|
|
|
|
/// <summary>
|
|
/// Whether the property is read-only
|
|
/// </summary>
|
|
public bool IsReadOnly { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether the property is browsable (visible)
|
|
/// </summary>
|
|
public bool IsBrowsable { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Whether the property is expandable (has sub-properties)
|
|
/// </summary>
|
|
public bool IsExpandable { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether the property is currently expanded
|
|
/// </summary>
|
|
public bool IsExpanded
|
|
{
|
|
get => _isExpanded;
|
|
set
|
|
{
|
|
if (_isExpanded != value)
|
|
{
|
|
_isExpanded = value;
|
|
OnPropertyChanged(nameof(IsExpanded));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Child properties (for complex types)
|
|
/// </summary>
|
|
public List<PropertyItem> Children { get; set; } = new();
|
|
|
|
/// <summary>
|
|
/// The PropertyInfo reflection object
|
|
/// </summary>
|
|
public PropertyInfo? PropertyInfo { get; set; }
|
|
|
|
/// <summary>
|
|
/// The object instance this property belongs to
|
|
/// </summary>
|
|
public object? Instance { get; set; }
|
|
|
|
/// <summary>
|
|
/// Custom editor type for this property
|
|
/// </summary>
|
|
public Type? EditorType { get; set; }
|
|
|
|
/// <summary>
|
|
/// The indentation level (for nested properties)
|
|
/// </summary>
|
|
public int IndentLevel { get; set; }
|
|
|
|
/// <summary>
|
|
/// Custom converter for this property
|
|
/// </summary>
|
|
public TypeConverter? Converter { get; set; }
|
|
|
|
/// <summary>
|
|
/// Order/priority for display
|
|
/// </summary>
|
|
public int Order { get; set; }
|
|
|
|
/// <summary>
|
|
/// Raises the PropertyChanged event.
|
|
/// </summary>
|
|
protected virtual void OnPropertyChanged(string propertyName)
|
|
{
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refreshes the value from the source instance
|
|
/// </summary>
|
|
public void RefreshValue()
|
|
{
|
|
if (PropertyInfo != null && Instance != null)
|
|
{
|
|
try
|
|
{
|
|
_value = PropertyInfo.GetValue(Instance);
|
|
OnPropertyChanged(nameof(Value));
|
|
OnPropertyChanged(nameof(ValueString));
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors during refresh
|
|
}
|
|
}
|
|
}
|
|
}
|