Search code examples
c#winformsuser-controlsbordercustom-controls

Custom border width and border color for UserControl or Panel


I want to make the same thing "Balazs Tihanyi" do here: https://stackoverflow.com/a/9772020/8458887 But with a TableLayoutPanel. I tested his code an worked. But when i change the TextBox for a TableLayoutPanel don't work.

Picture: First is "Balazs Tihanyi" TextBox and second my TableLayoutPanel

Example

Picture 2: This is how the TableLayoutPanel looks when i add a TextBox

Example 2

The changed code:

    public class BorderedPanel : UserControl
    {
        TableLayoutPanel tableLayoutPanel;

        public BorderedPanel()
        {
            tableLayoutPanel = new TableLayoutPanel()
            {
                BackColor = SystemColors.Window,
                //AutoSize = true,
                //CellBorderStyle = TableLayoutPanelCellBorderStyle.None,   //Single
                BorderStyle = BorderStyle.None,   //FixedSingle
                ColumnCount = 1,
                RowCount = 1,
                Location = new Point(-1, -1),
                //Dock = DockStyle.Fill,
                Anchor = AnchorStyles.Top | AnchorStyles.Bottom |
                         AnchorStyles.Left | AnchorStyles.Right
            };
            Control container = new ContainerControl()
            {
                Dock = DockStyle.Fill,
                Padding = new Padding(-1)
            };
            container.Controls.Add(tableLayoutPanel);
            //Controls.Add(tableLayoutPanel);

            DefaultBorderColor = SystemColors.ControlDark;
            FocusedBorderColor = Color.Red;
            BackColor = DefaultBorderColor;
            Padding = new Padding(1);
            Size = tableLayoutPanel.Size;
        }

        public Color DefaultBorderColor { get; set; }
        public Color FocusedBorderColor { get; set; }

        //public override string Text
        //{
        //    get { return textBox.Text; }
        //    set { textBox.Text = value; }
        //}

        protected override void OnEnter(EventArgs e)
        {
            BackColor = FocusedBorderColor;
            base.OnEnter(e);
        }

        protected override void OnLeave(EventArgs e)
        {
            BackColor = DefaultBorderColor;
            base.OnLeave(e);
        }

        //protected override void SetBoundsCore(int x, int y,
        //    int width, int height, BoundsSpecified specified)
        //{
        //    base.SetBoundsCore(x, y, width, height, specified);
        //}
    }

