Using Margins to Position Controls in FlowLayoutPanel


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.

FlowLayoutPanel C#

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

FlowLayoutPanel C#

FlowLayoutPanel C#

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.