Windows Forms Layout Managers
by Richard Grimes


Listing One

class App : Form
{
   App()
   {
      Button b1, b2, b3;
      b1 = new Button();
      b1.Left = 0;
      b1.Width = 60;
      b1.Text = "One";
      b1.Click+=new EventHandler(click);
      b2 = new Button();
      b2.Left = 40;
      b2Width = 60;
      b2.Text = "Two";
      b2.Click+=new EventHandler(click);
      b3 = new Button();
      b3.Left = 80;
      b3.Width = 60;
      b3.Text = "Three";
      b3.Click+=new EventHandler(click);
      this.Controls.AddRange(new Control[]{b1, b2, b3});
   }
   void click(object sender, EventArgs args)
   {
      Controls.SetChildIndex((Control)sender, 0);
   }
   static void Main()
   {
      Application.Run(new App());
   }
}

Listing Two

protected virtual void OnLayout(LayoutEventArgs levent)
{ 
   LayoutEventHandler handler;
   if (this.CanRaiseEvents)
   {
      handler = ((LayoutEventHandler) 
         base.Events[Control.EventLayout]);
      if (handler != null)
      {
         handler.Invoke(this, levent);
      } 
   }
   LayoutManager.OnLayout(this, levent);
}

Listing Three

public class LayoutPanelDesigner : ParentControlDesigner
{
   private LayoutPanel panel;
   public override void Initialize(IComponent component)
   {
      base.Initialize(component);
      // Cache the control so that we can use it later
      panel = component as LayoutPanel;
   }
   protected override void OnPaintAdornments(PaintEventArgs e)
   {
      base.OnPaintAdornments(e);
      using (Pen pen = new Pen(Color.Gray, 1))
      {
         pen.DashStyle = DashStyle.Dash;
         e.Graphics.DrawRectangle(pen, 0, 0, panel.Width-1, panel.Height-1);
      }
   }
}

Listing Four

[Designer(typeof(LayoutPanelDesigner))]
public abstract class LayoutPanel : Control
{
   public LayoutPanel()
   {
      ResizeRedraw = true;
   }
   protected abstract override void 
      OnLayout(LayoutEventArgs e);
}

Listing Five

public enum VerticalAlignment : byte
{ Top=0, Middle=1, Bottom=2 }

[ToolboxBitmap(typeof(FlowPanel), "FlowPanel.bmp")]
public class FlowPanel : LayoutPanel
{
   private int vertPad = 5;
   private int horizPad = 5;
   private VerticalAlignment align = VerticalAlignment.Top;
   protected override void OnLayout(LayoutEventArgs e);
   [Category("Layout"), DefaultValue(5), 
    Description("The vertical distance " + "between controls")]
   public int VerticalPad
   {
      get{return vertPad;}
      set
      {
         vertPad = value;
         PerformLayout();
      }
   }
   [Category("Layout"), DefaultValue(5),
    Description("The horizontal distance " + "between controls")]
   public int HorizontalPad
   {
      get{return horizPad;}
      set
      {
         horizPad = value;
         PerformLayout();
      }
   }
   [Category("Layout"),
    DefaultValue(VerticalAlignment.Top),
    Description("Determines how controls " + "are aligned on a line")]
   public VerticalAlignment Alignment
   {
      get{return align;}
      set
      {
         align = value;
         PerformLayout();
      }
   }
}

Listing Six

protected override void OnLayout(LayoutEventArgs e)
{
   if (Controls.Count > 0) 
   {
      int nextCtrlRefLine = 0;
      int prevCtrlRefLine = 0;
      int ctrlLeft = horizPad;
      int ctrlRefLine = 0;
      if (align == VerticalAlignment.Top)
         ctrlRefLine = vertPad;
      // This contains the controls on the current line	
      ArrayList thisLine = new ArrayList();
      foreach (Control c in Controls) 
      {
         // Make invisible to stop flicker
         c.Visible = false;
         // See if right edge of control is beyond the panels right edge
         if (ctrlLeft + c.Width > this.Width) 
         {
            prevCtrlRefLine = ctrlRefLine + vertPad/2;
            switch (align)
            {
            case VerticalAlignment.Top:
               ctrlRefLine = nextCtrlRefLine;
               break;
            case VerticalAlignment.Middle:
               ctrlRefLine = nextCtrlRefLine + c.Height/2 + vertPad/2;
               nextCtrlRefLine = ctrlRefLine + c.Height/2 + vertPad/2;
               break;
            case VerticalAlignment.Bottom: ctrlRefLine += c.Height + vertPad;
               break;
            }
            ctrlLeft = horizPad;
            thisLine.Clear();
         }
         // Calculate position of control; see if reference line should change
         switch (align)
         {
            // Reference line is the top of controls
            case VerticalAlignment.Top:
            {
               if (nextCtrlRefLine < (ctrlRefLine + c.Size.Height + vertPad)) 
                  nextCtrlRefLine = ctrlRefLine + c.Size.Height + vertPad;
               c.Location = new Point(ctrlLeft, ctrlRefLine);
               break;
            }
            // The reference line is halfway between rows of controls
            case VerticalAlignment.Middle:
            {
               // If this is first control added, calculate reference lines
               if (ctrlRefLine == 0 && nextCtrlRefLine == 0)
               {
                  ctrlRefLine = c.Height/2 + vertPad;
                  nextCtrlRefLine = c.Height + 3 * vertPad/2;
               }
               // If control is too high we need to move reference line and
               // reposition the controls in the current line
               if ((ctrlRefLine + c.Height/2 + vertPad/2) > nextCtrlRefLine)
               {
                  ctrlRefLine = ctrlRefLine*2 - nextCtrlRefLine 
                     + vertPad/2 + c.Height/2;
                  nextCtrlRefLine = ctrlRefLine + vertPad/2 + c.Height/2;
                  // Re-arrange other controls
                  foreach (object o in thisLine)
                  {
                     Control ctrl = o as Control;
                     ctrl.Top = ctrlRefLine - ctrl.Height/2;
                  }
               }
               c.Location = new Point(ctrlLeft, ctrlRefLine-c.Height/2);
               thisLine.Add(c);
               break;
            }
            case VerticalAlignment.Bottom:
            {
               // If this is first control added, calculate reference lines
               if (ctrlRefLine == 0)
               {
                  ctrlRefLine = c.Height + vertPad;
                  prevCtrlRefLine = vertPad/2;
               }
               // See if we need to re-arrange the controls on this line
               if ((ctrlRefLine-c.Height-vertPad/2) < prevCtrlRefLine)
               {
                  ctrlRefLine = prevCtrlRefLine + c.Height + vertPad/2;
                  // Re-arrange other controls
                  foreach (object o in thisLine)
                  {
                     Control ctrl = o as Control;
                     ctrl.Top = ctrlRefLine - ctrl.Height;
                  }
               }
               c.Location = new Point(ctrlLeft, ctrlRefLine-c.Height);
               thisLine.Add(c);
               break;
            }
         }
         // Update the left position of the next control
         ctrlLeft = ctrlLeft + c.Size.Width + horizPad;
      }
      // Show all controls
      foreach (Control c in Controls) 
      {
         c.Visible = true;
      }
   }
}







4


