You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AvalonDock加载布局后PropertyGrid无法显示文档内容问题

PropertyGrid Not Displaying Document Content After Loading AvalonDock Layout

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:

  1. Instance Replacement: When you serialize the layout, AvalonDock only saves structural data (panel positions, document states, etc.)—it does not serialize the actual Content of your LayoutDocument instances (like the Button or TextBox). During deserialization, AvalonDock creates new LayoutDocument objects, but the original Content isn't automatically reattached, or the LastFocusedDocument reference doesn't update correctly.
  2. Binding Failure: Your SelectedObject binding relies on _layoutRoot.LastFocusedDocument.Content. After deserialization, either:
    • LastFocusedDocument points to a new LayoutDocument with no Content set, or
    • The LastFocusedDocument property doesn't fire a PropertyChanged event, so the binding never updates to the new value.
  3. Why Update() Doesn't Work: Calling PropertyGrid.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:44:57