From da0c8c7bba44328456136396d2ef585b43a04f17 Mon Sep 17 00:00:00 2001 From: NaBian <836904362@qq.com> Date: Sat, 11 Jul 2020 15:48:49 +0800 Subject: [PATCH] fixed #430 --- .../Controls/Panel/RelativePanel.cs | 377 ++++++++++-------- .../Tools/Extension/ValueExtension.cs | 2 + 2 files changed, 214 insertions(+), 165 deletions(-) diff --git a/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs b/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs index cb546dd9..bfc1c878 100644 --- a/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs +++ b/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs @@ -8,6 +8,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Markup; using HandyControl.Data; +using HandyControl.Tools.Extension; namespace HandyControl.Controls { @@ -189,29 +190,11 @@ namespace HandyControl.Controls protected override Size MeasureOverride(Size availableSize) { - var maxSize = new Size(); - - foreach (UIElement child in InternalChildren) - { - if (child != null) - { - child.Measure(availableSize); - maxSize.Width = Math.Max(maxSize.Width, child.DesiredSize.Width); - maxSize.Height = Math.Max(maxSize.Height, child.DesiredSize.Height); - } - } - - return maxSize; - } - - protected override Size ArrangeOverride(Size arrangeSize) - { - _childGraph.Reset(arrangeSize); + _childGraph.Clear(); foreach (UIElement child in InternalChildren) { if (child == null) continue; - var node = _childGraph.AddNode(child); node.AlignLeftWithNode = _childGraph.AddLink(node, GetAlignLeftWith(child)); @@ -228,22 +211,30 @@ namespace HandyControl.Controls node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetAlignVerticalCenterWith(child)); } - if (_childGraph.CheckCyclic()) - { - throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete."); - } + _childGraph.Measure(availableSize); + return availableSize; + } + protected override Size ArrangeOverride(Size arrangeSize) + { + _childGraph.GetNodes().Do(node => node.Arrange(arrangeSize)); return arrangeSize; } private class GraphNode { - public Point Position { get; set; } - - public bool Arranged { get; set; } + public bool Measured { get; set; } public UIElement Element { get; } + public double Left { get; set; } = double.NaN; + + public double Top { get; set; } = double.NaN; + + public double Right { get; set; } = double.NaN; + + public double Bottom { get; set; } = double.NaN; + public HashSet OutgoingNodes { get; } public GraphNode AlignLeftWithNode { get; set; } @@ -271,17 +262,24 @@ namespace HandyControl.Controls OutgoingNodes = new HashSet(); Element = element; } + + public void Arrange(Size arrangeSize) => Element.Arrange(new Rect(Left, Top, Math.Max(arrangeSize.Width - Left - Right, 0), Math.Max(arrangeSize.Height - Top - Bottom, 0))); } private class Graph { private readonly Dictionary _nodeDic; - private Size _arrangeSize; + private Size AvailableSize { get; set; } - public Graph() + public Graph() => _nodeDic = new Dictionary(); + + public IEnumerable GetNodes() => _nodeDic.Values; + + public void Clear() { - _nodeDic = new Dictionary(); + AvailableSize = new Size(); + _nodeDic.Clear(); } public GraphNode AddLink(GraphNode from, UIElement to) @@ -315,20 +313,15 @@ namespace HandyControl.Controls return _nodeDic[value]; } - public void Reset(Size arrangeSize) + public void Measure(Size availableSize) { - _arrangeSize = arrangeSize; - _nodeDic.Clear(); + AvailableSize = availableSize; + Measure(_nodeDic.Values, null); } - public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null, null); - - private bool CheckCyclic(IEnumerable nodes, GraphNode waitNode, HashSet set) + private void Measure(IEnumerable nodes, HashSet set) { - if (set == null) - { - set = new HashSet(); - } + set ??= new HashSet(); foreach (var node in nodes) { @@ -336,189 +329,243 @@ namespace HandyControl.Controls * 该节点无任何依赖,所以从这里开始计算元素位置。 * 因为无任何依赖,所以忽略同级元素 */ - if (!node.Arranged && node.OutgoingNodes.Count == 0) + if (!node.Measured && node.OutgoingNodes.Count == 0) { - ArrangeChild(node, true); + MeasureChild(node); continue; } // 判断依赖元素是否全部排列完毕 - if (node.OutgoingNodes.All(item => item.Arranged)) + if (node.OutgoingNodes.All(item => item.Measured)) { - ArrangeChild(node); + MeasureChild(node); continue; } // 判断是否有循环 - if (!set.Add(node.Element)) return true; + if (!set.Add(node.Element)) + throw new Exception( + "RelativePanel error: Circular dependency detected. Layout could not complete."); // 没有循环,且有依赖,则继续往下 - return CheckCyclic(node.OutgoingNodes, node.Arranged ? null : node, set); - } + Measure(node.OutgoingNodes, set); - if (waitNode != null) - { - ArrangeChild(waitNode); + if (!node.Measured) + { + MeasureChild(node); + } } - return false; } - private void ArrangeChild(GraphNode node, bool ignoneSibling = false) + private void MeasureChild(GraphNode node) { var child = node.Element; - var childSize = child.DesiredSize; - var childPos = new Point(); - - #region 容器居中对齐 - - if (GetAlignHorizontalCenterWithPanel(child)) - { - childPos.X = (_arrangeSize.Width - childSize.Width) / 2; - } - - if (GetAlignVerticalCenterWithPanel(child)) - { - childPos.Y = (_arrangeSize.Height - childSize.Height) / 2; - } - - #endregion var alignLeftWithPanel = GetAlignLeftWithPanel(child); var alignTopWithPanel = GetAlignTopWithPanel(child); var alignRightWithPanel = GetAlignRightWithPanel(child); var alignBottomWithPanel = GetAlignBottomWithPanel(child); - if (!ignoneSibling) + #region Panel alignment + + if (alignLeftWithPanel) node.Left = 0; + if (alignTopWithPanel) node.Top = 0; + if (alignRightWithPanel) node.Right = 0; + if (alignBottomWithPanel) node.Bottom = 0; + + #endregion + + #region Sibling alignment + + if (node.AlignLeftWithNode != null) { - #region 元素间位置 - - if (node.LeftOfNode != null) - { - childPos.X = node.LeftOfNode.Position.X - childSize.Width; - } - - if (node.AboveNode != null) - { - childPos.Y = node.AboveNode.Position.Y - childSize.Height; - } - - if (node.RightOfNode != null) - { - childPos.X = node.RightOfNode.Position.X + node.RightOfNode.Element.DesiredSize.Width; - } - - if (node.BelowNode != null) - { - childPos.Y = node.BelowNode.Position.Y + node.BelowNode.Element.DesiredSize.Height; - } - - #endregion - - #region 元素居中对齐 - - if (node.AlignHorizontalCenterWith != null) - { - childPos.X = node.AlignHorizontalCenterWith.Position.X + - (node.AlignHorizontalCenterWith.Element.DesiredSize.Width - childSize.Width) / 2; - } - - if (node.AlignVerticalCenterWith != null) - { - childPos.Y = node.AlignVerticalCenterWith.Position.Y + - (node.AlignVerticalCenterWith.Element.DesiredSize.Height - childSize.Height) / 2; - } - - #endregion - - #region 元素间对齐 - - if (node.AlignLeftWithNode != null) - { - childPos.X = node.AlignLeftWithNode.Position.X; - } - - if (node.AlignTopWithNode != null) - { - childPos.Y = node.AlignTopWithNode.Position.Y; - } - - if (node.AlignRightWithNode != null) - { - childPos.X = node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width; - } - - if (node.AlignBottomWithNode != null) - { - childPos.Y = node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height; - } - - #endregion + node.Left = node.Left.IsNaN() ? node.AlignLeftWithNode.Left : node.AlignLeftWithNode.Left * 0.5; } - - #region 容器对齐 - if (alignLeftWithPanel) + if (node.AlignTopWithNode != null) { - if (node.AlignRightWithNode != null) + node.Top = node.Top.IsNaN() ? node.AlignTopWithNode.Top : node.AlignTopWithNode.Top * 0.5; + } + + if (node.AlignRightWithNode != null) + { + node.Right = node.Right.IsNaN() + ? node.AlignRightWithNode.Right + : node.AlignRightWithNode.Right * 0.5; + } + + if (node.AlignBottomWithNode != null) + { + node.Bottom = node.Bottom.IsNaN() + ? node.AlignBottomWithNode.Bottom + : node.AlignBottomWithNode.Bottom * 0.5; + } + + #endregion + + #region Measure + + var availableHeight = AvailableSize.Height - node.Top - node.Bottom; + if (availableHeight.IsNaN()) + { + availableHeight = AvailableSize.Height; + + if (!node.Top.IsNaN() && node.Bottom.IsNaN()) { - childPos.X = (node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width) / 2; + availableHeight -= node.Top; } - else + else if (node.Top.IsNaN() && !node.Bottom.IsNaN()) { - childPos.X = 0; + availableHeight -= node.Bottom; } } - if (alignTopWithPanel) + var availableWidth = AvailableSize.Width - node.Left - node.Right; + if (availableWidth.IsNaN()) { - if (node.AlignBottomWithNode != null) + availableWidth = AvailableSize.Width; + + if (!node.Left.IsNaN() && node.Right.IsNaN()) { - childPos.Y = (node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height) / 2; + availableWidth -= node.Left; } - else + else if (node.Left.IsNaN() && !node.Right.IsNaN()) { - childPos.Y = 0; + availableWidth -= node.Right; } } - if (alignRightWithPanel) + node.Element.Measure(new Size(Math.Max(availableWidth, 0), Math.Max(availableHeight, 0))); + var childSize = node.Element.DesiredSize; + + #endregion + + #region Sibling positional + + if (node.LeftOfNode != null && node.Left.IsNaN()) { - if (alignLeftWithPanel) + node.Left = node.LeftOfNode.Left - childSize.Width; + } + + if (node.AboveNode != null && node.Top.IsNaN()) + { + node.Top = node.AboveNode.Top - childSize.Height; + } + + if (node.RightOfNode != null) + { + if (node.Right.IsNaN()) { - childPos.X = (_arrangeSize.Width - childSize.Width) / 2; + node.Right = node.RightOfNode.Right - childSize.Width; } - else if(node.AlignLeftWithNode == null) + + if (node.Left.IsNaN()) { - childPos.X = _arrangeSize.Width - childSize.Width; - } - else - { - childPos.X = (_arrangeSize.Width + node.AlignLeftWithNode.Position.X - childSize.Width) / 2; + node.Left = AvailableSize.Width - node.RightOfNode.Right; } } - if (alignBottomWithPanel) + if (node.BelowNode != null) { - if (alignTopWithPanel) + if (node.Bottom.IsNaN()) { - childPos.Y = (_arrangeSize.Height - childSize.Height) / 2; + node.Bottom = node.BelowNode.Bottom - childSize.Height; } - else if (node.AlignTopWithNode == null) + + if (node.Top.IsNaN()) { - childPos.Y = _arrangeSize.Height - childSize.Height; - } - else - { - childPos.Y = (_arrangeSize.Height + node.AlignTopWithNode.Position.Y - childSize.Height) / 2; + node.Top = AvailableSize.Height - node.BelowNode.Bottom; } } #endregion - child.Arrange(new Rect(childPos.X, childPos.Y, childSize.Width, childSize.Height)); - node.Position = childPos; - node.Arranged = true; + #region Sibling-center alignment + + if (node.AlignHorizontalCenterWith != null) + { + var halfWidthLeft = (AvailableSize.Width + node.AlignHorizontalCenterWith.Left - node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5; + var halfWidthRight = (AvailableSize.Width - node.AlignHorizontalCenterWith.Left + node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5; + + if (node.Left.IsNaN()) node.Left = halfWidthLeft; + else node.Left = (node.Left + halfWidthLeft) * 0.5; + + if (node.Right.IsNaN()) node.Right = halfWidthRight; + else node.Right = (node.Right + halfWidthRight) * 0.5; + } + + if (node.AlignVerticalCenterWith != null) + { + var halfHeightTop = (AvailableSize.Height + node.AlignVerticalCenterWith.Top - node.AlignVerticalCenterWith.Bottom - childSize.Height) * 0.5; + var halfHeightBottom = (AvailableSize.Height - node.AlignVerticalCenterWith.Top + node.AlignVerticalCenterWith.Bottom - childSize.Height) * 0.5; + + if (node.Top.IsNaN()) node.Top = halfHeightTop; + else node.Top = (node.Top + halfHeightTop) * 0.5; + + if (node.Bottom.IsNaN()) node.Bottom = halfHeightBottom; + else node.Bottom = (node.Bottom + halfHeightBottom) * 0.5; + } + + #endregion + + #region Panel-center alignment + + if (GetAlignHorizontalCenterWithPanel(child)) + { + var halfSubWidth = (AvailableSize.Width - childSize.Width) * 0.5; + + if (node.Left.IsNaN()) node.Left = halfSubWidth; + else node.Left = (node.Left + halfSubWidth) * 0.5; + + if (node.Right.IsNaN()) node.Right = halfSubWidth; + else node.Right = (node.Right + halfSubWidth) * 0.5; + } + + if (GetAlignVerticalCenterWithPanel(child)) + { + var halfSubHeight = (AvailableSize.Height - childSize.Height) * 0.5; + + if (node.Top.IsNaN()) node.Top = halfSubHeight; + else node.Top = (node.Top + halfSubHeight) * 0.5; + + if (node.Bottom.IsNaN()) node.Bottom = halfSubHeight; + else node.Bottom = (node.Bottom + halfSubHeight) * 0.5; + } + + #endregion + + if (node.Left.IsNaN()) + { + if (!node.Right.IsNaN()) + node.Left = AvailableSize.Width - node.Right - childSize.Width; + else + { + node.Left = 0; + node.Right = AvailableSize.Width - childSize.Width; + } + } + else if (!node.Left.IsNaN() && node.Right.IsNaN()) + { + node.Right = AvailableSize.Width - node.Left - childSize.Width; + } + + if (node.Top.IsNaN()) + { + if (!node.Bottom.IsNaN()) + node.Top = AvailableSize.Height - node.Bottom - childSize.Height; + else + { + node.Top = 0; + node.Bottom = AvailableSize.Height - childSize.Height; + } + } + else if (!node.Top.IsNaN() && node.Bottom.IsNaN()) + { + node.Bottom = AvailableSize.Height - node.Top - childSize.Height; + } + + node.Measured = true; } } } -} +} \ No newline at end of file diff --git a/src/Shared/HandyControl_Shared/Tools/Extension/ValueExtension.cs b/src/Shared/HandyControl_Shared/Tools/Extension/ValueExtension.cs index 78afef86..ef3abf83 100644 --- a/src/Shared/HandyControl_Shared/Tools/Extension/ValueExtension.cs +++ b/src/Shared/HandyControl_Shared/Tools/Extension/ValueExtension.cs @@ -17,5 +17,7 @@ namespace HandyControl.Tools.Extension MathHelper.AreClose(thickness.Left, thickness.Top) && MathHelper.AreClose(thickness.Left, thickness.Right) && MathHelper.AreClose(thickness.Left, thickness.Bottom); + + public static bool IsNaN(this double value) => double.IsNaN(value); } } \ No newline at end of file