initial commit
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Avalonia.PropertyGrid.Models;
|
||||
|
||||
namespace Avalonia.PropertyGrid.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for creating PropertyItem collections from objects
|
||||
/// </summary>
|
||||
public class PropertyItemFactory
|
||||
{
|
||||
private static readonly HashSet<Type> SimpleTypes = new()
|
||||
{
|
||||
typeof(string), typeof(int), typeof(long), typeof(short), typeof(byte),
|
||||
typeof(uint), typeof(ulong), typeof(ushort), typeof(sbyte),
|
||||
typeof(float), typeof(double), typeof(decimal),
|
||||
typeof(bool), typeof(char), typeof(DateTime), typeof(DateTimeOffset),
|
||||
typeof(TimeSpan), typeof(Guid)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a collection of PropertyItems from an object
|
||||
/// </summary>
|
||||
public static List<PropertyItem> CreatePropertyItems(object? obj, int indentLevel = 0)
|
||||
{
|
||||
if (obj == null)
|
||||
return new List<PropertyItem>();
|
||||
|
||||
var properties = new List<PropertyItem>();
|
||||
var type = obj.GetType();
|
||||
|
||||
// Check if this is a collection (but not a string or dictionary)
|
||||
if (IsCollection(obj))
|
||||
{
|
||||
return CreateCollectionItems(obj, indentLevel);
|
||||
}
|
||||
|
||||
foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
// Skip indexed properties
|
||||
if (prop.GetIndexParameters().Length > 0)
|
||||
continue;
|
||||
|
||||
var item = CreatePropertyItem(obj, prop, indentLevel);
|
||||
|
||||
if (item != null && item.IsBrowsable)
|
||||
{
|
||||
properties.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by order, then by category, then by name
|
||||
return properties.OrderBy(p => p.Order)
|
||||
.ThenBy(p => p.Category)
|
||||
.ThenBy(p => p.DisplayName)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates PropertyItems for collection elements
|
||||
/// </summary>
|
||||
private static List<PropertyItem> CreateCollectionItems(object collection, int indentLevel)
|
||||
{
|
||||
var items = new List<PropertyItem>();
|
||||
|
||||
if (collection is System.Collections.IEnumerable enumerable)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (var element in enumerable)
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var elementType = element.GetType();
|
||||
var isSimple = IsSimpleType(elementType);
|
||||
|
||||
// For simple types, show the value in the display name
|
||||
string displayName = $"[{index}]";
|
||||
if (isSimple || elementType == typeof(string))
|
||||
{
|
||||
var valueStr = element.ToString();
|
||||
if (!string.IsNullOrEmpty(valueStr) && valueStr.Length <= 50)
|
||||
{
|
||||
displayName = $"[{index}] = {valueStr}";
|
||||
}
|
||||
}
|
||||
|
||||
var item = new PropertyItem
|
||||
{
|
||||
Name = $"[{index}]",
|
||||
DisplayName = displayName,
|
||||
Description = $"Item at index {index}",
|
||||
Category = "Items",
|
||||
PropertyType = elementType,
|
||||
IsReadOnly = true, // Collection items are read-only for now
|
||||
Instance = collection,
|
||||
IndentLevel = indentLevel,
|
||||
Value = element,
|
||||
Converter = TypeDescriptor.GetConverter(elementType),
|
||||
Order = index,
|
||||
IsBrowsable = true,
|
||||
IsExpandable = !isSimple && elementType.IsClass && elementType != typeof(string)
|
||||
};
|
||||
|
||||
items.Add(item);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an object is a collection (excluding strings and dictionaries)
|
||||
/// </summary>
|
||||
private static bool IsCollection(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
var type = obj.GetType();
|
||||
|
||||
// Exclude strings and dictionaries
|
||||
if (type == typeof(string))
|
||||
return false;
|
||||
|
||||
if (typeof(System.Collections.IDictionary).IsAssignableFrom(type))
|
||||
return false;
|
||||
|
||||
// Check if it implements IEnumerable
|
||||
return typeof(System.Collections.IEnumerable).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a single PropertyItem from a PropertyInfo
|
||||
/// </summary>
|
||||
public static PropertyItem? CreatePropertyItem(object instance, PropertyInfo propertyInfo, int indentLevel = 0)
|
||||
{
|
||||
// Check if browsable
|
||||
var browsableAttr = propertyInfo.GetCustomAttribute<BrowsableAttribute>();
|
||||
if (browsableAttr != null && !browsableAttr.Browsable)
|
||||
return null;
|
||||
|
||||
var item = new PropertyItem
|
||||
{
|
||||
Name = propertyInfo.Name,
|
||||
DisplayName = GetDisplayName(propertyInfo),
|
||||
Description = GetDescription(propertyInfo),
|
||||
Category = GetCategory(propertyInfo),
|
||||
PropertyType = propertyInfo.PropertyType,
|
||||
IsReadOnly = !propertyInfo.CanWrite || propertyInfo.GetCustomAttribute<ReadOnlyAttribute>()?.IsReadOnly == true,
|
||||
PropertyInfo = propertyInfo,
|
||||
Instance = instance,
|
||||
IndentLevel = indentLevel,
|
||||
Converter = TypeDescriptor.GetConverter(propertyInfo.PropertyType),
|
||||
Order = GetOrder(propertyInfo),
|
||||
IsBrowsable = true
|
||||
};
|
||||
|
||||
// Get the current value
|
||||
try
|
||||
{
|
||||
item.Value = propertyInfo.GetValue(instance);
|
||||
}
|
||||
catch
|
||||
{
|
||||
item.Value = null;
|
||||
}
|
||||
|
||||
// Check if expandable (complex type)
|
||||
var propType = propertyInfo.PropertyType;
|
||||
item.IsExpandable = !IsSimpleType(propType) && propType.IsClass && propType != typeof(string);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private static string GetDisplayName(PropertyInfo prop)
|
||||
{
|
||||
var displayAttr = prop.GetCustomAttribute<DisplayAttribute>();
|
||||
if (displayAttr?.Name != null)
|
||||
return displayAttr.Name;
|
||||
|
||||
var displayNameAttr = prop.GetCustomAttribute<DisplayNameAttribute>();
|
||||
if (displayNameAttr?.DisplayName != null)
|
||||
return displayNameAttr.DisplayName;
|
||||
|
||||
// Add spaces before capital letters
|
||||
return AddSpacesToCamelCase(prop.Name);
|
||||
}
|
||||
|
||||
private static string? GetDescription(PropertyInfo prop)
|
||||
{
|
||||
var displayAttr = prop.GetCustomAttribute<DisplayAttribute>();
|
||||
if (displayAttr?.Description != null)
|
||||
return displayAttr.Description;
|
||||
|
||||
var descriptionAttr = prop.GetCustomAttribute<DescriptionAttribute>();
|
||||
return descriptionAttr?.Description;
|
||||
}
|
||||
|
||||
private static string GetCategory(PropertyInfo prop)
|
||||
{
|
||||
var categoryAttr = prop.GetCustomAttribute<CategoryAttribute>();
|
||||
if (categoryAttr?.Category != null && categoryAttr.Category != CategoryAttribute.Default.Category)
|
||||
return categoryAttr.Category;
|
||||
|
||||
var displayAttr = prop.GetCustomAttribute<DisplayAttribute>();
|
||||
if (displayAttr?.GroupName != null)
|
||||
return displayAttr.GroupName;
|
||||
|
||||
return "Misc";
|
||||
}
|
||||
|
||||
private static int GetOrder(PropertyInfo prop)
|
||||
{
|
||||
var displayAttr = prop.GetCustomAttribute<DisplayAttribute>();
|
||||
return displayAttr?.GetOrder() ?? int.MaxValue;
|
||||
}
|
||||
|
||||
private static bool IsSimpleType(Type type)
|
||||
{
|
||||
type = Nullable.GetUnderlyingType(type) ?? type;
|
||||
return SimpleTypes.Contains(type) || type.IsEnum;
|
||||
}
|
||||
|
||||
private static string AddSpacesToCamelCase(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return string.Empty;
|
||||
|
||||
var result = new System.Text.StringBuilder();
|
||||
result.Append(text[0]);
|
||||
|
||||
for (int i = 1; i < text.Length; i++)
|
||||
{
|
||||
if (char.IsUpper(text[i]) && !char.IsUpper(text[i - 1]))
|
||||
{
|
||||
result.Append(' ');
|
||||
}
|
||||
result.Append(text[i]);
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user