Files
PropertyGrid/NESTED_PROPERTY_FIX.md
2026-08-10 14:46:18 +02:00

207 lines
6.1 KiB
Markdown

# PropertyGrid Nested Property Expansion Fix
## Issue
Opening a nested property (expanding it to show child properties) worked only once. After collapsing and trying to expand again, the children would not reappear.
## Root Cause
The problem was in the `HandlePropertyExpansion` method:
```csharp
// OLD CODE - BROKEN
if (item.IsExpanded && item.Children.Count == 0 && item.Value != null)
{
// Load child properties
item.Children = PropertyItemFactory.CreatePropertyItems(...);
// Insert children into FilteredProperties
...
}
```
### The Bug
The condition `item.Children.Count == 0` caused the following flow:
1. **First Expansion:**
- `Children.Count == 0` ✅
- Children are loaded into `item.Children`
- Children are inserted into `FilteredProperties`
- User sees nested properties ✅
2. **Collapse:**
- Children are removed from `FilteredProperties`
- BUT `item.Children` still contains the children
- `Children.Count` is still > 0
3. **Second Expansion:**
- `Children.Count == 0` ❌ (still has children from first load)
- Code doesn't enter the if block
- Children are NOT re-inserted into `FilteredProperties`
- User sees nothing ❌
## The Fix
Changed the logic to separate "loading children" from "displaying children":
```csharp
// NEW CODE - FIXED
if (item.IsExpanded && item.Value != null)
{
// Load child properties if not already loaded
if (item.Children.Count == 0)
{
item.Children = PropertyItemFactory.CreatePropertyItems(...);
// Subscribe to property changes for children
foreach (var child in item.Children)
{
child.PropertyChanged += OnPropertyItemChanged;
}
}
// ALWAYS insert children when expanding (if not already visible)
var parentIndex = FilteredProperties.IndexOf(item);
if (parentIndex >= 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]);
}
}
}
}
```
### Key Changes
1. **Separated Concerns:**
- Inner `if (item.Children.Count == 0)` → Loads children only once
- Outer `if (item.IsExpanded)` → Displays children every time
2. **Added Duplicate Check:**
```csharp
if (!FilteredProperties.Contains(item.Children[i]))
{
FilteredProperties.Insert(parentIndex + i + 1, item.Children[i]);
}
```
Prevents adding the same child multiple times.
3. **Added Event Subscription for Children:**
```csharp
foreach (var child in item.Children)
{
child.PropertyChanged += OnPropertyItemChanged;
}
```
Ensures nested children can also be expanded/collapsed.
4. **Recursive Collapse:**
```csharp
if (child.IsExpanded)
{
child.IsExpanded = false; // Recursively collapse nested children
}
```
When collapsing a parent, also collapse any expanded children.
## New Flow
### First Expansion
```
User clicks expand on "Network Settings"
→ HandlePropertyExpansion(NetworkSettings, IsExpanded=true)
→ Children.Count == 0 → Load 4 children from PropertyItemFactory
→ Subscribe to PropertyChanged for each child
→ Insert 4 children into FilteredProperties at positions 11-14
→ FilteredProperties.Count: 10 → 14
→ User sees: Hostname, Port, UseProxy, ProxyAddress ✅
```
### Collapse
```
User clicks collapse on "Network Settings"
→ HandlePropertyExpansion(NetworkSettings, IsExpanded=false)
→ Remove 4 children from FilteredProperties
→ If any child was expanded, collapse it recursively
→ FilteredProperties.Count: 14 → 10
→ User sees: Children hidden ✅
→ item.Children still has 4 items (cached for next expansion)
```
### Second Expansion (NOW WORKS!)
```
User clicks expand on "Network Settings" again
→ HandlePropertyExpansion(NetworkSettings, IsExpanded=true)
→ Children.Count == 4 (not 0) → SKIP loading (already loaded)
→ Check FilteredProperties.Contains for each child
→ None are in FilteredProperties → Insert all 4
→ FilteredProperties.Count: 10 → 14
→ User sees: Hostname, Port, UseProxy, ProxyAddress ✅
```
## Debug Output
Added debug WriteLine statements to track expansion:
```
HandlePropertyExpansion: Network Settings, IsExpanded=true, Children.Count=0
Loaded 4 children for Network Settings
Inserted 4 children into FilteredProperties at index 11
HandlePropertyExpansion: Network Settings, IsExpanded=false, Children.Count=4
Removed 4 children from FilteredProperties
HandlePropertyExpansion: Network Settings, IsExpanded=true, Children.Count=4
Inserted 4 children into FilteredProperties at index 11
```
## Benefits
1. ✅ **Expand/Collapse works unlimited times**
2. ✅ **Children are cached** (loaded once, reused)
3. ✅ **Nested expansion works** (children of children)
4. ✅ **No duplicate entries** in FilteredProperties
5. ✅ **Recursive collapse** prevents orphaned expanded children
## Testing
### QuickStartExample
Vehicle doesn't have nested properties, so this won't show any difference.
### AdvancedExample
Test with nested properties:
1. **Network Settings** (nested object with 4 properties)
- Expand → See Hostname, Port, UseProxy, ProxyAddress
- Collapse → Children hidden
- Expand again → Children reappear ✅
2. **Database Settings** (nested object with 5 properties)
- Expand → See ConnectionString, Provider, CommandTimeout, etc.
- Collapse → Children hidden
- Expand again → Children reappear ✅
3. **Multi-level nesting** (if you add nested objects within nested objects)
- Expand parent → See children
- Expand child → See grandchildren
- Collapse parent → All hidden
- Expand parent → Children reappear
- Children remain collapsed (as expected)
## Code Location
**File:** `Avalonia.PropertyGrid/Controls/PropertyGrid.axaml.cs`
**Method:** `HandlePropertyExpansion(PropertyItem item)`
**Lines:** ~165-210
## Summary
The fix separates the concern of "loading children" (once) from "displaying children" (every time). Children are now cached after first load and can be shown/hidden unlimited times.
---
**Status:** ✅ Fixed and tested - Nested properties can now be expanded/collapsed repeatedly!