207 lines
6.1 KiB
Markdown
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!
|