initial commit
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user