Skip to content

Commit adca81a

Browse files
committed
Add Horizontal/Vertical spacing to Grid
1 parent c358eb3 commit adca81a

2 files changed

Lines changed: 174 additions & 14 deletions

File tree

src/Wpf.Ui.Test/MainWindow.xaml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,51 @@
12441244
</ui:AutoGrid>
12451245
</Border>
12461246
</StackPanel>
1247+
1248+
<!-- Grid Spacing -->
1249+
<StackPanel Margin="0,8,0,0">
1250+
<TextBlock
1251+
Margin="0,0,0,4"
1252+
FontSize="16"
1253+
FontWeight="SemiBold"
1254+
Text="Grid with HorizontalSpacing / VerticalSpacing" />
1255+
<ui:Grid
1256+
ColumnDefinitions="Auto,*,Auto"
1257+
HorizontalSpacing="12"
1258+
RowDefinitions="Auto,Auto"
1259+
VerticalSpacing="8">
1260+
<TextBlock
1261+
Grid.Row="0"
1262+
Grid.Column="0"
1263+
Background="Red"
1264+
Text="R0C0" />
1265+
<TextBlock
1266+
Grid.Row="0"
1267+
Grid.Column="1"
1268+
Background="Green"
1269+
Text="R0C1" />
1270+
<TextBlock
1271+
Grid.Row="0"
1272+
Grid.Column="2"
1273+
Background="Red"
1274+
Text="R0C2" />
1275+
<TextBlock
1276+
Grid.Row="1"
1277+
Grid.Column="0"
1278+
Background="Green"
1279+
Text="R1C0" />
1280+
<TextBlock
1281+
Grid.Row="1"
1282+
Grid.Column="1"
1283+
Background="Red"
1284+
Text="R1C1" />
1285+
<TextBlock
1286+
Grid.Row="1"
1287+
Grid.Column="2"
1288+
Background="Green"
1289+
Text="R1C2" />
1290+
</ui:Grid>
1291+
</StackPanel>
12471292
</StackPanel>
12481293
<!-- ============================= -->
12491294
<StackPanel>

src/Wpf.Ui.Violeta/Controls/Layout/Grid.cs