How i use the code:

    public Form1()
    {
        InitializeComponent();
        BorderedPanel borderedPanel = new BorderedPanel();
        borderedPanel.Location = new Point(73, 150);   //73, 150   //12, 10
        borderedPanel.Size = new Size(319, 25);
        Controls.Add(borderedPanel);
        //borderedPanel.Controls.Add(txtPath);

Solution

  • As an option to add a custom border the non-client area of a panel, you can handle the following native messages:

    In the following screenshot you see a panel with BorderColor and BorderWidth custom properties, where the implementation works in right to left mode and also in auto-scroll mode:

    Customize panel border size and border color

    You can clone or download the code:

    I've explained more details about handling the messages in the post Panel border size and border color – Customize nonclient area, but here you can see the code as well:

    PanelEx

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing.Drawing2D;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading.Tasks;
    using static PanelBorderExample.Win32Helpers;
    namespace PanelBorderExample
    {
        public class PanelEx : Panel
        {
            public PanelEx()
            {
                BorderStyle = BorderStyle.FixedSingle;
            }
            private Color borderColor = Color.Blue;
            [DefaultValue(typeof(Color), "Blue")]
            public Color BorderColor
            {
                get { return borderColor; }
                set
                {
                    if (borderColor != value)
                    {
                        borderColor = value;
                        Redraw();
                    }
                }
            }
            private int borderWidth = 16;
            [DefaultValue(16)]
            public int BorderWidth
            {
                get { return borderWidth; }
                set
                {
                    if (value == 0)
                        throw new ArgumentException("The value should be greater than 0");
                    if (borderWidth != value)
                    {
                        borderWidth = value;
                        RecalculateClientSize();
                    }
                }
            }
    
            protected override void WndProc(ref Message m)
            {
                if (BorderStyle != BorderStyle.FixedSingle)
                {
                    base.WndProc(ref m);
                    return;
                }
                if (m.Msg == WM_NCPAINT)
                {
                    base.WndProc(ref m);
                    WmNCPaint(ref m);
                }
                else if (m.Msg == WM_NCCALCSIZE)
                {
                    base.WndProc(ref m);
                    WmNCCalcSize(ref m);
                }
                else if (m.Msg == WM_NCHITTEST)
                {
                    base.WndProc(ref m);
                    WmNCHitTest(ref m);
                }
                else
                    base.WndProc(ref m);
    
            }
            protected override void OnSizeChanged(EventArgs e)
            {
                base.OnSizeChanged(e);
                Redraw();
            }
            private void Redraw()
            {
                RedrawWindow(Handle, IntPtr.Zero, IntPtr.Zero,
                   RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW);
            }
            private void RecalculateClientSize()
            {
                SetWindowPos(this.Handle, IntPtr.Zero, 0, 0, 0, 0,
                    SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED | SWP_NOZOORDER);
            }
            private void WmNCCalcSize(ref Message m)
            {
                if (BorderStyle != BorderStyle.FixedSingle)
                    return;
    
                if (m.WParam != IntPtr.Zero)
                {
                    var nccsp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
                    nccsp.rgrc[0].top += borderWidth - 1;
                    nccsp.rgrc[0].bottom -= borderWidth - 1;
                    nccsp.rgrc[0].left += borderWidth - 1;
                    nccsp.rgrc[0].right -= borderWidth - 1;
                    Marshal.StructureToPtr(nccsp, m.LParam, true);
                    InvalidateRect(this.Handle, nccsp.rgrc[0], true);
                    m.Result = IntPtr.Zero;
                }
                else
                {
                    var clnRect = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
                    clnRect.top += borderWidth - 1;
                    clnRect.bottom -= borderWidth - 1;
                    clnRect.left += borderWidth - 1;
                    clnRect.right -= borderWidth - 1;
                    Marshal.StructureToPtr(clnRect, m.LParam, true);
                    m.Result = IntPtr.Zero;
                }
            }
            private void WmNCPaint(ref Message m)
            {
                var dc = GetWindowDC(Handle);
                using (var g = Graphics.FromHdc(dc))
                {
                    using (var p = new Pen(BorderColor, borderWidth) { Alignment = PenAlignment.Inset })
                    {
                        if (VScroll && HScroll)
                        {
                            Rectangle bottomCornerRectangle = new Rectangle(
                                Width - SystemInformation.VerticalScrollBarWidth - borderWidth,
                                Height - SystemInformation.HorizontalScrollBarHeight - borderWidth,
                                SystemInformation.VerticalScrollBarWidth,
                                SystemInformation.HorizontalScrollBarHeight);
                            if (RightToLeft == RightToLeft.Yes)
                            {
                                bottomCornerRectangle.X = Width - bottomCornerRectangle.Right;
                            }
                            g.FillRectangle(SystemBrushes.Control, bottomCornerRectangle);
                        }
                        var adjustment = borderWidth == 1 ? 1 : 0;
                        g.DrawRectangle(p, 0, 0, Width - adjustment, Height - adjustment);
                    }
                }
                ReleaseDC(Handle, dc);
                m.Result = IntPtr.Zero;
            }
            private void WmNCHitTest(ref Message m)
            {
                var pt = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
                var rect = Parent.RectangleToScreen(Bounds);
                if (((pt.X >= rect.Left && pt.X <= rect.Left + borderWidth) ||
                    (pt.X >= rect.Right - borderWidth && pt.X <= rect.Right)) ||
                    ((pt.Y >= rect.Top && pt.Y <= rect.Top + borderWidth) ||
                    (pt.Y >= rect.Bottom - borderWidth && pt.Y <= rect.Bottom)))
                    m.Result = (IntPtr)HTBORDER;
            }
        }
    }
    

    Win32Helpers

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace PanelBorderExample
    {
        public static class Win32Helpers
        {
            public const int WM_NCPAINT = 0x85;
            public const int RDW_INVALIDATE = 0x0001,
                RDW_ERASE = 0x0004,
                RDW_ALLCHILDREN = 0x0080,
                RDW_ERASENOW = 0x0200,
                RDW_UPDATENOW = 0x0100,
                RDW_FRAME = 0x0400;
            public const int WM_NCCALCSIZE = 0x0083;
            public const int SWP_FRAMECHANGED = 0x0020,
                SWP_NOMOVE = 0x0002,
                SWP_NOSIZE = 0x0001,
                SWP_NOZOORDER = 0x0004;
    
    
            public const int WM_NCHITTEST = 0x0084;
            public const int HTBORDER = 18;
            public const int HTHSCROLL = 6;
            public const int HTVSCROLL = 7;
            public const int HTCLIENT = 1;
    
            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                public int left, top, right, bottom;
            }
            [StructLayout(LayoutKind.Sequential)]
            public struct NCCALCSIZE_PARAMS
            {
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
                public RECT[] rgrc;
                public WINDOWPOS lppos;
            }
            [StructLayout(LayoutKind.Sequential)]
            public struct WINDOWPOS
            {
                public IntPtr hwnd;
                public IntPtr hwndInsertAfter;
                public int x;
                public int y;
                public int cx;
                public int cy;
                public uint flags;
            }
            public const int NM_FIRST = 0;
            public const int NM_CLICK = NM_FIRST - 2;
            public const int WM_REFLECT = 0x2000;
            public const int WM_NOFITY = 0x004e;
            public const int WM_CTLCOLORSCROLLBAR = 0x0137;
            [StructLayout(LayoutKind.Sequential)]
            public struct NMHDR
            {
                public IntPtr hwndFrom;
                public IntPtr idFrom;
                public int code;
            }
            [DllImport("user32.dll")]
            public static extern bool InvalidateRect(IntPtr hWnd, RECT lpRect, bool bErase);
            [DllImport("user32.dll")]
            public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
            [DllImport("user32")]
            public static extern IntPtr GetWindowDC(IntPtr hwnd);
            [DllImport("user32.dll")]
            public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprc, IntPtr hrgn, int flags);
            [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
            public static extern IntPtr SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
        }
    }