11using System ;
2+ using System . Collections . Generic ;
23using System . ComponentModel ;
34using System . Globalization ;
45using System . Reflection ;
@@ -11,6 +12,23 @@ namespace Wpf.Ui.Controls;
1112[ ContentProperty ( nameof ( Children ) ) ]
1213public 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