Search code examples
c#windowsformswinformspanel

Windows Forms Multiple Controls In One


I would like to ask for some advice. I don't have enough experience in windows forms programming so I don't know what is the appropriate way of dealing with this task.

I am currently creating a rectangle from four panels. (these rectangles do not represent the code below, they are using different sizing) enter image description here

private System.Windows.Forms.Panel rectangleLeftVertical;
private System.Windows.Forms.Panel rectangleRightVertical;
private System.Windows.Forms.Panel rectangleTopHorizontal;
private System.Windows.Forms.Panel rectangleBottomHorizontal;

// ...

this.rectangleLeftVertical.Location = new System.Drawing.Point(100, 100);
this.rectangleLeftVertical.Size = new System.Drawing.Size(3, 100);

this.rectangleRightVertical.Location = new System.Drawing.Point(200, 100);
this.rectangleRightVertical.Size = new System.Drawing.Size(3, 100);

this.rectangleTopHorizontal.Location = new System.Drawing.Point(100, 100);
this.rectangleTopHorizontal.Size = new System.Drawing.Size(100, 3);

this.rectangleBottomHorizontal.Location = new System.Drawing.Point(100, 200);
this.rectangleBottomHorizontal.Size = new System.Drawing.Size(103, 3);

It is working exactly as I want it to, I would just like to encapsulate everything into a custom Windows Control. The custom component size should of course resize the panels. It will also have a property that dictates border size.

I do not want to change anything, I don't want to do the drawing differently (using graphics.paint solutions). It is not appropriate for my use case.

I tried using a UserControl, however, it is not appropriate because it paints the entire inside of the rectangle - which is what I am trying to avoid.

What would be even better is if it could also be used in the Windows Forms Designer - but this is very unnecessary. It would be really nice though.

How do you guys recommend I tackle this? Any help would be much appreciated. It is likely not a difficult problem, I just lack experience. Thanks!


Solution

  • You can achieve this by creating a GraphicsPath object representing the areas of the form you want to keep/discard. Start with a Rectangle that covers the entire form, then remove the middles of the panels, leaving just the borders. Then build a Region from that GraphicsPath and assign it to the Region property of your Form. This will result in a Form that only exists where the borders are. The middle areas that were removed from the GraphicsPath will literally NOT EXIST in your form, thus anything below your overlay will show right on through.

    Here's a quick example that makes a four pane "window" that can be dragged around by the borders of the panes:

    public partial class Form1 : Form
    {
    
        public Form1()
        {
            InitializeComponent();
            this.TopMost = true;
            this.BackColor = Color.Red; // just so you can see it better
            this.FormBorderStyle = FormBorderStyle.None;
        }
    
        private void Form1_Shown(object sender, EventArgs e)
        {
            GraphicsPath path = new GraphicsPath();
            // add the main rectangle:
            path.AddRectangle(new Rectangle(new Point(0, 0), this.Size));
            // punch some holes in our main rectangle
            // this will make a standard "windowpane" with four panes
            // and a border width of ten pixels
            Size sz = new Size((this.Width - (3 * 10))/2, (this.Height - (3 * 10))/2);
            path.FillMode = FillMode.Alternate;
            path.AddRectangle(new Rectangle(new Point(10, 10), sz));
            path.AddRectangle(new Rectangle(new Point(20 + sz.Width, 10), sz));
            path.AddRectangle(new Rectangle(new Point(10, 20 + sz.Height), sz));
            path.AddRectangle(new Rectangle(new Point(20 + sz.Width, 20 + sz.Height), sz));
            // build a region from our path and set the forms region to that:
            this.Region = new Region(path);
        }
    
        public const int HTCAPTION = 0x2;
        public const int WM_NCHITTEST = 0x84;
        public const int HTCLIENT = 1;
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
    
            if (m.Msg == WM_NCHITTEST)
            {
                if (m.Result.ToInt32() == HTCLIENT)
                    m.Result = (IntPtr)HTCAPTION;
            }
        }
    
    }
    

    Example of it running on top of this page as I edit it:

    enter image description here

    If your overlay can be resized and/or adjusted, just rebuild a new GraphicsPath, change up the rectangles and re-assign the Region built from the new GraphicsPath.

    You could also drop the .Opacity of the main form and you'd be able to partially see through your borders.