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);
}
}
}
};
}