When controls are added to FlowLayoutPanel, say in TopDown flow direction
without wrapping, width of the column in which controls are aligned incorporates
left and right margin of each control. Following code demonstrates how this is
done.
using
System;
using
System.Windows.Forms;
namespace
MarginsDemo
{
public class
MarginsTest: Form
{
public MarginsTest()
{
this.FormBorderStyle =
FormBorderStyle.FixedToolWindow;
FlowLayoutPanel flp =
new FlowLayoutPanel();
flp.FlowDirection = FlowDirection.TopDown;
flp.WrapContents = false;
flp.Dock = DockStyle.Fill;
Controls.Add(flp);
for (int i = 0;
i < 3; i++)
{
Button btn =
new Button();
btn.Name = btn.Text = string.Format("Button{0}",
i + 1);
btn.Margin = new
Padding(5, 5, 5, 5);
btn.LocationChanged += new
EventHandler(ButtonLocationChanged);
flp.Controls.Add(btn);
}
Console.WriteLine("FlowLayoutPanel.PreferredSize
= ({0}, {1})",
flp.PreferredSize.Width,
flp.PreferredSize.Height);
Size = SizeFromClientSize(flp.PreferredSize);
}
void ButtonLocationChanged(object
sender, EventArgs e)
{
Button btn = sender
as Button;
Console.WriteLine("{0}
Size=({1}, {2}) Location=({3}, {4})",
btn.Name, btn.Width, btn.Height, btn.Left,
btn.Top);
}
}
}
This test creates three buttons and
sets their margins to 5 pixels each. LocationChanged event on buttons is handled
to print current size and location of each button, which will help demonstrate
how FlowLayoutPanel handles margins. Output printed this application will look
something like this:
Button1 Size=(75, 23) Location=(5, 5)
Button2 Size=(75, 23) Location=(5, 38)
Button3 Size=(75, 23) Location=(5, 71)
FlowLayoutPanel.PreferredSize = (85, 99)
As expected, Left property of all buttons is 5, which is width of the left
margin (same amount of space is left on the right of each button). Vertically,
first button starts at Y=5 and ends above Y=28. Then bottom margin of Button1
comes (5 pixels), followed by top margin of Button2 (another 5 pixels). Only
then, Button2 is rendered starting at Y=38. Similarly, 10 pixels margins are
left below Button2, so Button3 starts at Y=71, ending 23 pixels lower. Total
preferred height of the FlowLayoutPanel is calculated as sum of vertical
dimensions (heights, top and bottom margins) of all controls. Since each button
occupies 23 pixels and has 5 pixels top and bottom margins, totaling out at 33
pixels, all three buttons require 99 pixels to draw.
Horizontally, each control requires own width plus left and right margin. Width
of the implied column is then found as maximum of required widths of all
controls. In our case, all three buttons require same width, which is 75 pixels
for the button and 5 pixels for left and right margin each, total of 85 pixels.
This can be verified as PreferredSize of the panel, after three buttons are
added, is 85x99 pixels, as we have shown.
Picture: result of popping out the dialog with three buttons on a
FlowLayoutPanel.
This way of calculating width and height becomes important when controls with
different sizes and margins are added to FlowLayoutPanel. For example, consider
following task. We wish to create a FlowLayoutPanel which would contain labels,
each label containing one line of C# source code. However, leading tabs in each
line of code should be converted to fixed-width tabbing, which in turn will be
implemented as left margin of the containing label. For example, if tab distance
is set to 30 pixels and line of code begins with three tabs, left margin of the
label would be 90. Source code of the Form class which implements this task
follows.
using
System;
using
System.Windows.Forms;
using
System.Drawing;
namespace
IndentationDemo
{
public partial
class MainForm:
Form
{
public MainForm()
{
FormBorderStyle =
FormBorderStyle.FixedToolWindow;
string[] program = new
string[]
{
"public class HelloWorld",
"{",
"\tpublic static void Main()",
"\t{",
"\t\tfor (int i = 0; i < 10; i++)",
"\t\t\tConsole.WriteLine(\"Hello
World #{0}.\", i + 1)",
"\t}",
"}"
};
Font font = new
System.Drawing.Font("Consolas",
Font.Size);
FlowLayoutPanel pnl =
CreatePanel(font, program, BorderStyle.None);
pnl.Location =
new System.Drawing.Point(pnl.Margin.Left,
pnl.Margin.Top);
Size = SizeFromClientSize(pnl.Size + pnl.Margin.Size);
Controls.Add(pnl);
}
private static
FlowLayoutPanel CreatePanel(Font
font, string[] lines,
BorderStyle borderStyle)
{
float tabDist = 3 * font.Size;
// Heuristical tab distance
FlowLayoutPanel pnl =
new FlowLayoutPanel();
pnl.Padding = pnl.Margin;
pnl.BorderStyle = borderStyle;
pnl.Font = font;
pnl.FlowDirection = FlowDirection.TopDown;
pnl.WrapContents = false;
Console.WriteLine("Tab
distance = {0}", tabDist);
for(int i = 0; i
< lines.Length; i++)
{
Label lbl = new
Label();
lbl.BorderStyle = borderStyle;
lbl.Font = font;
// Causes label's preferred size to be calculated right
int tabs = 0; //
Count leading tab characters
while (tabs <
lines[i].Length && lines[i][tabs] == '\t')
tabs++;
lbl.Text =
lines[i].Substring(tabs); // Remove
leading tab characters
lbl.Margin = new
Padding((int)(tabs
* tabDist + 0.5F), 0, 0, 0);
lbl.Size = lbl.PreferredSize;
pnl.Controls.Add(lbl);
int width = lbl.Margin.Left + lbl.Width +
lbl.Margin.Right;
Console.WriteLine("Label{0}
Size=({1}, {2}) Location=({3}, {4}) Margin={5} Req.width {6}",
i + 1, lbl.Width, lbl.Height, lbl.Left,
lbl.Top,
lbl.Margin.Left, width);
}
Console.WriteLine("FlowLayoutPanel
Padding=({0}, {1}, {2}, {3}) PreferredSize=({4}, {5})",
pnl.Padding.Left, pnl.Padding.Top,
pnl.Padding.Right, pnl.Padding.Bottom,
pnl.PreferredSize.Width,
pnl.PreferredSize.Height);
pnl.MinimumSize = pnl.PreferredSize;
return pnl;
}
}
}
This code creates one label for every line of code and sets its left margin to
specific tab stop. At the same time, sum of left and right margins and width of
every label is calculated and printed on the console. Here is the output
generated by this program:
Tab distance = 24.75
Label1 Size=(145, 13) Location=(3, 3) Margin=0 Req.width 145
Label2 Size=(13, 13) Location=(3, 16) Margin=0 Req.width 13
Label3 Size=(157, 13) Location=(28, 29) Margin=25 Req.width 182
Label4 Size=(13, 13) Location=(28, 42) Margin=25 Req.width 38
Label5 Size=(175, 13) Location=(53, 55) Margin=50 Req.width 225
Label6 Size=(277, 13) Location=(77, 68) Margin=74 Req.width 351
Label7 Size=(13, 13) Location=(28, 81) Margin=25 Req.width 38
Label8 Size=(13, 13) Location=(3, 94) Margin=0 Req.width 13
FlowLayoutPanel Padding=(3, 3, 3, 3) PreferredSize=(357, 110)
Now it can be seen that Label6 requires widest space. That line begins with
three tabs, which sets its beginning at X=74 (three times 24.75, rounded), and
text requires further 277 pixels to be drawn, total of 351 pixel in width.
Panel's left and right padding should be added to this value (3 pixels on each
side), resulting in total 357 pixels required to draw the longest line, and that
becomes preferred width of the complete panel. Note that panel's left padding (3
pixels) causes Left of Label6 to be 77, rather than 74. Actually, Label6.Left
property value is sum of panel's left padding and label's left margin.
Picture: (a) Dialog with indented labels without borders (b) Same with
borders
Picture (a) shows labels layout with different left margin values. Implied
column width of the FlowLayoutPanel is just wide enough to show contents of all
labels without wrapping or clipping. Better insight into layout is gained when
CreatePanel method is invoked with BorderStyle.FixedSingle parameter:
FlowLayoutPanel
pnl = CreatePanel(font, program, BorderStyle.FixedSingle);
Result is shown on Picture (b), where each contained control is outlined to help
discover its actual position.