using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Shapes; using miew.Enumerable; namespace agree.Wpf.Util { using Math = System.Math; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public class TreeLayoutPanel : Panel { static TreeLayoutPanel() { Control.VerticalContentAlignmentProperty.AddOwner(typeof(TreeLayoutPanel), new FrameworkPropertyMetadata( VerticalAlignment.Top, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsRender | 0, null, (e, o) => { VerticalAlignment va = (VerticalAlignment)o; return va == VerticalAlignment.Stretch ? VerticalAlignment.Center : va; }, true )); Shape.StrokeProperty.AddOwner(typeof(TreeLayoutPanel), new FrameworkPropertyMetadata( Brushes.Black, 0, (e, o) => { TreeLayoutPanel p = (TreeLayoutPanel)e; p.InvalidateVisual(); }, null, false)); Shape.StrokeThicknessProperty.AddOwner(typeof(TreeLayoutPanel), new FrameworkPropertyMetadata( 1.0, 0, (e, o) => { TreeLayoutPanel p = (TreeLayoutPanel)e; p.InvalidateVisual(); }, null, false)); //TextBlock.FontSizeProperty.AddOwner(typeof(TreeLayoutPanel), // new FrameworkPropertyMetadata( // 15.0, // 0, // (e, o) => // { // TreeLayoutPanel p = (TreeLayoutPanel)e; // p.InvalidateVisual(); // }, // null, // false)); //DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeLayoutPanel), new FrameworkPropertyMetadata(typeof(TreeLayoutPanel))); } public VerticalAlignment VerticalContentAlignment { get { return (VerticalAlignment)GetValue(Control.VerticalContentAlignmentProperty); } set { SetValue(Control.VerticalContentAlignmentProperty, value); } } public Brush Stroke { get { return (Brush)GetValue(Shape.StrokeProperty); } set { SetValue(Shape.StrokeProperty, value); } } public Double StrokeThickness { get { return (Double)GetValue(Shape.StrokeThicknessProperty); } set { SetValue(Shape.StrokeThicknessProperty, value); } } //public Double FontSize //{ // get { return (Double)GetValue(TextBlock.FontSizeProperty); } // set { SetValue(TextBlock.FontSizeProperty, value); } //} List<Double> m_layer_heights = new List<Double>(); /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// class NodeLayoutInfo { public List<FrameworkElement> m_children = new List<FrameworkElement>(); public Point top_handle; public Point bot_handle; public Double SubTreeWidth; public Double pxLeftPosRelativeToParent; public Double pxLeftPosRelativeToBoundingBox; public Double pxToLeftSibling; public Double pxFromTop; public Double pxFromLeft; public List<Double> lstPosLeftBoundaryRelativeToRoot = new List<Double>(); public List<Double> lstPosRightBoundaryRelativeToRoot = new List<Double>(); } Dictionary<FrameworkElement, NodeLayoutInfo> nli_dict = new Dictionary<FrameworkElement, NodeLayoutInfo>(); public static readonly DependencyProperty TreeParentProperty = DependencyProperty.RegisterAttached("TreeParent", typeof(FrameworkElement), typeof(TreeLayoutPanel), new FrameworkPropertyMetadata( default(FrameworkElement), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsRender, null, null, true)); public static FrameworkElement GetTreeParent(FrameworkElement e) { return (FrameworkElement)e.GetValue(TreeParentProperty); } public static void SetTreeParent(FrameworkElement e, FrameworkElement par) { e.SetValue(TreeParentProperty, par); } public static readonly DependencyProperty VerticalBufferProperty = DependencyProperty.Register( "VerticalBuffer", typeof(double), typeof(TreeLayoutPanel), new FrameworkPropertyMetadata( 10.0, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsRender | 0, null, null, false ), null ); public double VerticalBuffer { get { return (double)GetValue(VerticalBufferProperty); } set { SetValue(VerticalBufferProperty, value); } } public readonly static DependencyProperty HorizontalBufferSubtreeProperty = DependencyProperty.Register( "HorizontalBufferSubtree", typeof(double), typeof(TreeLayoutPanel), new FrameworkPropertyMetadata( 10.0, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsRender | 0, null, null, false ), null ); public double HorizontalBufferSubtree { get { return (double)GetValue(HorizontalBufferSubtreeProperty); } set { SetValue(HorizontalBufferSubtreeProperty, value); } } public readonly static DependencyProperty HorizontalBufferProperty = DependencyProperty.Register( "HorizontalBuffer", typeof(double), typeof(TreeLayoutPanel), new FrameworkPropertyMetadata( 10.0, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsRender | 0, null, null, false ), null ); public double HorizontalBuffer { get { return (double)GetValue(HorizontalBufferProperty); } set { SetValue(HorizontalBufferProperty, value); } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// IList<FrameworkElement> GetVisibleNodeChildren(FrameworkElement tn) { NodeLayoutInfo nli; if (tn.Visibility != System.Windows.Visibility.Collapsed && nli_dict.TryGetValue(tn, out nli)) return nli.m_children; return new FrameworkElement[0]; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CalculateLayout(FrameworkElement node, int iLayer) { NodeLayoutInfo ni = nli_dict[node]; IList<FrameworkElement> tng = GetVisibleNodeChildren(node); Double width = node.DesiredSize.Width; if (tng.Count == 0) { ni.SubTreeWidth = width; ni.lstPosLeftBoundaryRelativeToRoot.Add(0); ni.lstPosRightBoundaryRelativeToRoot.Add(width); } else { List<Double> lstLeftToBB = new List<Double>(); List<int> lstResponsible = new List<int>(); for (int i = 0; i < tng.Count; i++) { FrameworkElement tn = tng[i]; CalculateLayout(tn, iLayer + 1); RepositionSubtree(i, tng, lstLeftToBB, lstResponsible); } // If a subtree extends deeper than it's left neighbors then at that lower level it could potentially extend beyond those neighbors // on the left. We have to check for this and make adjustements after the loop if it occurred. Double pxWidth = 0.0; Double pxUndercut = 0.0; Double pxWidthCur = Double.NaN; foreach (FrameworkElement tn in ni.m_children) { NodeLayoutInfo nlic = nli_dict[tn]; if (Double.IsNaN(pxWidthCur)) pxWidthCur = nlic.pxLeftPosRelativeToBoundingBox; pxWidthCur += nlic.pxToLeftSibling; if (nlic.pxLeftPosRelativeToBoundingBox > pxWidthCur) pxUndercut = Math.Max(pxUndercut, nlic.pxLeftPosRelativeToBoundingBox - pxWidthCur); // pxWidth might already be wider than the current node's subtree if earlier nodes "undercut" on the // right hand side so we have to take the Max here... pxWidth = Math.Max(pxWidth, pxWidthCur + nlic.SubTreeWidth - nlic.pxLeftPosRelativeToBoundingBox); // After this next statement, the BoundingBox we're relative to is the one of our parent's subtree rather than // our own subtree (with the exception of undercut considerations) nlic.pxLeftPosRelativeToBoundingBox = pxWidthCur; } if (pxUndercut > 0.0) { foreach (FrameworkElement tn in ni.m_children) nli_dict[tn].pxLeftPosRelativeToBoundingBox += pxUndercut; pxWidth += pxUndercut; } // We are never narrower than our root node's width which we haven't taken into account yet so // we do that here. ni.SubTreeWidth = Math.Max(width, pxWidth); // ...so that this centering may place the parent node negatively while the "width" is the width of // all the child nodes. // We should be centered between the connection points of our children... FrameworkElement tnLeftMost = ni.m_children[0]; Double pxLeftChild = nli_dict[tnLeftMost].pxLeftPosRelativeToBoundingBox + tnLeftMost.DesiredSize.Width / 2; FrameworkElement tnRightMost = ni.m_children[ni.m_children.Count - 1]; Double pxRightChild = nli_dict[tnRightMost].pxLeftPosRelativeToBoundingBox + tnRightMost.DesiredSize.Width / 2; ni.pxLeftPosRelativeToBoundingBox = (pxLeftChild + pxRightChild - width) / 2; // If the root node was wider than the subtree, then we'll have a negative position for it. We need // to readjust things so that the left of the root node represents the left of the bounding box and // the child distances to the Bounding box need to be adjusted accordingly. if (ni.pxLeftPosRelativeToBoundingBox < 0) { foreach (FrameworkElement tnChildCur in ni.m_children) nli_dict[tnChildCur].pxLeftPosRelativeToBoundingBox -= ni.pxLeftPosRelativeToBoundingBox; ni.pxLeftPosRelativeToBoundingBox = 0; } foreach (FrameworkElement tn in tng) { NodeLayoutInfo ltiCur = nli_dict[tn]; ltiCur.pxLeftPosRelativeToParent = ltiCur.pxLeftPosRelativeToBoundingBox - ni.pxLeftPosRelativeToBoundingBox; } ni.lstPosLeftBoundaryRelativeToRoot.Add(0.0); ni.lstPosRightBoundaryRelativeToRoot.Add(width); DetermineBoundary(ni.m_children, true, ni.lstPosLeftBoundaryRelativeToRoot); DetermineBoundary(ni.m_children.AsEnumerable().Reverse(), false, ni.lstPosRightBoundaryRelativeToRoot); } while (m_layer_heights.Count <= iLayer) m_layer_heights.Add(0.0); m_layer_heights[iLayer] = Math.Max(node.DesiredSize.Height, m_layer_heights[iLayer]); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void DetermineBoundary(IEnumerable<FrameworkElement> entn, bool fLeft, List<Double> lstPos) { int cLayersDeep = 1; foreach (FrameworkElement tnChild in entn) { NodeLayoutInfo ltiChild = nli_dict[tnChild]; List<Double> lstPosCur = fLeft ? ltiChild.lstPosLeftBoundaryRelativeToRoot : ltiChild.lstPosRightBoundaryRelativeToRoot; if (lstPosCur.Count >= lstPos.Count) { foreach (var e in lstPosCur.Skip(cLayersDeep - 1)) { lstPos.Add(e + ltiChild.pxLeftPosRelativeToParent); cLayersDeep++; } } } } struct boundary_calc { public int i_cur; public Double delta; public int i_resp; }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// IEnumerable<boundary_calc> MergeBoundaryEnums(IEnumerable<Double> eLeft, IEnumerable<Double> eRight, IEnumerable<int> eResponsible) { IEnumerator<Double> enLeft = eLeft.GetEnumerator(); IEnumerator<Double> enRight = eRight.GetEnumerator(); IEnumerator<int> enResponsible = eResponsible.GetEnumerator(); int i = 0; while (enLeft.MoveNext() && enRight.MoveNext() && enResponsible.MoveNext()) { yield return new boundary_calc { i_cur = i++, delta = enLeft.Current - enRight.Current, i_resp = enResponsible.Current }; } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void RepositionSubtree(int ix, IList<FrameworkElement> tngSiblings, List<Double> lstLeftToBB, List<int> lsttnResponsible) { NodeLayoutInfo lti = nli_dict[tngSiblings[ix]]; if (ix == 0) { // No shifting but we still have to prepare the initial version of the // left hand skeleton list foreach (Double pxRelativeToRoot in lti.lstPosRightBoundaryRelativeToRoot) { lstLeftToBB.Add(pxRelativeToRoot + lti.pxLeftPosRelativeToBoundingBox); lsttnResponsible.Add(0); } return; } boundary_calc bc_max = new boundary_calc { delta = 0, i_resp = -1, i_cur = 0 }; foreach (boundary_calc bc in MergeBoundaryEnums( lstLeftToBB, lti.lstPosLeftBoundaryRelativeToRoot, lsttnResponsible)) { if (bc.delta > bc_max.delta) bc_max = bc; } Double horz_node_pad = HorizontalBuffer; Double horz_tree_pad = HorizontalBufferSubtree; Double pxHorizontalBuffer = bc_max.i_cur == 0 ? horz_node_pad : horz_tree_pad; FrameworkElement tnLeft = tngSiblings[ix - 1]; lti.pxToLeftSibling = bc_max.delta - lstLeftToBB[0] + tnLeft.DesiredSize.Width + pxHorizontalBuffer; int cLevels = Math.Min(lti.lstPosRightBoundaryRelativeToRoot.Count, lstLeftToBB.Count); for (int i = 0; i < cLevels; i++) { lstLeftToBB[i] = lti.lstPosRightBoundaryRelativeToRoot[i] + bc_max.delta + pxHorizontalBuffer; lsttnResponsible[i] = ix; } for (int i = lstLeftToBB.Count; i < lti.lstPosRightBoundaryRelativeToRoot.Count; i++) { lstLeftToBB.Add(lti.lstPosRightBoundaryRelativeToRoot[i] + bc_max.delta + pxHorizontalBuffer); lsttnResponsible.Add(ix); } Double pxSlop = lti.pxToLeftSibling - tnLeft.DesiredSize.Width - horz_node_pad; if (pxSlop > 0) { for (int i = bc_max.i_resp + 1; i < ix; i++) nli_dict[tngSiblings[i]].pxToLeftSibling += pxSlop * (i - bc_max.i_resp) / (ix - bc_max.i_resp); lti.pxToLeftSibling -= (ix - bc_max.i_resp - 1) * pxSlop / (ix - bc_max.i_resp); } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Double CalcJustify(Double height, Double pxRowHeight) { switch (VerticalContentAlignment) { case VerticalAlignment.Top: return 0; case VerticalAlignment.Center: return (pxRowHeight - height) / 2; case VerticalAlignment.Bottom: return pxRowHeight - height; } throw new InvalidOperationException(); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Size DetermineFinalPositions(FrameworkElement tn, int iLayer, Double pxFromTop, Double pxParentFromLeft) { NodeLayoutInfo nli = nli_dict[tn]; Double pxRowHeight = m_layer_heights[iLayer++]; nli.pxFromTop = pxFromTop + CalcJustify(tn.DesiredSize.Height, pxRowHeight); nli.pxFromLeft = nli.pxLeftPosRelativeToParent + pxParentFromLeft; nli.bot_handle = new Point(nli.pxFromLeft + tn.DesiredSize.Width / 2, nli.pxFromTop + tn.DesiredSize.Height); nli.top_handle = new Point(nli.pxFromLeft + tn.DesiredSize.Width / 2, nli.pxFromTop); Double pxBottom = nli.pxFromTop + tn.DesiredSize.Height; Double vert_node_pad = VerticalBuffer; foreach (FrameworkElement tnCur in GetVisibleNodeChildren(tn)) { Double b = DetermineFinalPositions(tnCur, iLayer, pxFromTop + pxRowHeight + vert_node_pad, nli.pxFromLeft).Height; pxBottom = Math.Max(pxBottom, b); } return new Size(nli.SubTreeWidth, pxBottom); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected override Size MeasureOverride(Size availableSize) { FrameworkElement[] children = InternalChildren.OfType<FrameworkElement>().ToArray(); if (children.Length == 0) return new Size(100, 100); nli_dict = new Dictionary<FrameworkElement, NodeLayoutInfo>(); Size szFinal = new Size(0, 0); foreach (FrameworkElement child in children) { child.Measure(availableSize); Size szThis = child.DesiredSize; if (szThis.Width > szFinal.Width || szThis.Height > szFinal.Height) { szFinal = new Size(Math.Max(szThis.Width, szFinal.Width), Math.Max(szThis.Height, szFinal.Height)); } nli_dict.Add(child, new NodeLayoutInfo()); } FrameworkElement root = children.FirstOrDefault(fe => fe.GetValue(TreeParentProperty) == null); if (root == null) return szFinal; foreach (FrameworkElement child in children) { FrameworkElement tpar = child.GetValue(TreeParentProperty) as FrameworkElement; if (tpar == null) continue; nli_dict[tpar].m_children.Add(child); } CalculateLayout(root, 0); return DetermineFinalPositions(root, 0, 0, nli_dict[root].pxLeftPosRelativeToBoundingBox); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected override Size ArrangeOverride(Size finalSize) { foreach (FrameworkElement tn in InternalChildren.OfType<FrameworkElement>()) { Point ptLocation = new Point(0, 0); NodeLayoutInfo nli; if (nli_dict.TryGetValue(tn, out nli)) ptLocation = new Point(nli.pxFromLeft, nli.pxFromTop); tn.Arrange(new Rect(ptLocation, tn.DesiredSize)); } return finalSize; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// protected override void OnRender(DrawingContext dc) { base.OnRender(dc); Pen pen = new Pen(Stroke, StrokeThickness); foreach (FrameworkElement tn in InternalChildren.OfType<FrameworkElement>()) { NodeLayoutInfo nli = nli_dict[tn]; Point b = nli.bot_handle; foreach (FrameworkElement child in nli.m_children) { dc.DrawLine(pen, b, nli_dict[child].top_handle); } } } }; }