Post

WPF Grid 에 표처럼 Border 를 그려보자

주의 사항

이 글은 예전 블로그에서 옮겨온 오래 된 글입니다. 현재 상황과는 다를 수 있으며, 잘못 된 정보가 있을 수 있습니다.


WPF 에는 표처럼 오브젝트를 배치할 수 있는 Grid 라는 Layout 컨테이너가 존재한다

그런데 이 컨테이너는 Layout 만 잡아 줄 뿐 표처럼 선을 그려주는 기능은 없… 진 않지만 내장되어 있는 ShowGridLines 는 사실상 UI 디버깅 용도 외에는 쓰기 힘들정도로 심각하게 기능이 구리다.

그래서 유명하신 정성태님이 아래와 같은 글을 쓰셧다

WPF - Grid 컨트롤의 ShowGridLine 개선

윗 링크의 글을 보면 아래와 같은 부분이 있다

“생각 자체는 그리 어렵지 않습니다. 예를 들어, 가로선을 그을 때(Row별로) 컬럼 단위만큼 그리며 진행하다가 다음번 그려야 될 곳이 RowSpan으로 되어 있으면 그 구획은 긋지 말고 건너뛰면 됩니다. 세로선도 마찬가지겠지요.”

그리고 해당 컨트롤을 구현한 솔루션이 링크되어 있는데… 실제로는 구현이 반대로 되어 있다. Element 를 뒤져 가며 Span 된 부분을 건너뛰는 것이 아니라 Span 이 아닌 부분을 그리는 식으로. 사실 이렇게 하더라도 저 위 샘플에선 전혀 문제가 없이 나오는데… 문제는 저 Grid에 어떤 Element 도 없는 “빈칸” 이 존재할 경우 그 부분은 아예 Border 가 그려지지 않는다는 것이다…

그래서 위에 인용한 글 대로 Skip 하는 식으로 변형을 해서 쓰기로 했고, 그 코드를 공개한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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;
            }
        }
    }
}
This post is licensed under CC BY 4.0 by the author.