diff --git a/source/Components/AvalonDock/Layout/Serialization/AsyncXmlLayoutSerializer.cs b/source/Components/AvalonDock/Layout/Serialization/AsyncXmlLayoutSerializer.cs new file mode 100644 index 00000000..b6cb9612 --- /dev/null +++ b/source/Components/AvalonDock/Layout/Serialization/AsyncXmlLayoutSerializer.cs @@ -0,0 +1,112 @@ +/************************************************************************ + AvalonDock + + Copyright (C) 2007-2013 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at https://opensource.org/licenses/MS-PL + ************************************************************************/ + +using System; +using System.IO; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; + +namespace AvalonDock.Layout.Serialization +{ + /// Implements a layout serialization/deserialization method of the docking framework. + public class AsyncXmlLayoutSerializer : LayoutSerializerBase + { + #region Constructors + + /// + /// Class constructor from instance. + /// + /// + public AsyncXmlLayoutSerializer(DockingManager manager) + : base(manager) + { + } + + #endregion Constructors + + #region Public Methods + + /// Serialize the layout into a . + /// + public void Serialize(XmlWriter writer) + { + var serializer = new XmlSerializer(typeof(LayoutRoot)); + serializer.Serialize(writer, Manager.Layout); + } + + /// Serialize the layout into a . + /// + public void Serialize(TextWriter writer) + { + var serializer = new XmlSerializer(typeof(LayoutRoot)); + serializer.Serialize(writer, Manager.Layout); + } + + /// Serialize the layout into a . + /// + public void Serialize(Stream stream) + { + var serializer = new XmlSerializer(typeof(LayoutRoot)); + serializer.Serialize(stream, Manager.Layout); + } + + /// Serialize the layout into a file using a . + /// + public void Serialize(string filepath) + { + using (var stream = new StreamWriter(filepath)) + { + Serialize(stream); + } + } + + /// Deserialize the layout a file from a . + /// + public async Task Deserialize(System.IO.Stream stream) + { + var serializer = new XmlSerializer(typeof(LayoutRoot)); + var layout = (LayoutRoot)serializer.Deserialize(stream); + await FixupLayout(layout); + Manager.Layout = layout; + } + + /// Deserialize the layout a file from a . + /// + public async Task Deserialize(TextReader reader) + { + var serializer = new XmlSerializer(typeof(LayoutRoot)); + var layout = (LayoutRoot)serializer.Deserialize(reader); + await FixupLayout(layout); + Manager.Layout = layout; + } + + /// Deserialize the layout a file from a . + /// + public async Task Deserialize(XmlReader reader) + { + var serializer = new XmlSerializer(typeof(LayoutRoot)); + var layout = (LayoutRoot)serializer.Deserialize(reader); + await FixupLayout(layout); + Manager.Layout = layout; + } + + /// Deserialize the layout from a file using a . + /// + public async Task Deserialize(string filepath) + { + using (var stream = new StreamReader(filepath)) + { + await Deserialize(stream); + } + } + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/Serialization/LayoutRestoreEventArgs.cs b/source/Components/AvalonDock/Layout/Serialization/LayoutRestoreEventArgs.cs new file mode 100644 index 00000000..d97dbf31 --- /dev/null +++ b/source/Components/AvalonDock/Layout/Serialization/LayoutRestoreEventArgs.cs @@ -0,0 +1,76 @@ +/************************************************************************ + AvalonDock + + Copyright (C) 2007-2013 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at https://opensource.org/licenses/MS-PL + ************************************************************************/ + +using System.ComponentModel; + +namespace AvalonDock.Layout.Serialization +{ + /// + /// Implements an event that can be used to communicate between deserialization method + /// and client application that a new item (LayoutAnchorable or Document) is about to + /// be constructed and should be attached to a corresponding viewmodel. + /// + /// The client application can use this event to Cancel reloading the item or + /// attach (a viewmodel) content to the view item that is about to be reloaded and presented in the UI. + /// + /// Use the Cancel property to indicate the case in which an item should not be deserialized. + /// + public class LayoutRestoreEventArgs + { + #region constructors + + /// + /// Class constructor from and object. + /// + /// The model of the view that has been deserialized. + /// The content if it was available in previous layout. + public LayoutRestoreEventArgs(LayoutContent model, object previousContent) + { + Cancel = false; // reloading an item is not by cancelled by default + Handled = false; // an item is not handled by default + Model = model; + Content = previousContent; + } + + #endregion constructors + + #region Properties + + /// + /// Gets or sets a value indicating whether the event should be canceled. + /// + public bool Cancel + { + get; private set; + } + + /// + /// Gets or sets a value indicating whether the event should continue processing. + /// + public bool Handled + { + get; private set; + } + + /// + /// Gets the model of the view that is about to be deserialized. + /// + public LayoutContent Model + { + get; private set; + } + + /// + /// Gets/sets the content for the that is about to be deserialized. + /// + public object Content { get; set; } + + #endregion Properties + } +} \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/Serialization/LayoutSerializerBase.cs b/source/Components/AvalonDock/Layout/Serialization/LayoutSerializerBase.cs new file mode 100644 index 00000000..4ff702d5 --- /dev/null +++ b/source/Components/AvalonDock/Layout/Serialization/LayoutSerializerBase.cs @@ -0,0 +1,253 @@ +/************************************************************************ + AvalonDock + + Copyright (C) 2007-2013 Xceed Software Inc. + + This program is provided to you under the terms of the Microsoft Public + License (Ms-PL) as published at https://opensource.org/licenses/MS-PL + ************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace AvalonDock.Layout.Serialization +{ + /// Implements a base class for the layout serialization/deserialization of the docking framework. + public abstract class LayoutSerializerBase : IDisposable + { + #region Properties + + #region PreviousAnchorables + protected IEnumerable PreviousAnchorables => _previousAnchorables; + private readonly LayoutAnchorable[] _previousAnchorables = null; + + #endregion PreviousAnchorables + + #region PreviousDocuments + + protected IEnumerable PreviousDocuments => _previousDocuments; + private readonly LayoutDocument[] _previousDocuments = null; + + #endregion PreviousDocuments + + #endregion fields + + #region Constructors + + /// + /// Class constructor from instance. + /// + /// + public LayoutSerializerBase(DockingManager manager) + { + Manager = manager ?? throw new ArgumentNullException(nameof(manager)); + Manager.SuspendDocumentsSourceBinding = true; + Manager.SuspendAnchorablesSourceBinding = true; + + _previousAnchorables = Manager.Layout.Descendents().OfType().ToArray(); + _previousDocuments = Manager.Layout.Descendents().OfType().ToArray(); + _layoutRestore = new List(); + } + + #endregion Constructors + + #region Delegates + + /// + /// Method descriptor for . + /// + /// The layout serializer. + /// An instance of that allows to cancel the creation and/or the further processing. + /// + public delegate Task LayoutRestoreDelegate(object sender, LayoutRestoreEventArgs e); + + #endregion + + #region Events + + + /// + /// Raises an event when the layout serializer is about to deserialize an item to ask the + /// client application whether the item should be deserialized and re-displayed and what content + /// should be used if so. + /// + public event LayoutRestoreDelegate LayoutRestore + { + add => _layoutRestore.Add(value); + remove => _layoutRestore.Remove(value); + } + private List _layoutRestore; + + #endregion Events + + #region Properties + + /// + /// Gets the root of the docking library. + /// + public DockingManager Manager { get; } + + #endregion Properties + + #region Protected Methods + + protected virtual void Dispose() + { + Manager.SuspendDocumentsSourceBinding = false; + Manager.SuspendAnchorablesSourceBinding = false; + } + + /// + /// Fixes the reference after + /// deserializing the to point towards the matching container again. + /// + /// + /// Uses first occurance where + /// is equivalent to . + /// + /// + protected virtual async Task FixupPreviousContainerReference(LayoutRoot layoutRoot) + { + foreach (var lcToAttach in layoutRoot.Descendents() + .OfType().Where(lc => lc.PreviousContainerId != null)) + { + var paneContainerToAttach = layoutRoot.Descendents() + .OfType().FirstOrDefault(lps => lps.Id == lcToAttach.PreviousContainerId); + if (!(paneContainerToAttach is ILayoutContainer layoutContainer)) + { + throw new ArgumentException($"Unable to find a pane with id ='{lcToAttach.PreviousContainerId}'"); + } + await Application.Current.Dispatcher.InvokeAsync(() => lcToAttach.PreviousContainer = layoutContainer); + } + } + + protected virtual async Task ReapplyAnchorablesContent(LayoutRoot layout) + { + foreach (var lcToFix in layout.Descendents().OfType().Where(lc => lc.Content == null).ToArray()) + { + // Try find the content in replaced layout + LayoutAnchorable previousAchorable = null; + if (lcToFix.ContentId != null) + { + previousAchorable = _previousAnchorables.FirstOrDefault(a => a.ContentId == lcToFix.ContentId); + } + + if (_layoutRestore.Any()) + { + // Ask client application via callback if item should be deserialized + var eventArgs = new LayoutRestoreEventArgs(lcToFix, previousAchorable?.Content); + foreach (var callback in _layoutRestore) + { + await callback(this, eventArgs); + if (eventArgs.Cancel || eventArgs.Handled) + { + break; + } + } + // Close anchorable if client app decided to cancel it + if (eventArgs.Cancel) + { + await Application.Current.Dispatcher.InvokeAsync(() => lcToFix.Close()); + } + // update anchorable content if client provided content + else if (eventArgs.Content != null) + { + await Application.Current.Dispatcher.InvokeAsync(() => lcToFix.Content = eventArgs.Content); + } + // If client has not provided any content and + // has not explicitly set the content on the LayoutContent + // then hide the anchorable + else if (eventArgs.Model.Content != null) + { + await Application.Current.Dispatcher.InvokeAsync(() => lcToFix.Hide(false)); + } + } + // Ensure a previousAnchorable exists, otherwise hide this (skip) + else if (previousAchorable == null) + { + await Application.Current.Dispatcher.InvokeAsync(() => lcToFix.Hide(false)); + } + // Load content from previous anchorable + else + { + lcToFix.Content = previousAchorable.Content; + lcToFix.IconSource = previousAchorable.IconSource; + } + } + } + protected virtual async Task ReapplyDocumentsContent(LayoutRoot layout) + { + foreach (var lcToFix in layout.Descendents().OfType().Where(lc => lc.Content == null).ToArray()) + { + // Try find the content in replaced layout + LayoutDocument previousDocument = null; + if (lcToFix.ContentId != null) + { + previousDocument = _previousDocuments.FirstOrDefault(a => a.ContentId == lcToFix.ContentId); + } + + if (_layoutRestore.Any()) + { + // Ask client application via callback if item should be deserialized + var eventArgs = new LayoutRestoreEventArgs(lcToFix, previousDocument?.Content); + foreach (var callback in _layoutRestore) + { + await callback(this, eventArgs); + if (eventArgs.Cancel || eventArgs.Handled) + { + break; + } + } + // Close anchorable if client app decided to cancel it + if (eventArgs.Cancel) + { + await Application.Current.Dispatcher.InvokeAsync(() => lcToFix.Close()); + } + // update anchorable content if client provided content + else if (eventArgs.Content != null) + { + await Application.Current.Dispatcher.InvokeAsync(() => lcToFix.Content = eventArgs.Content); + } + // If client has not provided any content and + // has not explicitly set the content on the LayoutContent + // then hide the anchorable + else if (eventArgs.Model.Content != null) + { + await Application.Current.Dispatcher.InvokeAsync(() => lcToFix.Close()); + } + } + // Ensure a previousAnchorable exists, otherwise hide this (skip) + else if (previousDocument == null) + { + await Application.Current.Dispatcher.InvokeAsync(() => lcToFix.Close()); + + } + // Load content from previous anchorable + else + { + lcToFix.Content = previousDocument.Content; + lcToFix.IconSource = previousDocument.IconSource; + } + } + } + protected virtual async Task FixupLayout(LayoutRoot layout) + { + await FixupPreviousContainerReference(layout); + await ReapplyAnchorablesContent(layout); + await ReapplyDocumentsContent(layout); + + await Application.Current.Dispatcher.InvokeAsync(() => layout.CollectGarbage()); + } + + #endregion Methods + + #region IDisposable + + void IDisposable.Dispose() => this.Dispose(); + + #endregion IDisposable + } +} \ No newline at end of file