diff --git a/GridLayout.cs b/GridLayout.cs new file mode 100644 index 00000000..f8329ca8 --- /dev/null +++ b/GridLayout.cs @@ -0,0 +1,107 @@ +// http://daniel-albuschat.blogspot.ru/2011/07/gridlayout-for-wpf-escape-margin-hell.html +// + +using System.Windows; +using System.Windows.Controls; + +namespace GridLayoutTest +{ + // The GridLayout is a special Panel that can be used exactly like the Grid Panel, except that it + // defines a new property ChildMargin. ChildMargin's left, top, right and bottom margins will be applied + // to all children in a way that the children will have a vertical space of ChildMargin.Top+ChildMargin.Bottom + // and a horizontal space of ChildMargin.Left+ChildMargin.Right between them. + // However, there is no margin for the borders of the internal widget, so that the GridLayout itself can be + // aligned to another element without a margin. + // It's best to have a look at TestWindow, which effectively tests all possible alignments of children. + + public class GridLayout : Grid + { + public static readonly DependencyProperty ChildMarginProperty = DependencyProperty.Register( + "ChildMargin", + typeof(Thickness), + typeof(GridLayout), + new FrameworkPropertyMetadata(new Thickness (5)) + { + AffectsArrange = true, + AffectsMeasure = true + }); + // The child margin defines a margin that will be automatically applied to all children of this Grid. + // However, the children at the edges will have the respective margins remove. E.g. the leftmost children will have + // a Margin.Left of 0 and the children in the first row will have a Margin.Top of 0. + // The margins that are not set to 0 are set to half the ChildMargin's value, since it's neighbour will also apply it, + // effectively doubling it. + + public Thickness ChildMargin + { + get { return (Thickness)GetValue(ChildMarginProperty); } + set + { + SetValue(ChildMarginProperty, value); + UpdateChildMargins(); + } + } + + // UpdateChildMargin first finds out what's the rightmost column and bottom row and then applies + // the correct margins to all children. + + public void UpdateChildMargins() + { + int maxColumn = 0; + int maxRow = 0; + foreach (UIElement element in InternalChildren) + { + int row = GetRow(element); + int rowSpan = GetRowSpan(element); + int column = GetColumn(element); + int columnSpan = GetColumnSpan(element); + + if (row + rowSpan > maxRow) + maxRow = row + rowSpan; + if (column + columnSpan> maxColumn) + maxColumn = column + columnSpan; + } + foreach (UIElement element in InternalChildren) + { + FrameworkElement fe = element as FrameworkElement; + if (null != fe) + { + int row = GetRow(fe); + int rowSpan = GetRowSpan(fe); + int column = GetColumn(fe); + int columnSpan = GetColumnSpan(fe); + double factorLeft = 0.5; + double factorTop = 0.5; + double factorRight = 0.5; + double factorBottom = 0.5; + // Top row - no top margin + if (row == 0) + factorTop = 0; + // Bottom row - no bottom margin + if (row + rowSpan >= maxRow) + factorBottom = 0; + // Leftmost column = no left margin + if (column == 0) + factorLeft = 0; + // Rightmost column - no right margin + if (column + columnSpan >= maxColumn) + factorRight = 0; + fe.Margin = new Thickness (ChildMargin.Left * factorLeft, + ChildMargin.Top * factorTop, + ChildMargin.Right * factorRight, + ChildMargin.Bottom * factorBottom); + } + } + } + + // We change all children's margins in MeasureOverride, since this is called right before + // the layouting takes place. I was first skeptical to do this here, because I thought changing + // the margin will trigger a LayoutUpdate, which in turn would lead to an endless recursion, + // but apparantly WPF takes care of this. + + protected override Size MeasureOverride(Size availableSize) + { + UpdateChildMargins(); + return base.MeasureOverride(availableSize); + } + } +}