4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 // Copyright (c) 2006 Jonathan Pobst
26 // Jonathan Pobst (monkey@jpobst.com)
35 namespace System
.Windows
.Forms
.Layout
37 internal class TableLayout
: LayoutEngine
39 private static Control dummy_control
= new Control ("Dummy"); // Used as a placeholder for row/col spans
41 public TableLayout () : base ()
45 public override void InitLayout (object child
, BoundsSpecified specified
)
47 base.InitLayout (child
, specified
);
50 // There are 3 steps to doing a table layout:
51 // 1) Figure out which row/column each control goes into
52 // 2) Figure out the sizes of each row/column
53 // 3) Size and position each control
54 public override bool Layout (object container
, LayoutEventArgs args
)
56 TableLayoutPanel panel
= container
as TableLayoutPanel
;
57 TableLayoutSettings settings
= panel
.LayoutSettings
;
60 Console
.WriteLine ("Beginning layout on panel: {0}, control count: {1}, col/row count: {2}x{3}", panel
.Name
, panel
.Controls
.Count
, settings
.ColumnCount
, settings
.RowCount
);
64 // - Figure out which row/column each control goes into
65 // - Store data in the TableLayoutPanel.actual_positions
66 panel
.actual_positions
= CalculateControlPositions (panel
, Math
.Max (settings
.ColumnCount
, 1), Math
.Max (settings
.RowCount
, 1));
69 // - Figure out the sizes of each row/column
70 // - Store data in the TableLayoutPanel.widths/heights
71 CalculateColumnRowSizes (panel
, panel
.actual_positions
.GetLength (0), panel
.actual_positions
.GetLength (1));
74 // - Size and position each control
75 LayoutControls(panel
);
78 Console
.WriteLine ("-- CalculatedPositions:");
79 OutputControlGrid (panel
.actual_positions
, panel
);
81 Console
.WriteLine ("Finished layout on panel: {0}", panel
.Name
);
88 internal Control
[,] CalculateControlPositions (TableLayoutPanel panel
, int columns
, int rows
)
90 Control
[,] grid
= new Control
[columns
, rows
];
92 TableLayoutSettings settings
= panel
.LayoutSettings
;
94 // First place all controls that have an explicit col/row
95 foreach (Control c
in panel
.Controls
) {
96 int col
= settings
.GetColumn (c
);
97 int row
= settings
.GetRow (c
);
98 if (col
>= 0 && row
>= 0) {
100 return CalculateControlPositions (panel
, col
+ 1, rows
);
102 return CalculateControlPositions (panel
, columns
, row
+ 1);
104 if (grid
[col
, row
] == null) {
105 int col_span
= Math
.Min (settings
.GetColumnSpan (c
), columns
);
106 int row_span
= Math
.Min (settings
.GetRowSpan (c
), rows
);
108 if (col
+ col_span
> columns
) {
109 if (row
+ 1 < rows
) {
110 grid
[col
, row
] = dummy_control
;
114 else if (settings
.GrowStyle
== TableLayoutPanelGrowStyle
.AddColumns
)
115 return CalculateControlPositions (panel
, columns
+ 1, rows
);
117 throw new ArgumentException ();
120 if (row
+ row_span
> rows
) {
121 if (settings
.GrowStyle
== TableLayoutPanelGrowStyle
.AddRows
)
122 return CalculateControlPositions (panel
, columns
, rows
+ 1);
124 throw new ArgumentException ();
129 // Fill in the rest of this control's row/column extent with dummy
130 // controls, so that other controls don't get put there.
131 for (int i
= 0; i
< col_span
; i
++)
132 for (int j
= 0; j
< row_span
; j
++)
133 if (i
!= 0 || j
!= 0)
134 grid
[col
+ i
, row
+ j
] = dummy_control
;
142 // Fill in gaps with controls that do not have an explicit col/row
143 foreach (Control c
in panel
.Controls
) {
144 int col
= settings
.GetColumn (c
);
145 int row
= settings
.GetRow (c
);
147 if ((col
>= 0 && col
< columns
) && (row
>= 0 && row
< rows
) && (grid
[col
, row
] == c
|| grid
[col
, row
] == dummy_control
))
150 for (int y
= y_pointer
; y
< rows
; y
++) {
154 for (int x
= x_pointer
; x
< columns
; x
++) {
157 if (grid
[x
, y
] == null) {
158 int col_span
= Math
.Min (settings
.GetColumnSpan (c
), columns
);
159 int row_span
= Math
.Min (settings
.GetRowSpan (c
), rows
);
161 if (x
+ col_span
> columns
) {
164 else if (settings
.GrowStyle
== TableLayoutPanelGrowStyle
.AddColumns
)
165 return CalculateControlPositions (panel
, columns
+ 1, rows
);
167 throw new ArgumentException ();
170 if (y
+ row_span
> rows
) {
173 else if (settings
.GrowStyle
== TableLayoutPanelGrowStyle
.AddRows
)
174 return CalculateControlPositions (panel
, columns
, rows
+ 1);
176 throw new ArgumentException ();
181 // Fill in the rest of this control's row/column extent with dummy
182 // controls, so that other controls don't get put there.
183 for (int i
= 0; i
< col_span
; i
++)
184 for (int j
= 0; j
< row_span
; j
++)
185 if (i
!= 0 || j
!= 0)
186 grid
[x
+ i
, y
+ j
] = dummy_control
;
188 // I know someone will kill me for using a goto, but
189 // sometimes they really are the easiest way...
192 // MS adds the controls only to the first row if
193 // GrowStyle is AddColumns and RowCount is 0,
194 // so interrupt the search for a free horizontal cell
195 // beyond the first one in the given vertical
196 if (settings
.GrowStyle
== TableLayoutPanelGrowStyle
.AddColumns
&&
197 settings
.RowCount
== 0)
203 // MS adds rows instead of columns even when GrowStyle is AddColumns,
204 // but RowCount is 0.
205 TableLayoutPanelGrowStyle adjustedGrowStyle
= settings
.GrowStyle
;
206 if (settings
.GrowStyle
== TableLayoutPanelGrowStyle
.AddColumns
) {
207 if (settings
.RowCount
== 0)
208 adjustedGrowStyle
= TableLayoutPanelGrowStyle
.AddRows
;
211 switch (adjustedGrowStyle
) {
212 case TableLayoutPanelGrowStyle
.AddColumns
:
213 return CalculateControlPositions (panel
, columns
+ 1, rows
);
214 case TableLayoutPanelGrowStyle
.AddRows
:
216 return CalculateControlPositions (panel
, columns
, rows
+ 1);
217 case TableLayoutPanelGrowStyle
.FixedSize
:
218 throw new ArgumentException ();
227 private void CalculateColumnRowSizes (TableLayoutPanel panel
, int columns
, int rows
)
229 TableLayoutSettings settings
= panel
.LayoutSettings
;
231 panel
.column_widths
= new int[panel
.actual_positions
.GetLength (0)];
232 panel
.row_heights
= new int[panel
.actual_positions
.GetLength (1)];
234 int border_width
= TableLayoutPanel
.GetCellBorderWidth (panel
.CellBorderStyle
);
236 Rectangle parentDisplayRectangle
= panel
.DisplayRectangle
;
238 TableLayoutColumnStyleCollection col_styles
= new TableLayoutColumnStyleCollection (panel
);
240 foreach (ColumnStyle cs
in settings
.ColumnStyles
)
241 col_styles
.Add( new ColumnStyle(cs
.SizeType
, cs
.Width
));
243 TableLayoutRowStyleCollection row_styles
= new TableLayoutRowStyleCollection (panel
);
245 foreach (RowStyle rs
in settings
.RowStyles
)
246 row_styles
.Add (new RowStyle (rs
.SizeType
, rs
.Height
));
248 // If we have more columns than columnstyles, temporarily add enough columnstyles
249 if (columns
> col_styles
.Count
)
251 for (int i
= col_styles
.Count
; i
< columns
; i
++)
252 col_styles
.Add(new ColumnStyle());
256 if (rows
> row_styles
.Count
)
258 for (int i
= row_styles
.Count
; i
< rows
; i
++)
259 row_styles
.Add (new RowStyle ());
262 while (row_styles
.Count
> rows
)
263 row_styles
.RemoveAt (row_styles
.Count
- 1);
264 while (col_styles
.Count
> columns
)
265 col_styles
.RemoveAt (col_styles
.Count
- 1);
267 // Find the largest column-span/row-span values.
268 int max_colspan
= 0, max_rowspan
= 0;
269 foreach (Control c
in panel
.Controls
) {
270 max_colspan
= Math
.Max (max_colspan
, settings
.GetColumnSpan (c
));
271 max_rowspan
= Math
.Max (max_rowspan
, settings
.GetRowSpan (c
));
274 // Figure up all the column widths
275 int total_width
= parentDisplayRectangle
.Width
- (border_width
* (columns
+ 1));
278 // First assign all the Absolute sized columns..
279 foreach (ColumnStyle cs
in col_styles
) {
280 if (cs
.SizeType
== SizeType
.Absolute
) {
281 panel
.column_widths
[index
] = (int)cs
.Width
;
282 total_width
-= (int)cs
.Width
;
288 // Next, assign all the AutoSize columns to the width of their widest
289 // control. If the table-layout is auto-sized, then make sure that
290 // no column with Percent styling clips its contents.
291 // (per http://msdn.microsoft.com/en-us/library/ms171690.aspx)
292 for (int colspan
= 0; colspan
< max_colspan
; ++colspan
)
294 for (index
= colspan
; index
< col_styles
.Count
- colspan
; ++index
)
296 ColumnStyle cs
= col_styles
[index
];
297 if (cs
.SizeType
== SizeType
.AutoSize
298 || (panel
.AutoSize
&& cs
.SizeType
== SizeType
.Percent
))
300 int max_width
= panel
.column_widths
[index
];
302 // Find the widest control in the column
303 for (int i
= 0; i
< rows
; i
++)
305 Control c
= panel
.actual_positions
[index
- colspan
, i
];
307 if (c
!= null && c
!= dummy_control
&& c
.VisibleInternal
)
309 // Skip any controls not being sized in this pass.
310 if (settings
.GetColumnSpan (c
) != colspan
+ 1)
313 // Calculate the maximum control width.
315 max_width
= Math
.Max (max_width
, c
.PreferredSize
.Width
+ c
.Margin
.Horizontal
);
317 max_width
= Math
.Max (max_width
, c
.ExplicitBounds
.Width
+ c
.Margin
.Horizontal
);
318 max_width
= Math
.Max (max_width
, c
.Width
+ c
.Margin
.Left
+ c
.Margin
.Right
);
322 // Subtract the width of prior columns, if any.
323 for (int i
= Math
.Max (index
- colspan
, 0); i
< index
; ++i
)
324 max_width
-= panel
.column_widths
[i
];
326 // If necessary, increase this column's width.
327 if (max_width
> panel
.column_widths
[index
])
329 max_width
-= panel
.column_widths
[index
];
330 panel
.column_widths
[index
] += max_width
;
331 total_width
-= max_width
;
338 float total_percent
= 0;
340 // Finally, assign the remaining space to Percent columns, if any.
343 int percent_width
= total_width
;
345 // Find the total percent (not always 100%)
346 foreach (ColumnStyle cs
in col_styles
)
348 if (cs
.SizeType
== SizeType
.Percent
)
349 total_percent
+= cs
.Width
;
352 // Divvy up the space..
353 foreach (ColumnStyle cs
in col_styles
)
355 if (cs
.SizeType
== SizeType
.Percent
)
357 int width_change
= (int)(((cs
.Width
/ total_percent
) * percent_width
)
358 - panel
.column_widths
[index
]);
359 if (width_change
> 0)
361 panel
.column_widths
[index
] += width_change
;
362 total_width
-= width_change
;
372 // Find the last column that isn't an Absolute SizeType, and give it
373 // all this free space. (Absolute sized columns need to retain their
374 // absolute width if at all possible!)
375 int col
= col_styles
.Count
- 1;
376 for (; col
>= 0; --col
)
378 if (col_styles
[col
].SizeType
!= SizeType
.Absolute
)
382 col
= col_styles
.Count
- 1;
383 panel
.column_widths
[col
] += total_width
;
386 // Figure up all the row heights
387 int total_height
= parentDisplayRectangle
.Height
- (border_width
* (rows
+ 1));
390 // First assign all the Absolute sized rows..
391 foreach (RowStyle rs
in row_styles
) {
392 if (rs
.SizeType
== SizeType
.Absolute
) {
393 panel
.row_heights
[index
] = (int)rs
.Height
;
394 total_height
-= (int)rs
.Height
;
402 // Next, assign all the AutoSize rows to the height of their tallest
403 // control. If the table-layout is auto-sized, then make sure that
404 // no row with Percent styling clips its contents.
405 // (per http://msdn.microsoft.com/en-us/library/ms171690.aspx)
406 for (int rowspan
= 0; rowspan
< max_rowspan
; ++rowspan
)
408 for (index
= rowspan
; index
< row_styles
.Count
- rowspan
; ++index
)
410 RowStyle rs
= row_styles
[index
];
411 if (rs
.SizeType
== SizeType
.AutoSize
412 || (panel
.AutoSize
&& rs
.SizeType
== SizeType
.Percent
))
414 int max_height
= panel
.row_heights
[index
];
416 // Find the tallest control in the row
417 for (int i
= 0; i
< columns
; i
++) {
418 Control c
= panel
.actual_positions
[i
, index
- rowspan
];
420 if (c
!= null && c
!= dummy_control
&& c
.VisibleInternal
)
422 // Skip any controls not being sized in this pass.
423 if (settings
.GetRowSpan (c
) != rowspan
+ 1)
426 // Calculate the maximum control height.
428 max_height
= Math
.Max (max_height
, c
.PreferredSize
.Height
+ c
.Margin
.Vertical
);
430 max_height
= Math
.Max (max_height
, c
.ExplicitBounds
.Height
+ c
.Margin
.Vertical
);
431 max_height
= Math
.Max (max_height
, c
.Height
+ c
.Margin
.Top
+ c
.Margin
.Bottom
);
435 // Subtract the height of prior rows, if any.
436 for (int i
= Math
.Max (index
- rowspan
, 0); i
< index
; ++i
)
437 max_height
-= panel
.row_heights
[i
];
439 // If necessary, increase this row's height.
440 if (max_height
> panel
.row_heights
[index
])
442 max_height
-= panel
.row_heights
[index
];
443 panel
.row_heights
[index
] += max_height
;
444 total_height
-= max_height
;
453 // Finally, assign the remaining space to Percent rows, if any.
454 if (total_height
> 0) {
455 int percent_height
= total_height
;
457 // Find the total percent (not always 100%)
458 foreach (RowStyle rs
in row_styles
) {
459 if (rs
.SizeType
== SizeType
.Percent
)
460 total_percent
+= rs
.Height
;
463 // Divvy up the space..
464 foreach (RowStyle rs
in row_styles
) {
465 if (rs
.SizeType
== SizeType
.Percent
) {
466 int height_change
= (int)(((rs
.Height
/ total_percent
) * percent_height
)
467 - panel
.row_heights
[index
]);
468 if (height_change
> 0)
470 panel
.row_heights
[index
] += height_change
;
471 total_height
-= height_change
;
479 if (total_height
> 0)
481 // Find the last row that isn't an Absolute SizeType, and give it
482 // all this free space. (Absolute sized rows need to retain their
483 // absolute height if at all possible!)
484 int row
= row_styles
.Count
- 1;
485 for (; row
>= 0; --row
)
487 if (row_styles
[row
].SizeType
!= SizeType
.Absolute
)
491 row
= row_styles
.Count
- 1;
492 panel
.row_heights
[row
] += total_height
;
496 private void LayoutControls (TableLayoutPanel panel
)
498 TableLayoutSettings settings
= panel
.LayoutSettings
;
500 int border_width
= TableLayoutPanel
.GetCellBorderWidth (panel
.CellBorderStyle
);
502 int columns
= panel
.actual_positions
.GetLength(0);
503 int rows
= panel
.actual_positions
.GetLength(1);
505 Point current_pos
= new Point (panel
.DisplayRectangle
.Left
+ border_width
, panel
.DisplayRectangle
.Top
+ border_width
);
507 for (int y
= 0; y
< rows
; y
++)
509 for (int x
= 0; x
< columns
; x
++)
511 Control c
= panel
.actual_positions
[x
,y
];
513 if(c
!= null && c
!= dummy_control
) {
517 preferred
= c
.PreferredSize
;
519 preferred
= c
.ExplicitBounds
.Size
;
526 // Figure out the width of the control
527 int column_width
= panel
.column_widths
[x
];
529 for (int i
= 1; i
< Math
.Min (settings
.GetColumnSpan(c
), panel
.column_widths
.Length
); i
++)
530 column_width
+= panel
.column_widths
[x
+ i
];
532 if (c
.Dock
== DockStyle
.Fill
|| c
.Dock
== DockStyle
.Top
|| c
.Dock
== DockStyle
.Bottom
|| ((c
.Anchor
& AnchorStyles
.Left
) == AnchorStyles
.Left
&& (c
.Anchor
& AnchorStyles
.Right
) == AnchorStyles
.Right
))
533 new_width
= column_width
- c
.Margin
.Left
- c
.Margin
.Right
;
535 new_width
= Math
.Min (preferred
.Width
, column_width
- c
.Margin
.Left
- c
.Margin
.Right
);
537 // Figure out the height of the control
538 int column_height
= panel
.row_heights
[y
];
540 for (int i
= 1; i
< Math
.Min (settings
.GetRowSpan (c
), panel
.row_heights
.Length
); i
++)
541 column_height
+= panel
.row_heights
[y
+ i
];
543 if (c
.Dock
== DockStyle
.Fill
|| c
.Dock
== DockStyle
.Left
|| c
.Dock
== DockStyle
.Right
|| ((c
.Anchor
& AnchorStyles
.Top
) == AnchorStyles
.Top
&& (c
.Anchor
& AnchorStyles
.Bottom
) == AnchorStyles
.Bottom
))
544 new_height
= column_height
- c
.Margin
.Top
- c
.Margin
.Bottom
;
546 new_height
= Math
.Min (preferred
.Height
, column_height
- c
.Margin
.Top
- c
.Margin
.Bottom
);
548 // Figure out the left location of the control
549 if (c
.Dock
== DockStyle
.Left
|| c
.Dock
== DockStyle
.Fill
|| (c
.Anchor
& AnchorStyles
.Left
) == AnchorStyles
.Left
)
550 new_x
= current_pos
.X
+ c
.Margin
.Left
;
551 else if (c
.Dock
== DockStyle
.Right
|| (c
.Anchor
& AnchorStyles
.Right
) == AnchorStyles
.Right
)
552 new_x
= (current_pos
.X
+ column_width
) - new_width
- c
.Margin
.Right
;
553 else // (center control)
554 new_x
= (current_pos
.X
+ (column_width
- c
.Margin
.Left
- c
.Margin
.Right
) / 2) + c
.Margin
.Left
- (new_width
/ 2);
556 // Figure out the top location of the control
557 if (c
.Dock
== DockStyle
.Top
|| c
.Dock
== DockStyle
.Fill
|| (c
.Anchor
& AnchorStyles
.Top
) == AnchorStyles
.Top
)
558 new_y
= current_pos
.Y
+ c
.Margin
.Top
;
559 else if (c
.Dock
== DockStyle
.Bottom
|| (c
.Anchor
& AnchorStyles
.Bottom
) == AnchorStyles
.Bottom
)
560 new_y
= (current_pos
.Y
+ column_height
) - new_height
- c
.Margin
.Bottom
;
561 else // (center control)
562 new_y
= (current_pos
.Y
+ (column_height
- c
.Margin
.Top
- c
.Margin
.Bottom
) / 2) + c
.Margin
.Top
- (new_height
/ 2);
564 c
.SetBoundsInternal (new_x
, new_y
, new_width
, new_height
, BoundsSpecified
.None
);
567 current_pos
.Offset (panel
.column_widths
[x
] + border_width
, 0);
570 current_pos
.Offset ((-1 * current_pos
.X
) + border_width
+ panel
.DisplayRectangle
.Left
, panel
.row_heights
[y
] + border_width
);
575 private void OutputControlGrid (Control
[,] grid
, TableLayoutPanel panel
)
577 Console
.WriteLine (" Size: {0}x{1}", grid
.GetLength (0), grid
.GetLength (1));
581 foreach (int i
in panel
.column_widths
)
582 Console
.Write (" {0}px ", i
.ToString ().PadLeft (3));
584 Console
.WriteLine ();
586 for (int y
= 0; y
< grid
.GetLength (1); y
++) {
587 Console
.Write (" {0}px |", panel
.row_heights
[y
].ToString ().PadLeft (3));
589 for (int x
= 0; x
< grid
.GetLength (0); x
++) {
590 if (grid
[x
, y
] == null)
591 Console
.Write (" --- |");
592 else if (string.IsNullOrEmpty (grid
[x
, y
].Name
))
593 Console
.Write (" ??? |");
595 Console
.Write (" {0} |", grid
[x
, y
].Name
.PadRight (5).Substring (0, 5));
598 Console
.WriteLine ();