initial commit
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Avalonia.PropertyGrid.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a category group in the PropertyGrid
|
||||
/// </summary>
|
||||
public class CategoryGroup : INotifyPropertyChanged
|
||||
{
|
||||
private bool _isExpanded = true;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The category name
|
||||
/// </summary>
|
||||
public string CategoryName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Properties in this category
|
||||
/// </summary>
|
||||
public ObservableCollection<PropertyItem> Properties { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the category is expanded
|
||||
/// </summary>
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set
|
||||
{
|
||||
if (_isExpanded != value)
|
||||
{
|
||||
_isExpanded = value;
|
||||
OnPropertyChanged(nameof(IsExpanded));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of properties in this category
|
||||
/// </summary>
|
||||
public int PropertyCount => Properties.Count;
|
||||
|
||||
protected virtual void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Avalonia.PropertyGrid.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a category group of properties
|
||||
/// </summary>
|
||||
public class PropertyCategory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the category name.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of properties in this category.
|
||||
/// </summary>
|
||||
public List<PropertyItem> Properties { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the category is expanded in the UI.
|
||||
/// </summary>
|
||||
public bool IsExpanded { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display order of the category.
|
||||
/// </summary>
|
||||
public int Order { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper for grouping properties by category
|
||||
/// </summary>
|
||||
public static class PropertyCategoryHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Groups a list of properties by their category.
|
||||
/// </summary>
|
||||
public static List<PropertyCategory> GroupByCategory(List<PropertyItem> properties)
|
||||
{
|
||||
return properties.GroupBy(p => p.Category)
|
||||
.Select(g => new PropertyCategory
|
||||
{
|
||||
Name = g.Key,
|
||||
Properties = g.ToList(),
|
||||
IsExpanded = true,
|
||||
Order = g.Min(p => p.Order)
|
||||
})
|
||||
.OrderBy(c => c.Order)
|
||||
.ThenBy(c => c.Name)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user