WPF 에는 표처럼 오브젝트를 배치할 수 있는 Grid 라는 Layout 컨테이너가 존재한다
그런데 이 컨테이너는 Layout 만 잡아 줄 뿐 표처럼 선을 그려주는 기능은 없... 진 않지만 내장되어 있는 ShowGridLines 는 사실상 UI 디버깅 용도 외에는 쓰기 힘들정도로 심각하게 기능이 구리다.
그래서 유명하신 정성태님이 아래와 같은 글을 쓰셧다
WPF - Grid 컨트롤의 ShowGridLine 개선
윗 링크의 글을 보면 아래와 같은 부분이 있다
"생각 자체는 그리 어렵지 않습니다. 예를 들어, 가로선을 그을 때(Row별로) 컬럼 단위만큼 그리며 진행하다가 다음번 그려야 될 곳이 RowSpan으로 되어 있으면 그 구획은 긋지 말고 건너뛰면 됩니다. 세로선도 마찬가지겠지요."
그리고 해당 컨트롤을 구현한 솔루션이 링크되어 있는데... 실제로는 구현이 반대로 되어 있다. Element 를 뒤져 가며 Span 된 부분을 건너뛰는 것이 아니라 Span 이 아닌 부분을 그리는 식으로. 사실 이렇게 하더라도 저 위 샘플에선 전혀 문제가 없이 나오는데... 문제는 저 Grid에 어떤 Element 도 없는 "빈칸" 이 존재할 경우 그 부분은 아예 Border 가 그려지지 않는다는 것이다...
그래서 위에 인용한 글 대로 Skip 하는 식으로 변형을 해서 쓰기로 했고, 그 코드를 공개한다
public class BorderGrid : Grid { public BorderGrid() : base() { } public Thickness CellPadding { get { return (Thickness)this.GetValue(CellPaddingProperty); } set { this.SetValue(CellPaddingProperty, value); } } public static readonly DependencyProperty CellPaddingProperty = DependencyProperty.Register("CellPadding", typeof(Thickness), typeof(BorderGrid), new FrameworkPropertyMetadata(new Thickness(0.0, 0.0, 0.0, 0.0), FrameworkPropertyMetadataOptions.AffectsArrange, OnCellPaddingChanged) ); static void OnCellPaddingChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) { BorderGrid grid = (dependencyObject as BorderGrid); foreach (UIElement uiElement in grid.Children) { ApplyMargin(grid, uiElement); } } protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) { FrameworkElement childElement = visualAdded as FrameworkElement; ApplyMargin(this, childElement); base.OnVisualChildrenChanged(visualAdded, visualRemoved); } public static void ApplyMargin(BorderGrid PaddingGrid, UIElement element) { FrameworkElement childElement = element as FrameworkElement; Thickness cellPadding = PaddingGrid.CellPadding; BorderGrid childPaddingGrid = element as BorderGrid; if (childPaddingGrid != null) { childPaddingGrid.CellPadding = cellPadding; } else { if (childElement != null) { childElement.Margin = cellPadding; } } } Pen line = new Pen(Brushes.Black, 2); protected override void OnRender(System.Windows.Media.DrawingContext dc) { base.OnRender(dc); BorderGrid customGrid = this.Parent as BorderGrid; if (customGrid == null) { dc.DrawRectangle(null, line, new Rect(0, 0, this.ActualWidth, this.ActualHeight)); } double linePoint = 0; double posFrom = 0.0; double posTo = 0.0; int rowCount = Math.Max(this.RowDefinitions.Count, 1); int columnCount = Math.Max(this.ColumnDefinitions.Count, 1); bool[,] rowCellIgnoreStatus; bool[,] columnCellIgnoreStatus; GetRowLineCellStatus(rowCount, columnCount, out rowCellIgnoreStatus, out columnCellIgnoreStatus); if (this.ColumnDefinitions.Count != 0) { for (int row = 0; row < rowCount - 1; row++) { var r = this.RowDefinitions[row]; linePoint += r.ActualHeight; for (int column = 0; column < columnCount; column++) { bool ignoreLine = rowCellIgnoreStatus[row + 1, column]; posTo += this.ColumnDefinitions[column].ActualWidth; if (ignoreLine == false) { dc.DrawLine(line, new Point(posFrom, linePoint), new Point(posTo, linePoint)); } posFrom = posTo; } posFrom = 0.0; posTo = 0.0; } } linePoint = 0; posFrom = 0.0; posTo = 0.0; if (this.RowDefinitions.Count != 0) { for (int column = 0; column < columnCount - 1; column++) { var r = this.ColumnDefinitions[column]; linePoint += r.ActualWidth; for (int row = 0; row < rowCount; row++) { bool ignoreLine = columnCellIgnoreStatus[row, column + 1]; posTo += this.RowDefinitions[row].ActualHeight; if (ignoreLine == false) { dc.DrawLine(line, new Point(linePoint, posFrom), new Point(linePoint, posTo)); } posFrom = posTo; } posTo = 0.0; posFrom = 0.0; } } } private void GetRowLineCellStatus(int rowCount, int columnCount, out bool[,] rowCellIgnoreStatus, out bool[,] columnCellIgnoreStatus) { rowCellIgnoreStatus = new bool[rowCount, columnCount]; columnCellIgnoreStatus = new bool[rowCount, columnCount]; foreach (UIElement element in this.Children) { int row = Grid.GetRow(element); int column = Grid.GetColumn(element); int spanCount = Grid.GetColumnSpan(element); for (int span = 1; span < spanCount; span++) { try { columnCellIgnoreStatus[row, column + span] = true; } catch (Exception) { // } } spanCount = Grid.GetRowSpan(element); for (int span = 1; span < spanCount; span++) { rowCellIgnoreStatus[row + span, column] = true; } } } }