How do I automatically load a layout into my AvalonDock instance

Jan 29, 2013 at 11:18 PM

I have integrated AvalonDock 2.0 into my application. I've bound the document and anchor-able sources to my view-model which are rendered with the proper user controls via DataTemplates.

I can load and save layouts with the XmlLayoutSerializer. I need to support loading predefined layouts on demand (via Buttons and ICommands). That works as well.

The thing I can't get working is loading a serialized layout automatically when the DockingManager is done loading the view-models and their views (user controls).

I've tried loading my layout on DockingManager.DataContextChanged but I think it fires too early because the layout loads with the documents in the hidden section and duplicate documents in the visible sections. The rendered panes do not reflect the loaded layout and when the layout is saved again the duplicates are accumulated in the hidden section.

<ad:DockingManager Name="DockingManager"
                       DataContext="{Binding Project}"
                       DataContextChanged="DockingManager_OnDataContextChanged"
                       ActiveContent="{Binding Active}"
                       AnchorablesSource="{Binding Anchorables}"
                       DocumentsSource="{Binding Documents}">
        <ad:DockingManager.Theme>
            <ad:AeroTheme/>
        </ad:DockingManager.Theme>

        <ad:DockingManager.LayoutItemTemplateSelector>
            <views:PanesTemplateSelector>
                <views:PanesTemplateSelector.ChartTemplate>
                    <DataTemplate>
                        <views:Chart/>
                    </DataTemplate>
                </views:PanesTemplateSelector.ChartTemplate>
                <views:PanesTemplateSelector.WorkspaceTemplate>
                    <DataTemplate>
                        <Views:Workspace/>
                    </DataTemplate>
                </views:PanesTemplateSelector.WorkspaceTemplate>
                ...
            </views:PanesTemplateSelector>
        </ad:DockingManager.LayoutItemTemplateSelector>

        <ad:DockingManager.LayoutItemContainerStyle>
            <Style TargetType="{x:Type ad:LayoutItem}">
                <Setter Property="Title" Value="{Binding Model.DisplayText}"/>
                <Setter Property="ContentId" Value="{Binding Model.ContentId}"/>
            </Style>
        </ad:DockingManager.LayoutItemContainerStyle>

        <ad:LayoutRoot>
            <!--<ad:LayoutPanel>
                <ad:LayoutDocumentPane/>
                <ad:LayoutAnchorablePane/>
            </ad:LayoutPanel>-->
        </ad:LayoutRoot>
    </ad:DockingManager>

... and the code-behind ...

    private void SaveLayout() {
        if (this.DataContext == null)
            return;
        var xmlLayoutSerializer = new XmlLayoutSerializer(this.DockingManager);
        var stringBuilder = new StringBuilder();
        using (var textWriter = new StringWriter(stringBuilder))
            xmlLayoutSerializer.Serialize(textWriter);
        var serialized = stringBuilder.ToString();
        (this.DataContext as dynamic).XmlSerializedAndEscapedLayout = HttpUtility.HtmlEncode(serialized);
    }
    private void LoadLayout()
    {
        if (DataContext == null)
            return;
        var encoded = (DataContext as dynamic).XmlSerializedAndEscapedLayout;
        var window = Window.GetWindow(this);
        window.Closing += (sender, args) => SaveLayout();
        if (String.IsNullOrWhiteSpace(encoded))
            return;
        var serialized = HttpUtility.HtmlDecode(encoded);
        var xmlLayoutSerializer = new XmlLayoutSerializer(DockingManager);
        using (var stringReader = new StringReader(serialized))
            xmlLayoutSerializer.Deserialize(stringReader);
    }
    private void DockingManager_OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue != null) // A type check here would be best, I know.
            LoadLayout();
    }

... And the view-model ...

public class ProjectViewModel
    {
        public IModel Model { get; private set; }
        public String SerializedLayout { get; set; }
        public ViewModelBase Active { get; set; }
        private readonly ObservableCollection<ViewModelBase> documents;
        public ReadOnlyObservableCollection<ViewModelBase> Documents { get; private set; }
        private readonly ObservableCollection<ViewModelBase> anchorables;
        public ReadOnlyObservableCollection<ViewModelBase> Anchorables { get; private set; }

        public ProjectViewModel(String filePath, String serializedLayout)
        {
            SerializedLayout = serializedLayout;
            using (var fileStream = new FileStream(filePath, FileMode.Open))
            {
                IModelRepository modelRepository = Ioc.DependencyInjectionContainer.DefaultContainer.Resolve<IModelRepository>();
                Model = modelRepository.Load(fileStream);
            }
            documents = new ObservableCollection<ViewModelBase>();
            anchorables = new ObservableCollection<ViewModelBase>();
            documents.Add(new Workspace());
            anchorables.Add(new RiskLimitsViewModel(Model.RiskLimits));
            ...
            Documents = new ReadOnlyObservableCollection<ViewModelBase>(documents);
            Anchorables = new ReadOnlyObservableCollection<ViewModelBase>(anchorables);
        }
    }

Any insight would be greatly appreciated. Thanks!

Jan 30, 2013 at 5:17 AM
Edited Jan 30, 2013 at 5:17 AM

Figured it out myself, answered my stackoverflow question here http://stackoverflow.com/questions/14585556/how-do-i-automatically-load-a-layout-into-my-avalondock-instance/14597843#14597843