6.0 KiB
6.0 KiB
PropertyGrid Binding Fix - Direct SelectedObject Assignment
Issue
The PropertyGrid was still not displaying properties even after adding property editors.
Root Cause Analysis
The binding SelectedObject="{Binding}" in XAML might not be working correctly due to:
- Compiled Bindings - The Window has
x:DataType="models:Vehicle"which enables compiled bindings, but the PropertyGrid is a custom control and the binding might not propagate correctly through the visual tree - Timing Issues - The PropertyGrid might not be receiving the DataContext at the right time during initialization
- Template Application - Custom controls with templates might have timing issues with property bindings
Solution
Set the SelectedObject property directly in code-behind after the window opens, bypassing the XAML binding entirely.
Implementation
QuickStartExample/MainWindow.axaml.cs
public MainWindow()
{
InitializeComponent();
var vehicle = new Vehicle();
DataContext = vehicle; // Still set for other potential bindings
// Find the PropertyGrid and set SelectedObject directly after window opens
this.Opened += (s, e) =>
{
var propertyGrid = this.FindDescendantOfType<Avalonia.PropertyGrid.Controls.PropertyGrid>();
if (propertyGrid != null)
{
propertyGrid.SelectedObject = vehicle; // Direct assignment!
System.Diagnostics.Debug.WriteLine("PropertyGrid found and SelectedObject set directly");
}
else
{
System.Diagnostics.Debug.WriteLine("PropertyGrid NOT found!");
}
};
}
Helper Extension Method
public static class ControlExtensions
{
public static T? FindDescendantOfType<T>(this Control control) where T : class
{
if (control is T result)
return result;
foreach (var child in control.GetLogicalChildren())
{
if (child is Control childControl)
{
var found = FindDescendantOfType<T>(childControl);
if (found != null)
return found;
}
}
return null;
}
}
Why This Works
1. Window.Opened Event
- Fires after the window is fully initialized
- Ensures all controls are created and templates are applied
- PropertyGrid's template has been loaded and it's ready to receive data
2. Direct Property Assignment
- Bypasses any XAML binding issues
- Guarantees the property is set with the correct object reference
- No ambiguity about timing or binding context
###3. FindDescendantOfType Helper
- Recursively searches the logical tree for the PropertyGrid
- Works even with complex nested layouts
- Returns the PropertyGrid instance so we can set properties directly
Debug Output
Added debug WriteLine statements to track:
- When SelectedObject changes in PropertyGrid
- How many properties are created by PropertyItemFactory
- How many items are in FilteredProperties after filtering
- Whether the PropertyGrid is found in the visual tree
Expected Flow
-
Window Initialization
MainWindow() constructor called → DataContext set to Vehicle/AdvancedConfiguration → InitializeComponent() builds UI -
Window Opened Event
Window.Opened fires → FindDescendantOfType searches for PropertyGrid → PropertyGrid found → SelectedObject set directly to vehicle/config object -
PropertyGrid Receives Object
Debug: "PropertyGrid: SelectedObject changed to Vehicle" → OnSelectedObjectChanged() fires → RefreshProperties() called -
Properties Created
Debug: "PropertyGrid: Created 10 property items" (for Vehicle) → PropertyItemFactory creates PropertyItems for each public property → FilterProperties() populates FilteredProperties Debug: "PropertyGrid: FilteredProperties count = 10" -
UI Updates
ItemsControl.ItemsSource = FilteredProperties → DataTemplates render each PropertyItem → Property editors display with correct visibility → User sees the properties!
Verification Steps
Run QuickStartExample
dotnet run --project Examples/QuickStartExample/QuickStartExample.csproj
Check Debug Output (should see):
PropertyGrid found and SelectedObject set directly
PropertyGrid: SelectedObject changed to Vehicle
PropertyGrid: Created 10 property items
PropertyGrid: FilteredProperties count = 10
Visual Result:
- Make, Model (TextBox)
- Year (TextBox with number)
- Color (TextBox)
- Mileage (TextBox with decimal)
- IsElectric (CheckBox)
- FuelType (ComboBox dropdown)
- Price (TextBox with decimal)
- PurchaseDate (TextBox with date)
- VIN (TextBox, disabled/read-only)
Run AdvancedExample
dotnet run --project Examples/AdvancedExample/AdvancedExample.csproj
Check Debug Output (should see):
AdvancedExample: PropertyGrid found and SelectedObject set directly
PropertyGrid: SelectedObject changed to AdvancedConfiguration
PropertyGrid: Created 25+ property items
PropertyGrid: FilteredProperties count = 25+
Alternative Approaches (if this doesn't work)
Option 1: Named PropertyGrid
<pg:PropertyGrid x:Name="MyPropertyGrid" ... />
public MainWindow()
{
InitializeComponent();
var vehicle = new Vehicle();
this.Opened += (s, e) => MyPropertyGrid.SelectedObject = vehicle;
}
Option 2: RelativeSource Binding
<pg:PropertyGrid SelectedObject="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext}" />
Option 3: Property in Window
public Vehicle MyVehicle { get; set; } = new Vehicle();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
<pg:PropertyGrid SelectedObject="{Binding MyVehicle}" />
Files Modified
Examples/QuickStartExample/MainWindow.axaml.cs- Direct SelectedObject assignmentExamples/AdvancedExample/MainWindow.axaml.cs- Direct SelectedObject assignmentAvalonia.PropertyGrid/Controls/PropertyGrid.axaml.cs- Added debug output
Next Steps
Run the examples and check the debug output to confirm properties are being loaded and displayed.
Status: ✅ Built successfully with direct property assignment approach