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)
31 using System.Collections.Generic;
34 namespace System.Windows.Forms.Layout
36 class FlowLayout : LayoutEngine
42 public override void InitLayout (object child, BoundsSpecified specified)
44 base.InitLayout (child, specified);
47 public override bool Layout (object container, LayoutEventArgs args)
49 Control parent = container as Control;
50 FlowLayoutSettings settings = (parent as FlowLayoutPanel).LayoutSettings;
52 // Nothing to layout, exit method
53 if (parent.Controls.Count == 0) return false;
55 // Use DisplayRectangle so that parent.Padding is honored.
56 Rectangle parentDisplayRectangle = parent.DisplayRectangle;
57 Point currentLocation;
59 // Set our starting point based on flow direction
60 switch (settings.FlowDirection) {
61 case FlowDirection.BottomUp:
62 currentLocation = new Point (parentDisplayRectangle.Left, parentDisplayRectangle.Bottom);
64 case FlowDirection.LeftToRight:
65 case FlowDirection.TopDown:
67 currentLocation = parentDisplayRectangle.Location;
69 case FlowDirection.RightToLeft:
70 currentLocation = new Point (parentDisplayRectangle.Right, parentDisplayRectangle.Top);
74 bool forceFlowBreak = false;
76 List<Control> rowControls = new List<Control> ();
78 foreach (Control c in parent.Controls) {
79 // Only apply layout to visible controls.
80 if (!c.Visible) { continue; }
82 // Resize any AutoSize controls to their preferred size
83 if (c.AutoSize == true)
84 c.Size = c.GetPreferredSize (c.Size);
86 switch (settings.FlowDirection) {
87 case FlowDirection.BottomUp:
88 // Decide if it's time to start a new column
89 // - Our settings must be WrapContents, and we ran out of room or the previous control's FlowBreak == true
90 if (settings.WrapContents)
91 if ((currentLocation.Y) < (c.Height + c.Margin.Top + c.Margin.Bottom) || forceFlowBreak) {
93 currentLocation.X = FinishColumn (rowControls);
94 currentLocation.Y = parentDisplayRectangle.Bottom;
96 forceFlowBreak = false;
100 // Offset the right margin and set the control to our point
101 currentLocation.Offset (0, c.Margin.Bottom * -1);
102 c.Location = new Point (currentLocation.X + c.Margin.Left, currentLocation.Y - c.Height);
104 // Update our location pointer
105 currentLocation.Y -= (c.Height + c.Margin.Top);
107 case FlowDirection.LeftToRight:
109 // Decide if it's time to start a new row
110 // - Our settings must be WrapContents, and we ran out of room or the previous control's FlowBreak == true
111 if (settings.WrapContents)
112 if ((parentDisplayRectangle.Width - currentLocation.X) < (c.Width + c.Margin.Left + c.Margin.Right) || forceFlowBreak) {
114 currentLocation.Y = FinishRow (rowControls);
115 currentLocation.X = parentDisplayRectangle.Left;
117 forceFlowBreak = false;
118 rowControls.Clear ();
121 // Offset the left margin and set the control to our point
122 currentLocation.Offset (c.Margin.Left, 0);
123 c.Location = new Point (currentLocation.X, currentLocation.Y + c.Margin.Top);
125 // Update our location pointer
126 currentLocation.X += c.Width + c.Margin.Right;
128 case FlowDirection.RightToLeft:
129 // Decide if it's time to start a new row
130 // - Our settings must be WrapContents, and we ran out of room or the previous control's FlowBreak == true
131 if (settings.WrapContents)
132 if ((currentLocation.X) < (c.Width + c.Margin.Left + c.Margin.Right) || forceFlowBreak) {
134 currentLocation.Y = FinishRow (rowControls);
135 currentLocation.X = parentDisplayRectangle.Right;
137 forceFlowBreak = false;
138 rowControls.Clear ();
141 // Offset the right margin and set the control to our point
142 currentLocation.Offset (c.Margin.Right * -1, 0);
143 c.Location = new Point (currentLocation.X - c.Width, currentLocation.Y + c.Margin.Top);
145 // Update our location pointer
146 currentLocation.X -= (c.Width + c.Margin.Left);
148 case FlowDirection.TopDown:
149 // Decide if it's time to start a new column
150 // - Our settings must be WrapContents, and we ran out of room or the previous control's FlowBreak == true
151 if (settings.WrapContents)
152 if ((parentDisplayRectangle.Height - currentLocation.Y) < (c.Height + c.Margin.Top + c.Margin.Bottom) || forceFlowBreak) {
154 currentLocation.X = FinishColumn (rowControls);
155 currentLocation.Y = parentDisplayRectangle.Top;
157 forceFlowBreak = false;
158 rowControls.Clear ();
161 // Offset the top margin and set the control to our point
162 currentLocation.Offset (0, c.Margin.Top);
163 c.Location = new Point (currentLocation.X + c.Margin.Left, currentLocation.Y);
165 // Update our location pointer
166 currentLocation.Y += c.Height + c.Margin.Bottom;
169 // Add it to our list of things to adjust the second dimension of
172 // If user set a flowbreak on this control, it will be the last one in this row/column
173 if (settings.GetFlowBreak (c))
174 forceFlowBreak = true;
177 // Set the control heights/widths for the last row/column
178 if (settings.FlowDirection == FlowDirection.LeftToRight || settings.FlowDirection == FlowDirection.RightToLeft)
179 FinishRow (rowControls);
181 FinishColumn (rowControls);
186 // Calculate the heights of the controls, returns the y coordinate of the greatest height it uses
187 private int FinishRow (List<Control> row)
190 if (row.Count == 0) return 0;
192 int rowTop = int.MaxValue;
194 bool allDockFill = true;
197 // Special semantics if all controls are Dock.Fill/Anchor:Top,Bottom or AutoSize = true
198 foreach (Control c in row) {
199 if (c.Dock != DockStyle.Fill && !((c.Anchor & AnchorStyles.Top) == AnchorStyles.Top && (c.Anchor & AnchorStyles.Bottom) == AnchorStyles.Bottom))
201 if (c.AutoSize == true)
205 // Find the tallest control with a concrete height
206 foreach (Control c in row) {
207 if (c.Bottom + c.Margin.Bottom > rowBottom && (c.Dock != DockStyle.Fill) && ((c.Anchor & AnchorStyles.Top) != AnchorStyles.Top || (c.Anchor & AnchorStyles.Bottom) != AnchorStyles.Bottom || c.AutoSize == true))
208 rowBottom = c.Bottom + c.Margin.Bottom;
209 if (c.Top - c.Margin.Top < rowTop)
210 rowTop = c.Top - c.Margin.Top;
213 // Find the tallest control that is AutoSize = true
215 foreach (Control c in row)
216 if (c.Bottom + c.Margin.Bottom > rowBottom && (c.Dock != DockStyle.Fill && c.AutoSize == true))
217 rowBottom = c.Bottom + c.Margin.Bottom;
219 // Find the tallest control that is Dock = Fill
221 foreach (Control c in row)
222 if (c.Bottom + c.Margin.Bottom > rowBottom && (c.Dock == DockStyle.Fill))
223 rowBottom = c.Bottom + c.Margin.Bottom;
225 // Set the new heights for each control
226 foreach (Control c in row)
227 if (allDockFill && noAuto)
229 else if (c.Dock == DockStyle.Fill || ((c.Anchor & AnchorStyles.Top) == AnchorStyles.Top) && ((c.Anchor & AnchorStyles.Bottom) == AnchorStyles.Bottom))
230 c.Height = rowBottom - c.Top - c.Margin.Bottom;
231 else if (c.Dock == DockStyle.Bottom || ((c.Anchor & AnchorStyles.Bottom) == AnchorStyles.Bottom))
232 c.Top = rowBottom - c.Margin.Bottom - c.Height;
233 else if (c.Dock == DockStyle.Top || ((c.Anchor & AnchorStyles.Top) == AnchorStyles.Top))
236 c.Top = ((rowBottom - rowTop) / 2) - (c.Height / 2) + (int)Math.Floor (((c.Margin.Top - c.Margin.Bottom) / 2.0)) + rowTop;
238 // Return bottom y of this row used
245 // Calculate the widths of the controls, returns the x coordinate of the greatest width it uses
246 private int FinishColumn (List<Control> col)
249 if (col.Count == 0) return 0;
251 int rowLeft = int.MaxValue;
253 bool allDockFill = true;
256 // Special semantics if all controls are Dock.Fill/Anchor:Left,Right or AutoSize = true
257 foreach (Control c in col) {
258 if (c.Dock != DockStyle.Fill && !((c.Anchor & AnchorStyles.Left) == AnchorStyles.Left && (c.Anchor & AnchorStyles.Right) == AnchorStyles.Right))
260 if (c.AutoSize == true)
264 // Find the widest control with a concrete width
265 foreach (Control c in col) {
266 if (c.Right + c.Margin.Right > rowRight && (c.Dock != DockStyle.Fill) && ((c.Anchor & AnchorStyles.Left) != AnchorStyles.Left || (c.Anchor & AnchorStyles.Right) != AnchorStyles.Right || c.AutoSize == true))
267 rowRight = c.Right + c.Margin.Right;
268 if (c.Left - c.Margin.Left < rowLeft)
269 rowLeft = c.Left - c.Margin.Left;
272 // Find the widest control that is AutoSize = true
274 foreach (Control c in col)
275 if (c.Right + c.Margin.Right > rowRight && (c.Dock != DockStyle.Fill && c.AutoSize == true))
276 rowRight = c.Right + c.Margin.Right;
278 // Find the widest control that is Dock = Fill
280 foreach (Control c in col)
281 if (c.Right + c.Margin.Right > rowRight && (c.Dock == DockStyle.Fill))
282 rowRight = c.Right + c.Margin.Right;
284 // Set the new widths for each control
285 foreach (Control c in col)
286 if (allDockFill && noAuto)
288 else if (c.Dock == DockStyle.Fill || ((c.Anchor & AnchorStyles.Left) == AnchorStyles.Left) && ((c.Anchor & AnchorStyles.Right) == AnchorStyles.Right))
289 c.Width = rowRight - c.Left - c.Margin.Right;
290 else if (c.Dock == DockStyle.Right || ((c.Anchor & AnchorStyles.Right) == AnchorStyles.Right))
291 c.Left = rowRight - c.Margin.Right - c.Width;
292 else if (c.Dock == DockStyle.Left || ((c.Anchor & AnchorStyles.Left) == AnchorStyles.Left))
295 c.Left = ((rowRight - rowLeft) / 2) - (c.Width / 2) + (int)Math.Floor (((c.Margin.Left - c.Margin.Right) / 2.0)) + rowLeft;
297 // Return rightmost x of this row used