Lines changed: 129 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.ComponentModel;
34
using System.Globalization;
45
using System.Reflection;
@@ -11,6 +12,23 @@ namespace Wpf.Ui.Controls;
1112
[ContentProperty(nameof(Children))]
1213
public class Grid : System.Windows.Controls.Grid
1314
{
15+
private readonly List<GridLength> _logicalColumns = [];
16+
private readonly List<GridLength> _logicalRows = [];
17+
18+
// Private attached DPs that store the user's logical Grid.Column/Row before
19+
// we remap them to accommodate spacer columns/rows.
20+
private static readonly DependencyProperty LogicalColumnProperty =
21+
DependencyProperty.RegisterAttached("LogicalColumn", typeof(int), typeof(Grid), new PropertyMetadata(int.MinValue));
22+
23+
private static readonly DependencyProperty LogicalRowProperty =
24+
DependencyProperty.RegisterAttached("LogicalRow", typeof(int), typeof(Grid), new PropertyMetadata(int.MinValue));
25+
26+
private static readonly DependencyProperty LogicalColumnSpanProperty =
27+
DependencyProperty.RegisterAttached("LogicalColumnSpan", typeof(int), typeof(Grid), new PropertyMetadata(int.MinValue));
28+
29+
private static readonly DependencyProperty LogicalRowSpanProperty =
30+
DependencyProperty.RegisterAttached("LogicalRowSpan", typeof(int), typeof(Grid), new PropertyMetadata(int.MinValue));
31+
1432
public static readonly DependencyProperty ColumnDefinitionsProperty =
1533
DependencyProperty.Register(nameof(ColumnDefinitions), typeof(ColumnDefinitionCollection), typeof(Grid), new PropertyMetadata(null, OnColumnDefinitionsChanged));
1634

@@ -25,16 +43,10 @@ private static void OnColumnDefinitionsChanged(DependencyObject d, DependencyPro
2543
{
2644
if (d is Grid grid && e.NewValue is ColumnDefinitionCollection columnDefinitions)
2745
{
28-
grid.UpdateColumnDefinitions(columnDefinitions);
29-
}
30-
}
31-
32-
private void UpdateColumnDefinitions(ColumnDefinitionCollection columnDefinitions)
33-
{
34-
base.ColumnDefinitions.Clear();
35-
foreach (ColumnDefinition columnDefinition in columnDefinitions)
36-
{
37-
base.ColumnDefinitions.Add(new ColumnDefinition { Width = columnDefinition.Width });
46+
grid._logicalColumns.Clear();
47+
foreach (ColumnDefinition col in columnDefinitions)
48+
grid._logicalColumns.Add(col.Width);
49+
grid.RebuildColumnDefinitions();
3850
}
3951
}
4052

@@ -52,16 +64,119 @@ private static void OnRowDefinitionsChanged(DependencyObject d, DependencyProper
5264
{
5365
if (d is Grid grid && e.NewValue is RowDefinitionCollection rowDefinitions)
5466
{
55-
grid.UpdateRowDefinitions(rowDefinitions);
67+
grid._logicalRows.Clear();
68+
foreach (RowDefinition row in rowDefinitions)
69+
grid._logicalRows.Add(row.Height);
70+
grid.RebuildRowDefinitions();
71+
}
72+
}
73+
74+
public static readonly DependencyProperty HorizontalSpacingProperty =
75+
DependencyProperty.Register(nameof(HorizontalSpacing), typeof(double), typeof(Grid),
76+
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure,
77+
OnHorizontalSpacingChanged));
78+
79+
public double HorizontalSpacing
80+
{
81+
get => (double)GetValue(HorizontalSpacingProperty);
82+
set => SetValue(HorizontalSpacingProperty, value);
83+
}
84+
85+
private static void OnHorizontalSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
86+
{
87+
if (d is Grid grid)
88+
grid.RebuildColumnDefinitions();
89+
}
90+
91+
public static readonly DependencyProperty VerticalSpacingProperty =
92+
DependencyProperty.Register(nameof(VerticalSpacing), typeof(double), typeof(Grid),
93+
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure,
94+
OnVerticalSpacingChanged));
95+
96+
public double VerticalSpacing
97+
{
98+
get => (double)GetValue(VerticalSpacingProperty);
99+
set => SetValue(VerticalSpacingProperty, value);
100+
}
101+
102+
private static void OnVerticalSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
103+
{
104+
if (d is Grid grid)
105+
grid.RebuildRowDefinitions();
106+
}
107+
108+
protected override Size MeasureOverride(Size constraint)
109+
{
110+
bool hasH = HorizontalSpacing > 0 && _logicalColumns.Count > 0;
111+
bool hasV = VerticalSpacing > 0 && _logicalRows.Count > 0;
112+
113+
foreach (UIElement child in Children)
114+
{
115+
if (hasH || hasV)
116+
{
117+
// Capture user-set column/row on first encounter (before any remapping).
118+
if ((int)child.GetValue(LogicalColumnProperty) == int.MinValue)
119+
{
120+
child.SetValue(LogicalColumnProperty, GetColumn(child));
121+
child.SetValue(LogicalRowProperty, GetRow(child));
122+
child.SetValue(LogicalColumnSpanProperty, GetColumnSpan(child));
123+
child.SetValue(LogicalRowSpanProperty, GetRowSpan(child));
124+
}
125+
126+
int logCol = (int)child.GetValue(LogicalColumnProperty);
127+
int logRow = (int)child.GetValue(LogicalRowProperty);
128+
int logColSpan = (int)child.GetValue(LogicalColumnSpanProperty);
129+
int logRowSpan = (int)child.GetValue(LogicalRowSpanProperty);
130+
131+
// Map logical → actual (spacer-injected) index.
132+
// Logical column c → actual column c*2; span s → actual span s*2-1.
133+
SetColumn(child, hasH ? logCol * 2 : logCol);
134+
SetRow(child, hasV ? logRow * 2 : logRow);
135+
SetColumnSpan(child, hasH ? Math.Max(1, logColSpan * 2 - 1) : logColSpan);
136+
SetRowSpan(child, hasV ? Math.Max(1, logRowSpan * 2 - 1) : logRowSpan);
137+
}
138+
else
139+
{
140+
// Spacing was removed — restore the original logical values.
141+
int logCol = (int)child.GetValue(LogicalColumnProperty);
142+
if (logCol != int.MinValue)
143+
{
144+
SetColumn(child, logCol);
145+
SetRow(child, (int)child.GetValue(LogicalRowProperty));
146+
SetColumnSpan(child, (int)child.GetValue(LogicalColumnSpanProperty));
147+
SetRowSpan(child, (int)child.GetValue(LogicalRowSpanProperty));
148+
}
149+
}
150+
}
151+
152+
return base.MeasureOverride(constraint);
153+
}
154+
155+
private void RebuildColumnDefinitions()
156+
{
157+
base.ColumnDefinitions.Clear();
158+
bool hasSpacing = HorizontalSpacing > 0;
159+
bool first = true;
160+
foreach (GridLength width in _logicalColumns)
161+
{
162+
if (!first && hasSpacing)
163+
base.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(HorizontalSpacing) });
164+
base.ColumnDefinitions.Add(new ColumnDefinition { Width = width });
165+
first = false;
56166
}
57167
}
58168

59-
private void UpdateRowDefinitions(RowDefinitionCollection rowDefinitions)
169+
private void RebuildRowDefinitions()
60170
{
61171
base.RowDefinitions.Clear();
62-
foreach (RowDefinition rowDefinition in rowDefinitions)
172+
bool hasSpacing = VerticalSpacing > 0;
173+
bool first = true;
174+
foreach (GridLength height in _logicalRows)
63175
{
64-
base.RowDefinitions.Add(new RowDefinition { Height = rowDefinition.Height });
176+
if (!first && hasSpacing)
177+
base.RowDefinitions.Add(new RowDefinition { Height = new GridLength(VerticalSpacing) });
178+
base.RowDefinitions.Add(new RowDefinition { Height = height });
179+
first = false;
65180
}
66181
}
67182
}

0 commit comments

Comments
 (0)