AvalonDock加载布局后PropertyGrid无法显示文档内容问题
Problem Statement
I'm evaluating AvalonDock for a new project, and I've implemented the sample from the LayoutRoot class documentation, plus added logic to save/restore the layout on app start/stop. The layout restores correctly, but after reloading the layout from file, the PropertyGrid no longer shows the content of the current document. I've tried manually calling PropertyGrid.Update() multiple times after layout reload with no luck.
- Before Save: PropertyGrid shows properties of the button in Document 1
- After Layout Reload: PropertyGrid does NOT show properties of the button in Document 1
Relevant Code
XAML Layout
<xcad:LayoutRoot x:Name="_layoutRoot" > <xcad:LayoutPanel Orientation="Horizontal"> <xcad:LayoutAnchorablePane DockWidth="200"> <xcad:LayoutAnchorable ContentId="properties" Title="Properties" CanHide="False" CanClose="False" AutoHideWidth="240" > <xctk:PropertyGrid NameColumnWidth="110" Name="pg" SelectedObject="{Binding ElementName=_layoutRoot, Path=LastFocusedDocument.Content}" /> </xcad:LayoutAnchorable> </xcad:LayoutAnchorablePane> <xcad:LayoutDocumentPaneGroup > <xcad:LayoutDocumentPane> <xcad:LayoutDocument ContentId="document1" Title="Document 1" IconSource="Images/document.png" > <Button Content="Document 1 Content" HorizontalAlignment="Center" VerticalAlignment="Center"/> </xcad:LayoutDocument> <xcad:LayoutDocument ContentId="document2" Title="Document 2" IconSource="Images/document.png"> <TextBox Text="Document 2 Content" AcceptsReturn="True"/> </xcad:LayoutDocument> </xcad:LayoutDocumentPane> </xcad:LayoutDocumentPaneGroup > <xcad:LayoutAnchorablePaneGroup DockWidth="125"> <xcad:LayoutAnchorablePane> <xcad:LayoutAnchorable ContentId="alarms" Title="Alarms" > <ListBox> <s:String>Alarm 1</s:String> <s:String>Alarm 2</s:String> <s:String>Alarm 3</s:String> </ListBox> </xcad:LayoutAnchorable> <xcad:LayoutAnchorable ContentId="journal" Title="Journal" > <RichTextBox> <FlowDocument> <Paragraph FontSize="14" FontFamily="Segoe"> This is the content of the Journal Pane. <LineBreak/> A <Bold>RichTextBox</Bold> has been added here </Paragraph> </FlowDocument> </RichTextBox> </xcad:LayoutAnchorable> </xcad:LayoutAnchorablePane> </xcad:LayoutAnchorablePaneGroup> </xcad:LayoutPanel> <xcad:LayoutRoot.LeftSide> <xcad:LayoutAnchorSide> <xcad:LayoutAnchorGroup> <xcad:LayoutAnchorable Title="Agenda" ContentId="agenda" > <TextBlock Text="Agenda Content" Margin="10" FontSize="18" FontWeight="Black" TextWrapping="Wrap"/> </xcad:LayoutAnchorable> <xcad:LayoutAnchorable Title="Contacts" ContentId="contacts" > <TextBlock Text="Contacts Content" Margin="10" FontSize="18" FontWeight="Black" TextWrapping="Wrap"/> </xcad:LayoutAnchorable> </xcad:LayoutAnchorGroup> </xcad:LayoutAnchorSide> </xcad:LayoutRoot.LeftSide> </xcad:LayoutRoot>
Layout Load Method
private void LoadDockingManagerLayout(DockingManager docManager) { string layoutFileName = System.IO.Path.Combine(DirAppData, LayoutFileName); if (System.IO.File.Exists(layoutFileName) == false) return; var layoutSerializer = new XmlLayoutSerializer(docManager); layoutSerializer.Deserialize(layoutFileName); }
Root Cause Analysis
The issue stems from how AvalonDock handles layout deserialization and binding updates:
- Instance Replacement: When you serialize the layout, AvalonDock only saves structural data (panel positions, document states, etc.)—it does not serialize the actual
Contentof yourLayoutDocumentinstances (like the Button or TextBox). During deserialization, AvalonDock creates newLayoutDocumentobjects, but the original Content isn't automatically reattached, or theLastFocusedDocumentreference doesn't update correctly. - Binding Failure: Your
SelectedObjectbinding relies on_layoutRoot.LastFocusedDocument.Content. After deserialization, either:LastFocusedDocumentpoints to a newLayoutDocumentwith no Content set, or- The
LastFocusedDocumentproperty doesn't fire aPropertyChangedevent, so the binding never updates to the new value.
- Why
Update()Doesn't Work: CallingPropertyGrid.Update()only refreshes the control's display—it doesn't fix the underlying issue where the binding source is invalid or hasn't notified the control of a change.
Solutions
1. Fix with LayoutDeserialized Event
Subscribe to the XmlLayoutSerializer.LayoutDeserialized event to manually set the PropertyGrid's SelectedObject once deserialization completes. This ensures you target the newly restored active document:
private void LoadDockingManagerLayout(DockingManager docManager) { string layoutFileName = Path.Combine(DirAppData, LayoutFileName); if (!File.Exists(layoutFileName)) return; var layoutSerializer = new XmlLayoutSerializer(docManager); // Hook into deserialization completion layoutSerializer.LayoutDeserialized += (sender, e) => { // Find the currently active document in the restored layout var activeDoc = docManager.Layout.Descendents() .OfType<LayoutDocument>() .FirstOrDefault(d => d.IsActive); if (activeDoc != null) { // Manually assign the content to the PropertyGrid pg.SelectedObject = activeDoc.Content; } }; layoutSerializer.Deserialize(layoutFileName); }
2. MVVM-Driven Approach (Recommended)
For a more robust solution, use a ViewModel to manage the selected document content. This decouples your UI from AvalonDock's internal state and ensures bindings stay reliable:
Step 1: Create a Main ViewModel
public class MainViewModel : INotifyPropertyChanged { private object _selectedDocumentContent; public object SelectedDocumentContent { get => _selectedDocumentContent; set { if (_selectedDocumentContent != value) { _selectedDocumentContent = value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); } }
Step 2: Update XAML Binding
Set the window's DataContext and bind the PropertyGrid to the ViewModel property:
<Window.DataContext> <local:MainViewModel /> </Window.DataContext> <!-- Inside your LayoutAnchorable for Properties --> <xctk:PropertyGrid NameColumnWidth="110" Name="pg" SelectedObject="{Binding SelectedDocumentContent}" />
Step 3: Sync with AvalonDock's Active Content
Subscribe to LayoutRoot.ActiveContentChanged to update the ViewModel when the active document changes:
public MainWindow() { InitializeComponent(); var vm = (MainViewModel)DataContext; // Update ViewModel when active document changes _layoutRoot.ActiveContentChanged += (s, e) => { if (_layoutRoot.LastFocusedDocument != null) { vm.SelectedDocumentContent = _layoutRoot.LastFocusedDocument.Content; } }; }
Step 4: Refresh After Deserialization
Add the same LayoutDeserialized logic to update the ViewModel after layout reload:
layoutSerializer.LayoutDeserialized += (sender, e) => { var vm = (MainViewModel)DataContext; var activeDoc = docManager.Layout.Descendents() .OfType<LayoutDocument>() .FirstOrDefault(d => d.IsActive); if (activeDoc != null) { vm.SelectedDocumentContent = activeDoc.Content; } };
3. Force Binding Update (Quick Fix)
If you prefer not to use MVVM, you can force the binding to refresh after deserialization. Note that this is less reliable than the other methods:
layoutSerializer.Deserialize(layoutFileName); // Force the PropertyGrid binding to update BindingOperations.GetBindingExpression(pg, PropertyGrid.SelectedObjectProperty)?.UpdateTarget();
内容的提问来源于stack exchange,提问作者Kieran

