initial commit

This commit is contained in:
Matthias Heil
2026-08-10 14:46:18 +02:00
commit c946aa63e6
69 changed files with 8865 additions and 0 deletions
@@ -0,0 +1,364 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Avalonia.PropertyGrid.Controls"
xmlns:models="using:Avalonia.PropertyGrid.Models"
xmlns:sys="clr-namespace:System;assembly=System.Runtime">
<Design.PreviewWith>
<Border Padding="20" Width="400" Height="600">
<controls:PropertyGrid />
</Border>
</Design.PreviewWith>
<Style Selector="controls|PropertyGrid">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundAltHighBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlForegroundBaseMediumLowBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<Grid RowDefinitions="Auto,*">
<!-- Search/Filter Box -->
<Border Grid.Row="0"
Padding="8"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="0,0,0,1">
<StackPanel>
<TextBox Name="PART_SearchBox"
PlaceholderText="Search properties..."
Text="{Binding $parent[controls:PropertyGrid].SearchText, Mode=TwoWay}" />
<!-- Debug: Show count -->
<TextBlock Text="{Binding $parent[controls:PropertyGrid].FilteredProperties.Count, StringFormat='Properties: {0}'}"
FontSize="10"
Foreground="Gray"
Margin="0,4,0,0" />
</StackPanel>
</Border>
<!-- Properties List -->
<ScrollViewer Grid.Row="1"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<Panel>
<!-- Category View -->
<ItemsControl Name="PART_CategoryItemsControl"
ItemsSource="{TemplateBinding CategoryGroups}"
IsVisible="{TemplateBinding ShowCategories}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="models:CategoryGroup">
<StackPanel>
<!-- Category Header -->
<Border Background="{DynamicResource SystemControlHighlightAccentBrush}"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="0,0,0,1"
Padding="8,4"
HorizontalAlignment="Left">
<StackPanel Orientation="Horizontal" Spacing="4">
<!-- Expand/Collapse Toggle -->
<ToggleButton IsChecked="{Binding IsExpanded, Mode=TwoWay}"
Width="16" Height="16"
Padding="0"
VerticalAlignment="Center">
<Panel>
<!-- Right Arrow (collapsed/closed state) -->
<Path IsVisible="{Binding !IsExpanded}"
Data="M 4,2 L 10,8 L 4,14 Z"
Fill="White"
Width="12" Height="12"
Stretch="Uniform" />
<!-- Down Arrow (expanded/open state) -->
<Path IsVisible="{Binding IsExpanded}"
Data="M 2,4 L 8,10 L 14,4 Z"
Fill="White"
Width="12" Height="12"
Stretch="Uniform" />
</Panel>
</ToggleButton>
<!-- Category Name -->
<TextBlock Text="{Binding CategoryName}"
FontWeight="Bold"
Foreground="White"
VerticalAlignment="Center" />
<!-- Property Count -->
<TextBlock Text="{Binding PropertyCount, StringFormat='({0})'}"
FontSize="11"
Foreground="White"
Opacity="0.8"
VerticalAlignment="Center"
Margin="4,0,0,0" />
</StackPanel>
</Border>
<!-- Properties in Category -->
<ItemsControl ItemsSource="{Binding Properties}"
IsVisible="{Binding IsExpanded}"
Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="models:PropertyItem">
<Border Margin="16,0,0,0" BorderThickness="0">
<Grid MinHeight="32"
Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="150" MaxWidth="400" SharedSizeGroup="PropertyName" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<!-- Property Name -->
<Border Grid.Column="0"
Padding="8,4"
Margin="{Binding IndentLevel, Converter={StaticResource IndentConverter}}"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="0,0,1,1">
<StackPanel Orientation="Horizontal" Spacing="4">
<!-- Expander for complex types with arrow icons -->
<ToggleButton IsVisible="{Binding IsExpandable}"
IsChecked="{Binding IsExpanded}"
Width="16" Height="16"
Padding="0"
VerticalAlignment="Center">
<Panel>
<!-- Right Arrow (collapsed/closed state) -->
<Path IsVisible="{Binding !IsExpanded}"
Data="M 4,2 L 10,8 L 4,14 Z"
Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
Width="12" Height="12"
Stretch="Uniform" />
<!-- Down Arrow (expanded/open state) -->
<Path IsVisible="{Binding IsExpanded}"
Data="M 2,4 L 8,10 L 14,4 Z"
Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
Width="12" Height="12"
Stretch="Uniform" />
</Panel>
</ToggleButton>
<TextBlock Text="{Binding DisplayName}"
VerticalAlignment="Center"
ToolTip.Tip="{Binding Description}" />
</StackPanel>
</Border>
<!-- Splitter -->
<GridSplitter Grid.Column="1"
Width="4"
Background="Transparent"
ResizeDirection="Columns" />
<!-- Property Value Editor -->
<Border Grid.Column="2"
Padding="4"
Background="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="0,0,0,1">
<Grid>
<!-- Color Picker for Color properties with colored background -->
<Border Background="{Binding ColorValue, Converter={StaticResource ColorToBrushConverter}}"
IsVisible="{Binding PropertyType, Converter={StaticResource IsColorTypeConverter}}"
CornerRadius="4"
Padding="4">
<ColorPicker Color="{Binding ColorValue, Mode=TwoWay}"
IsEnabled="{Binding !IsReadOnly}"
IsAlphaEnabled="True"
HorizontalAlignment="Stretch">
<ColorPicker.Styles>
<Style Selector="ColorPicker">
<Setter Property="MaxWidth" Value="{x:Static sys:Double.PositiveInfinity}" />
</Style>
</ColorPicker.Styles>
</ColorPicker>
</Border>
<!-- NumericUpDown for numeric properties -->
<NumericUpDown Value="{Binding NumericValue, Mode=TwoWay}"
IsVisible="{Binding PropertyType, Converter={StaticResource IsNumericTypeConverter}}"
IsReadOnly="{Binding IsReadOnly}"
FormatString="{Binding PropertyType, Converter={StaticResource NumericFormatConverter}}"
AllowSpin="True"
ShowButtonSpinner="True"
HorizontalAlignment="Stretch" />
<!-- CheckBox for boolean properties -->
<CheckBox IsChecked="{Binding BooleanValue, Mode=TwoWay}"
IsVisible="{Binding PropertyType, Converter={StaticResource IsBooleanTypeConverter}}"
IsEnabled="{Binding !IsReadOnly}"
Content="{Binding DisplayName}"
VerticalAlignment="Center"
HorizontalAlignment="Left" />
<!-- ComboBox for enum properties -->
<ComboBox SelectedItem="{Binding Value, Mode=TwoWay}"
ItemsSource="{Binding PropertyType, Converter={StaticResource EnumValuesConverter}}"
IsVisible="{Binding PropertyType, Converter={StaticResource IsEnumConverter}}"
IsEnabled="{Binding !IsReadOnly}"
HorizontalAlignment="Stretch" />
<!-- TextBox for other properties -->
<TextBox Text="{Binding ValueString, Mode=TwoWay}"
IsVisible="{Binding PropertyType, Converter={StaticResource IsOtherTypeConverter}}"
IsReadOnly="{Binding IsReadOnly}"
HorizontalAlignment="Stretch" />
</Grid>
</Border>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Flat List View -->
<ItemsControl Name="PART_ItemsControl"
ItemsSource="{TemplateBinding FilteredProperties}"
IsVisible="{TemplateBinding ShowCategories, Converter={StaticResource NegateBoolConverter}}"
Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="models:PropertyItem">
<Border Margin="0,0,0,0" BorderThickness="0">
<Grid MinHeight="32"
Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="150" MaxWidth="400" SharedSizeGroup="PropertyName" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<!-- Property Name -->
<Border Grid.Column="0"
Padding="8,4"
Margin="{Binding IndentLevel, Converter={StaticResource IndentConverter}}"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="0,0,1,1">
<StackPanel Orientation="Horizontal" Spacing="4">
<!-- Expander for complex types with arrow icons -->
<ToggleButton IsVisible="{Binding IsExpandable}"
IsChecked="{Binding IsExpanded}"
Width="16" Height="16"
Padding="0"
VerticalAlignment="Center">
<Panel>
<!-- Right Arrow (collapsed/closed state) -->
<Path IsVisible="{Binding !IsExpanded}"
Data="M 4,2 L 10,8 L 4,14 Z"
Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
Width="12" Height="12"
Stretch="Uniform" />
<!-- Down Arrow (expanded/open state) -->
<Path IsVisible="{Binding IsExpanded}"
Data="M 2,4 L 8,10 L 14,4 Z"
Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
Width="12" Height="12"
Stretch="Uniform" />
</Panel>
</ToggleButton>
<TextBlock Text="{Binding DisplayName}"
VerticalAlignment="Center"/>
</StackPanel>
</Border>
<!-- Splitter -->
<GridSplitter Grid.Column="1"
Width="4"
Background="Transparent"
ResizeDirection="Columns" />
<!-- Property Value -->
<Border Grid.Column="2"
Padding="8,4"
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
BorderThickness="0,0,0,1">
<Grid>
<!-- ColorPicker for color properties with colored background -->
<Border Background="{Binding ColorValue, Converter={StaticResource ColorToBrushConverter}}"
IsVisible="{Binding PropertyType, Converter={StaticResource IsColorTypeConverter}}"
CornerRadius="4"
Padding="4">
<ColorPicker Color="{Binding ColorValue, Mode=TwoWay}"
IsEnabled="{Binding !IsReadOnly}"
HorizontalAlignment="Stretch">
<ColorPicker.Styles>
<Style Selector="ColorPicker">
<Setter Property="Width" Value="{x:Static sys:Double.PositiveInfinity}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</ColorPicker.Styles>
</ColorPicker>
</Border>
<!-- NumericUpDown for numeric properties -->
<NumericUpDown Value="{Binding NumericValue, Mode=TwoWay}"
IsVisible="{Binding PropertyType, Converter={StaticResource IsNumericTypeConverter}}"
IsReadOnly="{Binding IsReadOnly}"
FormatString="{Binding PropertyType, Converter={StaticResource NumericFormatConverter}}"
AllowSpin="True"
ShowButtonSpinner="True"
HorizontalAlignment="Stretch" />
<!-- CheckBox for boolean properties -->
<CheckBox IsChecked="{Binding BooleanValue, Mode=TwoWay}"
IsVisible="{Binding PropertyType, Converter={StaticResource IsBooleanTypeConverter}}"
IsEnabled="{Binding !IsReadOnly}"
Content="{Binding DisplayName}"
VerticalAlignment="Center"
HorizontalAlignment="Left" />
<!-- ComboBox for enum properties -->
<ComboBox SelectedItem="{Binding Value, Mode=TwoWay}"
ItemsSource="{Binding PropertyType, Converter={StaticResource EnumValuesConverter}}"
IsVisible="{Binding PropertyType, Converter={StaticResource IsEnumConverter}}"
IsEnabled="{Binding !IsReadOnly}"
HorizontalAlignment="Stretch" />
<!-- TextBox for other properties -->
<TextBox Text="{Binding ValueString, Mode=TwoWay}"
IsVisible="{Binding PropertyType, Converter={StaticResource IsOtherTypeConverter}}"
IsReadOnly="{Binding IsReadOnly}"
HorizontalAlignment="Stretch" />
</Grid>
</Border>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Panel>
</ScrollViewer>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<!-- Converters and Resources -->
<Style Selector="controls|PropertyGrid">
<Style.Resources>
<controls:IndentToMarginConverter x:Key="IndentConverter" />
<controls:EnumValuesConverter x:Key="EnumValuesConverter" />
<controls:IsEnumConverter x:Key="IsEnumConverter" />
<controls:IsSimpleTypeConverter x:Key="IsSimpleTypeConverter" />
<controls:TypeToBoolConverter x:Key="TypeToBoolConverter" />
<controls:ColorToStringConverter x:Key="ColorToStringConverter" />
<controls:ColorToBrushConverter x:Key="ColorToBrushConverter" />
<controls:IsColorTypeConverter x:Key="IsColorTypeConverter" />
<controls:IsNumericTypeConverter x:Key="IsNumericTypeConverter" />
<controls:IsBooleanTypeConverter x:Key="IsBooleanTypeConverter" />
<controls:IsOtherTypeConverter x:Key="IsOtherTypeConverter" />
<controls:NumericFormatConverter x:Key="NumericFormatConverter" />
<controls:NegateBoolConverter x:Key="NegateBoolConverter" />
</Style.Resources>
</Style>
</Styles>
@@ -0,0 +1,343 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.PropertyGrid.Models;
using Avalonia.PropertyGrid.Services;
namespace Avalonia.PropertyGrid.Controls;
/// <summary>
/// A PropertyGrid control for editing object properties with Xceed-like functionality
/// </summary>
public class PropertyGrid : TemplatedControl
{
private object? _selectedObject;
private string _searchText = string.Empty;
/// <summary>
/// Defines the SelectedObject property.
/// </summary>
public static readonly StyledProperty<object?> SelectedObjectProperty =
AvaloniaProperty.Register<PropertyGrid, object?>(nameof(SelectedObject));
/// <summary>
/// Defines the SearchText property.
/// </summary>
public static readonly StyledProperty<string> SearchTextProperty =
AvaloniaProperty.Register<PropertyGrid, string>(nameof(SearchText), string.Empty);
/// <summary>
/// Defines the ShowCategories property.
/// </summary>
public static readonly StyledProperty<bool> ShowCategoriesProperty =
AvaloniaProperty.Register<PropertyGrid, bool>(nameof(ShowCategories), true);
/// <summary>
/// Defines the ShowDescriptions property.
/// </summary>
public static readonly StyledProperty<bool> ShowDescriptionsProperty =
AvaloniaProperty.Register<PropertyGrid, bool>(nameof(ShowDescriptions), true);
/// <summary>
/// Defines the FilteredProperties property.
/// </summary>
public static readonly StyledProperty<ObservableCollection<PropertyItem>> FilteredPropertiesProperty =
AvaloniaProperty.Register<PropertyGrid, ObservableCollection<PropertyItem>>(
nameof(FilteredProperties),
new ObservableCollection<PropertyItem>());
/// <summary>
/// Defines the CategoryGroups property.
/// </summary>
public static readonly StyledProperty<ObservableCollection<CategoryGroup>> CategoryGroupsProperty =
AvaloniaProperty.Register<PropertyGrid, ObservableCollection<CategoryGroup>>(
nameof(CategoryGroups),
new ObservableCollection<CategoryGroup>());
static PropertyGrid()
{
SelectedObjectProperty.Changed.AddClassHandler<PropertyGrid>((grid, args) =>
grid.OnSelectedObjectChanged(args));
SearchTextProperty.Changed.AddClassHandler<PropertyGrid>((grid, args) =>
grid.OnSearchTextChanged(args));
}
/// <summary>
/// Initializes a new instance of the PropertyGrid class.
/// </summary>
public PropertyGrid()
{
FilteredProperties = new ObservableCollection<PropertyItem>();
}
/// <summary>
/// The object whose properties are being edited
/// </summary>
public object? SelectedObject
{
get => GetValue(SelectedObjectProperty);
set => SetValue(SelectedObjectProperty, value);
}
/// <summary>
/// The search/filter text
/// </summary>
public string SearchText
{
get => GetValue(SearchTextProperty);
set => SetValue(SearchTextProperty, value);
}
/// <summary>
/// Whether to show properties grouped by categories
/// </summary>
public bool ShowCategories
{
get => GetValue(ShowCategoriesProperty);
set => SetValue(ShowCategoriesProperty, value);
}
/// <summary>
/// Whether to show property descriptions
/// </summary>
public bool ShowDescriptions
{
get => GetValue(ShowDescriptionsProperty);
set => SetValue(ShowDescriptionsProperty, value);
}
/// <summary>
/// The filtered list of properties to display
/// </summary>
public ObservableCollection<PropertyItem> FilteredProperties
{
get => GetValue(FilteredPropertiesProperty);
set => SetValue(FilteredPropertiesProperty, value);
}
/// <summary>
/// The category groups to display
/// </summary>
public ObservableCollection<CategoryGroup> CategoryGroups
{
get => GetValue(CategoryGroupsProperty);
set => SetValue(CategoryGroupsProperty, value);
}
private List<PropertyItem> _allProperties = new();
private void OnSelectedObjectChanged(AvaloniaPropertyChangedEventArgs args)
{
_selectedObject = args.NewValue;
System.Diagnostics.Debug.WriteLine($"PropertyGrid: SelectedObject changed to {_selectedObject?.GetType().Name ?? "null"}");
RefreshProperties();
}
private void OnSearchTextChanged(AvaloniaPropertyChangedEventArgs args)
{
_searchText = args.NewValue as string ?? string.Empty;
FilterProperties();
}
/// <summary>
/// Refreshes the property list from the selected object
/// </summary>
public void RefreshProperties()
{
_allProperties.Clear();
FilteredProperties.Clear();
if (_selectedObject == null)
{
System.Diagnostics.Debug.WriteLine("PropertyGrid: RefreshProperties - SelectedObject is null");
return;
}
_allProperties = PropertyItemFactory.CreatePropertyItems(_selectedObject);
System.Diagnostics.Debug.WriteLine($"PropertyGrid: Created {_allProperties.Count} property items");
// Subscribe to property changes
foreach (var item in _allProperties)
{
item.PropertyChanged += OnPropertyItemChanged;
}
FilterProperties();
System.Diagnostics.Debug.WriteLine($"PropertyGrid: FilteredProperties count = {FilteredProperties.Count}");
}
private void OnPropertyItemChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(PropertyItem.IsExpanded) && sender is PropertyItem item)
{
HandlePropertyExpansion(item);
}
}
private void HandlePropertyExpansion(PropertyItem item)
{
System.Diagnostics.Debug.WriteLine($"HandlePropertyExpansion: {item.DisplayName}, IsExpanded={item.IsExpanded}, Children.Count={item.Children.Count}");
if (item.IsExpanded && item.Value != null)
{
// Load child properties if not already loaded
if (item.Children.Count == 0)
{
item.Children = PropertyItemFactory.CreatePropertyItems(item.Value, item.IndentLevel + 1);
System.Diagnostics.Debug.WriteLine($" Loaded {item.Children.Count} children for {item.DisplayName}");
// Subscribe to property changes for children
foreach (var child in item.Children)
{
child.PropertyChanged += OnPropertyItemChanged;
}
}
// Insert children after the parent in the filtered list
var parentIndex = FilteredProperties.IndexOf(item);
if (parentIndex >= 0)
{
int insertedCount = 0;
for (int i = 0; i < item.Children.Count; i++)
{
// Only insert if not already in the list
if (!FilteredProperties.Contains(item.Children[i]))
{
FilteredProperties.Insert(parentIndex + i + 1, item.Children[i]);
insertedCount++;
}
}
System.Diagnostics.Debug.WriteLine($" Inserted {insertedCount} children into FilteredProperties at index {parentIndex + 1}");
}
// Also insert into CategoryGroups if ShowCategories is enabled
if (ShowCategories)
{
foreach (var categoryGroup in CategoryGroups)
{
var itemIndexInCategory = categoryGroup.Properties.IndexOf(item);
if (itemIndexInCategory >= 0)
{
// Insert children after the parent in the category
for (int i = 0; i < item.Children.Count; i++)
{
if (!categoryGroup.Properties.Contains(item.Children[i]))
{
categoryGroup.Properties.Insert(itemIndexInCategory + i + 1, item.Children[i]);
}
}
System.Diagnostics.Debug.WriteLine($" Inserted {item.Children.Count} children into category '{categoryGroup.CategoryName}'");
break;
}
}
}
}
else if (!item.IsExpanded)
{
// Remove children from the filtered list (but keep them in item.Children for re-expansion)
int removedCount = 0;
foreach (var child in item.Children)
{
if (FilteredProperties.Remove(child))
{
removedCount++;
}
// If child is also expanded, recursively collapse it
if (child.IsExpanded)
{
child.IsExpanded = false;
}
}
System.Diagnostics.Debug.WriteLine($" Removed {removedCount} children from FilteredProperties");
// Also remove from CategoryGroups if ShowCategories is enabled
if (ShowCategories)
{
foreach (var categoryGroup in CategoryGroups)
{
if (categoryGroup.Properties.Contains(item))
{
int categoryRemovedCount = 0;
foreach (var child in item.Children)
{
if (categoryGroup.Properties.Remove(child))
{
categoryRemovedCount++;
}
}
System.Diagnostics.Debug.WriteLine($" Removed {categoryRemovedCount} children from category '{categoryGroup.CategoryName}'");
break;
}
}
}
}
}
private void FilterProperties()
{
FilteredProperties.Clear();
CategoryGroups.Clear();
var filtered = _allProperties.AsEnumerable();
// Apply search filter
if (!string.IsNullOrWhiteSpace(_searchText))
{
var searchLower = _searchText.ToLowerInvariant();
filtered = filtered.Where(p =>
p.DisplayName.ToLowerInvariant().Contains(searchLower) ||
(p.Category?.ToLowerInvariant().Contains(searchLower) ?? false) ||
(p.Description?.ToLowerInvariant().Contains(searchLower) ?? false));
}
// Populate flat list
foreach (var item in filtered)
{
FilteredProperties.Add(item);
}
System.Diagnostics.Debug.WriteLine($"PropertyGrid: FilterProperties - ShowCategories={ShowCategories}, filtered count={filtered.Count()}");
// Group by category for category view
if (ShowCategories)
{
var groupedByCategory = filtered
.GroupBy(p => p.Category ?? "Misc")
.OrderBy(g => g.Key);
foreach (var group in groupedByCategory)
{
var categoryGroup = new CategoryGroup
{
CategoryName = group.Key
};
foreach (var item in group.OrderBy(p => p.Order).ThenBy(p => p.DisplayName))
{
categoryGroup.Properties.Add(item);
}
CategoryGroups.Add(categoryGroup);
System.Diagnostics.Debug.WriteLine($"PropertyGrid: Added category '{categoryGroup.CategoryName}' with {categoryGroup.PropertyCount} properties");
}
System.Diagnostics.Debug.WriteLine($"PropertyGrid: Total CategoryGroups={CategoryGroups.Count}");
}
}
/// <summary>
/// Refreshes all property values from the source object
/// </summary>
public void RefreshValues()
{
foreach (var item in _allProperties)
{
item.RefreshValue();
}
}
}