From 8646d520d37935c81f5a163121bd9a3f97d44c50 Mon Sep 17 00:00:00 2001 From: radvile Date: Fri, 12 Nov 2021 12:26:21 +0200 Subject: [PATCH 1/3] Changed serialization/deserialization. --- .../Layout/LayoutAnchorableFloatingWindow.cs | 390 ++-- .../Layout/LayoutDocumentFloatingWindow.cs | 360 +-- .../AvalonDock/Layout/LayoutFloatingWindow.cs | 144 +- .../AvalonDock/Layout/LayoutGroup.cs | 534 +++-- .../AvalonDock/Layout/LayoutRoot.cs | 1975 ++++++++--------- .../Layout/Serialization/LayoutSerializer.cs | 293 +-- .../Serialization/XmlLayoutSerializer.cs | 251 +-- 7 files changed, 1963 insertions(+), 1984 deletions(-) diff --git a/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs b/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs index 7db59a02..4f148b4c 100644 --- a/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs +++ b/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs @@ -1,196 +1,196 @@ -/************************************************************************ - 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.Diagnostics; -using System.Linq; -using System.Windows.Markup; -using System.Xml; -using System.Xml.Serialization; - -namespace AvalonDock.Layout -{ - /// Implements the model for a floating window control that can host an anchorable control (tool window) in a floating window. - [Serializable] - [ContentProperty(nameof(RootPanel))] - public class LayoutAnchorableFloatingWindow : LayoutFloatingWindow, ILayoutElementWithVisibility - { - #region fields - - private LayoutAnchorablePaneGroup _rootPanel; - - [NonSerialized] - private bool _isVisible = true; - - #endregion fields - - #region Events - - /// Event is invoked when the visibility of this object has changed. - public event EventHandler IsVisibleChanged; - - #endregion Events - - #region Properties - - public bool IsSinglePane => RootPanel != null && RootPanel.Descendents().OfType().Count(p => p.IsVisible) == 1; - - /// Gets/sets whether this object is in a state where it is visible in the UI or not. - [XmlIgnore] - public bool IsVisible - { - get => _isVisible; - private set - { - if (value == _isVisible) return; - RaisePropertyChanging(nameof(IsVisible)); - _isVisible = value; - RaisePropertyChanged(nameof(IsVisible)); - IsVisibleChanged?.Invoke(this, EventArgs.Empty); - } - } - - public LayoutAnchorablePaneGroup RootPanel - { - get => _rootPanel; - set - { - if (value == _rootPanel) return; - RaisePropertyChanging(nameof(RootPanel)); - if (_rootPanel != null) _rootPanel.ChildrenTreeChanged -= _rootPanel_ChildrenTreeChanged; - _rootPanel = value; - if (_rootPanel != null) - { - _rootPanel.Parent = this; - _rootPanel.ChildrenTreeChanged += _rootPanel_ChildrenTreeChanged; - } - - RaisePropertyChanged(nameof(RootPanel)); - RaisePropertyChanged(nameof(IsSinglePane)); - RaisePropertyChanged(nameof(SinglePane)); - RaisePropertyChanged(nameof(Children)); - RaisePropertyChanged(nameof(ChildrenCount)); - ((ILayoutElementWithVisibility)this).ComputeVisibility(); - } - } - - public ILayoutAnchorablePane SinglePane - { - get - { - if (!IsSinglePane) return null; - var singlePane = RootPanel.Descendents().OfType().Single(p => p.IsVisible); - singlePane.UpdateIsDirectlyHostedInFloatingWindow(); - return singlePane; - } - } - - #endregion Properties - - #region ILayoutElementWithVisibility Interface - - /// - void ILayoutElementWithVisibility.ComputeVisibility() => ComputeVisibility(); - - #endregion ILayoutElementWithVisibility Interface - - #region Overrides - - /// - public override IEnumerable Children - { - get { if (ChildrenCount == 1) yield return RootPanel; } - } - - /// - public override void RemoveChild(ILayoutElement element) - { - Debug.Assert(element == RootPanel && element != null); - RootPanel = null; - } - - /// - public override void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) - { - Debug.Assert(oldElement == RootPanel && oldElement != null); - RootPanel = newElement as LayoutAnchorablePaneGroup; - } - - /// - public override int ChildrenCount => RootPanel == null ? 0 : 1; - - /// - public override bool IsValid => RootPanel != null; - - /// - public override void ReadXml(XmlReader reader) - { - reader.MoveToContent(); - if (reader.IsEmptyElement) - { - reader.Read(); - ComputeVisibility(); - return; - } - - var localName = reader.LocalName; - reader.Read(); - - while (true) - { - if (reader.LocalName.Equals(localName) && reader.NodeType == XmlNodeType.EndElement) break; - - if (reader.NodeType == XmlNodeType.Whitespace) - { - reader.Read(); - continue; - } - - XmlSerializer serializer; - if (reader.LocalName.Equals(nameof(LayoutAnchorablePaneGroup))) - serializer = new XmlSerializer(typeof(LayoutAnchorablePaneGroup)); - else - { - var type = LayoutRoot.FindType(reader.LocalName); - if (type == null) - throw new ArgumentException("AvalonDock.LayoutAnchorableFloatingWindow doesn't know how to deserialize " + reader.LocalName); - serializer = new XmlSerializer(type); - } - RootPanel = (LayoutAnchorablePaneGroup)serializer.Deserialize(reader); - } - reader.ReadEndElement(); - } - -#if TRACE - /// - public override void ConsoleDump(int tab) - { - System.Diagnostics.Trace.Write(new string(' ', tab * 4)); - System.Diagnostics.Trace.WriteLine("FloatingAnchorableWindow()"); - - RootPanel.ConsoleDump(tab + 1); - } -#endif - - #endregion Overrides - - #region Private Methods - - private void _rootPanel_ChildrenTreeChanged(object sender, ChildrenTreeChangedEventArgs e) - { - RaisePropertyChanged(nameof(IsSinglePane)); - RaisePropertyChanged(nameof(SinglePane)); - } - - private void ComputeVisibility() => IsVisible = RootPanel != null && RootPanel.IsVisible; - - #endregion Private Methods - } +/************************************************************************ + 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.Diagnostics; +using System.Linq; +using System.Windows.Markup; +using System.Xml; +using System.Xml.Serialization; + +namespace AvalonDock.Layout +{ + /// Implements the model for a floating window control that can host an anchorable control (tool window) in a floating window. + [Serializable] + [ContentProperty(nameof(RootPanel))] + public class LayoutAnchorableFloatingWindow : LayoutFloatingWindow, ILayoutElementWithVisibility + { + #region fields + + private LayoutAnchorablePaneGroup _rootPanel; + + [NonSerialized] + private bool _isVisible = true; + + #endregion fields + + #region Events + + /// Event is invoked when the visibility of this object has changed. + public event EventHandler IsVisibleChanged; + + #endregion Events + + #region Properties + + public bool IsSinglePane => RootPanel != null && RootPanel.Descendents().OfType().Count(p => p.IsVisible) == 1; + + /// Gets/sets whether this object is in a state where it is visible in the UI or not. + [XmlIgnore] + public bool IsVisible + { + get => _isVisible; + private set + { + if (value == _isVisible) return; + RaisePropertyChanging(nameof(IsVisible)); + _isVisible = value; + RaisePropertyChanged(nameof(IsVisible)); + IsVisibleChanged?.Invoke(this, EventArgs.Empty); + } + } + + public LayoutAnchorablePaneGroup RootPanel + { + get => _rootPanel; + set + { + if (value == _rootPanel) return; + RaisePropertyChanging(nameof(RootPanel)); + if (_rootPanel != null) _rootPanel.ChildrenTreeChanged -= _rootPanel_ChildrenTreeChanged; + _rootPanel = value; + if (_rootPanel != null) + { + _rootPanel.Parent = this; + _rootPanel.ChildrenTreeChanged += _rootPanel_ChildrenTreeChanged; + } + + RaisePropertyChanged(nameof(RootPanel)); + RaisePropertyChanged(nameof(IsSinglePane)); + RaisePropertyChanged(nameof(SinglePane)); + RaisePropertyChanged(nameof(Children)); + RaisePropertyChanged(nameof(ChildrenCount)); + ((ILayoutElementWithVisibility)this).ComputeVisibility(); + } + } + + public ILayoutAnchorablePane SinglePane + { + get + { + if (!IsSinglePane) return null; + var singlePane = RootPanel.Descendents().OfType().Single(p => p.IsVisible); + singlePane.UpdateIsDirectlyHostedInFloatingWindow(); + return singlePane; + } + } + + #endregion Properties + + #region ILayoutElementWithVisibility Interface + + /// + void ILayoutElementWithVisibility.ComputeVisibility() => ComputeVisibility(); + + #endregion ILayoutElementWithVisibility Interface + + #region Overrides + + /// + public override IEnumerable Children + { + get { if (ChildrenCount == 1) yield return RootPanel; } + } + + /// + public override void RemoveChild(ILayoutElement element) + { + Debug.Assert(element == RootPanel && element != null); + RootPanel = null; + } + + /// + public override void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) + { + Debug.Assert(oldElement == RootPanel && oldElement != null); + RootPanel = newElement as LayoutAnchorablePaneGroup; + } + + /// + public override int ChildrenCount => RootPanel == null ? 0 : 1; + + /// + public override bool IsValid => RootPanel != null; + + /// + public override void ReadXml(XmlReader reader) + { + reader.MoveToContent(); + if (reader.IsEmptyElement) + { + reader.Read(); + ComputeVisibility(); + return; + } + + var localName = reader.LocalName; + reader.Read(); + + while (true) + { + if (reader.LocalName.Equals(localName) && reader.NodeType == XmlNodeType.EndElement) break; + + if (reader.NodeType == XmlNodeType.Whitespace) + { + reader.Read(); + continue; + } + + XmlSerializer serializer; + if (reader.LocalName.Equals(nameof(LayoutAnchorablePaneGroup))) + serializer = XmlSerializer.FromTypes(new[] { typeof(LayoutAnchorablePaneGroup) })[0]; + else + { + var type = LayoutRoot.FindType(reader.LocalName); + if (type == null) + throw new ArgumentException("AvalonDock.LayoutAnchorableFloatingWindow doesn't know how to deserialize " + reader.LocalName); + serializer = XmlSerializer.FromTypes(new[] { type })[0]; + } + RootPanel = (LayoutAnchorablePaneGroup)serializer.Deserialize(reader); + } + reader.ReadEndElement(); + } + +#if TRACE + /// + public override void ConsoleDump(int tab) + { + System.Diagnostics.Trace.Write(new string(' ', tab * 4)); + System.Diagnostics.Trace.WriteLine("FloatingAnchorableWindow()"); + + RootPanel.ConsoleDump(tab + 1); + } +#endif + + #endregion Overrides + + #region Private Methods + + private void _rootPanel_ChildrenTreeChanged(object sender, ChildrenTreeChangedEventArgs e) + { + RaisePropertyChanged(nameof(IsSinglePane)); + RaisePropertyChanged(nameof(SinglePane)); + } + + private void ComputeVisibility() => IsVisible = RootPanel != null && RootPanel.IsVisible; + + #endregion Private Methods + } } \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/LayoutDocumentFloatingWindow.cs b/source/Components/AvalonDock/Layout/LayoutDocumentFloatingWindow.cs index 68d07d6a..fa39b7e0 100644 --- a/source/Components/AvalonDock/Layout/LayoutDocumentFloatingWindow.cs +++ b/source/Components/AvalonDock/Layout/LayoutDocumentFloatingWindow.cs @@ -1,181 +1,181 @@ -/************************************************************************ - 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.Diagnostics; -using System.Linq; -using System.Windows.Markup; -using System.Xml; -using System.Xml.Serialization; - -namespace AvalonDock.Layout -{ - /// Implements the layout model for the . - [ContentProperty(nameof(RootPanel))] - [Serializable] - public class LayoutDocumentFloatingWindow : LayoutFloatingWindow, ILayoutElementWithVisibility - { - #region fields - - private LayoutDocumentPaneGroup _rootPanel = null; - - [NonSerialized] - private bool _isVisible = true; - - #endregion fields - - public event EventHandler IsVisibleChanged; - - #region Properties - - #region RootPanel - - public LayoutDocumentPaneGroup RootPanel - { - get => _rootPanel; - set - { - if (_rootPanel == value) return; - if (_rootPanel != null) _rootPanel.ChildrenTreeChanged -= _rootPanel_ChildrenTreeChanged; - - _rootPanel = value; - if (_rootPanel != null) _rootPanel.Parent = this; - if (_rootPanel != null) _rootPanel.ChildrenTreeChanged += _rootPanel_ChildrenTreeChanged; - - RaisePropertyChanged(nameof(RootPanel)); - RaisePropertyChanged(nameof(IsSinglePane)); - RaisePropertyChanged(nameof(SinglePane)); - RaisePropertyChanged(nameof(Children)); - RaisePropertyChanged(nameof(ChildrenCount)); - ((ILayoutElementWithVisibility)this).ComputeVisibility(); - } - } - - private void _rootPanel_ChildrenTreeChanged(object sender, ChildrenTreeChangedEventArgs e) - { - RaisePropertyChanged(nameof(IsSinglePane)); - RaisePropertyChanged(nameof(SinglePane)); - } - - #endregion RootPanel - - #region IsSinglePane - - public bool IsSinglePane => RootPanel?.Descendents().OfType().Count(p => p.IsVisible) == 1; - - public LayoutDocumentPane SinglePane - { - get - { - if (!IsSinglePane) return null; - var singlePane = RootPanel.Descendents().OfType().Single(p => p.IsVisible); - //singlePane.UpdateIsDirectlyHostedInFloatingWindow(); - return singlePane; - } - } - - #endregion IsSinglePane - - [XmlIgnore] - public bool IsVisible - { - get => _isVisible; - private set - { - if (_isVisible == value) return; - RaisePropertyChanging(nameof(IsVisible)); - _isVisible = value; - RaisePropertyChanged(nameof(IsVisible)); - IsVisibleChanged?.Invoke(this, EventArgs.Empty); - } - } - - #endregion Properties - - #region Overrides - - /// - public override IEnumerable Children - { - get { if (ChildrenCount == 1) yield return RootPanel; } - } - - /// - public override void RemoveChild(ILayoutElement element) - { - Debug.Assert(element == RootPanel && element != null); - RootPanel = null; - } - - /// - public override void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) - { - Debug.Assert(oldElement == RootPanel && oldElement != null); - RootPanel = newElement as LayoutDocumentPaneGroup; - } - - /// - public override int ChildrenCount => RootPanel == null ? 0 : 1; - - void ILayoutElementWithVisibility.ComputeVisibility() => IsVisible = RootPanel != null && RootPanel.IsVisible; - - /// - public override bool IsValid => RootPanel != null; - - /// - public override void ReadXml(XmlReader reader) - { - reader.MoveToContent(); - if (reader.IsEmptyElement) - { - reader.Read(); - return; - } - - var localName = reader.LocalName; - reader.Read(); - - while (true) - { - if (reader.LocalName.Equals(localName) && reader.NodeType == XmlNodeType.EndElement) break; - if (reader.NodeType == XmlNodeType.Whitespace) - { - reader.Read(); - continue; - } - - XmlSerializer serializer; - if (reader.LocalName.Equals(nameof(LayoutDocument))) - serializer = new XmlSerializer(typeof(LayoutDocument)); - else - { - var type = LayoutRoot.FindType(reader.LocalName); - if (type == null) throw new ArgumentException("AvalonDock.LayoutDocumentFloatingWindow doesn't know how to deserialize " + reader.LocalName); - serializer = new XmlSerializer(type); - } - RootPanel = (LayoutDocumentPaneGroup)serializer.Deserialize(reader); - } - - reader.ReadEndElement(); - } - -#if TRACE - public override void ConsoleDump(int tab) - { - System.Diagnostics.Trace.Write(new string(' ', tab * 4)); - System.Diagnostics.Trace.WriteLine("FloatingDocumentWindow()"); - - RootPanel.ConsoleDump(tab + 1); - } -#endif - - #endregion Overrides - } +/************************************************************************ + 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.Diagnostics; +using System.Linq; +using System.Windows.Markup; +using System.Xml; +using System.Xml.Serialization; + +namespace AvalonDock.Layout +{ + /// Implements the layout model for the . + [ContentProperty(nameof(RootPanel))] + [Serializable] + public class LayoutDocumentFloatingWindow : LayoutFloatingWindow, ILayoutElementWithVisibility + { + #region fields + + private LayoutDocumentPaneGroup _rootPanel = null; + + [NonSerialized] + private bool _isVisible = true; + + #endregion fields + + public event EventHandler IsVisibleChanged; + + #region Properties + + #region RootPanel + + public LayoutDocumentPaneGroup RootPanel + { + get => _rootPanel; + set + { + if (_rootPanel == value) return; + if (_rootPanel != null) _rootPanel.ChildrenTreeChanged -= _rootPanel_ChildrenTreeChanged; + + _rootPanel = value; + if (_rootPanel != null) _rootPanel.Parent = this; + if (_rootPanel != null) _rootPanel.ChildrenTreeChanged += _rootPanel_ChildrenTreeChanged; + + RaisePropertyChanged(nameof(RootPanel)); + RaisePropertyChanged(nameof(IsSinglePane)); + RaisePropertyChanged(nameof(SinglePane)); + RaisePropertyChanged(nameof(Children)); + RaisePropertyChanged(nameof(ChildrenCount)); + ((ILayoutElementWithVisibility)this).ComputeVisibility(); + } + } + + private void _rootPanel_ChildrenTreeChanged(object sender, ChildrenTreeChangedEventArgs e) + { + RaisePropertyChanged(nameof(IsSinglePane)); + RaisePropertyChanged(nameof(SinglePane)); + } + + #endregion RootPanel + + #region IsSinglePane + + public bool IsSinglePane => RootPanel?.Descendents().OfType().Count(p => p.IsVisible) == 1; + + public LayoutDocumentPane SinglePane + { + get + { + if (!IsSinglePane) return null; + var singlePane = RootPanel.Descendents().OfType().Single(p => p.IsVisible); + //singlePane.UpdateIsDirectlyHostedInFloatingWindow(); + return singlePane; + } + } + + #endregion IsSinglePane + + [XmlIgnore] + public bool IsVisible + { + get => _isVisible; + private set + { + if (_isVisible == value) return; + RaisePropertyChanging(nameof(IsVisible)); + _isVisible = value; + RaisePropertyChanged(nameof(IsVisible)); + IsVisibleChanged?.Invoke(this, EventArgs.Empty); + } + } + + #endregion Properties + + #region Overrides + + /// + public override IEnumerable Children + { + get { if (ChildrenCount == 1) yield return RootPanel; } + } + + /// + public override void RemoveChild(ILayoutElement element) + { + Debug.Assert(element == RootPanel && element != null); + RootPanel = null; + } + + /// + public override void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) + { + Debug.Assert(oldElement == RootPanel && oldElement != null); + RootPanel = newElement as LayoutDocumentPaneGroup; + } + + /// + public override int ChildrenCount => RootPanel == null ? 0 : 1; + + void ILayoutElementWithVisibility.ComputeVisibility() => IsVisible = RootPanel != null && RootPanel.IsVisible; + + /// + public override bool IsValid => RootPanel != null; + + /// + public override void ReadXml(XmlReader reader) + { + reader.MoveToContent(); + if (reader.IsEmptyElement) + { + reader.Read(); + return; + } + + var localName = reader.LocalName; + reader.Read(); + + while (true) + { + if (reader.LocalName.Equals(localName) && reader.NodeType == XmlNodeType.EndElement) break; + if (reader.NodeType == XmlNodeType.Whitespace) + { + reader.Read(); + continue; + } + + XmlSerializer serializer; + if (reader.LocalName.Equals(nameof(LayoutDocument))) + serializer = XmlSerializer.FromTypes(new[] { typeof(LayoutDocument) })[0]; + else + { + var type = LayoutRoot.FindType(reader.LocalName); + if (type == null) throw new ArgumentException("AvalonDock.LayoutDocumentFloatingWindow doesn't know how to deserialize " + reader.LocalName); + serializer = XmlSerializer.FromTypes(new[] { type })[0]; + } + RootPanel = (LayoutDocumentPaneGroup)serializer.Deserialize(reader); + } + + reader.ReadEndElement(); + } + +#if TRACE + public override void ConsoleDump(int tab) + { + System.Diagnostics.Trace.Write(new string(' ', tab * 4)); + System.Diagnostics.Trace.WriteLine("FloatingDocumentWindow()"); + + RootPanel.ConsoleDump(tab + 1); + } +#endif + + #endregion Overrides + } } \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs b/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs index 0a479b47..72467ad8 100644 --- a/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs +++ b/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs @@ -1,73 +1,73 @@ -/************************************************************************ - 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.Xml; -using System.Xml.Schema; -using System.Xml.Serialization; - -namespace AvalonDock.Layout -{ - /// - /// Provides an abstract class to implement a concrete floating window layout model. - /// - [Serializable] - public abstract class LayoutFloatingWindow : LayoutElement, ILayoutContainer, IXmlSerializable - { - #region Properties - - /// Gets the list of based children below this object. - public abstract IEnumerable Children { get; } - - /// Gets the number of children below this object. - public abstract int ChildrenCount { get; } - - public abstract bool IsValid { get; } - - #endregion Properties - - #region Public Methods - - /// Remove the child element from the collection of children. - /// - public abstract void RemoveChild(ILayoutElement element); - - /// Replace the child element with a new child in the collection of children. - /// - /// - public abstract void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement); - - #region IXmlSerializable interface members - - /// - public XmlSchema GetSchema() - { - return null; - } - - /// - public abstract void ReadXml(XmlReader reader); - - /// - public virtual void WriteXml(XmlWriter writer) - { - foreach (var child in Children) - { - var type = child.GetType(); - var serializer = new XmlSerializer(type); - serializer.Serialize(writer, child); - } - } - - #endregion IXmlSerializable interface members - - #endregion Public Methods - } +/************************************************************************ + 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.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; + +namespace AvalonDock.Layout +{ + /// + /// Provides an abstract class to implement a concrete floating window layout model. + /// + [Serializable] + public abstract class LayoutFloatingWindow : LayoutElement, ILayoutContainer, IXmlSerializable + { + #region Properties + + /// Gets the list of based children below this object. + public abstract IEnumerable Children { get; } + + /// Gets the number of children below this object. + public abstract int ChildrenCount { get; } + + public abstract bool IsValid { get; } + + #endregion Properties + + #region Public Methods + + /// Remove the child element from the collection of children. + /// + public abstract void RemoveChild(ILayoutElement element); + + /// Replace the child element with a new child in the collection of children. + /// + /// + public abstract void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement); + + #region IXmlSerializable interface members + + /// + public XmlSchema GetSchema() + { + return null; + } + + /// + public abstract void ReadXml(XmlReader reader); + + /// + public virtual void WriteXml(XmlWriter writer) + { + foreach (var child in Children) + { + var type = child.GetType(); + var serializer = XmlSerializer.FromTypes(new[] { type })[0]; + serializer.Serialize(writer, child); + } + } + + #endregion IXmlSerializable interface members + + #endregion Public Methods + } } \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/LayoutGroup.cs b/source/Components/AvalonDock/Layout/LayoutGroup.cs index 98f70163..3a6cdc60 100644 --- a/source/Components/AvalonDock/Layout/LayoutGroup.cs +++ b/source/Components/AvalonDock/Layout/LayoutGroup.cs @@ -1,276 +1,260 @@ -/************************************************************************ - 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; -using System.Xml.Serialization; - -namespace AvalonDock.Layout -{ - /// - /// Provides a base class for layout anchorable (group and non-group) related classes - /// that implement the viewmodel aspect for layout anchorable controls. - /// - /// - [Serializable] - public abstract class LayoutGroup : LayoutGroupBase, ILayoutGroup, IXmlSerializable where T : class, ILayoutElement - { - #region fields - - private readonly ObservableCollection _children = new ObservableCollection(); - private bool _isVisible = true; - - #endregion fields - - #region Constructors - - /// Class constructor. - internal LayoutGroup() - { - _children.CollectionChanged += Children_CollectionChanged; - } - - #endregion Constructors - - #region Properties - - /// Gets a collection of children objects below this object. - public ObservableCollection Children => _children; - - /// Gets the number of of children objects below this object. - public int ChildrenCount => _children.Count; - - /// Gets a collection of based children objects below this object. - IEnumerable ILayoutContainer.Children => _children.Cast(); - - /// Gets whether this object is visible or not. - public bool IsVisible - { - get => _isVisible; - protected set - { - if (value == _isVisible) return; - RaisePropertyChanging(nameof(IsVisible)); - _isVisible = value; - OnIsVisibleChanged(); - RaisePropertyChanged(nameof(IsVisible)); - } - } - - #endregion Properties - - #region Public Methods - - /// - public void ComputeVisibility() => IsVisible = GetVisibility(); - - /// - public void MoveChild(int oldIndex, int newIndex) - { - if (oldIndex == newIndex) return; - _children.Move(oldIndex, newIndex); - ChildMoved(oldIndex, newIndex); - } - - /// - public void RemoveChildAt(int childIndex) - { - _children.RemoveAt(childIndex); - } - - /// - public int IndexOfChild(ILayoutElement element) - { - return _children.Cast().ToList().IndexOf(element); - } - - /// - public void InsertChildAt(int index, ILayoutElement element) - { - if (element is T t) - _children.Insert(index, t); - } - - /// - public void RemoveChild(ILayoutElement element) - { - if (element is T t) - _children.Remove(t); - } - - /// - public void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) - { - if (oldElement is T oldT && newElement is T newT) - { - var index = _children.IndexOf(oldT); - _children.Insert(index, newT); - _children.RemoveAt(index + 1); - } - } - - /// - public void ReplaceChildAt(int index, ILayoutElement element) - { - _children[index] = (T)element; - } - - /// - System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; - - /// - /// provides a standard overridable implementation for deriving classes. - public virtual void ReadXml(System.Xml.XmlReader reader) - { - reader.MoveToContent(); - if (reader.IsEmptyElement) - { - reader.Read(); - ComputeVisibility(); - return; - } - var localName = reader.LocalName; - reader.Read(); - while (true) - { - if (reader.LocalName == localName && reader.NodeType == System.Xml.XmlNodeType.EndElement) - break; - if (reader.NodeType == System.Xml.XmlNodeType.Whitespace) - { - reader.Read(); - continue; - } - - XmlSerializer serializer = null; - if (reader.LocalName == nameof(LayoutAnchorablePaneGroup)) - serializer = new XmlSerializer(typeof(LayoutAnchorablePaneGroup)); - else if (reader.LocalName == nameof(LayoutAnchorablePane)) - serializer = new XmlSerializer(typeof(LayoutAnchorablePane)); - else if (reader.LocalName == nameof(LayoutAnchorable)) - serializer = new XmlSerializer(typeof(LayoutAnchorable)); - else if (reader.LocalName == nameof(LayoutDocumentPaneGroup)) - serializer = new XmlSerializer(typeof(LayoutDocumentPaneGroup)); - else if (reader.LocalName == nameof(LayoutDocumentPane)) - serializer = new XmlSerializer(typeof(LayoutDocumentPane)); - else if (reader.LocalName == nameof(LayoutDocument)) - serializer = new XmlSerializer(typeof(LayoutDocument)); - else if (reader.LocalName == nameof(LayoutAnchorGroup)) - serializer = new XmlSerializer(typeof(LayoutAnchorGroup)); - else if (reader.LocalName == nameof(LayoutPanel)) - serializer = new XmlSerializer(typeof(LayoutPanel)); - else - { - var type = FindType(reader.LocalName); - if (type == null) - throw new ArgumentException("AvalonDock.LayoutGroup doesn't know how to deserialize " + reader.LocalName); - serializer = new XmlSerializer(type); - } - - Children.Add((T)serializer.Deserialize(reader)); - } - - reader.ReadEndElement(); - } - - /// - /// provides a standard overridable implementation for deriving classes. - public virtual void WriteXml(System.Xml.XmlWriter writer) - { - foreach (var child in Children) - { - var type = child.GetType(); - var serializer = new XmlSerializer(type); - serializer.Serialize(writer, child); - } - } - - #endregion Public Methods - - #region Internal Methods - - protected virtual void OnIsVisibleChanged() - { - UpdateParentVisibility(); - } - - protected abstract bool GetVisibility(); - - protected virtual void ChildMoved(int oldIndex, int newIndex) - { - } - - #endregion Internal Methods - - #region Overrides - - /// - protected override void OnParentChanged(ILayoutContainer oldValue, ILayoutContainer newValue) - { - base.OnParentChanged(oldValue, newValue); - ComputeVisibility(); - } - - #endregion Overrides - - #region Private Methods - - private void Children_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace) - { - if (e.OldItems != null) - { - foreach (LayoutElement element in e.OldItems) - if (element.Parent == this || e.Action == NotifyCollectionChangedAction.Remove) element.Parent = null; - } - } - if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Replace) - { - if (e.NewItems != null) - { - foreach (LayoutElement element in e.NewItems) - { - if (element.Parent == this) continue; - element.Parent?.RemoveChild(element); - element.Parent = this; - } - } - } - - ComputeVisibility(); - OnChildrenCollectionChanged(); - - if (e.Action == NotifyCollectionChangedAction.Add) - // #81 - Make parents update their children up the tree. Otherwise, they will not be redrawn. - RaiseChildrenTreeChanged(); - else - NotifyChildrenTreeChanged(ChildrenTreeChange.DirectChildrenChanged); - RaisePropertyChanged(nameof(ChildrenCount)); - } - - private void UpdateParentVisibility() - { - if (Parent is ILayoutElementWithVisibility parentPane) - parentPane.ComputeVisibility(); - } - - private Type FindType(string name) - { - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) - foreach (var t in a.GetTypes()) - if (t.Name.Equals(name)) return t; - return null; - } - - #endregion Private Methods - } +/************************************************************************ + 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.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using System.Xml.Serialization; + +namespace AvalonDock.Layout +{ + /// + /// Provides a base class for layout anchorable (group and non-group) related classes + /// that implement the viewmodel aspect for layout anchorable controls. + /// + /// + [Serializable] + public abstract class LayoutGroup : LayoutGroupBase, ILayoutGroup, IXmlSerializable where T : class, ILayoutElement + { + #region fields + + private readonly ObservableCollection _children = new ObservableCollection(); + private bool _isVisible = true; + + #endregion fields + + #region Constructors + + /// Class constructor. + internal LayoutGroup() + { + _children.CollectionChanged += Children_CollectionChanged; + } + + #endregion Constructors + + #region Properties + + /// Gets a collection of children objects below this object. + public ObservableCollection Children => _children; + + /// Gets the number of of children objects below this object. + public int ChildrenCount => _children.Count; + + /// Gets a collection of based children objects below this object. + IEnumerable ILayoutContainer.Children => _children.Cast(); + + /// Gets whether this object is visible or not. + public bool IsVisible + { + get => _isVisible; + protected set + { + if (value == _isVisible) return; + RaisePropertyChanging(nameof(IsVisible)); + _isVisible = value; + OnIsVisibleChanged(); + RaisePropertyChanged(nameof(IsVisible)); + } + } + + #endregion Properties + + #region Public Methods + + /// + public void ComputeVisibility() => IsVisible = GetVisibility(); + + /// + public void MoveChild(int oldIndex, int newIndex) + { + if (oldIndex == newIndex) return; + _children.Move(oldIndex, newIndex); + ChildMoved(oldIndex, newIndex); + } + + /// + public void RemoveChildAt(int childIndex) + { + _children.RemoveAt(childIndex); + } + + /// + public int IndexOfChild(ILayoutElement element) + { + return _children.Cast().ToList().IndexOf(element); + } + + /// + public void InsertChildAt(int index, ILayoutElement element) + { + if (element is T t) + _children.Insert(index, t); + } + + /// + public void RemoveChild(ILayoutElement element) + { + if (element is T t) + _children.Remove(t); + } + + /// + public void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) + { + if (oldElement is T oldT && newElement is T newT) + { + var index = _children.IndexOf(oldT); + _children.Insert(index, newT); + _children.RemoveAt(index + 1); + } + } + + /// + public void ReplaceChildAt(int index, ILayoutElement element) + { + _children[index] = (T)element; + } + + /// + System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; + + /// + /// provides a standard overridable implementation for deriving classes. + public virtual void ReadXml(System.Xml.XmlReader reader) + { + reader.MoveToContent(); + if (reader.IsEmptyElement) + { + reader.Read(); + ComputeVisibility(); + return; + } + var localName = reader.LocalName; + reader.Read(); + while (true) + { + if (reader.LocalName == localName && reader.NodeType == System.Xml.XmlNodeType.EndElement) + break; + if (reader.NodeType == System.Xml.XmlNodeType.Whitespace) + { + reader.Read(); + continue; + } + string fullName = String.Format("{0}.{1}", GetType().Namespace, reader.LocalName); + Type typeForSerializer = Type.GetType(fullName); + + if (typeForSerializer == null) + typeForSerializer = FindType(reader.LocalName); + + if (typeForSerializer == null) + throw new ArgumentException("AvalonDock.LayoutGroup doesn't know how to deserialize " + reader.LocalName); + + XmlSerializer serializer = XmlSerializer.FromTypes(new[] { typeForSerializer })[0]; + Children.Add((T)serializer.Deserialize(reader)); + } + + reader.ReadEndElement(); + } + + /// + /// provides a standard overridable implementation for deriving classes. + public virtual void WriteXml(System.Xml.XmlWriter writer) + { + foreach (var child in Children) + { + var type = child.GetType(); + var serializer = XmlSerializer.FromTypes(new[] { type })[0]; + serializer.Serialize(writer, child); + } + } + + #endregion Public Methods + + #region Internal Methods + + protected virtual void OnIsVisibleChanged() + { + UpdateParentVisibility(); + } + + protected abstract bool GetVisibility(); + + protected virtual void ChildMoved(int oldIndex, int newIndex) + { + } + + #endregion Internal Methods + + #region Overrides + + /// + protected override void OnParentChanged(ILayoutContainer oldValue, ILayoutContainer newValue) + { + base.OnParentChanged(oldValue, newValue); + ComputeVisibility(); + } + + #endregion Overrides + + #region Private Methods + + private void Children_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace) + { + if (e.OldItems != null) + { + foreach (LayoutElement element in e.OldItems) + if (element.Parent == this || e.Action == NotifyCollectionChangedAction.Remove) element.Parent = null; + } + } + if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Replace) + { + if (e.NewItems != null) + { + foreach (LayoutElement element in e.NewItems) + { + if (element.Parent == this) continue; + element.Parent?.RemoveChild(element); + element.Parent = this; + } + } + } + + ComputeVisibility(); + OnChildrenCollectionChanged(); + + if (e.Action == NotifyCollectionChangedAction.Add) + // #81 - Make parents update their children up the tree. Otherwise, they will not be redrawn. + RaiseChildrenTreeChanged(); + else + NotifyChildrenTreeChanged(ChildrenTreeChange.DirectChildrenChanged); + RaisePropertyChanged(nameof(ChildrenCount)); + } + + private void UpdateParentVisibility() + { + if (Parent is ILayoutElementWithVisibility parentPane) + parentPane.ComputeVisibility(); + } + + private Type FindType(string name) + { + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + foreach (var t in a.GetTypes()) + if (t.Name.Equals(name)) return t; + return null; + } + + #endregion Private Methods + } } \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/LayoutRoot.cs b/source/Components/AvalonDock/Layout/LayoutRoot.cs index 2cad3519..5dc4e595 100644 --- a/source/Components/AvalonDock/Layout/LayoutRoot.cs +++ b/source/Components/AvalonDock/Layout/LayoutRoot.cs @@ -1,989 +1,988 @@ -/************************************************************************ - 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.Collections.ObjectModel; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Windows.Controls; -using System.Windows.Markup; -using System.Xml; -using System.Xml.Schema; -using System.Xml.Serialization; - -namespace AvalonDock.Layout -{ - /// - /// Implements the root of the layout viewmodel (see property). - /// This root includes a property for binding content, side panel properties - /// and many other layout related root items. - /// - /// This class implements and - /// to support direct UI binding scenarios - /// with view updates supported. - /// - [ContentProperty(nameof(RootPanel))] - [Serializable] - public class LayoutRoot : LayoutElement, ILayoutContainer, ILayoutRoot, IXmlSerializable - { - #region fields - - private LayoutPanel _rootPanel; - private LayoutAnchorSide _topSide = null; - private LayoutAnchorSide _rightSide; - private LayoutAnchorSide _leftSide = null; - private LayoutAnchorSide _bottomSide = null; - - private ObservableCollection _floatingWindows = null; - private ObservableCollection _hiddenAnchorables = null; - - [field: NonSerialized] - private WeakReference _activeContent = null; - - private bool _activeContentSet = false; - - [field: NonSerialized] - private WeakReference _lastFocusedDocument = null; - - [NonSerialized] - private DockingManager _manager = null; - - #endregion fields - - #region Constructors - - /// Standard class constructor - public LayoutRoot() - { - RightSide = new LayoutAnchorSide(); - LeftSide = new LayoutAnchorSide(); - TopSide = new LayoutAnchorSide(); - BottomSide = new LayoutAnchorSide(); - RootPanel = new LayoutPanel(new LayoutDocumentPane()); - } - - #endregion Constructors - - #region Events - - /// - /// Raised when the layout is updated. This event is raised via method - /// when a parent of a LayoutElement has changed. - /// - public event EventHandler Updated; - - /// Raised when an element is added to the layout. - public event EventHandler ElementAdded; - - /// Raised when an element is removed from the layout. - public event EventHandler ElementRemoved; - - #endregion Events - - #region Properties - - /// Gets/sets the root layout panel that contains the . - public LayoutPanel RootPanel - { - get => _rootPanel; - set - { - if (_rootPanel == value) return; - RaisePropertyChanging(nameof(RootPanel)); - if (_rootPanel != null && _rootPanel.Parent == this) _rootPanel.Parent = null; - _rootPanel = value ?? new LayoutPanel(new LayoutDocumentPane()); - _rootPanel.Parent = this; - RaisePropertyChanged(nameof(RootPanel)); - } - } - - /// Gets or sets the top side of the layout root. - public LayoutAnchorSide TopSide - { - get => _topSide; - set - { - if (_topSide == value) return; - RaisePropertyChanging(nameof(TopSide)); - _topSide = value; - if (_topSide != null) _topSide.Parent = this; - RaisePropertyChanged(nameof(TopSide)); - } - } - - /// Gets or sets the right side of the layout root. - public LayoutAnchorSide RightSide - { - get => _rightSide; - set - { - if (_rightSide == value) return; - RaisePropertyChanging(nameof(RightSide)); - _rightSide = value; - if (_rightSide != null) _rightSide.Parent = this; - RaisePropertyChanged(nameof(RightSide)); - } - } - - /// Gets or sets the left side of the layout root. - public LayoutAnchorSide LeftSide - { - get => _leftSide; - set - { - if (value == _leftSide) return; - RaisePropertyChanging(nameof(LeftSide)); - _leftSide = value; - if (_leftSide != null) _leftSide.Parent = this; - RaisePropertyChanged(nameof(LeftSide)); - } - } - - /// Gets or sets the bottom side of the layout root. - public LayoutAnchorSide BottomSide - { - get => _bottomSide; - set - { - if (value == _bottomSide) return; - RaisePropertyChanging(nameof(BottomSide)); - _bottomSide = value; - if (_bottomSide != null) _bottomSide.Parent = this; - RaisePropertyChanged(nameof(BottomSide)); - } - } - - /// Gets the floating windows that are part of this layout. - public ObservableCollection FloatingWindows - { - get - { - if (_floatingWindows == null) - { - _floatingWindows = new ObservableCollection(); - _floatingWindows.CollectionChanged += _floatingWindows_CollectionChanged; - } - - return _floatingWindows; - } - } - - /// Gets the hidden anchorables in the layout. - public ObservableCollection Hidden - { - get - { - if (_hiddenAnchorables == null) - { - _hiddenAnchorables = new ObservableCollection(); - _hiddenAnchorables.CollectionChanged += _hiddenAnchorables_CollectionChanged; - } - - return _hiddenAnchorables; - } - } - - #region Children - - /// Gets the child elements of the layout root. - public IEnumerable Children - { - get - { - if (RootPanel != null) - yield return RootPanel; - if (_floatingWindows != null) - { - foreach (var floatingWindow in _floatingWindows) - yield return floatingWindow; - } - if (TopSide != null) - yield return TopSide; - if (RightSide != null) - yield return RightSide; - if (BottomSide != null) - yield return BottomSide; - if (LeftSide != null) - yield return LeftSide; - if (_hiddenAnchorables != null) - { - foreach (var hiddenAnchorable in _hiddenAnchorables) - yield return hiddenAnchorable; - } - } - } - - /// Gets the number of child elements of the layout root. - public int ChildrenCount => 5 + (_floatingWindows?.Count ?? 0) + (_hiddenAnchorables?.Count ?? 0); - - #endregion Children - - /// Gets the active LayoutContent-derived element. - [XmlIgnore] - public LayoutContent ActiveContent - { - get - { - return _activeContent.GetValueOrDefault(); - } - set - { - var currentValue = ActiveContent; - if (currentValue != value) - { - InternalSetActiveContent(currentValue, value); - } - } - } - - [XmlIgnore] - public LayoutContent LastFocusedDocument - { - get => _lastFocusedDocument.GetValueOrDefault(); - private set - { - var currentValue = LastFocusedDocument; - if (currentValue != value) - { - RaisePropertyChanging(nameof(LastFocusedDocument)); - if (currentValue != null) currentValue.IsLastFocusedDocument = false; - _lastFocusedDocument = new WeakReference(value); - currentValue = LastFocusedDocument; - if (currentValue != null) currentValue.IsLastFocusedDocument = true; - RaisePropertyChanged(nameof(LastFocusedDocument)); - } - } - } - - /// Gets/sets the docking manager root control for this library. - [XmlIgnore] - public DockingManager Manager - { - get => _manager; - internal set - { - if (value == _manager) return; - RaisePropertyChanging(nameof(Manager)); - _manager = value; - RaisePropertyChanged(nameof(Manager)); - } - } - - #endregion Properties - - #region Overrides - -#if TRACE - public override void ConsoleDump(int tab) - { - System.Diagnostics.Trace.Write(new string(' ', tab * 4)); - System.Diagnostics.Trace.WriteLine("RootPanel()"); - - RootPanel.ConsoleDump(tab + 1); - - System.Diagnostics.Trace.Write(new string(' ', tab * 4)); - System.Diagnostics.Trace.WriteLine("FloatingWindows()"); - - foreach (var fw in FloatingWindows) - fw.ConsoleDump(tab + 1); - - System.Diagnostics.Trace.Write(new string(' ', tab * 4)); - System.Diagnostics.Trace.WriteLine("Hidden()"); - - foreach (var hidden in Hidden) - hidden.ConsoleDump(tab + 1); - } -#endif - - #endregion Overrides - - #region Public Methods - - public void RemoveChild(ILayoutElement element) - { - if (element == RootPanel) - RootPanel = null; - else if (_floatingWindows != null && _floatingWindows.Contains(element)) - _floatingWindows.Remove(element as LayoutFloatingWindow); - else if (_hiddenAnchorables != null && _hiddenAnchorables.Contains(element)) - _hiddenAnchorables.Remove(element as LayoutAnchorable); - else if (element == TopSide) - TopSide = null; - else if (element == RightSide) - RightSide = null; - else if (element == BottomSide) - BottomSide = null; - else if (element == LeftSide) - LeftSide = null; - } - - public void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) - { - if (oldElement == RootPanel) - RootPanel = (LayoutPanel)newElement; - else if (_floatingWindows != null && _floatingWindows.Contains(oldElement)) - { - var index = _floatingWindows.IndexOf(oldElement as LayoutFloatingWindow); - _floatingWindows.Remove(oldElement as LayoutFloatingWindow); - _floatingWindows.Insert(index, newElement as LayoutFloatingWindow); - } - else if (_hiddenAnchorables != null && _hiddenAnchorables.Contains(oldElement)) - { - var index = _hiddenAnchorables.IndexOf(oldElement as LayoutAnchorable); - _hiddenAnchorables.Remove(oldElement as LayoutAnchorable); - _hiddenAnchorables.Insert(index, newElement as LayoutAnchorable); - } - else if (oldElement == TopSide) - TopSide = (LayoutAnchorSide)newElement; - else if (oldElement == RightSide) - RightSide = (LayoutAnchorSide)newElement; - else if (oldElement == BottomSide) - BottomSide = (LayoutAnchorSide)newElement; - else if (oldElement == LeftSide) - LeftSide = (LayoutAnchorSide)newElement; - } - - /// Removes any empty container not directly referenced by other layout items. - public void CollectGarbage() - { - var exitFlag = true; - - #region collect empty panes - - do - { - exitFlag = true; - - //for each content that references via PreviousContainer a disconnected Pane set the property to null - foreach (var content in this.Descendents().OfType().Where(c => c.PreviousContainer != null && - (c.PreviousContainer.Parent == null || c.PreviousContainer.Parent.Root != this))) - { - content.PreviousContainer = null; - } - - //for each pane that is empty - foreach (var emptyPane in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) - { - //...set null any reference coming from contents not yet hosted in a floating window - foreach (var contentReferencingEmptyPane in this.Descendents().OfType() - .Where(c => ((ILayoutPreviousContainer)c).PreviousContainer == emptyPane && !c.IsFloating)) - { - if (contentReferencingEmptyPane is LayoutAnchorable anchorable && - !anchorable.IsVisible) - continue; - - ((ILayoutPreviousContainer)contentReferencingEmptyPane).PreviousContainer = null; - contentReferencingEmptyPane.PreviousContainerIndex = -1; - } - - //...if this pane is the only documentpane present in the layout of the main window (not floating) then skip it - if (emptyPane is LayoutDocumentPane && - emptyPane.FindParent() == null && - this.Descendents().OfType().Count(c => c != emptyPane && c.FindParent() == null) == 0) - continue; - - //...if this empty pane is not referenced by anyone, then remove it from its parent container - if (!this.Descendents().OfType().Any(c => c.PreviousContainer == emptyPane)) - { - var parentGroup = emptyPane.Parent; - parentGroup.RemoveChild(emptyPane); - exitFlag = false; - break; - } - } - - if (!exitFlag) - { - //removes any empty anchorable pane group - foreach (var emptyLayoutAnchorablePaneGroup in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) - { - var parentGroup = emptyLayoutAnchorablePaneGroup.Parent; - parentGroup.RemoveChild(emptyLayoutAnchorablePaneGroup); - exitFlag = false; - break; - } - } - - if (!exitFlag) - { - //removes any empty layout panel - foreach (var emptyLayoutPanel in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) - { - var parentGroup = emptyLayoutPanel.Parent; - parentGroup.RemoveChild(emptyLayoutPanel); - exitFlag = false; - break; - } - foreach (var emptyLayoutDocumentPane in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) - { - var parentGroup = emptyLayoutDocumentPane.Parent; - if (!(parentGroup.Parent is LayoutDocumentFloatingWindow)) continue; - var index = RootPanel.IndexOfChild(this.Descendents().OfType().First()); - parentGroup.RemoveChild(emptyLayoutDocumentPane); - if (!this.Descendents().OfType().Any()) - { - // Now the last Pane container is deleted, at least one is required for documents to be added. - // We did not want to keep an empty window floating, but add a new one to the main window - RootPanel.Children.Insert(index < 0 ? 0 : index, emptyLayoutDocumentPane); - } - exitFlag = false; - break; - } - } - - if (!exitFlag) - { - //removes any empty floating window - foreach (var emptyLayoutFloatingWindow in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) - { - var parentGroup = emptyLayoutFloatingWindow.Parent; - parentGroup.RemoveChild(emptyLayoutFloatingWindow); - exitFlag = false; - break; - } - } - - if (!exitFlag) - { - //removes any empty anchor group - foreach (var emptyLayoutAnchorGroup in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) - { - if (!this.Descendents().OfType().Any(c => c.PreviousContainer == emptyLayoutAnchorGroup)) - { - var parentGroup = emptyLayoutAnchorGroup.Parent; - parentGroup.RemoveChild(emptyLayoutAnchorGroup); - exitFlag = false; - break; - } - } - } - } - while (!exitFlag); - - #endregion collect empty panes - - #region collapse single child anchorable pane groups - - do - { - exitFlag = true; - //for each pane that is empty - foreach (var paneGroupToCollapse in this.Descendents().OfType().Where(p => p.ChildrenCount == 1 && p.Children[0] is LayoutAnchorablePaneGroup).ToArray()) - { - var singleChild = paneGroupToCollapse.Children[0] as LayoutAnchorablePaneGroup; - paneGroupToCollapse.Orientation = singleChild.Orientation; - while (singleChild.ChildrenCount > 0) - paneGroupToCollapse.InsertChildAt(paneGroupToCollapse.ChildrenCount, singleChild.Children[0]); - paneGroupToCollapse.RemoveChild(singleChild); - exitFlag = false; - break; - } - } - while (!exitFlag); - - #endregion collapse single child anchorable pane groups - - #region collapse single child document pane groups - - do - { - exitFlag = true; - //for each pane that is empty - foreach (var paneGroupToCollapse in this.Descendents().OfType().Where(p => p.ChildrenCount == 1 && p.Children[0] is LayoutDocumentPaneGroup).ToArray()) - { - var singleChild = paneGroupToCollapse.Children[0] as LayoutDocumentPaneGroup; - paneGroupToCollapse.Orientation = singleChild.Orientation; - while (singleChild.ChildrenCount > 0) - paneGroupToCollapse.InsertChildAt(paneGroupToCollapse.ChildrenCount, singleChild.Children[0]); - paneGroupToCollapse.RemoveChild(singleChild); - exitFlag = false; - break; - } - } - while (!exitFlag); - - #endregion collapse single child document pane groups - - ////do - ////{ - //// exitFlag = true; - //// //for each panel that has only one child - //// foreach( var panelToCollapse in this.Descendents().OfType().Where( p => p.ChildrenCount == 1 && p.Children[ 0 ] is LayoutPanel ).ToArray() ) - //// { - //// var singleChild = panelToCollapse.Children[ 0 ] as LayoutPanel; - //// panelToCollapse.Orientation = singleChild.Orientation; - //// panelToCollapse.RemoveChild( singleChild ); - //// ILayoutPanelElement[] singleChildChildren = new ILayoutPanelElement[ singleChild.ChildrenCount ]; - //// singleChild.Children.CopyTo( singleChildChildren, 0 ); - //// while( singleChild.ChildrenCount > 0 ) - //// { - //// panelToCollapse.InsertChildAt( - //// panelToCollapse.ChildrenCount, singleChildChildren[ panelToCollapse.ChildrenCount ] ); - //// } - - //// exitFlag = false; - //// break; - //// } - ////} - ////while( !exitFlag ); - - // Update ActiveContent and LastFocusedDocument properties - UpdateActiveContentProperty(); - -#if DEBUG - Debug.Assert(!this.Descendents().OfType().Any(a => a.ChildrenCount == 0 && a.IsVisible)); - //DumpTree(true); -#if TRACE - RootPanel.ConsoleDump(4); -#endif -#endif - } - - #region IXmlSerializable interface members - - /// - XmlSchema IXmlSerializable.GetSchema() => null; - - /// - void IXmlSerializable.ReadXml(XmlReader reader) - { - reader.MoveToContent(); - if (reader.IsEmptyElement) - { - reader.Read(); - return; - } - - var layoutPanelElements = ReadRootPanel(reader, out var orientation, out var canDock); - if (layoutPanelElements != null) - { - RootPanel = new LayoutPanel { Orientation = orientation, CanDock = canDock }; - //Add all children to RootPanel - foreach (var panel in layoutPanelElements) RootPanel.Children.Add(panel); - } - - TopSide = new LayoutAnchorSide(); - if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, TopSide); - RightSide = new LayoutAnchorSide(); - if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, RightSide); - LeftSide = new LayoutAnchorSide(); - if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, LeftSide); - BottomSide = new LayoutAnchorSide(); - if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, BottomSide); - - FloatingWindows.Clear(); - var floatingWindows = ReadElementList(reader, true); - foreach (var floatingWindow in floatingWindows) FloatingWindows.Add((LayoutFloatingWindow)floatingWindow); - - Hidden.Clear(); - var hidden = ReadElementList(reader, false); - foreach (var hiddenObject in hidden) Hidden.Add((LayoutAnchorable)hiddenObject); - - //Read the closing end element of LayoutRoot - reader.ReadEndElement(); - } - - /// - void IXmlSerializable.WriteXml(XmlWriter writer) - { - writer.WriteStartElement(nameof(RootPanel)); - RootPanel?.WriteXml(writer); - writer.WriteEndElement(); - - writer.WriteStartElement(nameof(TopSide)); - TopSide?.WriteXml(writer); - writer.WriteEndElement(); - - writer.WriteStartElement(nameof(RightSide)); - RightSide?.WriteXml(writer); - writer.WriteEndElement(); - - writer.WriteStartElement(nameof(LeftSide)); - LeftSide?.WriteXml(writer); - writer.WriteEndElement(); - - writer.WriteStartElement(nameof(BottomSide)); - BottomSide?.WriteXml(writer); - writer.WriteEndElement(); - - // Write all floating windows (can be LayoutDocumentFloatingWindow or LayoutAnchorableFloatingWindow). - // To prevent "can not create instance of abstract type", the type is retrieved with GetType().Name - writer.WriteStartElement(nameof(FloatingWindows)); - foreach (var layoutFloatingWindow in FloatingWindows) - { - writer.WriteStartElement(layoutFloatingWindow.GetType().Name); - layoutFloatingWindow.WriteXml(writer); - writer.WriteEndElement(); - } - writer.WriteEndElement(); - - writer.WriteStartElement(nameof(Hidden)); - foreach (var layoutAnchorable in Hidden) - { - writer.WriteStartElement(layoutAnchorable.GetType().Name); - layoutAnchorable.WriteXml(writer); - writer.WriteEndElement(); - } - writer.WriteEndElement(); - } - - #endregion IXmlSerializable interface members - - #endregion Public Methods - - #region Internal Methods - - internal static Type FindType(string name) - { - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - foreach (var type in assembly.GetTypes()) - if (type.Name.Equals(name)) return type; - return null; - } - - internal void FireLayoutUpdated() => Updated?.Invoke(this, EventArgs.Empty); - - internal void OnLayoutElementAdded(LayoutElement element) => ElementAdded?.Invoke(this, new LayoutElementEventArgs(element)); - - internal void OnLayoutElementRemoved(LayoutElement element) - { - if (element.Descendents().OfType().Any(c => c == LastFocusedDocument)) - LastFocusedDocument = null; - if (element.Descendents().OfType().Any(c => c == ActiveContent)) - ActiveContent = null; - ElementRemoved?.Invoke(this, new LayoutElementEventArgs(element)); - } - - #endregion Internal Methods - - #region Private Methods - - private void _floatingWindows_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - var bNotifyChildren = false; - - if (e.OldItems != null && (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)) - { - foreach (LayoutFloatingWindow element in e.OldItems) - { - if (element.Parent != this) continue; - element.Parent = null; - bNotifyChildren = true; - } - } - - if (e.NewItems != null && (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)) - { - foreach (LayoutFloatingWindow element in e.NewItems) - { - element.Parent = this; - bNotifyChildren = true; - } - } - - // descendants of LayoutElement notify when their Children and ChildrenCount properties change - // https://github.com/xceedsoftware/wpftoolkit/issues/1313 - // - if (!bNotifyChildren) return; - switch (e.Action) - { - case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: - case System.Collections.Specialized.NotifyCollectionChangedAction.Add: - RaisePropertyChanged(nameof(Children)); - RaisePropertyChanged(nameof(ChildrenCount)); - break; - - case System.Collections.Specialized.NotifyCollectionChangedAction.Replace: - RaisePropertyChanged(nameof(Children)); - break; - } - } - - private void _hiddenAnchorables_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - var bNotifyChildren = false; - - if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) - { - if (e.OldItems != null) - { - foreach (LayoutAnchorable element in e.OldItems) - { - if (element.Parent != this) continue; - element.Parent = null; - bNotifyChildren = true; - } - } - } - - if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) - { - if (e.NewItems != null) - { - foreach (LayoutAnchorable element in e.NewItems) - { - if (element.Parent == this) continue; - element.Parent?.RemoveChild(element); - element.Parent = this; - bNotifyChildren = true; - } - } - } - - // descendants of LayoutElement notify when their Children and ChildrenCount properties change - // https://github.com/xceedsoftware/wpftoolkit/issues/1313 - // - if (!bNotifyChildren) return; - switch (e.Action) - { - case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: - case System.Collections.Specialized.NotifyCollectionChangedAction.Add: - RaisePropertyChanged(nameof(Children)); - RaisePropertyChanged(nameof(ChildrenCount)); - break; - - case System.Collections.Specialized.NotifyCollectionChangedAction.Replace: - RaisePropertyChanged(nameof(Children)); - break; - } - } - - private void InternalSetActiveContent(LayoutContent currentValue, LayoutContent newActiveContent) - { - RaisePropertyChanging(nameof(ActiveContent)); - if (currentValue != null && currentValue.IsActive) currentValue.IsActive = false; - _activeContent = new WeakReference(newActiveContent); - currentValue = ActiveContent; - if (currentValue != null && !currentValue.IsActive) currentValue.IsActive = true; - RaisePropertyChanged(nameof(ActiveContent)); - _activeContentSet = currentValue != null; - if (currentValue != null) - { - if (currentValue.Parent is LayoutDocumentPane || currentValue is LayoutDocument) - LastFocusedDocument = currentValue; - } - else - LastFocusedDocument = null; - } - - private void UpdateActiveContentProperty() - { - var activeContent = ActiveContent; - if (_activeContentSet && (activeContent == null || activeContent.Root != this)) - { - _activeContentSet = false; - InternalSetActiveContent(activeContent, null); - } - } - - private void FillLayoutAnchorSide(XmlReader reader, LayoutAnchorSide layoutAnchorSide) - { - var result = new List(); - - while (true) - { - //Read all layoutAnchorSide children - if (ReadElement(reader) is LayoutAnchorGroup element) result.Add(element); - else if (reader.NodeType == XmlNodeType.EndElement) break; - } - - reader.ReadEndElement(); - foreach (var las in result) - { - layoutAnchorSide.Children.Add(las); - } - } - - /// - /// Reads all properties of the and returns them. - /// - /// - /// - /// - /// - private List ReadRootPanel(XmlReader reader - , out Orientation orientation - , out bool canDock) - { - orientation = Orientation.Horizontal; - canDock = true; - - var result = new List(); - var startElementName = reader.LocalName; - reader.Read(); - if (reader.LocalName.Equals(startElementName) && reader.NodeType == XmlNodeType.EndElement) return null; - - while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); - - if (reader.LocalName.Equals(nameof(RootPanel))) - { - orientation = (Orientation)Enum.Parse(typeof(Orientation), reader.GetAttribute(nameof(Orientation)), true); - - var canDockStr = reader.GetAttribute("CanDock"); - if (canDockStr != null) - canDock = bool.Parse(canDockStr); - - reader.Read(); - while (true) - { - //Read all RootPanel children - if (ReadElement(reader) is ILayoutPanelElement element) result.Add(element); - else if (reader.NodeType == XmlNodeType.EndElement) break; - } - } - - reader.ReadEndElement(); - return result; - } - - private List ReadElementList(XmlReader reader, bool isFloatingWindow) - { - var resultList = new List(); - while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); - if (reader.NodeType == XmlNodeType.EndElement) return resultList; - - if (reader.IsEmptyElement) - { - reader.Read(); - return resultList; - } - - var startElementName = reader.LocalName; - reader.Read(); - if (reader.LocalName.Equals(startElementName) && reader.NodeType == XmlNodeType.EndElement) return null; - - while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); - - while (true) - { - if (isFloatingWindow) - { - if (!(ReadElement(reader) is LayoutFloatingWindow result)) break; - resultList.Add(result); - } - else - { - if (!(ReadElement(reader) is LayoutAnchorable result)) break; - resultList.Add(result); - } - } - - reader.ReadEndElement(); - return resultList; - } - - private object ReadElement(XmlReader reader) - { - while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); - if (reader.NodeType == XmlNodeType.EndElement) return null; - - XmlSerializer serializer; - switch (reader.LocalName) - { - case nameof(LayoutAnchorablePaneGroup): - serializer = new XmlSerializer(typeof(LayoutAnchorablePaneGroup)); - break; - - case nameof(LayoutAnchorablePane): - serializer = new XmlSerializer(typeof(LayoutAnchorablePane)); - break; - - case nameof(LayoutAnchorable): - serializer = new XmlSerializer(typeof(LayoutAnchorable)); - break; - - case nameof(LayoutDocumentPaneGroup): - serializer = new XmlSerializer(typeof(LayoutDocumentPaneGroup)); - break; - - case nameof(LayoutDocumentPane): - serializer = new XmlSerializer(typeof(LayoutDocumentPane)); - break; - - case nameof(LayoutDocument): - serializer = new XmlSerializer(typeof(LayoutDocument)); - break; - - case nameof(LayoutAnchorGroup): - serializer = new XmlSerializer(typeof(LayoutAnchorGroup)); - break; - - case nameof(LayoutPanel): - serializer = new XmlSerializer(typeof(LayoutPanel)); - break; - - case nameof(LayoutDocumentFloatingWindow): - serializer = new XmlSerializer(typeof(LayoutDocumentFloatingWindow)); - break; - - case nameof(LayoutAnchorableFloatingWindow): - serializer = new XmlSerializer(typeof(LayoutAnchorableFloatingWindow)); - break; - - case nameof(LeftSide): - case nameof(RightSide): - case nameof(TopSide): - case nameof(BottomSide): - if (reader.IsEmptyElement) - { - reader.Read(); - return null; - } - return reader.Read(); - - default: - var type = FindType(reader.LocalName); - if (type == null) - throw new ArgumentException("AvalonDock.LayoutRoot doesn't know how to deserialize " + reader.LocalName); - serializer = new XmlSerializer(type); - break; - } - - return serializer.Deserialize(reader); - } - - #endregion Private Methods - - #region Diagnostic tools - -#if DEBUG - - public void DumpTree(bool shortPropertyNames = false) - { - void DumpElement(ILayoutElement element, StringBuilder indent, int childID, bool isLastChild) - { - Debug.Write($"{indent}{(indent.Length > 0 ? isLastChild ? " └─ " : " ├─ " : "")}{childID:D2} 0x{element.GetHashCode():X8} " + - $"{element.GetType().Name} {(shortPropertyNames ? "P" : "Parent")}:0x{element.Parent?.GetHashCode() ?? 0:X8} " + - $"{(shortPropertyNames ? "R" : "Root")}:0x{element.Root?.GetHashCode() ?? 0:X8}"); - if (!(element is ILayoutContainer containerElement)) - { - Debug.WriteLine(""); - return; - } - Debug.WriteLine($" {(shortPropertyNames ? "C" : "Children")}:{containerElement.ChildrenCount}"); - var nrChild = 0; - indent.Append(isLastChild ? " " : " │ "); - foreach (var child in containerElement.Children) - { - var lastChild = nrChild == containerElement.ChildrenCount - 1; - DumpElement(child, indent, nrChild++, lastChild); - } - indent.Remove(indent.Length - 3, 3); - } - - DumpElement(this, new StringBuilder(), 0, true); - } - -#endif - - #endregion Diagnostic tools - } +/************************************************************************ + 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.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows.Markup; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; + +namespace AvalonDock.Layout +{ + /// + /// Implements the root of the layout viewmodel (see property). + /// This root includes a property for binding content, side panel properties + /// and many other layout related root items. + /// + /// This class implements and + /// to support direct UI binding scenarios + /// with view updates supported. + /// + [ContentProperty(nameof(RootPanel))] + [Serializable] + public class LayoutRoot : LayoutElement, ILayoutContainer, ILayoutRoot, IXmlSerializable + { + #region fields + + private LayoutPanel _rootPanel; + private LayoutAnchorSide _topSide = null; + private LayoutAnchorSide _rightSide; + private LayoutAnchorSide _leftSide = null; + private LayoutAnchorSide _bottomSide = null; + + private ObservableCollection _floatingWindows = null; + private ObservableCollection _hiddenAnchorables = null; + + [field: NonSerialized] + private WeakReference _activeContent = null; + + private bool _activeContentSet = false; + + [field: NonSerialized] + private WeakReference _lastFocusedDocument = null; + + [NonSerialized] + private DockingManager _manager = null; + + #endregion fields + + #region Constructors + + /// Standard class constructor + public LayoutRoot() + { + RightSide = new LayoutAnchorSide(); + LeftSide = new LayoutAnchorSide(); + TopSide = new LayoutAnchorSide(); + BottomSide = new LayoutAnchorSide(); + RootPanel = new LayoutPanel(new LayoutDocumentPane()); + } + + #endregion Constructors + + #region Events + + /// + /// Raised when the layout is updated. This event is raised via method + /// when a parent of a LayoutElement has changed. + /// + public event EventHandler Updated; + + /// Raised when an element is added to the layout. + public event EventHandler ElementAdded; + + /// Raised when an element is removed from the layout. + public event EventHandler ElementRemoved; + + #endregion Events + + #region Properties + + /// Gets/sets the root layout panel that contains the . + public LayoutPanel RootPanel + { + get => _rootPanel; + set + { + if (_rootPanel == value) return; + RaisePropertyChanging(nameof(RootPanel)); + if (_rootPanel != null && _rootPanel.Parent == this) _rootPanel.Parent = null; + _rootPanel = value ?? new LayoutPanel(new LayoutDocumentPane()); + _rootPanel.Parent = this; + RaisePropertyChanged(nameof(RootPanel)); + } + } + + /// Gets or sets the top side of the layout root. + public LayoutAnchorSide TopSide + { + get => _topSide; + set + { + if (_topSide == value) return; + RaisePropertyChanging(nameof(TopSide)); + _topSide = value; + if (_topSide != null) _topSide.Parent = this; + RaisePropertyChanged(nameof(TopSide)); + } + } + + /// Gets or sets the right side of the layout root. + public LayoutAnchorSide RightSide + { + get => _rightSide; + set + { + if (_rightSide == value) return; + RaisePropertyChanging(nameof(RightSide)); + _rightSide = value; + if (_rightSide != null) _rightSide.Parent = this; + RaisePropertyChanged(nameof(RightSide)); + } + } + + /// Gets or sets the left side of the layout root. + public LayoutAnchorSide LeftSide + { + get => _leftSide; + set + { + if (value == _leftSide) return; + RaisePropertyChanging(nameof(LeftSide)); + _leftSide = value; + if (_leftSide != null) _leftSide.Parent = this; + RaisePropertyChanged(nameof(LeftSide)); + } + } + + /// Gets or sets the bottom side of the layout root. + public LayoutAnchorSide BottomSide + { + get => _bottomSide; + set + { + if (value == _bottomSide) return; + RaisePropertyChanging(nameof(BottomSide)); + _bottomSide = value; + if (_bottomSide != null) _bottomSide.Parent = this; + RaisePropertyChanged(nameof(BottomSide)); + } + } + + /// Gets the floating windows that are part of this layout. + public ObservableCollection FloatingWindows + { + get + { + if (_floatingWindows == null) + { + _floatingWindows = new ObservableCollection(); + _floatingWindows.CollectionChanged += _floatingWindows_CollectionChanged; + } + + return _floatingWindows; + } + } + + /// Gets the hidden anchorables in the layout. + public ObservableCollection Hidden + { + get + { + if (_hiddenAnchorables == null) + { + _hiddenAnchorables = new ObservableCollection(); + _hiddenAnchorables.CollectionChanged += _hiddenAnchorables_CollectionChanged; + } + + return _hiddenAnchorables; + } + } + + #region Children + + /// Gets the child elements of the layout root. + public IEnumerable Children + { + get + { + if (RootPanel != null) + yield return RootPanel; + if (_floatingWindows != null) + { + foreach (var floatingWindow in _floatingWindows) + yield return floatingWindow; + } + if (TopSide != null) + yield return TopSide; + if (RightSide != null) + yield return RightSide; + if (BottomSide != null) + yield return BottomSide; + if (LeftSide != null) + yield return LeftSide; + if (_hiddenAnchorables != null) + { + foreach (var hiddenAnchorable in _hiddenAnchorables) + yield return hiddenAnchorable; + } + } + } + + /// Gets the number of child elements of the layout root. + public int ChildrenCount => 5 + (_floatingWindows?.Count ?? 0) + (_hiddenAnchorables?.Count ?? 0); + + #endregion Children + + /// Gets the active LayoutContent-derived element. + [XmlIgnore] + public LayoutContent ActiveContent + { + get + { + return _activeContent.GetValueOrDefault(); + } + set + { + var currentValue = ActiveContent; + if (currentValue != value) + { + InternalSetActiveContent(currentValue, value); + } + } + } + + [XmlIgnore] + public LayoutContent LastFocusedDocument + { + get => _lastFocusedDocument.GetValueOrDefault(); + private set + { + var currentValue = LastFocusedDocument; + if (currentValue != value) + { + RaisePropertyChanging(nameof(LastFocusedDocument)); + if (currentValue != null) currentValue.IsLastFocusedDocument = false; + _lastFocusedDocument = new WeakReference(value); + currentValue = LastFocusedDocument; + if (currentValue != null) currentValue.IsLastFocusedDocument = true; + RaisePropertyChanged(nameof(LastFocusedDocument)); + } + } + } + + /// Gets/sets the docking manager root control for this library. + [XmlIgnore] + public DockingManager Manager + { + get => _manager; + internal set + { + if (value == _manager) return; + RaisePropertyChanging(nameof(Manager)); + _manager = value; + RaisePropertyChanged(nameof(Manager)); + } + } + + #endregion Properties + + #region Overrides + +#if TRACE + public override void ConsoleDump(int tab) + { + System.Diagnostics.Trace.Write(new string(' ', tab * 4)); + System.Diagnostics.Trace.WriteLine("RootPanel()"); + + RootPanel.ConsoleDump(tab + 1); + + System.Diagnostics.Trace.Write(new string(' ', tab * 4)); + System.Diagnostics.Trace.WriteLine("FloatingWindows()"); + + foreach (var fw in FloatingWindows) + fw.ConsoleDump(tab + 1); + + System.Diagnostics.Trace.Write(new string(' ', tab * 4)); + System.Diagnostics.Trace.WriteLine("Hidden()"); + + foreach (var hidden in Hidden) + hidden.ConsoleDump(tab + 1); + } +#endif + + #endregion Overrides + + #region Public Methods + + public void RemoveChild(ILayoutElement element) + { + if (element == RootPanel) + RootPanel = null; + else if (_floatingWindows != null && _floatingWindows.Contains(element)) + _floatingWindows.Remove(element as LayoutFloatingWindow); + else if (_hiddenAnchorables != null && _hiddenAnchorables.Contains(element)) + _hiddenAnchorables.Remove(element as LayoutAnchorable); + else if (element == TopSide) + TopSide = null; + else if (element == RightSide) + RightSide = null; + else if (element == BottomSide) + BottomSide = null; + else if (element == LeftSide) + LeftSide = null; + } + + public void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) + { + if (oldElement == RootPanel) + RootPanel = (LayoutPanel)newElement; + else if (_floatingWindows != null && _floatingWindows.Contains(oldElement)) + { + var index = _floatingWindows.IndexOf(oldElement as LayoutFloatingWindow); + _floatingWindows.Remove(oldElement as LayoutFloatingWindow); + _floatingWindows.Insert(index, newElement as LayoutFloatingWindow); + } + else if (_hiddenAnchorables != null && _hiddenAnchorables.Contains(oldElement)) + { + var index = _hiddenAnchorables.IndexOf(oldElement as LayoutAnchorable); + _hiddenAnchorables.Remove(oldElement as LayoutAnchorable); + _hiddenAnchorables.Insert(index, newElement as LayoutAnchorable); + } + else if (oldElement == TopSide) + TopSide = (LayoutAnchorSide)newElement; + else if (oldElement == RightSide) + RightSide = (LayoutAnchorSide)newElement; + else if (oldElement == BottomSide) + BottomSide = (LayoutAnchorSide)newElement; + else if (oldElement == LeftSide) + LeftSide = (LayoutAnchorSide)newElement; + } + + /// Removes any empty container not directly referenced by other layout items. + public void CollectGarbage() + { + var exitFlag = true; + + #region collect empty panes + + do + { + exitFlag = true; + + //for each content that references via PreviousContainer a disconnected Pane set the property to null + foreach (var content in this.Descendents().OfType().Where(c => c.PreviousContainer != null && + (c.PreviousContainer.Parent == null || c.PreviousContainer.Parent.Root != this))) + { + content.PreviousContainer = null; + } + + //for each pane that is empty + foreach (var emptyPane in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) + { + //...set null any reference coming from contents not yet hosted in a floating window + foreach (var contentReferencingEmptyPane in this.Descendents().OfType() + .Where(c => ((ILayoutPreviousContainer)c).PreviousContainer == emptyPane && !c.IsFloating)) + { + if (contentReferencingEmptyPane is LayoutAnchorable anchorable && + !anchorable.IsVisible) + continue; + + ((ILayoutPreviousContainer)contentReferencingEmptyPane).PreviousContainer = null; + contentReferencingEmptyPane.PreviousContainerIndex = -1; + } + + //...if this pane is the only documentpane present in the layout of the main window (not floating) then skip it + if (emptyPane is LayoutDocumentPane && + emptyPane.FindParent() == null && + this.Descendents().OfType().Count(c => c != emptyPane && c.FindParent() == null) == 0) + continue; + + //...if this empty pane is not referenced by anyone, then remove it from its parent container + if (!this.Descendents().OfType().Any(c => c.PreviousContainer == emptyPane)) + { + var parentGroup = emptyPane.Parent; + parentGroup.RemoveChild(emptyPane); + exitFlag = false; + break; + } + } + + if (!exitFlag) + { + //removes any empty anchorable pane group + foreach (var emptyLayoutAnchorablePaneGroup in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) + { + var parentGroup = emptyLayoutAnchorablePaneGroup.Parent; + parentGroup.RemoveChild(emptyLayoutAnchorablePaneGroup); + exitFlag = false; + break; + } + } + + if (!exitFlag) + { + //removes any empty layout panel + foreach (var emptyLayoutPanel in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) + { + var parentGroup = emptyLayoutPanel.Parent; + parentGroup.RemoveChild(emptyLayoutPanel); + exitFlag = false; + break; + } + foreach (var emptyLayoutDocumentPane in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) + { + var parentGroup = emptyLayoutDocumentPane.Parent; + if (!(parentGroup.Parent is LayoutDocumentFloatingWindow)) continue; + var index = RootPanel.IndexOfChild(this.Descendents().OfType().First()); + parentGroup.RemoveChild(emptyLayoutDocumentPane); + if (!this.Descendents().OfType().Any()) + { + // Now the last Pane container is deleted, at least one is required for documents to be added. + // We did not want to keep an empty window floating, but add a new one to the main window + RootPanel.Children.Insert(index < 0 ? 0 : index, emptyLayoutDocumentPane); + } + exitFlag = false; + break; + } + } + + if (!exitFlag) + { + //removes any empty floating window + foreach (var emptyLayoutFloatingWindow in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) + { + var parentGroup = emptyLayoutFloatingWindow.Parent; + parentGroup.RemoveChild(emptyLayoutFloatingWindow); + exitFlag = false; + break; + } + } + + if (!exitFlag) + { + //removes any empty anchor group + foreach (var emptyLayoutAnchorGroup in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) + { + if (!this.Descendents().OfType().Any(c => c.PreviousContainer == emptyLayoutAnchorGroup)) + { + var parentGroup = emptyLayoutAnchorGroup.Parent; + parentGroup.RemoveChild(emptyLayoutAnchorGroup); + exitFlag = false; + break; + } + } + } + } + while (!exitFlag); + + #endregion collect empty panes + + #region collapse single child anchorable pane groups + + do + { + exitFlag = true; + //for each pane that is empty + foreach (var paneGroupToCollapse in this.Descendents().OfType().Where(p => p.ChildrenCount == 1 && p.Children[0] is LayoutAnchorablePaneGroup).ToArray()) + { + var singleChild = paneGroupToCollapse.Children[0] as LayoutAnchorablePaneGroup; + paneGroupToCollapse.Orientation = singleChild.Orientation; + while (singleChild.ChildrenCount > 0) + paneGroupToCollapse.InsertChildAt(paneGroupToCollapse.ChildrenCount, singleChild.Children[0]); + paneGroupToCollapse.RemoveChild(singleChild); + exitFlag = false; + break; + } + } + while (!exitFlag); + + #endregion collapse single child anchorable pane groups + + #region collapse single child document pane groups + + do + { + exitFlag = true; + //for each pane that is empty + foreach (var paneGroupToCollapse in this.Descendents().OfType().Where(p => p.ChildrenCount == 1 && p.Children[0] is LayoutDocumentPaneGroup).ToArray()) + { + var singleChild = paneGroupToCollapse.Children[0] as LayoutDocumentPaneGroup; + paneGroupToCollapse.Orientation = singleChild.Orientation; + while (singleChild.ChildrenCount > 0) + paneGroupToCollapse.InsertChildAt(paneGroupToCollapse.ChildrenCount, singleChild.Children[0]); + paneGroupToCollapse.RemoveChild(singleChild); + exitFlag = false; + break; + } + } + while (!exitFlag); + + #endregion collapse single child document pane groups + + ////do + ////{ + //// exitFlag = true; + //// //for each panel that has only one child + //// foreach( var panelToCollapse in this.Descendents().OfType().Where( p => p.ChildrenCount == 1 && p.Children[ 0 ] is LayoutPanel ).ToArray() ) + //// { + //// var singleChild = panelToCollapse.Children[ 0 ] as LayoutPanel; + //// panelToCollapse.Orientation = singleChild.Orientation; + //// panelToCollapse.RemoveChild( singleChild ); + //// ILayoutPanelElement[] singleChildChildren = new ILayoutPanelElement[ singleChild.ChildrenCount ]; + //// singleChild.Children.CopyTo( singleChildChildren, 0 ); + //// while( singleChild.ChildrenCount > 0 ) + //// { + //// panelToCollapse.InsertChildAt( + //// panelToCollapse.ChildrenCount, singleChildChildren[ panelToCollapse.ChildrenCount ] ); + //// } + + //// exitFlag = false; + //// break; + //// } + ////} + ////while( !exitFlag ); + + // Update ActiveContent and LastFocusedDocument properties + UpdateActiveContentProperty(); + +#if DEBUG + Debug.Assert(!this.Descendents().OfType().Any(a => a.ChildrenCount == 0 && a.IsVisible)); + //DumpTree(true); +#if TRACE + RootPanel.ConsoleDump(4); +#endif +#endif + } + + #region IXmlSerializable interface members + + /// + XmlSchema IXmlSerializable.GetSchema() => null; + + /// + void IXmlSerializable.ReadXml(XmlReader reader) + { + reader.MoveToContent(); + if (reader.IsEmptyElement) + { + reader.Read(); + return; + } + + var layoutPanelElements = ReadRootPanel(reader, out var orientation, out var canDock); + if (layoutPanelElements != null) + { + RootPanel = new LayoutPanel { Orientation = orientation, CanDock = canDock }; + //Add all children to RootPanel + foreach (var panel in layoutPanelElements) RootPanel.Children.Add(panel); + } + + TopSide = new LayoutAnchorSide(); + if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, TopSide); + RightSide = new LayoutAnchorSide(); + if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, RightSide); + LeftSide = new LayoutAnchorSide(); + if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, LeftSide); + BottomSide = new LayoutAnchorSide(); + if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, BottomSide); + + FloatingWindows.Clear(); + var floatingWindows = ReadElementList(reader, true); + foreach (var floatingWindow in floatingWindows) FloatingWindows.Add((LayoutFloatingWindow)floatingWindow); + + Hidden.Clear(); + var hidden = ReadElementList(reader, false); + foreach (var hiddenObject in hidden) Hidden.Add((LayoutAnchorable)hiddenObject); + + //Read the closing end element of LayoutRoot + reader.ReadEndElement(); + } + + /// + void IXmlSerializable.WriteXml(XmlWriter writer) + { + writer.WriteStartElement(nameof(RootPanel)); + RootPanel?.WriteXml(writer); + writer.WriteEndElement(); + + writer.WriteStartElement(nameof(TopSide)); + TopSide?.WriteXml(writer); + writer.WriteEndElement(); + + writer.WriteStartElement(nameof(RightSide)); + RightSide?.WriteXml(writer); + writer.WriteEndElement(); + + writer.WriteStartElement(nameof(LeftSide)); + LeftSide?.WriteXml(writer); + writer.WriteEndElement(); + + writer.WriteStartElement(nameof(BottomSide)); + BottomSide?.WriteXml(writer); + writer.WriteEndElement(); + + // Write all floating windows (can be LayoutDocumentFloatingWindow or LayoutAnchorableFloatingWindow). + // To prevent "can not create instance of abstract type", the type is retrieved with GetType().Name + writer.WriteStartElement(nameof(FloatingWindows)); + foreach (var layoutFloatingWindow in FloatingWindows) + { + writer.WriteStartElement(layoutFloatingWindow.GetType().Name); + layoutFloatingWindow.WriteXml(writer); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + + writer.WriteStartElement(nameof(Hidden)); + foreach (var layoutAnchorable in Hidden) + { + writer.WriteStartElement(layoutAnchorable.GetType().Name); + layoutAnchorable.WriteXml(writer); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + + #endregion IXmlSerializable interface members + + #endregion Public Methods + + #region Internal Methods + + internal static Type FindType(string name) + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + foreach (var type in assembly.GetTypes()) + if (type.Name.Equals(name)) return type; + return null; + } + + internal void FireLayoutUpdated() => Updated?.Invoke(this, EventArgs.Empty); + + internal void OnLayoutElementAdded(LayoutElement element) => ElementAdded?.Invoke(this, new LayoutElementEventArgs(element)); + + internal void OnLayoutElementRemoved(LayoutElement element) + { + if (element.Descendents().OfType().Any(c => c == LastFocusedDocument)) + LastFocusedDocument = null; + if (element.Descendents().OfType().Any(c => c == ActiveContent)) + ActiveContent = null; + ElementRemoved?.Invoke(this, new LayoutElementEventArgs(element)); + } + + #endregion Internal Methods + + #region Private Methods + + private void _floatingWindows_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + var bNotifyChildren = false; + + if (e.OldItems != null && (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)) + { + foreach (LayoutFloatingWindow element in e.OldItems) + { + if (element.Parent != this) continue; + element.Parent = null; + bNotifyChildren = true; + } + } + + if (e.NewItems != null && (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)) + { + foreach (LayoutFloatingWindow element in e.NewItems) + { + element.Parent = this; + bNotifyChildren = true; + } + } + + // descendants of LayoutElement notify when their Children and ChildrenCount properties change + // https://github.com/xceedsoftware/wpftoolkit/issues/1313 + // + if (!bNotifyChildren) return; + switch (e.Action) + { + case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: + case System.Collections.Specialized.NotifyCollectionChangedAction.Add: + RaisePropertyChanged(nameof(Children)); + RaisePropertyChanged(nameof(ChildrenCount)); + break; + + case System.Collections.Specialized.NotifyCollectionChangedAction.Replace: + RaisePropertyChanged(nameof(Children)); + break; + } + } + + private void _hiddenAnchorables_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + var bNotifyChildren = false; + + if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) + { + if (e.OldItems != null) + { + foreach (LayoutAnchorable element in e.OldItems) + { + if (element.Parent != this) continue; + element.Parent = null; + bNotifyChildren = true; + } + } + } + + if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) + { + if (e.NewItems != null) + { + foreach (LayoutAnchorable element in e.NewItems) + { + if (element.Parent == this) continue; + element.Parent?.RemoveChild(element); + element.Parent = this; + bNotifyChildren = true; + } + } + } + + // descendants of LayoutElement notify when their Children and ChildrenCount properties change + // https://github.com/xceedsoftware/wpftoolkit/issues/1313 + // + if (!bNotifyChildren) return; + switch (e.Action) + { + case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: + case System.Collections.Specialized.NotifyCollectionChangedAction.Add: + RaisePropertyChanged(nameof(Children)); + RaisePropertyChanged(nameof(ChildrenCount)); + break; + + case System.Collections.Specialized.NotifyCollectionChangedAction.Replace: + RaisePropertyChanged(nameof(Children)); + break; + } + } + + private void InternalSetActiveContent(LayoutContent currentValue, LayoutContent newActiveContent) + { + RaisePropertyChanging(nameof(ActiveContent)); + if (currentValue != null && currentValue.IsActive) currentValue.IsActive = false; + _activeContent = new WeakReference(newActiveContent); + currentValue = ActiveContent; + if (currentValue != null && !currentValue.IsActive) currentValue.IsActive = true; + RaisePropertyChanged(nameof(ActiveContent)); + _activeContentSet = currentValue != null; + if (currentValue != null) + { + if (currentValue.Parent is LayoutDocumentPane || currentValue is LayoutDocument) + LastFocusedDocument = currentValue; + } + else + LastFocusedDocument = null; + } + + private void UpdateActiveContentProperty() + { + var activeContent = ActiveContent; + if (_activeContentSet && (activeContent == null || activeContent.Root != this)) + { + _activeContentSet = false; + InternalSetActiveContent(activeContent, null); + } + } + + private void FillLayoutAnchorSide(XmlReader reader, LayoutAnchorSide layoutAnchorSide) + { + var result = new List(); + + while (true) + { + //Read all layoutAnchorSide children + if (ReadElement(reader) is LayoutAnchorGroup element) result.Add(element); + else if (reader.NodeType == XmlNodeType.EndElement) break; + } + + reader.ReadEndElement(); + foreach (var las in result) + { + layoutAnchorSide.Children.Add(las); + } + } + + /// + /// Reads all properties of the and returns them. + /// + /// + /// + /// + /// + private List ReadRootPanel(XmlReader reader + , out Orientation orientation + , out bool canDock) + { + orientation = Orientation.Horizontal; + canDock = true; + + var result = new List(); + var startElementName = reader.LocalName; + reader.Read(); + if (reader.LocalName.Equals(startElementName) && reader.NodeType == XmlNodeType.EndElement) return null; + + while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); + + if (reader.LocalName.Equals(nameof(RootPanel))) + { + orientation = (Orientation)Enum.Parse(typeof(Orientation), reader.GetAttribute(nameof(Orientation)), true); + + var canDockStr = reader.GetAttribute("CanDock"); + if (canDockStr != null) + canDock = bool.Parse(canDockStr); + + reader.Read(); + while (true) + { + //Read all RootPanel children + if (ReadElement(reader) is ILayoutPanelElement element) result.Add(element); + else if (reader.NodeType == XmlNodeType.EndElement) break; + } + } + + reader.ReadEndElement(); + return result; + } + + private List ReadElementList(XmlReader reader, bool isFloatingWindow) + { + var resultList = new List(); + while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); + if (reader.NodeType == XmlNodeType.EndElement) return resultList; + + if (reader.IsEmptyElement) + { + reader.Read(); + return resultList; + } + + var startElementName = reader.LocalName; + reader.Read(); + if (reader.LocalName.Equals(startElementName) && reader.NodeType == XmlNodeType.EndElement) return null; + + while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); + + while (true) + { + if (isFloatingWindow) + { + if (!(ReadElement(reader) is LayoutFloatingWindow result)) break; + resultList.Add(result); + } + else + { + if (!(ReadElement(reader) is LayoutAnchorable result)) break; + resultList.Add(result); + } + } + + reader.ReadEndElement(); + return resultList; + } + + private object ReadElement(XmlReader reader) + { + while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); + if (reader.NodeType == XmlNodeType.EndElement) return null; + + Type typeToSerialize; + switch (reader.LocalName) + { + case nameof(LayoutAnchorablePaneGroup): + typeToSerialize = typeof(LayoutAnchorablePaneGroup); + break; + + case nameof(LayoutAnchorablePane): + typeToSerialize = typeof(LayoutAnchorablePane); + break; + + case nameof(LayoutAnchorable): + typeToSerialize = typeof(LayoutAnchorable); + break; + + case nameof(LayoutDocumentPaneGroup): + typeToSerialize = typeof(LayoutDocumentPaneGroup); + break; + + case nameof(LayoutDocumentPane): + typeToSerialize = typeof(LayoutDocumentPane); + break; + + case nameof(LayoutDocument): + typeToSerialize = typeof(LayoutDocument); + break; + + case nameof(LayoutAnchorGroup): + typeToSerialize = typeof(LayoutAnchorGroup); + break; + + case nameof(LayoutPanel): + typeToSerialize = typeof(LayoutPanel); + break; + + case nameof(LayoutDocumentFloatingWindow): + typeToSerialize = typeof(LayoutDocumentFloatingWindow); + break; + + case nameof(LayoutAnchorableFloatingWindow): + typeToSerialize = typeof(LayoutAnchorableFloatingWindow); + break; + + case nameof(LeftSide): + case nameof(RightSide): + case nameof(TopSide): + case nameof(BottomSide): + if (reader.IsEmptyElement) + { + reader.Read(); + return null; + } + return reader.Read(); + + default: + typeToSerialize = FindType(reader.LocalName); + if (typeToSerialize == null) + throw new ArgumentException("AvalonDock.LayoutRoot doesn't know how to deserialize " + reader.LocalName); + break; + } + XmlSerializer serializer = XmlSerializer.FromTypes(new[] { typeToSerialize })[0]; + return serializer.Deserialize(reader); + } + + #endregion Private Methods + + #region Diagnostic tools + +#if DEBUG + + public void DumpTree(bool shortPropertyNames = false) + { + void DumpElement(ILayoutElement element, StringBuilder indent, int childID, bool isLastChild) + { + Debug.Write($"{indent}{(indent.Length > 0 ? isLastChild ? " └─ " : " ├─ " : "")}{childID:D2} 0x{element.GetHashCode():X8} " + + $"{element.GetType().Name} {(shortPropertyNames ? "P" : "Parent")}:0x{element.Parent?.GetHashCode() ?? 0:X8} " + + $"{(shortPropertyNames ? "R" : "Root")}:0x{element.Root?.GetHashCode() ?? 0:X8}"); + if (!(element is ILayoutContainer containerElement)) + { + Debug.WriteLine(""); + return; + } + Debug.WriteLine($" {(shortPropertyNames ? "C" : "Children")}:{containerElement.ChildrenCount}"); + var nrChild = 0; + indent.Append(isLastChild ? " " : " │ "); + foreach (var child in containerElement.Children) + { + var lastChild = nrChild == containerElement.ChildrenCount - 1; + DumpElement(child, indent, nrChild++, lastChild); + } + indent.Remove(indent.Length - 3, 3); + } + + DumpElement(this, new StringBuilder(), 0, true); + } + +#endif + + #endregion Diagnostic tools + } } \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/Serialization/LayoutSerializer.cs b/source/Components/AvalonDock/Layout/Serialization/LayoutSerializer.cs index 7e7d0466..6c95346c 100644 --- a/source/Components/AvalonDock/Layout/Serialization/LayoutSerializer.cs +++ b/source/Components/AvalonDock/Layout/Serialization/LayoutSerializer.cs @@ -1,146 +1,149 @@ -/************************************************************************ - 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.Linq; - -namespace AvalonDock.Layout.Serialization -{ - /// Implements a base class for the layout serialization/deserialization of the docking framework. - public abstract class LayoutSerializer - { - #region fields - - private LayoutAnchorable[] _previousAnchorables = null; - private LayoutDocument[] _previousDocuments = null; - - #endregion fields - - #region Constructors - - /// - /// Class constructor from instance. - /// - /// - public LayoutSerializer(DockingManager manager) - { - Manager = manager ?? throw new ArgumentNullException(nameof(manager)); - _previousAnchorables = Manager.Layout.Descendents().OfType().ToArray(); - _previousDocuments = Manager.Layout.Descendents().OfType().ToArray(); - } - - #endregion Constructors - - #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 EventHandler LayoutSerializationCallback; - - #endregion Events - - #region Properties - - /// - /// Gets the root of the docking library. - /// - public DockingManager Manager { get; } - - #endregion Properties - - #region Methods - - protected virtual void FixupLayout(LayoutRoot layout) - { - //fix container panes - foreach (var lcToAttach in layout.Descendents().OfType().Where(lc => lc.PreviousContainerId != null)) - { - var paneContainerToAttach = layout.Descendents().OfType().FirstOrDefault(lps => lps.Id == lcToAttach.PreviousContainerId); - if (paneContainerToAttach == null) - throw new ArgumentException($"Unable to find a pane with id ='{lcToAttach.PreviousContainerId}'"); - lcToAttach.PreviousContainer = paneContainerToAttach as ILayoutContainer; - } - - //now fix the content of the layout anchorable contents - foreach (var lcToFix in layout.Descendents().OfType().Where(lc => lc.Content == null).ToArray()) - { - LayoutAnchorable previousAchorable = null; //try find the content in replaced layout - if (lcToFix.ContentId != null) - previousAchorable = _previousAnchorables.FirstOrDefault(a => a.ContentId == lcToFix.ContentId); - - if (LayoutSerializationCallback != null) - { - // Ask client application via callback if item should be deserialized - var args = new LayoutSerializationCallbackEventArgs(lcToFix, previousAchorable?.Content); - LayoutSerializationCallback(this, args); - if (args.Cancel) - lcToFix.Close(); - else if (args.Content != null) - lcToFix.Content = args.Content; - else if (args.Model.Content != null) - lcToFix.Hide(false); // hide layoutanchorable if client app supplied no content - } - else if (previousAchorable == null) // No Callback and no provious document -> skip this - lcToFix.Hide(false); - else - { // No Callback but previous anchoreable available -> load content from previous document - lcToFix.Content = previousAchorable.Content; - lcToFix.IconSource = previousAchorable.IconSource; - } - } - - //now fix the content of the layout document contents - foreach (var lcToFix in layout.Descendents().OfType().Where(lc => lc.Content == null).ToArray()) - { - LayoutDocument previousDocument = null; //try find the content in replaced layout - if (lcToFix.ContentId != null) - previousDocument = _previousDocuments.FirstOrDefault(a => a.ContentId == lcToFix.ContentId); - - if (LayoutSerializationCallback != null) - { - // Ask client application via callback if this realy should be deserialized - var args = new LayoutSerializationCallbackEventArgs(lcToFix, previousDocument?.Content); - LayoutSerializationCallback(this, args); - - if (args.Cancel) - lcToFix.Close(); - else if (args.Content != null) - lcToFix.Content = args.Content; - else if (args.Model.Content != null) // Close document if client app supplied no content - lcToFix.Close(); - } - else if (previousDocument == null) // No Callback and no provious document -> skip this - lcToFix.Close(); - else - { // No Callback but previous document available -> load content from previous document - lcToFix.Content = previousDocument.Content; - lcToFix.IconSource = previousDocument.IconSource; - } - } - - layout.CollectGarbage(); - } - - protected void StartDeserialization() - { - Manager.SuspendDocumentsSourceBinding = true; - Manager.SuspendAnchorablesSourceBinding = true; - } - - protected void EndDeserialization() - { - Manager.SuspendDocumentsSourceBinding = false; - Manager.SuspendAnchorablesSourceBinding = false; - } - - #endregion Methods - } +/************************************************************************ + 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.Linq; + +namespace AvalonDock.Layout.Serialization +{ + /// Implements a base class for the layout serialization/deserialization of the docking framework. + public abstract class LayoutSerializer + { + #region fields + + private LayoutAnchorable[] _previousAnchorables = null; + private LayoutDocument[] _previousDocuments = null; + + #endregion fields + + #region Constructors + + /// + /// Class constructor from instance. + /// + /// + public LayoutSerializer(DockingManager manager) + { + Manager = manager ?? throw new ArgumentNullException(nameof(manager)); + _previousAnchorables = Manager.Layout.Descendents().OfType().ToArray(); + _previousDocuments = Manager.Layout.Descendents().OfType().ToArray(); + } + + #endregion Constructors + + #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 EventHandler LayoutSerializationCallback; + + #endregion Events + + #region Properties + + /// + /// Gets the root of the docking library. + /// + public DockingManager Manager { get; } + + #endregion Properties + + #region Methods + + protected virtual void FixupLayout(LayoutRoot layout) + { + //fix container panes + foreach (var lcToAttach in layout.Descendents().OfType().Where(lc => lc.PreviousContainerId != null)) + { + var paneContainerToAttach = layout.Descendents().OfType().FirstOrDefault(lps => lps.Id == lcToAttach.PreviousContainerId); + if (paneContainerToAttach == null) + throw new ArgumentException($"Unable to find a pane with id ='{lcToAttach.PreviousContainerId}'"); + lcToAttach.PreviousContainer = paneContainerToAttach as ILayoutContainer; + } + + //now fix the content of the layout anchorable contents + foreach (var lcToFix in layout.Descendents().OfType().Where(lc => lc.Content == null).ToArray()) + { + LayoutAnchorable previousAchorable = null; //try find the content in replaced layout + if (lcToFix.ContentId != null) + previousAchorable = _previousAnchorables.FirstOrDefault(a => a.ContentId == lcToFix.ContentId); + + if(previousAchorable != null && previousAchorable.Title != null) + lcToFix.Title = previousAchorable.Title; + + if (LayoutSerializationCallback != null) + { + // Ask client application via callback if item should be deserialized + var args = new LayoutSerializationCallbackEventArgs(lcToFix, previousAchorable?.Content); + LayoutSerializationCallback(this, args); + if (args.Cancel) + lcToFix.Close(); + else if (args.Content != null) + lcToFix.Content = args.Content; + else if (args.Model.Content != null) + lcToFix.Hide(false); // hide layoutanchorable if client app supplied no content + } + else if (previousAchorable == null) // No Callback and no provious document -> skip this + lcToFix.Hide(false); + else + { // No Callback but previous anchoreable available -> load content from previous document + lcToFix.Content = previousAchorable.Content; + lcToFix.IconSource = previousAchorable.IconSource; + } + } + + //now fix the content of the layout document contents + foreach (var lcToFix in layout.Descendents().OfType().Where(lc => lc.Content == null).ToArray()) + { + LayoutDocument previousDocument = null; //try find the content in replaced layout + if (lcToFix.ContentId != null) + previousDocument = _previousDocuments.FirstOrDefault(a => a.ContentId == lcToFix.ContentId); + + if (LayoutSerializationCallback != null) + { + // Ask client application via callback if this realy should be deserialized + var args = new LayoutSerializationCallbackEventArgs(lcToFix, previousDocument?.Content); + LayoutSerializationCallback(this, args); + + if (args.Cancel) + lcToFix.Close(); + else if (args.Content != null) + lcToFix.Content = args.Content; + else if (args.Model.Content != null) // Close document if client app supplied no content + lcToFix.Close(); + } + else if (previousDocument == null) // No Callback and no provious document -> skip this + lcToFix.Close(); + else + { // No Callback but previous document available -> load content from previous document + lcToFix.Content = previousDocument.Content; + lcToFix.IconSource = previousDocument.IconSource; + } + } + + layout.CollectGarbage(); + } + + protected void StartDeserialization() + { + Manager.SuspendDocumentsSourceBinding = true; + Manager.SuspendAnchorablesSourceBinding = true; + } + + protected void EndDeserialization() + { + Manager.SuspendDocumentsSourceBinding = false; + Manager.SuspendAnchorablesSourceBinding = false; + } + + #endregion Methods + } } \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs b/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs index 438c472f..fbe67b05 100644 --- a/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs +++ b/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs @@ -1,130 +1,123 @@ -/************************************************************************ - 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.IO; -using System.Xml; -using System.Xml.Serialization; - -namespace AvalonDock.Layout.Serialization -{ - /// Implements a layout serialization/deserialization method of the docking framework. - public class XmlLayoutSerializer : LayoutSerializer - { - #region Constructors - - /// - /// Class constructor from instance. - /// - /// - public XmlLayoutSerializer(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 void Deserialize(System.IO.Stream stream) - { - try - { - StartDeserialization(); - var serializer = new XmlSerializer(typeof(LayoutRoot)); - var layout = serializer.Deserialize(stream) as LayoutRoot; - FixupLayout(layout); - Manager.Layout = layout; - } - finally - { - EndDeserialization(); - } - } - - /// Deserialize the layout a file from a . - /// - public void Deserialize(TextReader reader) - { - try - { - StartDeserialization(); - var serializer = new XmlSerializer(typeof(LayoutRoot)); - var layout = serializer.Deserialize(reader) as LayoutRoot; - FixupLayout(layout); - Manager.Layout = layout; - } - finally - { - EndDeserialization(); - } - } - - /// Deserialize the layout a file from a . - /// - public void Deserialize(XmlReader reader) - { - try - { - StartDeserialization(); - var serializer = new XmlSerializer(typeof(LayoutRoot)); - var layout = serializer.Deserialize(reader) as LayoutRoot; - FixupLayout(layout); - Manager.Layout = layout; - } - finally - { - EndDeserialization(); - } - } - - /// Deserialize the layout from a file using a . - /// - public void Deserialize(string filepath) - { - using (var stream = new StreamReader(filepath)) - Deserialize(stream); - } - - #endregion Public Methods - } +/************************************************************************ + 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.IO; +using System.Xml; +using System.Xml.Serialization; + +namespace AvalonDock.Layout.Serialization +{ + /// Implements a layout serialization/deserialization method of the docking framework. + public class XmlLayoutSerializer : LayoutSerializer + { + #region Constructors + + /// + /// Class constructor from instance. + /// + /// + public XmlLayoutSerializer(DockingManager manager) + : base(manager) + { + } + + #endregion Constructors + + #region Private Methods + + /// Function for LayoutRoot deserialization. + /// Desererialized LayoutRoot + private delegate LayoutRoot DeserelizeFunction(); + + /// Deserializes layout with the given function . + /// + private void DeserelizeCommon(DeserelizeFunction function) + { + try + { + StartDeserialization(); + var layout = function(); + FixupLayout(layout); + Manager.Layout = layout; + } + finally + { + EndDeserialization(); + } + } + #endregion Private Methods + + #region Public Methods + + readonly XmlSerializer _serializer = XmlSerializer.FromTypes(new[] { typeof(LayoutRoot) })[0]; + + /// Serialize the layout into a . + /// + public void Serialize(XmlWriter writer) + { + _serializer.Serialize(writer, Manager.Layout); + } + + /// Serialize the layout into a . + /// + public void Serialize(TextWriter writer) + { + _serializer.Serialize(writer, Manager.Layout); + } + + /// Serialize the layout into a . + /// + public void Serialize(Stream stream) + { + _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 void Deserialize(System.IO.Stream stream) + { + LayoutRoot function() => _serializer.Deserialize(stream) as LayoutRoot; + DeserelizeCommon(function); + } + + /// Deserialize the layout a file from a . + /// + public void Deserialize(TextReader reader) + { + LayoutRoot function() => _serializer.Deserialize(reader) as LayoutRoot; + DeserelizeCommon(function); + } + + /// Deserialize the layout a file from a . + /// + public void Deserialize(XmlReader reader) + { + LayoutRoot function() => _serializer.Deserialize(reader) as LayoutRoot; + DeserelizeCommon(function); + } + + /// Deserialize the layout from a file using a . + /// + public void Deserialize(string filepath) + { + using (var stream = new StreamReader(filepath, true)) + Deserialize(stream); ; + } + + #endregion Public Methods + } } \ No newline at end of file From 6287d5adb7fcf24b0246916de111f0c3abf5148e Mon Sep 17 00:00:00 2001 From: radvile Date: Fri, 12 Nov 2021 12:59:34 +0200 Subject: [PATCH 2/3] Revert because of the incorrect line format --- .../Layout/LayoutAnchorableFloatingWindow.cs | 390 ++-- .../Layout/LayoutDocumentFloatingWindow.cs | 360 +-- .../AvalonDock/Layout/LayoutFloatingWindow.cs | 144 +- .../AvalonDock/Layout/LayoutGroup.cs | 534 ++--- .../AvalonDock/Layout/LayoutRoot.cs | 1975 +++++++++-------- .../Layout/Serialization/LayoutSerializer.cs | 293 ++- .../Serialization/XmlLayoutSerializer.cs | 251 ++- 7 files changed, 1984 insertions(+), 1963 deletions(-) diff --git a/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs b/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs index 4f148b4c..7db59a02 100644 --- a/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs +++ b/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs @@ -1,196 +1,196 @@ -/************************************************************************ - 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.Diagnostics; -using System.Linq; -using System.Windows.Markup; -using System.Xml; -using System.Xml.Serialization; - -namespace AvalonDock.Layout -{ - /// Implements the model for a floating window control that can host an anchorable control (tool window) in a floating window. - [Serializable] - [ContentProperty(nameof(RootPanel))] - public class LayoutAnchorableFloatingWindow : LayoutFloatingWindow, ILayoutElementWithVisibility - { - #region fields - - private LayoutAnchorablePaneGroup _rootPanel; - - [NonSerialized] - private bool _isVisible = true; - - #endregion fields - - #region Events - - /// Event is invoked when the visibility of this object has changed. - public event EventHandler IsVisibleChanged; - - #endregion Events - - #region Properties - - public bool IsSinglePane => RootPanel != null && RootPanel.Descendents().OfType().Count(p => p.IsVisible) == 1; - - /// Gets/sets whether this object is in a state where it is visible in the UI or not. - [XmlIgnore] - public bool IsVisible - { - get => _isVisible; - private set - { - if (value == _isVisible) return; - RaisePropertyChanging(nameof(IsVisible)); - _isVisible = value; - RaisePropertyChanged(nameof(IsVisible)); - IsVisibleChanged?.Invoke(this, EventArgs.Empty); - } - } - - public LayoutAnchorablePaneGroup RootPanel - { - get => _rootPanel; - set - { - if (value == _rootPanel) return; - RaisePropertyChanging(nameof(RootPanel)); - if (_rootPanel != null) _rootPanel.ChildrenTreeChanged -= _rootPanel_ChildrenTreeChanged; - _rootPanel = value; - if (_rootPanel != null) - { - _rootPanel.Parent = this; - _rootPanel.ChildrenTreeChanged += _rootPanel_ChildrenTreeChanged; - } - - RaisePropertyChanged(nameof(RootPanel)); - RaisePropertyChanged(nameof(IsSinglePane)); - RaisePropertyChanged(nameof(SinglePane)); - RaisePropertyChanged(nameof(Children)); - RaisePropertyChanged(nameof(ChildrenCount)); - ((ILayoutElementWithVisibility)this).ComputeVisibility(); - } - } - - public ILayoutAnchorablePane SinglePane - { - get - { - if (!IsSinglePane) return null; - var singlePane = RootPanel.Descendents().OfType().Single(p => p.IsVisible); - singlePane.UpdateIsDirectlyHostedInFloatingWindow(); - return singlePane; - } - } - - #endregion Properties - - #region ILayoutElementWithVisibility Interface - - /// - void ILayoutElementWithVisibility.ComputeVisibility() => ComputeVisibility(); - - #endregion ILayoutElementWithVisibility Interface - - #region Overrides - - /// - public override IEnumerable Children - { - get { if (ChildrenCount == 1) yield return RootPanel; } - } - - /// - public override void RemoveChild(ILayoutElement element) - { - Debug.Assert(element == RootPanel && element != null); - RootPanel = null; - } - - /// - public override void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) - { - Debug.Assert(oldElement == RootPanel && oldElement != null); - RootPanel = newElement as LayoutAnchorablePaneGroup; - } - - /// - public override int ChildrenCount => RootPanel == null ? 0 : 1; - - /// - public override bool IsValid => RootPanel != null; - - /// - public override void ReadXml(XmlReader reader) - { - reader.MoveToContent(); - if (reader.IsEmptyElement) - { - reader.Read(); - ComputeVisibility(); - return; - } - - var localName = reader.LocalName; - reader.Read(); - - while (true) - { - if (reader.LocalName.Equals(localName) && reader.NodeType == XmlNodeType.EndElement) break; - - if (reader.NodeType == XmlNodeType.Whitespace) - { - reader.Read(); - continue; - } - - XmlSerializer serializer; - if (reader.LocalName.Equals(nameof(LayoutAnchorablePaneGroup))) - serializer = XmlSerializer.FromTypes(new[] { typeof(LayoutAnchorablePaneGroup) })[0]; - else - { - var type = LayoutRoot.FindType(reader.LocalName); - if (type == null) - throw new ArgumentException("AvalonDock.LayoutAnchorableFloatingWindow doesn't know how to deserialize " + reader.LocalName); - serializer = XmlSerializer.FromTypes(new[] { type })[0]; - } - RootPanel = (LayoutAnchorablePaneGroup)serializer.Deserialize(reader); - } - reader.ReadEndElement(); - } - -#if TRACE - /// - public override void ConsoleDump(int tab) - { - System.Diagnostics.Trace.Write(new string(' ', tab * 4)); - System.Diagnostics.Trace.WriteLine("FloatingAnchorableWindow()"); - - RootPanel.ConsoleDump(tab + 1); - } -#endif - - #endregion Overrides - - #region Private Methods - - private void _rootPanel_ChildrenTreeChanged(object sender, ChildrenTreeChangedEventArgs e) - { - RaisePropertyChanged(nameof(IsSinglePane)); - RaisePropertyChanged(nameof(SinglePane)); - } - - private void ComputeVisibility() => IsVisible = RootPanel != null && RootPanel.IsVisible; - - #endregion Private Methods - } +/************************************************************************ + 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.Diagnostics; +using System.Linq; +using System.Windows.Markup; +using System.Xml; +using System.Xml.Serialization; + +namespace AvalonDock.Layout +{ + /// Implements the model for a floating window control that can host an anchorable control (tool window) in a floating window. + [Serializable] + [ContentProperty(nameof(RootPanel))] + public class LayoutAnchorableFloatingWindow : LayoutFloatingWindow, ILayoutElementWithVisibility + { + #region fields + + private LayoutAnchorablePaneGroup _rootPanel; + + [NonSerialized] + private bool _isVisible = true; + + #endregion fields + + #region Events + + /// Event is invoked when the visibility of this object has changed. + public event EventHandler IsVisibleChanged; + + #endregion Events + + #region Properties + + public bool IsSinglePane => RootPanel != null && RootPanel.Descendents().OfType().Count(p => p.IsVisible) == 1; + + /// Gets/sets whether this object is in a state where it is visible in the UI or not. + [XmlIgnore] + public bool IsVisible + { + get => _isVisible; + private set + { + if (value == _isVisible) return; + RaisePropertyChanging(nameof(IsVisible)); + _isVisible = value; + RaisePropertyChanged(nameof(IsVisible)); + IsVisibleChanged?.Invoke(this, EventArgs.Empty); + } + } + + public LayoutAnchorablePaneGroup RootPanel + { + get => _rootPanel; + set + { + if (value == _rootPanel) return; + RaisePropertyChanging(nameof(RootPanel)); + if (_rootPanel != null) _rootPanel.ChildrenTreeChanged -= _rootPanel_ChildrenTreeChanged; + _rootPanel = value; + if (_rootPanel != null) + { + _rootPanel.Parent = this; + _rootPanel.ChildrenTreeChanged += _rootPanel_ChildrenTreeChanged; + } + + RaisePropertyChanged(nameof(RootPanel)); + RaisePropertyChanged(nameof(IsSinglePane)); + RaisePropertyChanged(nameof(SinglePane)); + RaisePropertyChanged(nameof(Children)); + RaisePropertyChanged(nameof(ChildrenCount)); + ((ILayoutElementWithVisibility)this).ComputeVisibility(); + } + } + + public ILayoutAnchorablePane SinglePane + { + get + { + if (!IsSinglePane) return null; + var singlePane = RootPanel.Descendents().OfType().Single(p => p.IsVisible); + singlePane.UpdateIsDirectlyHostedInFloatingWindow(); + return singlePane; + } + } + + #endregion Properties + + #region ILayoutElementWithVisibility Interface + + /// + void ILayoutElementWithVisibility.ComputeVisibility() => ComputeVisibility(); + + #endregion ILayoutElementWithVisibility Interface + + #region Overrides + + /// + public override IEnumerable Children + { + get { if (ChildrenCount == 1) yield return RootPanel; } + } + + /// + public override void RemoveChild(ILayoutElement element) + { + Debug.Assert(element == RootPanel && element != null); + RootPanel = null; + } + + /// + public override void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) + { + Debug.Assert(oldElement == RootPanel && oldElement != null); + RootPanel = newElement as LayoutAnchorablePaneGroup; + } + + /// + public override int ChildrenCount => RootPanel == null ? 0 : 1; + + /// + public override bool IsValid => RootPanel != null; + + /// + public override void ReadXml(XmlReader reader) + { + reader.MoveToContent(); + if (reader.IsEmptyElement) + { + reader.Read(); + ComputeVisibility(); + return; + } + + var localName = reader.LocalName; + reader.Read(); + + while (true) + { + if (reader.LocalName.Equals(localName) && reader.NodeType == XmlNodeType.EndElement) break; + + if (reader.NodeType == XmlNodeType.Whitespace) + { + reader.Read(); + continue; + } + + XmlSerializer serializer; + if (reader.LocalName.Equals(nameof(LayoutAnchorablePaneGroup))) + serializer = new XmlSerializer(typeof(LayoutAnchorablePaneGroup)); + else + { + var type = LayoutRoot.FindType(reader.LocalName); + if (type == null) + throw new ArgumentException("AvalonDock.LayoutAnchorableFloatingWindow doesn't know how to deserialize " + reader.LocalName); + serializer = new XmlSerializer(type); + } + RootPanel = (LayoutAnchorablePaneGroup)serializer.Deserialize(reader); + } + reader.ReadEndElement(); + } + +#if TRACE + /// + public override void ConsoleDump(int tab) + { + System.Diagnostics.Trace.Write(new string(' ', tab * 4)); + System.Diagnostics.Trace.WriteLine("FloatingAnchorableWindow()"); + + RootPanel.ConsoleDump(tab + 1); + } +#endif + + #endregion Overrides + + #region Private Methods + + private void _rootPanel_ChildrenTreeChanged(object sender, ChildrenTreeChangedEventArgs e) + { + RaisePropertyChanged(nameof(IsSinglePane)); + RaisePropertyChanged(nameof(SinglePane)); + } + + private void ComputeVisibility() => IsVisible = RootPanel != null && RootPanel.IsVisible; + + #endregion Private Methods + } } \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/LayoutDocumentFloatingWindow.cs b/source/Components/AvalonDock/Layout/LayoutDocumentFloatingWindow.cs index fa39b7e0..68d07d6a 100644 --- a/source/Components/AvalonDock/Layout/LayoutDocumentFloatingWindow.cs +++ b/source/Components/AvalonDock/Layout/LayoutDocumentFloatingWindow.cs @@ -1,181 +1,181 @@ -/************************************************************************ - 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.Diagnostics; -using System.Linq; -using System.Windows.Markup; -using System.Xml; -using System.Xml.Serialization; - -namespace AvalonDock.Layout -{ - /// Implements the layout model for the . - [ContentProperty(nameof(RootPanel))] - [Serializable] - public class LayoutDocumentFloatingWindow : LayoutFloatingWindow, ILayoutElementWithVisibility - { - #region fields - - private LayoutDocumentPaneGroup _rootPanel = null; - - [NonSerialized] - private bool _isVisible = true; - - #endregion fields - - public event EventHandler IsVisibleChanged; - - #region Properties - - #region RootPanel - - public LayoutDocumentPaneGroup RootPanel - { - get => _rootPanel; - set - { - if (_rootPanel == value) return; - if (_rootPanel != null) _rootPanel.ChildrenTreeChanged -= _rootPanel_ChildrenTreeChanged; - - _rootPanel = value; - if (_rootPanel != null) _rootPanel.Parent = this; - if (_rootPanel != null) _rootPanel.ChildrenTreeChanged += _rootPanel_ChildrenTreeChanged; - - RaisePropertyChanged(nameof(RootPanel)); - RaisePropertyChanged(nameof(IsSinglePane)); - RaisePropertyChanged(nameof(SinglePane)); - RaisePropertyChanged(nameof(Children)); - RaisePropertyChanged(nameof(ChildrenCount)); - ((ILayoutElementWithVisibility)this).ComputeVisibility(); - } - } - - private void _rootPanel_ChildrenTreeChanged(object sender, ChildrenTreeChangedEventArgs e) - { - RaisePropertyChanged(nameof(IsSinglePane)); - RaisePropertyChanged(nameof(SinglePane)); - } - - #endregion RootPanel - - #region IsSinglePane - - public bool IsSinglePane => RootPanel?.Descendents().OfType().Count(p => p.IsVisible) == 1; - - public LayoutDocumentPane SinglePane - { - get - { - if (!IsSinglePane) return null; - var singlePane = RootPanel.Descendents().OfType().Single(p => p.IsVisible); - //singlePane.UpdateIsDirectlyHostedInFloatingWindow(); - return singlePane; - } - } - - #endregion IsSinglePane - - [XmlIgnore] - public bool IsVisible - { - get => _isVisible; - private set - { - if (_isVisible == value) return; - RaisePropertyChanging(nameof(IsVisible)); - _isVisible = value; - RaisePropertyChanged(nameof(IsVisible)); - IsVisibleChanged?.Invoke(this, EventArgs.Empty); - } - } - - #endregion Properties - - #region Overrides - - /// - public override IEnumerable Children - { - get { if (ChildrenCount == 1) yield return RootPanel; } - } - - /// - public override void RemoveChild(ILayoutElement element) - { - Debug.Assert(element == RootPanel && element != null); - RootPanel = null; - } - - /// - public override void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) - { - Debug.Assert(oldElement == RootPanel && oldElement != null); - RootPanel = newElement as LayoutDocumentPaneGroup; - } - - /// - public override int ChildrenCount => RootPanel == null ? 0 : 1; - - void ILayoutElementWithVisibility.ComputeVisibility() => IsVisible = RootPanel != null && RootPanel.IsVisible; - - /// - public override bool IsValid => RootPanel != null; - - /// - public override void ReadXml(XmlReader reader) - { - reader.MoveToContent(); - if (reader.IsEmptyElement) - { - reader.Read(); - return; - } - - var localName = reader.LocalName; - reader.Read(); - - while (true) - { - if (reader.LocalName.Equals(localName) && reader.NodeType == XmlNodeType.EndElement) break; - if (reader.NodeType == XmlNodeType.Whitespace) - { - reader.Read(); - continue; - } - - XmlSerializer serializer; - if (reader.LocalName.Equals(nameof(LayoutDocument))) - serializer = XmlSerializer.FromTypes(new[] { typeof(LayoutDocument) })[0]; - else - { - var type = LayoutRoot.FindType(reader.LocalName); - if (type == null) throw new ArgumentException("AvalonDock.LayoutDocumentFloatingWindow doesn't know how to deserialize " + reader.LocalName); - serializer = XmlSerializer.FromTypes(new[] { type })[0]; - } - RootPanel = (LayoutDocumentPaneGroup)serializer.Deserialize(reader); - } - - reader.ReadEndElement(); - } - -#if TRACE - public override void ConsoleDump(int tab) - { - System.Diagnostics.Trace.Write(new string(' ', tab * 4)); - System.Diagnostics.Trace.WriteLine("FloatingDocumentWindow()"); - - RootPanel.ConsoleDump(tab + 1); - } -#endif - - #endregion Overrides - } +/************************************************************************ + 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.Diagnostics; +using System.Linq; +using System.Windows.Markup; +using System.Xml; +using System.Xml.Serialization; + +namespace AvalonDock.Layout +{ + /// Implements the layout model for the . + [ContentProperty(nameof(RootPanel))] + [Serializable] + public class LayoutDocumentFloatingWindow : LayoutFloatingWindow, ILayoutElementWithVisibility + { + #region fields + + private LayoutDocumentPaneGroup _rootPanel = null; + + [NonSerialized] + private bool _isVisible = true; + + #endregion fields + + public event EventHandler IsVisibleChanged; + + #region Properties + + #region RootPanel + + public LayoutDocumentPaneGroup RootPanel + { + get => _rootPanel; + set + { + if (_rootPanel == value) return; + if (_rootPanel != null) _rootPanel.ChildrenTreeChanged -= _rootPanel_ChildrenTreeChanged; + + _rootPanel = value; + if (_rootPanel != null) _rootPanel.Parent = this; + if (_rootPanel != null) _rootPanel.ChildrenTreeChanged += _rootPanel_ChildrenTreeChanged; + + RaisePropertyChanged(nameof(RootPanel)); + RaisePropertyChanged(nameof(IsSinglePane)); + RaisePropertyChanged(nameof(SinglePane)); + RaisePropertyChanged(nameof(Children)); + RaisePropertyChanged(nameof(ChildrenCount)); + ((ILayoutElementWithVisibility)this).ComputeVisibility(); + } + } + + private void _rootPanel_ChildrenTreeChanged(object sender, ChildrenTreeChangedEventArgs e) + { + RaisePropertyChanged(nameof(IsSinglePane)); + RaisePropertyChanged(nameof(SinglePane)); + } + + #endregion RootPanel + + #region IsSinglePane + + public bool IsSinglePane => RootPanel?.Descendents().OfType().Count(p => p.IsVisible) == 1; + + public LayoutDocumentPane SinglePane + { + get + { + if (!IsSinglePane) return null; + var singlePane = RootPanel.Descendents().OfType().Single(p => p.IsVisible); + //singlePane.UpdateIsDirectlyHostedInFloatingWindow(); + return singlePane; + } + } + + #endregion IsSinglePane + + [XmlIgnore] + public bool IsVisible + { + get => _isVisible; + private set + { + if (_isVisible == value) return; + RaisePropertyChanging(nameof(IsVisible)); + _isVisible = value; + RaisePropertyChanged(nameof(IsVisible)); + IsVisibleChanged?.Invoke(this, EventArgs.Empty); + } + } + + #endregion Properties + + #region Overrides + + /// + public override IEnumerable Children + { + get { if (ChildrenCount == 1) yield return RootPanel; } + } + + /// + public override void RemoveChild(ILayoutElement element) + { + Debug.Assert(element == RootPanel && element != null); + RootPanel = null; + } + + /// + public override void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) + { + Debug.Assert(oldElement == RootPanel && oldElement != null); + RootPanel = newElement as LayoutDocumentPaneGroup; + } + + /// + public override int ChildrenCount => RootPanel == null ? 0 : 1; + + void ILayoutElementWithVisibility.ComputeVisibility() => IsVisible = RootPanel != null && RootPanel.IsVisible; + + /// + public override bool IsValid => RootPanel != null; + + /// + public override void ReadXml(XmlReader reader) + { + reader.MoveToContent(); + if (reader.IsEmptyElement) + { + reader.Read(); + return; + } + + var localName = reader.LocalName; + reader.Read(); + + while (true) + { + if (reader.LocalName.Equals(localName) && reader.NodeType == XmlNodeType.EndElement) break; + if (reader.NodeType == XmlNodeType.Whitespace) + { + reader.Read(); + continue; + } + + XmlSerializer serializer; + if (reader.LocalName.Equals(nameof(LayoutDocument))) + serializer = new XmlSerializer(typeof(LayoutDocument)); + else + { + var type = LayoutRoot.FindType(reader.LocalName); + if (type == null) throw new ArgumentException("AvalonDock.LayoutDocumentFloatingWindow doesn't know how to deserialize " + reader.LocalName); + serializer = new XmlSerializer(type); + } + RootPanel = (LayoutDocumentPaneGroup)serializer.Deserialize(reader); + } + + reader.ReadEndElement(); + } + +#if TRACE + public override void ConsoleDump(int tab) + { + System.Diagnostics.Trace.Write(new string(' ', tab * 4)); + System.Diagnostics.Trace.WriteLine("FloatingDocumentWindow()"); + + RootPanel.ConsoleDump(tab + 1); + } +#endif + + #endregion Overrides + } } \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs b/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs index 72467ad8..0a479b47 100644 --- a/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs +++ b/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs @@ -1,73 +1,73 @@ -/************************************************************************ - 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.Xml; -using System.Xml.Schema; -using System.Xml.Serialization; - -namespace AvalonDock.Layout -{ - /// - /// Provides an abstract class to implement a concrete floating window layout model. - /// - [Serializable] - public abstract class LayoutFloatingWindow : LayoutElement, ILayoutContainer, IXmlSerializable - { - #region Properties - - /// Gets the list of based children below this object. - public abstract IEnumerable Children { get; } - - /// Gets the number of children below this object. - public abstract int ChildrenCount { get; } - - public abstract bool IsValid { get; } - - #endregion Properties - - #region Public Methods - - /// Remove the child element from the collection of children. - /// - public abstract void RemoveChild(ILayoutElement element); - - /// Replace the child element with a new child in the collection of children. - /// - /// - public abstract void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement); - - #region IXmlSerializable interface members - - /// - public XmlSchema GetSchema() - { - return null; - } - - /// - public abstract void ReadXml(XmlReader reader); - - /// - public virtual void WriteXml(XmlWriter writer) - { - foreach (var child in Children) - { - var type = child.GetType(); - var serializer = XmlSerializer.FromTypes(new[] { type })[0]; - serializer.Serialize(writer, child); - } - } - - #endregion IXmlSerializable interface members - - #endregion Public Methods - } +/************************************************************************ + 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.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; + +namespace AvalonDock.Layout +{ + /// + /// Provides an abstract class to implement a concrete floating window layout model. + /// + [Serializable] + public abstract class LayoutFloatingWindow : LayoutElement, ILayoutContainer, IXmlSerializable + { + #region Properties + + /// Gets the list of based children below this object. + public abstract IEnumerable Children { get; } + + /// Gets the number of children below this object. + public abstract int ChildrenCount { get; } + + public abstract bool IsValid { get; } + + #endregion Properties + + #region Public Methods + + /// Remove the child element from the collection of children. + /// + public abstract void RemoveChild(ILayoutElement element); + + /// Replace the child element with a new child in the collection of children. + /// + /// + public abstract void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement); + + #region IXmlSerializable interface members + + /// + public XmlSchema GetSchema() + { + return null; + } + + /// + public abstract void ReadXml(XmlReader reader); + + /// + public virtual void WriteXml(XmlWriter writer) + { + foreach (var child in Children) + { + var type = child.GetType(); + var serializer = new XmlSerializer(type); + serializer.Serialize(writer, child); + } + } + + #endregion IXmlSerializable interface members + + #endregion Public Methods + } } \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/LayoutGroup.cs b/source/Components/AvalonDock/Layout/LayoutGroup.cs index 3a6cdc60..98f70163 100644 --- a/source/Components/AvalonDock/Layout/LayoutGroup.cs +++ b/source/Components/AvalonDock/Layout/LayoutGroup.cs @@ -1,260 +1,276 @@ -/************************************************************************ - 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; -using System.Xml.Serialization; - -namespace AvalonDock.Layout -{ - /// - /// Provides a base class for layout anchorable (group and non-group) related classes - /// that implement the viewmodel aspect for layout anchorable controls. - /// - /// - [Serializable] - public abstract class LayoutGroup : LayoutGroupBase, ILayoutGroup, IXmlSerializable where T : class, ILayoutElement - { - #region fields - - private readonly ObservableCollection _children = new ObservableCollection(); - private bool _isVisible = true; - - #endregion fields - - #region Constructors - - /// Class constructor. - internal LayoutGroup() - { - _children.CollectionChanged += Children_CollectionChanged; - } - - #endregion Constructors - - #region Properties - - /// Gets a collection of children objects below this object. - public ObservableCollection Children => _children; - - /// Gets the number of of children objects below this object. - public int ChildrenCount => _children.Count; - - /// Gets a collection of based children objects below this object. - IEnumerable ILayoutContainer.Children => _children.Cast(); - - /// Gets whether this object is visible or not. - public bool IsVisible - { - get => _isVisible; - protected set - { - if (value == _isVisible) return; - RaisePropertyChanging(nameof(IsVisible)); - _isVisible = value; - OnIsVisibleChanged(); - RaisePropertyChanged(nameof(IsVisible)); - } - } - - #endregion Properties - - #region Public Methods - - /// - public void ComputeVisibility() => IsVisible = GetVisibility(); - - /// - public void MoveChild(int oldIndex, int newIndex) - { - if (oldIndex == newIndex) return; - _children.Move(oldIndex, newIndex); - ChildMoved(oldIndex, newIndex); - } - - /// - public void RemoveChildAt(int childIndex) - { - _children.RemoveAt(childIndex); - } - - /// - public int IndexOfChild(ILayoutElement element) - { - return _children.Cast().ToList().IndexOf(element); - } - - /// - public void InsertChildAt(int index, ILayoutElement element) - { - if (element is T t) - _children.Insert(index, t); - } - - /// - public void RemoveChild(ILayoutElement element) - { - if (element is T t) - _children.Remove(t); - } - - /// - public void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) - { - if (oldElement is T oldT && newElement is T newT) - { - var index = _children.IndexOf(oldT); - _children.Insert(index, newT); - _children.RemoveAt(index + 1); - } - } - - /// - public void ReplaceChildAt(int index, ILayoutElement element) - { - _children[index] = (T)element; - } - - /// - System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; - - /// - /// provides a standard overridable implementation for deriving classes. - public virtual void ReadXml(System.Xml.XmlReader reader) - { - reader.MoveToContent(); - if (reader.IsEmptyElement) - { - reader.Read(); - ComputeVisibility(); - return; - } - var localName = reader.LocalName; - reader.Read(); - while (true) - { - if (reader.LocalName == localName && reader.NodeType == System.Xml.XmlNodeType.EndElement) - break; - if (reader.NodeType == System.Xml.XmlNodeType.Whitespace) - { - reader.Read(); - continue; - } - string fullName = String.Format("{0}.{1}", GetType().Namespace, reader.LocalName); - Type typeForSerializer = Type.GetType(fullName); - - if (typeForSerializer == null) - typeForSerializer = FindType(reader.LocalName); - - if (typeForSerializer == null) - throw new ArgumentException("AvalonDock.LayoutGroup doesn't know how to deserialize " + reader.LocalName); - - XmlSerializer serializer = XmlSerializer.FromTypes(new[] { typeForSerializer })[0]; - Children.Add((T)serializer.Deserialize(reader)); - } - - reader.ReadEndElement(); - } - - /// - /// provides a standard overridable implementation for deriving classes. - public virtual void WriteXml(System.Xml.XmlWriter writer) - { - foreach (var child in Children) - { - var type = child.GetType(); - var serializer = XmlSerializer.FromTypes(new[] { type })[0]; - serializer.Serialize(writer, child); - } - } - - #endregion Public Methods - - #region Internal Methods - - protected virtual void OnIsVisibleChanged() - { - UpdateParentVisibility(); - } - - protected abstract bool GetVisibility(); - - protected virtual void ChildMoved(int oldIndex, int newIndex) - { - } - - #endregion Internal Methods - - #region Overrides - - /// - protected override void OnParentChanged(ILayoutContainer oldValue, ILayoutContainer newValue) - { - base.OnParentChanged(oldValue, newValue); - ComputeVisibility(); - } - - #endregion Overrides - - #region Private Methods - - private void Children_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace) - { - if (e.OldItems != null) - { - foreach (LayoutElement element in e.OldItems) - if (element.Parent == this || e.Action == NotifyCollectionChangedAction.Remove) element.Parent = null; - } - } - if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Replace) - { - if (e.NewItems != null) - { - foreach (LayoutElement element in e.NewItems) - { - if (element.Parent == this) continue; - element.Parent?.RemoveChild(element); - element.Parent = this; - } - } - } - - ComputeVisibility(); - OnChildrenCollectionChanged(); - - if (e.Action == NotifyCollectionChangedAction.Add) - // #81 - Make parents update their children up the tree. Otherwise, they will not be redrawn. - RaiseChildrenTreeChanged(); - else - NotifyChildrenTreeChanged(ChildrenTreeChange.DirectChildrenChanged); - RaisePropertyChanged(nameof(ChildrenCount)); - } - - private void UpdateParentVisibility() - { - if (Parent is ILayoutElementWithVisibility parentPane) - parentPane.ComputeVisibility(); - } - - private Type FindType(string name) - { - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) - foreach (var t in a.GetTypes()) - if (t.Name.Equals(name)) return t; - return null; - } - - #endregion Private Methods - } +/************************************************************************ + 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.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using System.Xml.Serialization; + +namespace AvalonDock.Layout +{ + /// + /// Provides a base class for layout anchorable (group and non-group) related classes + /// that implement the viewmodel aspect for layout anchorable controls. + /// + /// + [Serializable] + public abstract class LayoutGroup : LayoutGroupBase, ILayoutGroup, IXmlSerializable where T : class, ILayoutElement + { + #region fields + + private readonly ObservableCollection _children = new ObservableCollection(); + private bool _isVisible = true; + + #endregion fields + + #region Constructors + + /// Class constructor. + internal LayoutGroup() + { + _children.CollectionChanged += Children_CollectionChanged; + } + + #endregion Constructors + + #region Properties + + /// Gets a collection of children objects below this object. + public ObservableCollection Children => _children; + + /// Gets the number of of children objects below this object. + public int ChildrenCount => _children.Count; + + /// Gets a collection of based children objects below this object. + IEnumerable ILayoutContainer.Children => _children.Cast(); + + /// Gets whether this object is visible or not. + public bool IsVisible + { + get => _isVisible; + protected set + { + if (value == _isVisible) return; + RaisePropertyChanging(nameof(IsVisible)); + _isVisible = value; + OnIsVisibleChanged(); + RaisePropertyChanged(nameof(IsVisible)); + } + } + + #endregion Properties + + #region Public Methods + + /// + public void ComputeVisibility() => IsVisible = GetVisibility(); + + /// + public void MoveChild(int oldIndex, int newIndex) + { + if (oldIndex == newIndex) return; + _children.Move(oldIndex, newIndex); + ChildMoved(oldIndex, newIndex); + } + + /// + public void RemoveChildAt(int childIndex) + { + _children.RemoveAt(childIndex); + } + + /// + public int IndexOfChild(ILayoutElement element) + { + return _children.Cast().ToList().IndexOf(element); + } + + /// + public void InsertChildAt(int index, ILayoutElement element) + { + if (element is T t) + _children.Insert(index, t); + } + + /// + public void RemoveChild(ILayoutElement element) + { + if (element is T t) + _children.Remove(t); + } + + /// + public void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) + { + if (oldElement is T oldT && newElement is T newT) + { + var index = _children.IndexOf(oldT); + _children.Insert(index, newT); + _children.RemoveAt(index + 1); + } + } + + /// + public void ReplaceChildAt(int index, ILayoutElement element) + { + _children[index] = (T)element; + } + + /// + System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; + + /// + /// provides a standard overridable implementation for deriving classes. + public virtual void ReadXml(System.Xml.XmlReader reader) + { + reader.MoveToContent(); + if (reader.IsEmptyElement) + { + reader.Read(); + ComputeVisibility(); + return; + } + var localName = reader.LocalName; + reader.Read(); + while (true) + { + if (reader.LocalName == localName && reader.NodeType == System.Xml.XmlNodeType.EndElement) + break; + if (reader.NodeType == System.Xml.XmlNodeType.Whitespace) + { + reader.Read(); + continue; + } + + XmlSerializer serializer = null; + if (reader.LocalName == nameof(LayoutAnchorablePaneGroup)) + serializer = new XmlSerializer(typeof(LayoutAnchorablePaneGroup)); + else if (reader.LocalName == nameof(LayoutAnchorablePane)) + serializer = new XmlSerializer(typeof(LayoutAnchorablePane)); + else if (reader.LocalName == nameof(LayoutAnchorable)) + serializer = new XmlSerializer(typeof(LayoutAnchorable)); + else if (reader.LocalName == nameof(LayoutDocumentPaneGroup)) + serializer = new XmlSerializer(typeof(LayoutDocumentPaneGroup)); + else if (reader.LocalName == nameof(LayoutDocumentPane)) + serializer = new XmlSerializer(typeof(LayoutDocumentPane)); + else if (reader.LocalName == nameof(LayoutDocument)) + serializer = new XmlSerializer(typeof(LayoutDocument)); + else if (reader.LocalName == nameof(LayoutAnchorGroup)) + serializer = new XmlSerializer(typeof(LayoutAnchorGroup)); + else if (reader.LocalName == nameof(LayoutPanel)) + serializer = new XmlSerializer(typeof(LayoutPanel)); + else + { + var type = FindType(reader.LocalName); + if (type == null) + throw new ArgumentException("AvalonDock.LayoutGroup doesn't know how to deserialize " + reader.LocalName); + serializer = new XmlSerializer(type); + } + + Children.Add((T)serializer.Deserialize(reader)); + } + + reader.ReadEndElement(); + } + + /// + /// provides a standard overridable implementation for deriving classes. + public virtual void WriteXml(System.Xml.XmlWriter writer) + { + foreach (var child in Children) + { + var type = child.GetType(); + var serializer = new XmlSerializer(type); + serializer.Serialize(writer, child); + } + } + + #endregion Public Methods + + #region Internal Methods + + protected virtual void OnIsVisibleChanged() + { + UpdateParentVisibility(); + } + + protected abstract bool GetVisibility(); + + protected virtual void ChildMoved(int oldIndex, int newIndex) + { + } + + #endregion Internal Methods + + #region Overrides + + /// + protected override void OnParentChanged(ILayoutContainer oldValue, ILayoutContainer newValue) + { + base.OnParentChanged(oldValue, newValue); + ComputeVisibility(); + } + + #endregion Overrides + + #region Private Methods + + private void Children_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace) + { + if (e.OldItems != null) + { + foreach (LayoutElement element in e.OldItems) + if (element.Parent == this || e.Action == NotifyCollectionChangedAction.Remove) element.Parent = null; + } + } + if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Replace) + { + if (e.NewItems != null) + { + foreach (LayoutElement element in e.NewItems) + { + if (element.Parent == this) continue; + element.Parent?.RemoveChild(element); + element.Parent = this; + } + } + } + + ComputeVisibility(); + OnChildrenCollectionChanged(); + + if (e.Action == NotifyCollectionChangedAction.Add) + // #81 - Make parents update their children up the tree. Otherwise, they will not be redrawn. + RaiseChildrenTreeChanged(); + else + NotifyChildrenTreeChanged(ChildrenTreeChange.DirectChildrenChanged); + RaisePropertyChanged(nameof(ChildrenCount)); + } + + private void UpdateParentVisibility() + { + if (Parent is ILayoutElementWithVisibility parentPane) + parentPane.ComputeVisibility(); + } + + private Type FindType(string name) + { + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + foreach (var t in a.GetTypes()) + if (t.Name.Equals(name)) return t; + return null; + } + + #endregion Private Methods + } } \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/LayoutRoot.cs b/source/Components/AvalonDock/Layout/LayoutRoot.cs index 5dc4e595..2cad3519 100644 --- a/source/Components/AvalonDock/Layout/LayoutRoot.cs +++ b/source/Components/AvalonDock/Layout/LayoutRoot.cs @@ -1,988 +1,989 @@ -/************************************************************************ - 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.Collections.ObjectModel; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Windows.Controls; -using System.Windows.Markup; -using System.Xml; -using System.Xml.Schema; -using System.Xml.Serialization; - -namespace AvalonDock.Layout -{ - /// - /// Implements the root of the layout viewmodel (see property). - /// This root includes a property for binding content, side panel properties - /// and many other layout related root items. - /// - /// This class implements and - /// to support direct UI binding scenarios - /// with view updates supported. - /// - [ContentProperty(nameof(RootPanel))] - [Serializable] - public class LayoutRoot : LayoutElement, ILayoutContainer, ILayoutRoot, IXmlSerializable - { - #region fields - - private LayoutPanel _rootPanel; - private LayoutAnchorSide _topSide = null; - private LayoutAnchorSide _rightSide; - private LayoutAnchorSide _leftSide = null; - private LayoutAnchorSide _bottomSide = null; - - private ObservableCollection _floatingWindows = null; - private ObservableCollection _hiddenAnchorables = null; - - [field: NonSerialized] - private WeakReference _activeContent = null; - - private bool _activeContentSet = false; - - [field: NonSerialized] - private WeakReference _lastFocusedDocument = null; - - [NonSerialized] - private DockingManager _manager = null; - - #endregion fields - - #region Constructors - - /// Standard class constructor - public LayoutRoot() - { - RightSide = new LayoutAnchorSide(); - LeftSide = new LayoutAnchorSide(); - TopSide = new LayoutAnchorSide(); - BottomSide = new LayoutAnchorSide(); - RootPanel = new LayoutPanel(new LayoutDocumentPane()); - } - - #endregion Constructors - - #region Events - - /// - /// Raised when the layout is updated. This event is raised via method - /// when a parent of a LayoutElement has changed. - /// - public event EventHandler Updated; - - /// Raised when an element is added to the layout. - public event EventHandler ElementAdded; - - /// Raised when an element is removed from the layout. - public event EventHandler ElementRemoved; - - #endregion Events - - #region Properties - - /// Gets/sets the root layout panel that contains the . - public LayoutPanel RootPanel - { - get => _rootPanel; - set - { - if (_rootPanel == value) return; - RaisePropertyChanging(nameof(RootPanel)); - if (_rootPanel != null && _rootPanel.Parent == this) _rootPanel.Parent = null; - _rootPanel = value ?? new LayoutPanel(new LayoutDocumentPane()); - _rootPanel.Parent = this; - RaisePropertyChanged(nameof(RootPanel)); - } - } - - /// Gets or sets the top side of the layout root. - public LayoutAnchorSide TopSide - { - get => _topSide; - set - { - if (_topSide == value) return; - RaisePropertyChanging(nameof(TopSide)); - _topSide = value; - if (_topSide != null) _topSide.Parent = this; - RaisePropertyChanged(nameof(TopSide)); - } - } - - /// Gets or sets the right side of the layout root. - public LayoutAnchorSide RightSide - { - get => _rightSide; - set - { - if (_rightSide == value) return; - RaisePropertyChanging(nameof(RightSide)); - _rightSide = value; - if (_rightSide != null) _rightSide.Parent = this; - RaisePropertyChanged(nameof(RightSide)); - } - } - - /// Gets or sets the left side of the layout root. - public LayoutAnchorSide LeftSide - { - get => _leftSide; - set - { - if (value == _leftSide) return; - RaisePropertyChanging(nameof(LeftSide)); - _leftSide = value; - if (_leftSide != null) _leftSide.Parent = this; - RaisePropertyChanged(nameof(LeftSide)); - } - } - - /// Gets or sets the bottom side of the layout root. - public LayoutAnchorSide BottomSide - { - get => _bottomSide; - set - { - if (value == _bottomSide) return; - RaisePropertyChanging(nameof(BottomSide)); - _bottomSide = value; - if (_bottomSide != null) _bottomSide.Parent = this; - RaisePropertyChanged(nameof(BottomSide)); - } - } - - /// Gets the floating windows that are part of this layout. - public ObservableCollection FloatingWindows - { - get - { - if (_floatingWindows == null) - { - _floatingWindows = new ObservableCollection(); - _floatingWindows.CollectionChanged += _floatingWindows_CollectionChanged; - } - - return _floatingWindows; - } - } - - /// Gets the hidden anchorables in the layout. - public ObservableCollection Hidden - { - get - { - if (_hiddenAnchorables == null) - { - _hiddenAnchorables = new ObservableCollection(); - _hiddenAnchorables.CollectionChanged += _hiddenAnchorables_CollectionChanged; - } - - return _hiddenAnchorables; - } - } - - #region Children - - /// Gets the child elements of the layout root. - public IEnumerable Children - { - get - { - if (RootPanel != null) - yield return RootPanel; - if (_floatingWindows != null) - { - foreach (var floatingWindow in _floatingWindows) - yield return floatingWindow; - } - if (TopSide != null) - yield return TopSide; - if (RightSide != null) - yield return RightSide; - if (BottomSide != null) - yield return BottomSide; - if (LeftSide != null) - yield return LeftSide; - if (_hiddenAnchorables != null) - { - foreach (var hiddenAnchorable in _hiddenAnchorables) - yield return hiddenAnchorable; - } - } - } - - /// Gets the number of child elements of the layout root. - public int ChildrenCount => 5 + (_floatingWindows?.Count ?? 0) + (_hiddenAnchorables?.Count ?? 0); - - #endregion Children - - /// Gets the active LayoutContent-derived element. - [XmlIgnore] - public LayoutContent ActiveContent - { - get - { - return _activeContent.GetValueOrDefault(); - } - set - { - var currentValue = ActiveContent; - if (currentValue != value) - { - InternalSetActiveContent(currentValue, value); - } - } - } - - [XmlIgnore] - public LayoutContent LastFocusedDocument - { - get => _lastFocusedDocument.GetValueOrDefault(); - private set - { - var currentValue = LastFocusedDocument; - if (currentValue != value) - { - RaisePropertyChanging(nameof(LastFocusedDocument)); - if (currentValue != null) currentValue.IsLastFocusedDocument = false; - _lastFocusedDocument = new WeakReference(value); - currentValue = LastFocusedDocument; - if (currentValue != null) currentValue.IsLastFocusedDocument = true; - RaisePropertyChanged(nameof(LastFocusedDocument)); - } - } - } - - /// Gets/sets the docking manager root control for this library. - [XmlIgnore] - public DockingManager Manager - { - get => _manager; - internal set - { - if (value == _manager) return; - RaisePropertyChanging(nameof(Manager)); - _manager = value; - RaisePropertyChanged(nameof(Manager)); - } - } - - #endregion Properties - - #region Overrides - -#if TRACE - public override void ConsoleDump(int tab) - { - System.Diagnostics.Trace.Write(new string(' ', tab * 4)); - System.Diagnostics.Trace.WriteLine("RootPanel()"); - - RootPanel.ConsoleDump(tab + 1); - - System.Diagnostics.Trace.Write(new string(' ', tab * 4)); - System.Diagnostics.Trace.WriteLine("FloatingWindows()"); - - foreach (var fw in FloatingWindows) - fw.ConsoleDump(tab + 1); - - System.Diagnostics.Trace.Write(new string(' ', tab * 4)); - System.Diagnostics.Trace.WriteLine("Hidden()"); - - foreach (var hidden in Hidden) - hidden.ConsoleDump(tab + 1); - } -#endif - - #endregion Overrides - - #region Public Methods - - public void RemoveChild(ILayoutElement element) - { - if (element == RootPanel) - RootPanel = null; - else if (_floatingWindows != null && _floatingWindows.Contains(element)) - _floatingWindows.Remove(element as LayoutFloatingWindow); - else if (_hiddenAnchorables != null && _hiddenAnchorables.Contains(element)) - _hiddenAnchorables.Remove(element as LayoutAnchorable); - else if (element == TopSide) - TopSide = null; - else if (element == RightSide) - RightSide = null; - else if (element == BottomSide) - BottomSide = null; - else if (element == LeftSide) - LeftSide = null; - } - - public void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) - { - if (oldElement == RootPanel) - RootPanel = (LayoutPanel)newElement; - else if (_floatingWindows != null && _floatingWindows.Contains(oldElement)) - { - var index = _floatingWindows.IndexOf(oldElement as LayoutFloatingWindow); - _floatingWindows.Remove(oldElement as LayoutFloatingWindow); - _floatingWindows.Insert(index, newElement as LayoutFloatingWindow); - } - else if (_hiddenAnchorables != null && _hiddenAnchorables.Contains(oldElement)) - { - var index = _hiddenAnchorables.IndexOf(oldElement as LayoutAnchorable); - _hiddenAnchorables.Remove(oldElement as LayoutAnchorable); - _hiddenAnchorables.Insert(index, newElement as LayoutAnchorable); - } - else if (oldElement == TopSide) - TopSide = (LayoutAnchorSide)newElement; - else if (oldElement == RightSide) - RightSide = (LayoutAnchorSide)newElement; - else if (oldElement == BottomSide) - BottomSide = (LayoutAnchorSide)newElement; - else if (oldElement == LeftSide) - LeftSide = (LayoutAnchorSide)newElement; - } - - /// Removes any empty container not directly referenced by other layout items. - public void CollectGarbage() - { - var exitFlag = true; - - #region collect empty panes - - do - { - exitFlag = true; - - //for each content that references via PreviousContainer a disconnected Pane set the property to null - foreach (var content in this.Descendents().OfType().Where(c => c.PreviousContainer != null && - (c.PreviousContainer.Parent == null || c.PreviousContainer.Parent.Root != this))) - { - content.PreviousContainer = null; - } - - //for each pane that is empty - foreach (var emptyPane in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) - { - //...set null any reference coming from contents not yet hosted in a floating window - foreach (var contentReferencingEmptyPane in this.Descendents().OfType() - .Where(c => ((ILayoutPreviousContainer)c).PreviousContainer == emptyPane && !c.IsFloating)) - { - if (contentReferencingEmptyPane is LayoutAnchorable anchorable && - !anchorable.IsVisible) - continue; - - ((ILayoutPreviousContainer)contentReferencingEmptyPane).PreviousContainer = null; - contentReferencingEmptyPane.PreviousContainerIndex = -1; - } - - //...if this pane is the only documentpane present in the layout of the main window (not floating) then skip it - if (emptyPane is LayoutDocumentPane && - emptyPane.FindParent() == null && - this.Descendents().OfType().Count(c => c != emptyPane && c.FindParent() == null) == 0) - continue; - - //...if this empty pane is not referenced by anyone, then remove it from its parent container - if (!this.Descendents().OfType().Any(c => c.PreviousContainer == emptyPane)) - { - var parentGroup = emptyPane.Parent; - parentGroup.RemoveChild(emptyPane); - exitFlag = false; - break; - } - } - - if (!exitFlag) - { - //removes any empty anchorable pane group - foreach (var emptyLayoutAnchorablePaneGroup in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) - { - var parentGroup = emptyLayoutAnchorablePaneGroup.Parent; - parentGroup.RemoveChild(emptyLayoutAnchorablePaneGroup); - exitFlag = false; - break; - } - } - - if (!exitFlag) - { - //removes any empty layout panel - foreach (var emptyLayoutPanel in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) - { - var parentGroup = emptyLayoutPanel.Parent; - parentGroup.RemoveChild(emptyLayoutPanel); - exitFlag = false; - break; - } - foreach (var emptyLayoutDocumentPane in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) - { - var parentGroup = emptyLayoutDocumentPane.Parent; - if (!(parentGroup.Parent is LayoutDocumentFloatingWindow)) continue; - var index = RootPanel.IndexOfChild(this.Descendents().OfType().First()); - parentGroup.RemoveChild(emptyLayoutDocumentPane); - if (!this.Descendents().OfType().Any()) - { - // Now the last Pane container is deleted, at least one is required for documents to be added. - // We did not want to keep an empty window floating, but add a new one to the main window - RootPanel.Children.Insert(index < 0 ? 0 : index, emptyLayoutDocumentPane); - } - exitFlag = false; - break; - } - } - - if (!exitFlag) - { - //removes any empty floating window - foreach (var emptyLayoutFloatingWindow in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) - { - var parentGroup = emptyLayoutFloatingWindow.Parent; - parentGroup.RemoveChild(emptyLayoutFloatingWindow); - exitFlag = false; - break; - } - } - - if (!exitFlag) - { - //removes any empty anchor group - foreach (var emptyLayoutAnchorGroup in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) - { - if (!this.Descendents().OfType().Any(c => c.PreviousContainer == emptyLayoutAnchorGroup)) - { - var parentGroup = emptyLayoutAnchorGroup.Parent; - parentGroup.RemoveChild(emptyLayoutAnchorGroup); - exitFlag = false; - break; - } - } - } - } - while (!exitFlag); - - #endregion collect empty panes - - #region collapse single child anchorable pane groups - - do - { - exitFlag = true; - //for each pane that is empty - foreach (var paneGroupToCollapse in this.Descendents().OfType().Where(p => p.ChildrenCount == 1 && p.Children[0] is LayoutAnchorablePaneGroup).ToArray()) - { - var singleChild = paneGroupToCollapse.Children[0] as LayoutAnchorablePaneGroup; - paneGroupToCollapse.Orientation = singleChild.Orientation; - while (singleChild.ChildrenCount > 0) - paneGroupToCollapse.InsertChildAt(paneGroupToCollapse.ChildrenCount, singleChild.Children[0]); - paneGroupToCollapse.RemoveChild(singleChild); - exitFlag = false; - break; - } - } - while (!exitFlag); - - #endregion collapse single child anchorable pane groups - - #region collapse single child document pane groups - - do - { - exitFlag = true; - //for each pane that is empty - foreach (var paneGroupToCollapse in this.Descendents().OfType().Where(p => p.ChildrenCount == 1 && p.Children[0] is LayoutDocumentPaneGroup).ToArray()) - { - var singleChild = paneGroupToCollapse.Children[0] as LayoutDocumentPaneGroup; - paneGroupToCollapse.Orientation = singleChild.Orientation; - while (singleChild.ChildrenCount > 0) - paneGroupToCollapse.InsertChildAt(paneGroupToCollapse.ChildrenCount, singleChild.Children[0]); - paneGroupToCollapse.RemoveChild(singleChild); - exitFlag = false; - break; - } - } - while (!exitFlag); - - #endregion collapse single child document pane groups - - ////do - ////{ - //// exitFlag = true; - //// //for each panel that has only one child - //// foreach( var panelToCollapse in this.Descendents().OfType().Where( p => p.ChildrenCount == 1 && p.Children[ 0 ] is LayoutPanel ).ToArray() ) - //// { - //// var singleChild = panelToCollapse.Children[ 0 ] as LayoutPanel; - //// panelToCollapse.Orientation = singleChild.Orientation; - //// panelToCollapse.RemoveChild( singleChild ); - //// ILayoutPanelElement[] singleChildChildren = new ILayoutPanelElement[ singleChild.ChildrenCount ]; - //// singleChild.Children.CopyTo( singleChildChildren, 0 ); - //// while( singleChild.ChildrenCount > 0 ) - //// { - //// panelToCollapse.InsertChildAt( - //// panelToCollapse.ChildrenCount, singleChildChildren[ panelToCollapse.ChildrenCount ] ); - //// } - - //// exitFlag = false; - //// break; - //// } - ////} - ////while( !exitFlag ); - - // Update ActiveContent and LastFocusedDocument properties - UpdateActiveContentProperty(); - -#if DEBUG - Debug.Assert(!this.Descendents().OfType().Any(a => a.ChildrenCount == 0 && a.IsVisible)); - //DumpTree(true); -#if TRACE - RootPanel.ConsoleDump(4); -#endif -#endif - } - - #region IXmlSerializable interface members - - /// - XmlSchema IXmlSerializable.GetSchema() => null; - - /// - void IXmlSerializable.ReadXml(XmlReader reader) - { - reader.MoveToContent(); - if (reader.IsEmptyElement) - { - reader.Read(); - return; - } - - var layoutPanelElements = ReadRootPanel(reader, out var orientation, out var canDock); - if (layoutPanelElements != null) - { - RootPanel = new LayoutPanel { Orientation = orientation, CanDock = canDock }; - //Add all children to RootPanel - foreach (var panel in layoutPanelElements) RootPanel.Children.Add(panel); - } - - TopSide = new LayoutAnchorSide(); - if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, TopSide); - RightSide = new LayoutAnchorSide(); - if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, RightSide); - LeftSide = new LayoutAnchorSide(); - if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, LeftSide); - BottomSide = new LayoutAnchorSide(); - if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, BottomSide); - - FloatingWindows.Clear(); - var floatingWindows = ReadElementList(reader, true); - foreach (var floatingWindow in floatingWindows) FloatingWindows.Add((LayoutFloatingWindow)floatingWindow); - - Hidden.Clear(); - var hidden = ReadElementList(reader, false); - foreach (var hiddenObject in hidden) Hidden.Add((LayoutAnchorable)hiddenObject); - - //Read the closing end element of LayoutRoot - reader.ReadEndElement(); - } - - /// - void IXmlSerializable.WriteXml(XmlWriter writer) - { - writer.WriteStartElement(nameof(RootPanel)); - RootPanel?.WriteXml(writer); - writer.WriteEndElement(); - - writer.WriteStartElement(nameof(TopSide)); - TopSide?.WriteXml(writer); - writer.WriteEndElement(); - - writer.WriteStartElement(nameof(RightSide)); - RightSide?.WriteXml(writer); - writer.WriteEndElement(); - - writer.WriteStartElement(nameof(LeftSide)); - LeftSide?.WriteXml(writer); - writer.WriteEndElement(); - - writer.WriteStartElement(nameof(BottomSide)); - BottomSide?.WriteXml(writer); - writer.WriteEndElement(); - - // Write all floating windows (can be LayoutDocumentFloatingWindow or LayoutAnchorableFloatingWindow). - // To prevent "can not create instance of abstract type", the type is retrieved with GetType().Name - writer.WriteStartElement(nameof(FloatingWindows)); - foreach (var layoutFloatingWindow in FloatingWindows) - { - writer.WriteStartElement(layoutFloatingWindow.GetType().Name); - layoutFloatingWindow.WriteXml(writer); - writer.WriteEndElement(); - } - writer.WriteEndElement(); - - writer.WriteStartElement(nameof(Hidden)); - foreach (var layoutAnchorable in Hidden) - { - writer.WriteStartElement(layoutAnchorable.GetType().Name); - layoutAnchorable.WriteXml(writer); - writer.WriteEndElement(); - } - writer.WriteEndElement(); - } - - #endregion IXmlSerializable interface members - - #endregion Public Methods - - #region Internal Methods - - internal static Type FindType(string name) - { - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - foreach (var type in assembly.GetTypes()) - if (type.Name.Equals(name)) return type; - return null; - } - - internal void FireLayoutUpdated() => Updated?.Invoke(this, EventArgs.Empty); - - internal void OnLayoutElementAdded(LayoutElement element) => ElementAdded?.Invoke(this, new LayoutElementEventArgs(element)); - - internal void OnLayoutElementRemoved(LayoutElement element) - { - if (element.Descendents().OfType().Any(c => c == LastFocusedDocument)) - LastFocusedDocument = null; - if (element.Descendents().OfType().Any(c => c == ActiveContent)) - ActiveContent = null; - ElementRemoved?.Invoke(this, new LayoutElementEventArgs(element)); - } - - #endregion Internal Methods - - #region Private Methods - - private void _floatingWindows_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - var bNotifyChildren = false; - - if (e.OldItems != null && (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)) - { - foreach (LayoutFloatingWindow element in e.OldItems) - { - if (element.Parent != this) continue; - element.Parent = null; - bNotifyChildren = true; - } - } - - if (e.NewItems != null && (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)) - { - foreach (LayoutFloatingWindow element in e.NewItems) - { - element.Parent = this; - bNotifyChildren = true; - } - } - - // descendants of LayoutElement notify when their Children and ChildrenCount properties change - // https://github.com/xceedsoftware/wpftoolkit/issues/1313 - // - if (!bNotifyChildren) return; - switch (e.Action) - { - case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: - case System.Collections.Specialized.NotifyCollectionChangedAction.Add: - RaisePropertyChanged(nameof(Children)); - RaisePropertyChanged(nameof(ChildrenCount)); - break; - - case System.Collections.Specialized.NotifyCollectionChangedAction.Replace: - RaisePropertyChanged(nameof(Children)); - break; - } - } - - private void _hiddenAnchorables_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) - { - var bNotifyChildren = false; - - if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) - { - if (e.OldItems != null) - { - foreach (LayoutAnchorable element in e.OldItems) - { - if (element.Parent != this) continue; - element.Parent = null; - bNotifyChildren = true; - } - } - } - - if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) - { - if (e.NewItems != null) - { - foreach (LayoutAnchorable element in e.NewItems) - { - if (element.Parent == this) continue; - element.Parent?.RemoveChild(element); - element.Parent = this; - bNotifyChildren = true; - } - } - } - - // descendants of LayoutElement notify when their Children and ChildrenCount properties change - // https://github.com/xceedsoftware/wpftoolkit/issues/1313 - // - if (!bNotifyChildren) return; - switch (e.Action) - { - case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: - case System.Collections.Specialized.NotifyCollectionChangedAction.Add: - RaisePropertyChanged(nameof(Children)); - RaisePropertyChanged(nameof(ChildrenCount)); - break; - - case System.Collections.Specialized.NotifyCollectionChangedAction.Replace: - RaisePropertyChanged(nameof(Children)); - break; - } - } - - private void InternalSetActiveContent(LayoutContent currentValue, LayoutContent newActiveContent) - { - RaisePropertyChanging(nameof(ActiveContent)); - if (currentValue != null && currentValue.IsActive) currentValue.IsActive = false; - _activeContent = new WeakReference(newActiveContent); - currentValue = ActiveContent; - if (currentValue != null && !currentValue.IsActive) currentValue.IsActive = true; - RaisePropertyChanged(nameof(ActiveContent)); - _activeContentSet = currentValue != null; - if (currentValue != null) - { - if (currentValue.Parent is LayoutDocumentPane || currentValue is LayoutDocument) - LastFocusedDocument = currentValue; - } - else - LastFocusedDocument = null; - } - - private void UpdateActiveContentProperty() - { - var activeContent = ActiveContent; - if (_activeContentSet && (activeContent == null || activeContent.Root != this)) - { - _activeContentSet = false; - InternalSetActiveContent(activeContent, null); - } - } - - private void FillLayoutAnchorSide(XmlReader reader, LayoutAnchorSide layoutAnchorSide) - { - var result = new List(); - - while (true) - { - //Read all layoutAnchorSide children - if (ReadElement(reader) is LayoutAnchorGroup element) result.Add(element); - else if (reader.NodeType == XmlNodeType.EndElement) break; - } - - reader.ReadEndElement(); - foreach (var las in result) - { - layoutAnchorSide.Children.Add(las); - } - } - - /// - /// Reads all properties of the and returns them. - /// - /// - /// - /// - /// - private List ReadRootPanel(XmlReader reader - , out Orientation orientation - , out bool canDock) - { - orientation = Orientation.Horizontal; - canDock = true; - - var result = new List(); - var startElementName = reader.LocalName; - reader.Read(); - if (reader.LocalName.Equals(startElementName) && reader.NodeType == XmlNodeType.EndElement) return null; - - while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); - - if (reader.LocalName.Equals(nameof(RootPanel))) - { - orientation = (Orientation)Enum.Parse(typeof(Orientation), reader.GetAttribute(nameof(Orientation)), true); - - var canDockStr = reader.GetAttribute("CanDock"); - if (canDockStr != null) - canDock = bool.Parse(canDockStr); - - reader.Read(); - while (true) - { - //Read all RootPanel children - if (ReadElement(reader) is ILayoutPanelElement element) result.Add(element); - else if (reader.NodeType == XmlNodeType.EndElement) break; - } - } - - reader.ReadEndElement(); - return result; - } - - private List ReadElementList(XmlReader reader, bool isFloatingWindow) - { - var resultList = new List(); - while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); - if (reader.NodeType == XmlNodeType.EndElement) return resultList; - - if (reader.IsEmptyElement) - { - reader.Read(); - return resultList; - } - - var startElementName = reader.LocalName; - reader.Read(); - if (reader.LocalName.Equals(startElementName) && reader.NodeType == XmlNodeType.EndElement) return null; - - while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); - - while (true) - { - if (isFloatingWindow) - { - if (!(ReadElement(reader) is LayoutFloatingWindow result)) break; - resultList.Add(result); - } - else - { - if (!(ReadElement(reader) is LayoutAnchorable result)) break; - resultList.Add(result); - } - } - - reader.ReadEndElement(); - return resultList; - } - - private object ReadElement(XmlReader reader) - { - while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); - if (reader.NodeType == XmlNodeType.EndElement) return null; - - Type typeToSerialize; - switch (reader.LocalName) - { - case nameof(LayoutAnchorablePaneGroup): - typeToSerialize = typeof(LayoutAnchorablePaneGroup); - break; - - case nameof(LayoutAnchorablePane): - typeToSerialize = typeof(LayoutAnchorablePane); - break; - - case nameof(LayoutAnchorable): - typeToSerialize = typeof(LayoutAnchorable); - break; - - case nameof(LayoutDocumentPaneGroup): - typeToSerialize = typeof(LayoutDocumentPaneGroup); - break; - - case nameof(LayoutDocumentPane): - typeToSerialize = typeof(LayoutDocumentPane); - break; - - case nameof(LayoutDocument): - typeToSerialize = typeof(LayoutDocument); - break; - - case nameof(LayoutAnchorGroup): - typeToSerialize = typeof(LayoutAnchorGroup); - break; - - case nameof(LayoutPanel): - typeToSerialize = typeof(LayoutPanel); - break; - - case nameof(LayoutDocumentFloatingWindow): - typeToSerialize = typeof(LayoutDocumentFloatingWindow); - break; - - case nameof(LayoutAnchorableFloatingWindow): - typeToSerialize = typeof(LayoutAnchorableFloatingWindow); - break; - - case nameof(LeftSide): - case nameof(RightSide): - case nameof(TopSide): - case nameof(BottomSide): - if (reader.IsEmptyElement) - { - reader.Read(); - return null; - } - return reader.Read(); - - default: - typeToSerialize = FindType(reader.LocalName); - if (typeToSerialize == null) - throw new ArgumentException("AvalonDock.LayoutRoot doesn't know how to deserialize " + reader.LocalName); - break; - } - XmlSerializer serializer = XmlSerializer.FromTypes(new[] { typeToSerialize })[0]; - return serializer.Deserialize(reader); - } - - #endregion Private Methods - - #region Diagnostic tools - -#if DEBUG - - public void DumpTree(bool shortPropertyNames = false) - { - void DumpElement(ILayoutElement element, StringBuilder indent, int childID, bool isLastChild) - { - Debug.Write($"{indent}{(indent.Length > 0 ? isLastChild ? " └─ " : " ├─ " : "")}{childID:D2} 0x{element.GetHashCode():X8} " + - $"{element.GetType().Name} {(shortPropertyNames ? "P" : "Parent")}:0x{element.Parent?.GetHashCode() ?? 0:X8} " + - $"{(shortPropertyNames ? "R" : "Root")}:0x{element.Root?.GetHashCode() ?? 0:X8}"); - if (!(element is ILayoutContainer containerElement)) - { - Debug.WriteLine(""); - return; - } - Debug.WriteLine($" {(shortPropertyNames ? "C" : "Children")}:{containerElement.ChildrenCount}"); - var nrChild = 0; - indent.Append(isLastChild ? " " : " │ "); - foreach (var child in containerElement.Children) - { - var lastChild = nrChild == containerElement.ChildrenCount - 1; - DumpElement(child, indent, nrChild++, lastChild); - } - indent.Remove(indent.Length - 3, 3); - } - - DumpElement(this, new StringBuilder(), 0, true); - } - -#endif - - #endregion Diagnostic tools - } +/************************************************************************ + 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.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows.Markup; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; + +namespace AvalonDock.Layout +{ + /// + /// Implements the root of the layout viewmodel (see property). + /// This root includes a property for binding content, side panel properties + /// and many other layout related root items. + /// + /// This class implements and + /// to support direct UI binding scenarios + /// with view updates supported. + /// + [ContentProperty(nameof(RootPanel))] + [Serializable] + public class LayoutRoot : LayoutElement, ILayoutContainer, ILayoutRoot, IXmlSerializable + { + #region fields + + private LayoutPanel _rootPanel; + private LayoutAnchorSide _topSide = null; + private LayoutAnchorSide _rightSide; + private LayoutAnchorSide _leftSide = null; + private LayoutAnchorSide _bottomSide = null; + + private ObservableCollection _floatingWindows = null; + private ObservableCollection _hiddenAnchorables = null; + + [field: NonSerialized] + private WeakReference _activeContent = null; + + private bool _activeContentSet = false; + + [field: NonSerialized] + private WeakReference _lastFocusedDocument = null; + + [NonSerialized] + private DockingManager _manager = null; + + #endregion fields + + #region Constructors + + /// Standard class constructor + public LayoutRoot() + { + RightSide = new LayoutAnchorSide(); + LeftSide = new LayoutAnchorSide(); + TopSide = new LayoutAnchorSide(); + BottomSide = new LayoutAnchorSide(); + RootPanel = new LayoutPanel(new LayoutDocumentPane()); + } + + #endregion Constructors + + #region Events + + /// + /// Raised when the layout is updated. This event is raised via method + /// when a parent of a LayoutElement has changed. + /// + public event EventHandler Updated; + + /// Raised when an element is added to the layout. + public event EventHandler ElementAdded; + + /// Raised when an element is removed from the layout. + public event EventHandler ElementRemoved; + + #endregion Events + + #region Properties + + /// Gets/sets the root layout panel that contains the . + public LayoutPanel RootPanel + { + get => _rootPanel; + set + { + if (_rootPanel == value) return; + RaisePropertyChanging(nameof(RootPanel)); + if (_rootPanel != null && _rootPanel.Parent == this) _rootPanel.Parent = null; + _rootPanel = value ?? new LayoutPanel(new LayoutDocumentPane()); + _rootPanel.Parent = this; + RaisePropertyChanged(nameof(RootPanel)); + } + } + + /// Gets or sets the top side of the layout root. + public LayoutAnchorSide TopSide + { + get => _topSide; + set + { + if (_topSide == value) return; + RaisePropertyChanging(nameof(TopSide)); + _topSide = value; + if (_topSide != null) _topSide.Parent = this; + RaisePropertyChanged(nameof(TopSide)); + } + } + + /// Gets or sets the right side of the layout root. + public LayoutAnchorSide RightSide + { + get => _rightSide; + set + { + if (_rightSide == value) return; + RaisePropertyChanging(nameof(RightSide)); + _rightSide = value; + if (_rightSide != null) _rightSide.Parent = this; + RaisePropertyChanged(nameof(RightSide)); + } + } + + /// Gets or sets the left side of the layout root. + public LayoutAnchorSide LeftSide + { + get => _leftSide; + set + { + if (value == _leftSide) return; + RaisePropertyChanging(nameof(LeftSide)); + _leftSide = value; + if (_leftSide != null) _leftSide.Parent = this; + RaisePropertyChanged(nameof(LeftSide)); + } + } + + /// Gets or sets the bottom side of the layout root. + public LayoutAnchorSide BottomSide + { + get => _bottomSide; + set + { + if (value == _bottomSide) return; + RaisePropertyChanging(nameof(BottomSide)); + _bottomSide = value; + if (_bottomSide != null) _bottomSide.Parent = this; + RaisePropertyChanged(nameof(BottomSide)); + } + } + + /// Gets the floating windows that are part of this layout. + public ObservableCollection FloatingWindows + { + get + { + if (_floatingWindows == null) + { + _floatingWindows = new ObservableCollection(); + _floatingWindows.CollectionChanged += _floatingWindows_CollectionChanged; + } + + return _floatingWindows; + } + } + + /// Gets the hidden anchorables in the layout. + public ObservableCollection Hidden + { + get + { + if (_hiddenAnchorables == null) + { + _hiddenAnchorables = new ObservableCollection(); + _hiddenAnchorables.CollectionChanged += _hiddenAnchorables_CollectionChanged; + } + + return _hiddenAnchorables; + } + } + + #region Children + + /// Gets the child elements of the layout root. + public IEnumerable Children + { + get + { + if (RootPanel != null) + yield return RootPanel; + if (_floatingWindows != null) + { + foreach (var floatingWindow in _floatingWindows) + yield return floatingWindow; + } + if (TopSide != null) + yield return TopSide; + if (RightSide != null) + yield return RightSide; + if (BottomSide != null) + yield return BottomSide; + if (LeftSide != null) + yield return LeftSide; + if (_hiddenAnchorables != null) + { + foreach (var hiddenAnchorable in _hiddenAnchorables) + yield return hiddenAnchorable; + } + } + } + + /// Gets the number of child elements of the layout root. + public int ChildrenCount => 5 + (_floatingWindows?.Count ?? 0) + (_hiddenAnchorables?.Count ?? 0); + + #endregion Children + + /// Gets the active LayoutContent-derived element. + [XmlIgnore] + public LayoutContent ActiveContent + { + get + { + return _activeContent.GetValueOrDefault(); + } + set + { + var currentValue = ActiveContent; + if (currentValue != value) + { + InternalSetActiveContent(currentValue, value); + } + } + } + + [XmlIgnore] + public LayoutContent LastFocusedDocument + { + get => _lastFocusedDocument.GetValueOrDefault(); + private set + { + var currentValue = LastFocusedDocument; + if (currentValue != value) + { + RaisePropertyChanging(nameof(LastFocusedDocument)); + if (currentValue != null) currentValue.IsLastFocusedDocument = false; + _lastFocusedDocument = new WeakReference(value); + currentValue = LastFocusedDocument; + if (currentValue != null) currentValue.IsLastFocusedDocument = true; + RaisePropertyChanged(nameof(LastFocusedDocument)); + } + } + } + + /// Gets/sets the docking manager root control for this library. + [XmlIgnore] + public DockingManager Manager + { + get => _manager; + internal set + { + if (value == _manager) return; + RaisePropertyChanging(nameof(Manager)); + _manager = value; + RaisePropertyChanged(nameof(Manager)); + } + } + + #endregion Properties + + #region Overrides + +#if TRACE + public override void ConsoleDump(int tab) + { + System.Diagnostics.Trace.Write(new string(' ', tab * 4)); + System.Diagnostics.Trace.WriteLine("RootPanel()"); + + RootPanel.ConsoleDump(tab + 1); + + System.Diagnostics.Trace.Write(new string(' ', tab * 4)); + System.Diagnostics.Trace.WriteLine("FloatingWindows()"); + + foreach (var fw in FloatingWindows) + fw.ConsoleDump(tab + 1); + + System.Diagnostics.Trace.Write(new string(' ', tab * 4)); + System.Diagnostics.Trace.WriteLine("Hidden()"); + + foreach (var hidden in Hidden) + hidden.ConsoleDump(tab + 1); + } +#endif + + #endregion Overrides + + #region Public Methods + + public void RemoveChild(ILayoutElement element) + { + if (element == RootPanel) + RootPanel = null; + else if (_floatingWindows != null && _floatingWindows.Contains(element)) + _floatingWindows.Remove(element as LayoutFloatingWindow); + else if (_hiddenAnchorables != null && _hiddenAnchorables.Contains(element)) + _hiddenAnchorables.Remove(element as LayoutAnchorable); + else if (element == TopSide) + TopSide = null; + else if (element == RightSide) + RightSide = null; + else if (element == BottomSide) + BottomSide = null; + else if (element == LeftSide) + LeftSide = null; + } + + public void ReplaceChild(ILayoutElement oldElement, ILayoutElement newElement) + { + if (oldElement == RootPanel) + RootPanel = (LayoutPanel)newElement; + else if (_floatingWindows != null && _floatingWindows.Contains(oldElement)) + { + var index = _floatingWindows.IndexOf(oldElement as LayoutFloatingWindow); + _floatingWindows.Remove(oldElement as LayoutFloatingWindow); + _floatingWindows.Insert(index, newElement as LayoutFloatingWindow); + } + else if (_hiddenAnchorables != null && _hiddenAnchorables.Contains(oldElement)) + { + var index = _hiddenAnchorables.IndexOf(oldElement as LayoutAnchorable); + _hiddenAnchorables.Remove(oldElement as LayoutAnchorable); + _hiddenAnchorables.Insert(index, newElement as LayoutAnchorable); + } + else if (oldElement == TopSide) + TopSide = (LayoutAnchorSide)newElement; + else if (oldElement == RightSide) + RightSide = (LayoutAnchorSide)newElement; + else if (oldElement == BottomSide) + BottomSide = (LayoutAnchorSide)newElement; + else if (oldElement == LeftSide) + LeftSide = (LayoutAnchorSide)newElement; + } + + /// Removes any empty container not directly referenced by other layout items. + public void CollectGarbage() + { + var exitFlag = true; + + #region collect empty panes + + do + { + exitFlag = true; + + //for each content that references via PreviousContainer a disconnected Pane set the property to null + foreach (var content in this.Descendents().OfType().Where(c => c.PreviousContainer != null && + (c.PreviousContainer.Parent == null || c.PreviousContainer.Parent.Root != this))) + { + content.PreviousContainer = null; + } + + //for each pane that is empty + foreach (var emptyPane in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) + { + //...set null any reference coming from contents not yet hosted in a floating window + foreach (var contentReferencingEmptyPane in this.Descendents().OfType() + .Where(c => ((ILayoutPreviousContainer)c).PreviousContainer == emptyPane && !c.IsFloating)) + { + if (contentReferencingEmptyPane is LayoutAnchorable anchorable && + !anchorable.IsVisible) + continue; + + ((ILayoutPreviousContainer)contentReferencingEmptyPane).PreviousContainer = null; + contentReferencingEmptyPane.PreviousContainerIndex = -1; + } + + //...if this pane is the only documentpane present in the layout of the main window (not floating) then skip it + if (emptyPane is LayoutDocumentPane && + emptyPane.FindParent() == null && + this.Descendents().OfType().Count(c => c != emptyPane && c.FindParent() == null) == 0) + continue; + + //...if this empty pane is not referenced by anyone, then remove it from its parent container + if (!this.Descendents().OfType().Any(c => c.PreviousContainer == emptyPane)) + { + var parentGroup = emptyPane.Parent; + parentGroup.RemoveChild(emptyPane); + exitFlag = false; + break; + } + } + + if (!exitFlag) + { + //removes any empty anchorable pane group + foreach (var emptyLayoutAnchorablePaneGroup in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) + { + var parentGroup = emptyLayoutAnchorablePaneGroup.Parent; + parentGroup.RemoveChild(emptyLayoutAnchorablePaneGroup); + exitFlag = false; + break; + } + } + + if (!exitFlag) + { + //removes any empty layout panel + foreach (var emptyLayoutPanel in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) + { + var parentGroup = emptyLayoutPanel.Parent; + parentGroup.RemoveChild(emptyLayoutPanel); + exitFlag = false; + break; + } + foreach (var emptyLayoutDocumentPane in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) + { + var parentGroup = emptyLayoutDocumentPane.Parent; + if (!(parentGroup.Parent is LayoutDocumentFloatingWindow)) continue; + var index = RootPanel.IndexOfChild(this.Descendents().OfType().First()); + parentGroup.RemoveChild(emptyLayoutDocumentPane); + if (!this.Descendents().OfType().Any()) + { + // Now the last Pane container is deleted, at least one is required for documents to be added. + // We did not want to keep an empty window floating, but add a new one to the main window + RootPanel.Children.Insert(index < 0 ? 0 : index, emptyLayoutDocumentPane); + } + exitFlag = false; + break; + } + } + + if (!exitFlag) + { + //removes any empty floating window + foreach (var emptyLayoutFloatingWindow in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) + { + var parentGroup = emptyLayoutFloatingWindow.Parent; + parentGroup.RemoveChild(emptyLayoutFloatingWindow); + exitFlag = false; + break; + } + } + + if (!exitFlag) + { + //removes any empty anchor group + foreach (var emptyLayoutAnchorGroup in this.Descendents().OfType().Where(p => p.ChildrenCount == 0)) + { + if (!this.Descendents().OfType().Any(c => c.PreviousContainer == emptyLayoutAnchorGroup)) + { + var parentGroup = emptyLayoutAnchorGroup.Parent; + parentGroup.RemoveChild(emptyLayoutAnchorGroup); + exitFlag = false; + break; + } + } + } + } + while (!exitFlag); + + #endregion collect empty panes + + #region collapse single child anchorable pane groups + + do + { + exitFlag = true; + //for each pane that is empty + foreach (var paneGroupToCollapse in this.Descendents().OfType().Where(p => p.ChildrenCount == 1 && p.Children[0] is LayoutAnchorablePaneGroup).ToArray()) + { + var singleChild = paneGroupToCollapse.Children[0] as LayoutAnchorablePaneGroup; + paneGroupToCollapse.Orientation = singleChild.Orientation; + while (singleChild.ChildrenCount > 0) + paneGroupToCollapse.InsertChildAt(paneGroupToCollapse.ChildrenCount, singleChild.Children[0]); + paneGroupToCollapse.RemoveChild(singleChild); + exitFlag = false; + break; + } + } + while (!exitFlag); + + #endregion collapse single child anchorable pane groups + + #region collapse single child document pane groups + + do + { + exitFlag = true; + //for each pane that is empty + foreach (var paneGroupToCollapse in this.Descendents().OfType().Where(p => p.ChildrenCount == 1 && p.Children[0] is LayoutDocumentPaneGroup).ToArray()) + { + var singleChild = paneGroupToCollapse.Children[0] as LayoutDocumentPaneGroup; + paneGroupToCollapse.Orientation = singleChild.Orientation; + while (singleChild.ChildrenCount > 0) + paneGroupToCollapse.InsertChildAt(paneGroupToCollapse.ChildrenCount, singleChild.Children[0]); + paneGroupToCollapse.RemoveChild(singleChild); + exitFlag = false; + break; + } + } + while (!exitFlag); + + #endregion collapse single child document pane groups + + ////do + ////{ + //// exitFlag = true; + //// //for each panel that has only one child + //// foreach( var panelToCollapse in this.Descendents().OfType().Where( p => p.ChildrenCount == 1 && p.Children[ 0 ] is LayoutPanel ).ToArray() ) + //// { + //// var singleChild = panelToCollapse.Children[ 0 ] as LayoutPanel; + //// panelToCollapse.Orientation = singleChild.Orientation; + //// panelToCollapse.RemoveChild( singleChild ); + //// ILayoutPanelElement[] singleChildChildren = new ILayoutPanelElement[ singleChild.ChildrenCount ]; + //// singleChild.Children.CopyTo( singleChildChildren, 0 ); + //// while( singleChild.ChildrenCount > 0 ) + //// { + //// panelToCollapse.InsertChildAt( + //// panelToCollapse.ChildrenCount, singleChildChildren[ panelToCollapse.ChildrenCount ] ); + //// } + + //// exitFlag = false; + //// break; + //// } + ////} + ////while( !exitFlag ); + + // Update ActiveContent and LastFocusedDocument properties + UpdateActiveContentProperty(); + +#if DEBUG + Debug.Assert(!this.Descendents().OfType().Any(a => a.ChildrenCount == 0 && a.IsVisible)); + //DumpTree(true); +#if TRACE + RootPanel.ConsoleDump(4); +#endif +#endif + } + + #region IXmlSerializable interface members + + /// + XmlSchema IXmlSerializable.GetSchema() => null; + + /// + void IXmlSerializable.ReadXml(XmlReader reader) + { + reader.MoveToContent(); + if (reader.IsEmptyElement) + { + reader.Read(); + return; + } + + var layoutPanelElements = ReadRootPanel(reader, out var orientation, out var canDock); + if (layoutPanelElements != null) + { + RootPanel = new LayoutPanel { Orientation = orientation, CanDock = canDock }; + //Add all children to RootPanel + foreach (var panel in layoutPanelElements) RootPanel.Children.Add(panel); + } + + TopSide = new LayoutAnchorSide(); + if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, TopSide); + RightSide = new LayoutAnchorSide(); + if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, RightSide); + LeftSide = new LayoutAnchorSide(); + if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, LeftSide); + BottomSide = new LayoutAnchorSide(); + if (ReadElement(reader) != null) FillLayoutAnchorSide(reader, BottomSide); + + FloatingWindows.Clear(); + var floatingWindows = ReadElementList(reader, true); + foreach (var floatingWindow in floatingWindows) FloatingWindows.Add((LayoutFloatingWindow)floatingWindow); + + Hidden.Clear(); + var hidden = ReadElementList(reader, false); + foreach (var hiddenObject in hidden) Hidden.Add((LayoutAnchorable)hiddenObject); + + //Read the closing end element of LayoutRoot + reader.ReadEndElement(); + } + + /// + void IXmlSerializable.WriteXml(XmlWriter writer) + { + writer.WriteStartElement(nameof(RootPanel)); + RootPanel?.WriteXml(writer); + writer.WriteEndElement(); + + writer.WriteStartElement(nameof(TopSide)); + TopSide?.WriteXml(writer); + writer.WriteEndElement(); + + writer.WriteStartElement(nameof(RightSide)); + RightSide?.WriteXml(writer); + writer.WriteEndElement(); + + writer.WriteStartElement(nameof(LeftSide)); + LeftSide?.WriteXml(writer); + writer.WriteEndElement(); + + writer.WriteStartElement(nameof(BottomSide)); + BottomSide?.WriteXml(writer); + writer.WriteEndElement(); + + // Write all floating windows (can be LayoutDocumentFloatingWindow or LayoutAnchorableFloatingWindow). + // To prevent "can not create instance of abstract type", the type is retrieved with GetType().Name + writer.WriteStartElement(nameof(FloatingWindows)); + foreach (var layoutFloatingWindow in FloatingWindows) + { + writer.WriteStartElement(layoutFloatingWindow.GetType().Name); + layoutFloatingWindow.WriteXml(writer); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + + writer.WriteStartElement(nameof(Hidden)); + foreach (var layoutAnchorable in Hidden) + { + writer.WriteStartElement(layoutAnchorable.GetType().Name); + layoutAnchorable.WriteXml(writer); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + + #endregion IXmlSerializable interface members + + #endregion Public Methods + + #region Internal Methods + + internal static Type FindType(string name) + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + foreach (var type in assembly.GetTypes()) + if (type.Name.Equals(name)) return type; + return null; + } + + internal void FireLayoutUpdated() => Updated?.Invoke(this, EventArgs.Empty); + + internal void OnLayoutElementAdded(LayoutElement element) => ElementAdded?.Invoke(this, new LayoutElementEventArgs(element)); + + internal void OnLayoutElementRemoved(LayoutElement element) + { + if (element.Descendents().OfType().Any(c => c == LastFocusedDocument)) + LastFocusedDocument = null; + if (element.Descendents().OfType().Any(c => c == ActiveContent)) + ActiveContent = null; + ElementRemoved?.Invoke(this, new LayoutElementEventArgs(element)); + } + + #endregion Internal Methods + + #region Private Methods + + private void _floatingWindows_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + var bNotifyChildren = false; + + if (e.OldItems != null && (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)) + { + foreach (LayoutFloatingWindow element in e.OldItems) + { + if (element.Parent != this) continue; + element.Parent = null; + bNotifyChildren = true; + } + } + + if (e.NewItems != null && (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace)) + { + foreach (LayoutFloatingWindow element in e.NewItems) + { + element.Parent = this; + bNotifyChildren = true; + } + } + + // descendants of LayoutElement notify when their Children and ChildrenCount properties change + // https://github.com/xceedsoftware/wpftoolkit/issues/1313 + // + if (!bNotifyChildren) return; + switch (e.Action) + { + case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: + case System.Collections.Specialized.NotifyCollectionChangedAction.Add: + RaisePropertyChanged(nameof(Children)); + RaisePropertyChanged(nameof(ChildrenCount)); + break; + + case System.Collections.Specialized.NotifyCollectionChangedAction.Replace: + RaisePropertyChanged(nameof(Children)); + break; + } + } + + private void _hiddenAnchorables_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + var bNotifyChildren = false; + + if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) + { + if (e.OldItems != null) + { + foreach (LayoutAnchorable element in e.OldItems) + { + if (element.Parent != this) continue; + element.Parent = null; + bNotifyChildren = true; + } + } + } + + if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add || e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Replace) + { + if (e.NewItems != null) + { + foreach (LayoutAnchorable element in e.NewItems) + { + if (element.Parent == this) continue; + element.Parent?.RemoveChild(element); + element.Parent = this; + bNotifyChildren = true; + } + } + } + + // descendants of LayoutElement notify when their Children and ChildrenCount properties change + // https://github.com/xceedsoftware/wpftoolkit/issues/1313 + // + if (!bNotifyChildren) return; + switch (e.Action) + { + case System.Collections.Specialized.NotifyCollectionChangedAction.Remove: + case System.Collections.Specialized.NotifyCollectionChangedAction.Add: + RaisePropertyChanged(nameof(Children)); + RaisePropertyChanged(nameof(ChildrenCount)); + break; + + case System.Collections.Specialized.NotifyCollectionChangedAction.Replace: + RaisePropertyChanged(nameof(Children)); + break; + } + } + + private void InternalSetActiveContent(LayoutContent currentValue, LayoutContent newActiveContent) + { + RaisePropertyChanging(nameof(ActiveContent)); + if (currentValue != null && currentValue.IsActive) currentValue.IsActive = false; + _activeContent = new WeakReference(newActiveContent); + currentValue = ActiveContent; + if (currentValue != null && !currentValue.IsActive) currentValue.IsActive = true; + RaisePropertyChanged(nameof(ActiveContent)); + _activeContentSet = currentValue != null; + if (currentValue != null) + { + if (currentValue.Parent is LayoutDocumentPane || currentValue is LayoutDocument) + LastFocusedDocument = currentValue; + } + else + LastFocusedDocument = null; + } + + private void UpdateActiveContentProperty() + { + var activeContent = ActiveContent; + if (_activeContentSet && (activeContent == null || activeContent.Root != this)) + { + _activeContentSet = false; + InternalSetActiveContent(activeContent, null); + } + } + + private void FillLayoutAnchorSide(XmlReader reader, LayoutAnchorSide layoutAnchorSide) + { + var result = new List(); + + while (true) + { + //Read all layoutAnchorSide children + if (ReadElement(reader) is LayoutAnchorGroup element) result.Add(element); + else if (reader.NodeType == XmlNodeType.EndElement) break; + } + + reader.ReadEndElement(); + foreach (var las in result) + { + layoutAnchorSide.Children.Add(las); + } + } + + /// + /// Reads all properties of the and returns them. + /// + /// + /// + /// + /// + private List ReadRootPanel(XmlReader reader + , out Orientation orientation + , out bool canDock) + { + orientation = Orientation.Horizontal; + canDock = true; + + var result = new List(); + var startElementName = reader.LocalName; + reader.Read(); + if (reader.LocalName.Equals(startElementName) && reader.NodeType == XmlNodeType.EndElement) return null; + + while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); + + if (reader.LocalName.Equals(nameof(RootPanel))) + { + orientation = (Orientation)Enum.Parse(typeof(Orientation), reader.GetAttribute(nameof(Orientation)), true); + + var canDockStr = reader.GetAttribute("CanDock"); + if (canDockStr != null) + canDock = bool.Parse(canDockStr); + + reader.Read(); + while (true) + { + //Read all RootPanel children + if (ReadElement(reader) is ILayoutPanelElement element) result.Add(element); + else if (reader.NodeType == XmlNodeType.EndElement) break; + } + } + + reader.ReadEndElement(); + return result; + } + + private List ReadElementList(XmlReader reader, bool isFloatingWindow) + { + var resultList = new List(); + while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); + if (reader.NodeType == XmlNodeType.EndElement) return resultList; + + if (reader.IsEmptyElement) + { + reader.Read(); + return resultList; + } + + var startElementName = reader.LocalName; + reader.Read(); + if (reader.LocalName.Equals(startElementName) && reader.NodeType == XmlNodeType.EndElement) return null; + + while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); + + while (true) + { + if (isFloatingWindow) + { + if (!(ReadElement(reader) is LayoutFloatingWindow result)) break; + resultList.Add(result); + } + else + { + if (!(ReadElement(reader) is LayoutAnchorable result)) break; + resultList.Add(result); + } + } + + reader.ReadEndElement(); + return resultList; + } + + private object ReadElement(XmlReader reader) + { + while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); + if (reader.NodeType == XmlNodeType.EndElement) return null; + + XmlSerializer serializer; + switch (reader.LocalName) + { + case nameof(LayoutAnchorablePaneGroup): + serializer = new XmlSerializer(typeof(LayoutAnchorablePaneGroup)); + break; + + case nameof(LayoutAnchorablePane): + serializer = new XmlSerializer(typeof(LayoutAnchorablePane)); + break; + + case nameof(LayoutAnchorable): + serializer = new XmlSerializer(typeof(LayoutAnchorable)); + break; + + case nameof(LayoutDocumentPaneGroup): + serializer = new XmlSerializer(typeof(LayoutDocumentPaneGroup)); + break; + + case nameof(LayoutDocumentPane): + serializer = new XmlSerializer(typeof(LayoutDocumentPane)); + break; + + case nameof(LayoutDocument): + serializer = new XmlSerializer(typeof(LayoutDocument)); + break; + + case nameof(LayoutAnchorGroup): + serializer = new XmlSerializer(typeof(LayoutAnchorGroup)); + break; + + case nameof(LayoutPanel): + serializer = new XmlSerializer(typeof(LayoutPanel)); + break; + + case nameof(LayoutDocumentFloatingWindow): + serializer = new XmlSerializer(typeof(LayoutDocumentFloatingWindow)); + break; + + case nameof(LayoutAnchorableFloatingWindow): + serializer = new XmlSerializer(typeof(LayoutAnchorableFloatingWindow)); + break; + + case nameof(LeftSide): + case nameof(RightSide): + case nameof(TopSide): + case nameof(BottomSide): + if (reader.IsEmptyElement) + { + reader.Read(); + return null; + } + return reader.Read(); + + default: + var type = FindType(reader.LocalName); + if (type == null) + throw new ArgumentException("AvalonDock.LayoutRoot doesn't know how to deserialize " + reader.LocalName); + serializer = new XmlSerializer(type); + break; + } + + return serializer.Deserialize(reader); + } + + #endregion Private Methods + + #region Diagnostic tools + +#if DEBUG + + public void DumpTree(bool shortPropertyNames = false) + { + void DumpElement(ILayoutElement element, StringBuilder indent, int childID, bool isLastChild) + { + Debug.Write($"{indent}{(indent.Length > 0 ? isLastChild ? " └─ " : " ├─ " : "")}{childID:D2} 0x{element.GetHashCode():X8} " + + $"{element.GetType().Name} {(shortPropertyNames ? "P" : "Parent")}:0x{element.Parent?.GetHashCode() ?? 0:X8} " + + $"{(shortPropertyNames ? "R" : "Root")}:0x{element.Root?.GetHashCode() ?? 0:X8}"); + if (!(element is ILayoutContainer containerElement)) + { + Debug.WriteLine(""); + return; + } + Debug.WriteLine($" {(shortPropertyNames ? "C" : "Children")}:{containerElement.ChildrenCount}"); + var nrChild = 0; + indent.Append(isLastChild ? " " : " │ "); + foreach (var child in containerElement.Children) + { + var lastChild = nrChild == containerElement.ChildrenCount - 1; + DumpElement(child, indent, nrChild++, lastChild); + } + indent.Remove(indent.Length - 3, 3); + } + + DumpElement(this, new StringBuilder(), 0, true); + } + +#endif + + #endregion Diagnostic tools + } } \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/Serialization/LayoutSerializer.cs b/source/Components/AvalonDock/Layout/Serialization/LayoutSerializer.cs index 6c95346c..7e7d0466 100644 --- a/source/Components/AvalonDock/Layout/Serialization/LayoutSerializer.cs +++ b/source/Components/AvalonDock/Layout/Serialization/LayoutSerializer.cs @@ -1,149 +1,146 @@ -/************************************************************************ - 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.Linq; - -namespace AvalonDock.Layout.Serialization -{ - /// Implements a base class for the layout serialization/deserialization of the docking framework. - public abstract class LayoutSerializer - { - #region fields - - private LayoutAnchorable[] _previousAnchorables = null; - private LayoutDocument[] _previousDocuments = null; - - #endregion fields - - #region Constructors - - /// - /// Class constructor from instance. - /// - /// - public LayoutSerializer(DockingManager manager) - { - Manager = manager ?? throw new ArgumentNullException(nameof(manager)); - _previousAnchorables = Manager.Layout.Descendents().OfType().ToArray(); - _previousDocuments = Manager.Layout.Descendents().OfType().ToArray(); - } - - #endregion Constructors - - #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 EventHandler LayoutSerializationCallback; - - #endregion Events - - #region Properties - - /// - /// Gets the root of the docking library. - /// - public DockingManager Manager { get; } - - #endregion Properties - - #region Methods - - protected virtual void FixupLayout(LayoutRoot layout) - { - //fix container panes - foreach (var lcToAttach in layout.Descendents().OfType().Where(lc => lc.PreviousContainerId != null)) - { - var paneContainerToAttach = layout.Descendents().OfType().FirstOrDefault(lps => lps.Id == lcToAttach.PreviousContainerId); - if (paneContainerToAttach == null) - throw new ArgumentException($"Unable to find a pane with id ='{lcToAttach.PreviousContainerId}'"); - lcToAttach.PreviousContainer = paneContainerToAttach as ILayoutContainer; - } - - //now fix the content of the layout anchorable contents - foreach (var lcToFix in layout.Descendents().OfType().Where(lc => lc.Content == null).ToArray()) - { - LayoutAnchorable previousAchorable = null; //try find the content in replaced layout - if (lcToFix.ContentId != null) - previousAchorable = _previousAnchorables.FirstOrDefault(a => a.ContentId == lcToFix.ContentId); - - if(previousAchorable != null && previousAchorable.Title != null) - lcToFix.Title = previousAchorable.Title; - - if (LayoutSerializationCallback != null) - { - // Ask client application via callback if item should be deserialized - var args = new LayoutSerializationCallbackEventArgs(lcToFix, previousAchorable?.Content); - LayoutSerializationCallback(this, args); - if (args.Cancel) - lcToFix.Close(); - else if (args.Content != null) - lcToFix.Content = args.Content; - else if (args.Model.Content != null) - lcToFix.Hide(false); // hide layoutanchorable if client app supplied no content - } - else if (previousAchorable == null) // No Callback and no provious document -> skip this - lcToFix.Hide(false); - else - { // No Callback but previous anchoreable available -> load content from previous document - lcToFix.Content = previousAchorable.Content; - lcToFix.IconSource = previousAchorable.IconSource; - } - } - - //now fix the content of the layout document contents - foreach (var lcToFix in layout.Descendents().OfType().Where(lc => lc.Content == null).ToArray()) - { - LayoutDocument previousDocument = null; //try find the content in replaced layout - if (lcToFix.ContentId != null) - previousDocument = _previousDocuments.FirstOrDefault(a => a.ContentId == lcToFix.ContentId); - - if (LayoutSerializationCallback != null) - { - // Ask client application via callback if this realy should be deserialized - var args = new LayoutSerializationCallbackEventArgs(lcToFix, previousDocument?.Content); - LayoutSerializationCallback(this, args); - - if (args.Cancel) - lcToFix.Close(); - else if (args.Content != null) - lcToFix.Content = args.Content; - else if (args.Model.Content != null) // Close document if client app supplied no content - lcToFix.Close(); - } - else if (previousDocument == null) // No Callback and no provious document -> skip this - lcToFix.Close(); - else - { // No Callback but previous document available -> load content from previous document - lcToFix.Content = previousDocument.Content; - lcToFix.IconSource = previousDocument.IconSource; - } - } - - layout.CollectGarbage(); - } - - protected void StartDeserialization() - { - Manager.SuspendDocumentsSourceBinding = true; - Manager.SuspendAnchorablesSourceBinding = true; - } - - protected void EndDeserialization() - { - Manager.SuspendDocumentsSourceBinding = false; - Manager.SuspendAnchorablesSourceBinding = false; - } - - #endregion Methods - } +/************************************************************************ + 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.Linq; + +namespace AvalonDock.Layout.Serialization +{ + /// Implements a base class for the layout serialization/deserialization of the docking framework. + public abstract class LayoutSerializer + { + #region fields + + private LayoutAnchorable[] _previousAnchorables = null; + private LayoutDocument[] _previousDocuments = null; + + #endregion fields + + #region Constructors + + /// + /// Class constructor from instance. + /// + /// + public LayoutSerializer(DockingManager manager) + { + Manager = manager ?? throw new ArgumentNullException(nameof(manager)); + _previousAnchorables = Manager.Layout.Descendents().OfType().ToArray(); + _previousDocuments = Manager.Layout.Descendents().OfType().ToArray(); + } + + #endregion Constructors + + #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 EventHandler LayoutSerializationCallback; + + #endregion Events + + #region Properties + + /// + /// Gets the root of the docking library. + /// + public DockingManager Manager { get; } + + #endregion Properties + + #region Methods + + protected virtual void FixupLayout(LayoutRoot layout) + { + //fix container panes + foreach (var lcToAttach in layout.Descendents().OfType().Where(lc => lc.PreviousContainerId != null)) + { + var paneContainerToAttach = layout.Descendents().OfType().FirstOrDefault(lps => lps.Id == lcToAttach.PreviousContainerId); + if (paneContainerToAttach == null) + throw new ArgumentException($"Unable to find a pane with id ='{lcToAttach.PreviousContainerId}'"); + lcToAttach.PreviousContainer = paneContainerToAttach as ILayoutContainer; + } + + //now fix the content of the layout anchorable contents + foreach (var lcToFix in layout.Descendents().OfType().Where(lc => lc.Content == null).ToArray()) + { + LayoutAnchorable previousAchorable = null; //try find the content in replaced layout + if (lcToFix.ContentId != null) + previousAchorable = _previousAnchorables.FirstOrDefault(a => a.ContentId == lcToFix.ContentId); + + if (LayoutSerializationCallback != null) + { + // Ask client application via callback if item should be deserialized + var args = new LayoutSerializationCallbackEventArgs(lcToFix, previousAchorable?.Content); + LayoutSerializationCallback(this, args); + if (args.Cancel) + lcToFix.Close(); + else if (args.Content != null) + lcToFix.Content = args.Content; + else if (args.Model.Content != null) + lcToFix.Hide(false); // hide layoutanchorable if client app supplied no content + } + else if (previousAchorable == null) // No Callback and no provious document -> skip this + lcToFix.Hide(false); + else + { // No Callback but previous anchoreable available -> load content from previous document + lcToFix.Content = previousAchorable.Content; + lcToFix.IconSource = previousAchorable.IconSource; + } + } + + //now fix the content of the layout document contents + foreach (var lcToFix in layout.Descendents().OfType().Where(lc => lc.Content == null).ToArray()) + { + LayoutDocument previousDocument = null; //try find the content in replaced layout + if (lcToFix.ContentId != null) + previousDocument = _previousDocuments.FirstOrDefault(a => a.ContentId == lcToFix.ContentId); + + if (LayoutSerializationCallback != null) + { + // Ask client application via callback if this realy should be deserialized + var args = new LayoutSerializationCallbackEventArgs(lcToFix, previousDocument?.Content); + LayoutSerializationCallback(this, args); + + if (args.Cancel) + lcToFix.Close(); + else if (args.Content != null) + lcToFix.Content = args.Content; + else if (args.Model.Content != null) // Close document if client app supplied no content + lcToFix.Close(); + } + else if (previousDocument == null) // No Callback and no provious document -> skip this + lcToFix.Close(); + else + { // No Callback but previous document available -> load content from previous document + lcToFix.Content = previousDocument.Content; + lcToFix.IconSource = previousDocument.IconSource; + } + } + + layout.CollectGarbage(); + } + + protected void StartDeserialization() + { + Manager.SuspendDocumentsSourceBinding = true; + Manager.SuspendAnchorablesSourceBinding = true; + } + + protected void EndDeserialization() + { + Manager.SuspendDocumentsSourceBinding = false; + Manager.SuspendAnchorablesSourceBinding = false; + } + + #endregion Methods + } } \ No newline at end of file diff --git a/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs b/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs index fbe67b05..438c472f 100644 --- a/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs +++ b/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs @@ -1,123 +1,130 @@ -/************************************************************************ - 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.IO; -using System.Xml; -using System.Xml.Serialization; - -namespace AvalonDock.Layout.Serialization -{ - /// Implements a layout serialization/deserialization method of the docking framework. - public class XmlLayoutSerializer : LayoutSerializer - { - #region Constructors - - /// - /// Class constructor from instance. - /// - /// - public XmlLayoutSerializer(DockingManager manager) - : base(manager) - { - } - - #endregion Constructors - - #region Private Methods - - /// Function for LayoutRoot deserialization. - /// Desererialized LayoutRoot - private delegate LayoutRoot DeserelizeFunction(); - - /// Deserializes layout with the given function . - /// - private void DeserelizeCommon(DeserelizeFunction function) - { - try - { - StartDeserialization(); - var layout = function(); - FixupLayout(layout); - Manager.Layout = layout; - } - finally - { - EndDeserialization(); - } - } - #endregion Private Methods - - #region Public Methods - - readonly XmlSerializer _serializer = XmlSerializer.FromTypes(new[] { typeof(LayoutRoot) })[0]; - - /// Serialize the layout into a . - /// - public void Serialize(XmlWriter writer) - { - _serializer.Serialize(writer, Manager.Layout); - } - - /// Serialize the layout into a . - /// - public void Serialize(TextWriter writer) - { - _serializer.Serialize(writer, Manager.Layout); - } - - /// Serialize the layout into a . - /// - public void Serialize(Stream stream) - { - _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 void Deserialize(System.IO.Stream stream) - { - LayoutRoot function() => _serializer.Deserialize(stream) as LayoutRoot; - DeserelizeCommon(function); - } - - /// Deserialize the layout a file from a . - /// - public void Deserialize(TextReader reader) - { - LayoutRoot function() => _serializer.Deserialize(reader) as LayoutRoot; - DeserelizeCommon(function); - } - - /// Deserialize the layout a file from a . - /// - public void Deserialize(XmlReader reader) - { - LayoutRoot function() => _serializer.Deserialize(reader) as LayoutRoot; - DeserelizeCommon(function); - } - - /// Deserialize the layout from a file using a . - /// - public void Deserialize(string filepath) - { - using (var stream = new StreamReader(filepath, true)) - Deserialize(stream); ; - } - - #endregion Public Methods - } +/************************************************************************ + 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.IO; +using System.Xml; +using System.Xml.Serialization; + +namespace AvalonDock.Layout.Serialization +{ + /// Implements a layout serialization/deserialization method of the docking framework. + public class XmlLayoutSerializer : LayoutSerializer + { + #region Constructors + + /// + /// Class constructor from instance. + /// + /// + public XmlLayoutSerializer(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 void Deserialize(System.IO.Stream stream) + { + try + { + StartDeserialization(); + var serializer = new XmlSerializer(typeof(LayoutRoot)); + var layout = serializer.Deserialize(stream) as LayoutRoot; + FixupLayout(layout); + Manager.Layout = layout; + } + finally + { + EndDeserialization(); + } + } + + /// Deserialize the layout a file from a . + /// + public void Deserialize(TextReader reader) + { + try + { + StartDeserialization(); + var serializer = new XmlSerializer(typeof(LayoutRoot)); + var layout = serializer.Deserialize(reader) as LayoutRoot; + FixupLayout(layout); + Manager.Layout = layout; + } + finally + { + EndDeserialization(); + } + } + + /// Deserialize the layout a file from a . + /// + public void Deserialize(XmlReader reader) + { + try + { + StartDeserialization(); + var serializer = new XmlSerializer(typeof(LayoutRoot)); + var layout = serializer.Deserialize(reader) as LayoutRoot; + FixupLayout(layout); + Manager.Layout = layout; + } + finally + { + EndDeserialization(); + } + } + + /// Deserialize the layout from a file using a . + /// + public void Deserialize(string filepath) + { + using (var stream = new StreamReader(filepath)) + Deserialize(stream); + } + + #endregion Public Methods + } } \ No newline at end of file From 146fb78b677caadeb13acee3f52cea294d895022 Mon Sep 17 00:00:00 2001 From: radvile Date: Fri, 12 Nov 2021 13:39:03 +0200 Subject: [PATCH 3/3] Changed Serialization/Deserialization --- .../Layout/LayoutAnchorableFloatingWindow.cs | 6 +- .../AvalonDock/Layout/LayoutFloatingWindow.cs | 4 +- .../AvalonDock/Layout/LayoutGroup.cs | 37 +++------ .../AvalonDock/Layout/LayoutRoot.cs | 31 ++++--- .../Layout/Serialization/LayoutSerializer.cs | 5 +- .../Serialization/XmlLayoutSerializer.cs | 81 +++++++++---------- 6 files changed, 73 insertions(+), 91 deletions(-) diff --git a/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs b/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs index 7db59a02..6bfe812c 100644 --- a/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs +++ b/source/Components/AvalonDock/Layout/LayoutAnchorableFloatingWindow.cs @@ -1,4 +1,4 @@ -/************************************************************************ +/************************************************************************ AvalonDock Copyright (C) 2007-2013 Xceed Software Inc. @@ -155,13 +155,13 @@ public override void ReadXml(XmlReader reader) XmlSerializer serializer; if (reader.LocalName.Equals(nameof(LayoutAnchorablePaneGroup))) - serializer = new XmlSerializer(typeof(LayoutAnchorablePaneGroup)); + serializer = XmlSerializer.FromTypes(new[] { typeof(LayoutAnchorablePaneGroup) })[0]; else { var type = LayoutRoot.FindType(reader.LocalName); if (type == null) throw new ArgumentException("AvalonDock.LayoutAnchorableFloatingWindow doesn't know how to deserialize " + reader.LocalName); - serializer = new XmlSerializer(type); + serializer = XmlSerializer.FromTypes(new[] { type })[0]; } RootPanel = (LayoutAnchorablePaneGroup)serializer.Deserialize(reader); } diff --git a/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs b/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs index 0a479b47..44ae1f14 100644 --- a/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs +++ b/source/Components/AvalonDock/Layout/LayoutFloatingWindow.cs @@ -1,4 +1,4 @@ -/************************************************************************ +/************************************************************************ AvalonDock Copyright (C) 2007-2013 Xceed Software Inc. @@ -61,7 +61,7 @@ public virtual void WriteXml(XmlWriter writer) foreach (var child in Children) { var type = child.GetType(); - var serializer = new XmlSerializer(type); + var serializer = XmlSerializer.FromTypes(new[] { type })[0]; serializer.Serialize(writer, child); } } diff --git a/source/Components/AvalonDock/Layout/LayoutGroup.cs b/source/Components/AvalonDock/Layout/LayoutGroup.cs index 98f70163..7b243e8b 100644 --- a/source/Components/AvalonDock/Layout/LayoutGroup.cs +++ b/source/Components/AvalonDock/Layout/LayoutGroup.cs @@ -1,4 +1,4 @@ -/************************************************************************ +/************************************************************************ AvalonDock Copyright (C) 2007-2013 Xceed Software Inc. @@ -150,31 +150,16 @@ public virtual void ReadXml(System.Xml.XmlReader reader) continue; } - XmlSerializer serializer = null; - if (reader.LocalName == nameof(LayoutAnchorablePaneGroup)) - serializer = new XmlSerializer(typeof(LayoutAnchorablePaneGroup)); - else if (reader.LocalName == nameof(LayoutAnchorablePane)) - serializer = new XmlSerializer(typeof(LayoutAnchorablePane)); - else if (reader.LocalName == nameof(LayoutAnchorable)) - serializer = new XmlSerializer(typeof(LayoutAnchorable)); - else if (reader.LocalName == nameof(LayoutDocumentPaneGroup)) - serializer = new XmlSerializer(typeof(LayoutDocumentPaneGroup)); - else if (reader.LocalName == nameof(LayoutDocumentPane)) - serializer = new XmlSerializer(typeof(LayoutDocumentPane)); - else if (reader.LocalName == nameof(LayoutDocument)) - serializer = new XmlSerializer(typeof(LayoutDocument)); - else if (reader.LocalName == nameof(LayoutAnchorGroup)) - serializer = new XmlSerializer(typeof(LayoutAnchorGroup)); - else if (reader.LocalName == nameof(LayoutPanel)) - serializer = new XmlSerializer(typeof(LayoutPanel)); - else - { - var type = FindType(reader.LocalName); - if (type == null) - throw new ArgumentException("AvalonDock.LayoutGroup doesn't know how to deserialize " + reader.LocalName); - serializer = new XmlSerializer(type); - } + string fullName = String.Format("{0}.{1}", GetType().Namespace, reader.LocalName); + Type typeForSerializer = Type.GetType(fullName); + + if (typeForSerializer == null) + typeForSerializer = FindType(reader.LocalName); + + if (typeForSerializer == null) + throw new ArgumentException("AvalonDock.LayoutGroup doesn't know how to deserialize " + reader.LocalName); + XmlSerializer serializer = XmlSerializer.FromTypes(new[] { typeForSerializer })[0]; Children.Add((T)serializer.Deserialize(reader)); } @@ -188,7 +173,7 @@ public virtual void WriteXml(System.Xml.XmlWriter writer) foreach (var child in Children) { var type = child.GetType(); - var serializer = new XmlSerializer(type); + var serializer = XmlSerializer.FromTypes(new[] { type })[0]; serializer.Serialize(writer, child); } } diff --git a/source/Components/AvalonDock/Layout/LayoutRoot.cs b/source/Components/AvalonDock/Layout/LayoutRoot.cs index 2cad3519..73fe1b4a 100644 --- a/source/Components/AvalonDock/Layout/LayoutRoot.cs +++ b/source/Components/AvalonDock/Layout/LayoutRoot.cs @@ -1,4 +1,4 @@ -/************************************************************************ +/************************************************************************ AvalonDock Copyright (C) 2007-2013 Xceed Software Inc. @@ -885,47 +885,47 @@ private object ReadElement(XmlReader reader) while (reader.NodeType == XmlNodeType.Whitespace) reader.Read(); if (reader.NodeType == XmlNodeType.EndElement) return null; - XmlSerializer serializer; + Type typeToSerialize; switch (reader.LocalName) { case nameof(LayoutAnchorablePaneGroup): - serializer = new XmlSerializer(typeof(LayoutAnchorablePaneGroup)); + typeToSerialize = typeof(LayoutAnchorablePaneGroup); break; case nameof(LayoutAnchorablePane): - serializer = new XmlSerializer(typeof(LayoutAnchorablePane)); + typeToSerialize = typeof(LayoutAnchorablePane); break; case nameof(LayoutAnchorable): - serializer = new XmlSerializer(typeof(LayoutAnchorable)); + typeToSerialize = typeof(LayoutAnchorable); break; case nameof(LayoutDocumentPaneGroup): - serializer = new XmlSerializer(typeof(LayoutDocumentPaneGroup)); + typeToSerialize = typeof(LayoutDocumentPaneGroup); break; case nameof(LayoutDocumentPane): - serializer = new XmlSerializer(typeof(LayoutDocumentPane)); + typeToSerialize = typeof(LayoutDocumentPane); break; case nameof(LayoutDocument): - serializer = new XmlSerializer(typeof(LayoutDocument)); + typeToSerialize = typeof(LayoutDocument); break; case nameof(LayoutAnchorGroup): - serializer = new XmlSerializer(typeof(LayoutAnchorGroup)); + typeToSerialize = typeof(LayoutAnchorGroup); break; case nameof(LayoutPanel): - serializer = new XmlSerializer(typeof(LayoutPanel)); + typeToSerialize = typeof(LayoutPanel); break; case nameof(LayoutDocumentFloatingWindow): - serializer = new XmlSerializer(typeof(LayoutDocumentFloatingWindow)); + typeToSerialize = typeof(LayoutDocumentFloatingWindow); break; case nameof(LayoutAnchorableFloatingWindow): - serializer = new XmlSerializer(typeof(LayoutAnchorableFloatingWindow)); + typeToSerialize = typeof(LayoutAnchorableFloatingWindow); break; case nameof(LeftSide): @@ -940,13 +940,12 @@ private object ReadElement(XmlReader reader) return reader.Read(); default: - var type = FindType(reader.LocalName); - if (type == null) + typeToSerialize = FindType(reader.LocalName); + if (typeToSerialize == null) throw new ArgumentException("AvalonDock.LayoutRoot doesn't know how to deserialize " + reader.LocalName); - serializer = new XmlSerializer(type); break; } - + XmlSerializer serializer = XmlSerializer.FromTypes(new[] { typeToSerialize })[0]; return serializer.Deserialize(reader); } diff --git a/source/Components/AvalonDock/Layout/Serialization/LayoutSerializer.cs b/source/Components/AvalonDock/Layout/Serialization/LayoutSerializer.cs index 7e7d0466..7244045f 100644 --- a/source/Components/AvalonDock/Layout/Serialization/LayoutSerializer.cs +++ b/source/Components/AvalonDock/Layout/Serialization/LayoutSerializer.cs @@ -1,4 +1,4 @@ -/************************************************************************ +/************************************************************************ AvalonDock Copyright (C) 2007-2013 Xceed Software Inc. @@ -76,6 +76,9 @@ protected virtual void FixupLayout(LayoutRoot layout) if (lcToFix.ContentId != null) previousAchorable = _previousAnchorables.FirstOrDefault(a => a.ContentId == lcToFix.ContentId); + if (previousAchorable != null && previousAchorable.Title != null) + lcToFix.Title = previousAchorable.Title; + if (LayoutSerializationCallback != null) { // Ask client application via callback if item should be deserialized diff --git a/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs b/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs index 438c472f..d883b82d 100644 --- a/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs +++ b/source/Components/AvalonDock/Layout/Serialization/XmlLayoutSerializer.cs @@ -1,4 +1,4 @@ -/************************************************************************ +/************************************************************************ AvalonDock Copyright (C) 2007-2013 Xceed Software Inc. @@ -29,30 +29,53 @@ public XmlLayoutSerializer(DockingManager manager) #endregion Constructors + #region Private Methods + /// Desererialized LayoutRoot + /// Function for LayoutRoot deserialization. + + private delegate LayoutRoot DeserializeFunction(); + + /// Deserializes layout with the given function . + /// + private void DeserializeCommon(DeserializeFunction function) + { + try + { + StartDeserialization(); + var layout = function(); + FixupLayout(layout); + Manager.Layout = layout; + } + finally + { + EndDeserialization(); + } + } + #endregion + #region Public Methods + readonly XmlSerializer _serializer = XmlSerializer.FromTypes(new[] { typeof(LayoutRoot) })[0]; + /// Serialize the layout into a . /// public void Serialize(XmlWriter writer) { - var serializer = new XmlSerializer(typeof(LayoutRoot)); - serializer.Serialize(writer, Manager.Layout); + _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); + _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); + _serializer.Serialize(stream, Manager.Layout); } /// Serialize the layout into a file using a . @@ -67,54 +90,26 @@ public void Serialize(string filepath) /// public void Deserialize(System.IO.Stream stream) { - try - { - StartDeserialization(); - var serializer = new XmlSerializer(typeof(LayoutRoot)); - var layout = serializer.Deserialize(stream) as LayoutRoot; - FixupLayout(layout); - Manager.Layout = layout; - } - finally - { - EndDeserialization(); - } + LayoutRoot function() => _serializer.Deserialize(stream) as LayoutRoot; + DeserializeCommon(function); } /// Deserialize the layout a file from a . /// public void Deserialize(TextReader reader) { - try - { - StartDeserialization(); - var serializer = new XmlSerializer(typeof(LayoutRoot)); - var layout = serializer.Deserialize(reader) as LayoutRoot; - FixupLayout(layout); - Manager.Layout = layout; - } - finally - { - EndDeserialization(); - } + + LayoutRoot function() => _serializer.Deserialize(reader) as LayoutRoot; + DeserializeCommon(function); } /// Deserialize the layout a file from a . /// public void Deserialize(XmlReader reader) { - try - { - StartDeserialization(); - var serializer = new XmlSerializer(typeof(LayoutRoot)); - var layout = serializer.Deserialize(reader) as LayoutRoot; - FixupLayout(layout); - Manager.Layout = layout; - } - finally - { - EndDeserialization(); - } + + LayoutRoot function() => _serializer.Deserialize(reader) as LayoutRoot; + DeserializeCommon(function); } /// Deserialize the layout from a file using a .