How to set the client area (ClientRectangle) in a borderless form?

5.3k Views Asked by At

I want to know if it's possible to set the client area of a borderless form. Say for example I define a form like so:

Code

public class MyForm : Form
{
    public MyForm()
    {
        this.FormBorderStyle = FormBorderStyle.None;
    }
}

Result

Form with full client area

What I want to do is specify the client area, so that the form has a frame (like the standard windows frame, but custom drawn).

Result

Form with recalculated client area

Essentially, the blue area would become the non client area, and the gray area would remain as the client area.

I have tried to set the client area, but this just seems to resize the entire form, thus, is does not leave behind a "non-client" area

Is this possible?

1

There are 1 best solutions below

0
On BEST ANSWER

This is possible, however I don't know how well this works with a Windows Form with the WindowStyle set to Borderless. Using PInvoke (Platform Invoke) Functions, you can remove window themes which will give you a very basic looking Windows Form. You can then use various PInvoke functions to manipulate the Non-client area of the windows form.

I recommend that you read through these topics. They're designed for Win32 Applications using C++, but PInvoke is the process of calling these native APIs using Managed Code (C#)

WM_NCCALCSIZE: https://msdn.microsoft.com/en-us/library/windows/desktop/ms632634(v=vs.85).aspx

WM_NCPAINT: https://msdn.microsoft.com/en-us/library/windows/desktop/dd145212(v=vs.85).aspx

GetDCEx: https://msdn.microsoft.com/en-us/library/windows/desktop/dd144873(v=vs.85).aspx

GetWindowDC: https://msdn.microsoft.com/en-us/library/windows/desktop/dd144947(v=vs.85).aspx

SetWindowTheme: https://msdn.microsoft.com/en-us/library/windows/desktop/bb759827(v=vs.85).aspx

This example is very very crude, but it provides basic functionality. I don't know how SetWindowTheme works on Windows 8 or 8.1, but in Windows 7, it gives windows the "classic" theme.

public partial class MyForm : Form
{
    //Window Messages
    public const uint WM_NCPAINT = 0x85;
    public const uint WM_NCCALCSIZE = 0x83;
    public const uint WM_NCHITTEST = 0x84;

    //GetDCEx Flags
    public const int DCX_WINDOW = 0x00000001;
    public const int DCX_CACHE = 0x00000002;
    public const int DCX_PARENTCLIP = 0x00000020;
    public const int DCX_CLIPSIBLINGS = 0x00000010;
    public const int DCX_CLIPCHILDREN = 0x00000008;
    public const int DCX_NORESETATTRS = 0x00000004;
    public const int DCX_LOCKWINDOWUPDATE = 0x00000400;
    public const int DCX_EXCLUDERGN = 0x00000040;
    public const int DCX_INTERSECTRGN = 0x00000080;
    public const int DCX_INTERSECTUPDATE = 0x00000200;
    public const int DCX_VALIDATE = 0x00200000;

    //RECT Structure
    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct RECT
    {
        public int left, top, right, bottom;
    }

    //WINDOWPOS Structure
    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct WINDOWPOS
    {
        public IntPtr hwnd;
        public IntPtr hwndinsertafter;
        public int x, y, cx, cy;
        public int flags;
    }

    //NCCALCSIZE_PARAMS Structure
    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct NCCALCSIZE_PARAMS
    {
        public RECT rgrc0, rgrc1, rgrc2;
        public WINDOWPOS lppos;
    }

    //SetWindowTheme UXtheme Function
    [System.Runtime.InteropServices.DllImport("uxtheme.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
    public static extern int SetWindowTheme(
        IntPtr hWnd,
        String pszSubAppName,
        String pszSubIdList);

    //GetWindowRect User32 Function
    [System.Runtime.InteropServices.DllImport("user32.dll", ExactSpelling = true)]
    [return: System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bool)]
    public static extern bool GetWindowRect(
        IntPtr hwnd,
        out  RECT lpRect
        );

    //GetWindowDC User32 Function
    [System.Runtime.InteropServices.DllImport("user32.dll", ExactSpelling = true)]
    public static extern IntPtr GetWindowDC(
        IntPtr hWnd
        );

    //GetDCEx User32 Function
    [System.Runtime.InteropServices.DllImport("user32.dll", ExactSpelling = true)]
    public static extern IntPtr GetDCEx(
        IntPtr hWnd,
        IntPtr hrgnClip,
        int flags
        );

    //Window Procedure Hook
    protected override void WndProc(ref Message m)
    {
        //Don't style window in designer...
        if (DesignMode)
            base.WndProc(ref m);

        //Handle Message
        switch ((uint)m.Msg)
        {
            case WM_NCCALCSIZE: WmNCCalcSize(ref m); break;
            case WM_NCPAINT: WmNCPaint(ref m); break;
            default: base.WndProc(ref m); break;
        }
    }

    //Handle Creation
    protected override void OnHandleCreated(EventArgs e)
    {
        //Base Procedure...
        base.OnHandleCreated(e);

        //Remove Theme
        SetWindowTheme(this.Handle, string.Empty, string.Empty);
    }

    //WM_NCCALCSIZE
    private void WmNCCalcSize(ref Message m)
    {
        //Get Window Rect
        RECT formRect = new RECT();
        GetWindowRect(m.HWnd, out formRect);

        //Check WPARAM
        if (m.WParam != IntPtr.Zero)    //TRUE
        {
            //When TRUE, LPARAM Points to a NCCALCSIZE_PARAMS structure
            var nccsp = (NCCALCSIZE_PARAMS)System.Runtime.InteropServices.Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));

            //We're adjusting the size of the client area here. Right now, the client area is the whole form.
            //Adding to the Top, Bottom, Left, and Right will size the client area.
            nccsp.rgrc0.top += 30;      //30-pixel top border
            nccsp.rgrc0.bottom -= 4;    //4-pixel bottom (resize) border
            nccsp.rgrc0.left += 4;      //4-pixel left (resize) border
            nccsp.rgrc0.right -= 4;     //4-pixel right (resize) border

            //Set the structure back into memory
            System.Runtime.InteropServices.Marshal.StructureToPtr(nccsp, m.LParam, true);
        }
        else    //FALSE
        {
            //When FALSE, LPARAM Points to a RECT structure
            var clnRect = (RECT)System.Runtime.InteropServices.Marshal.PtrToStructure(m.LParam, typeof(RECT));

            //Like before, we're adjusting the rectangle...
            //Adding to the Top, Bottom, Left, and Right will size the client area.
            clnRect.top += 30;      //30-pixel top border
            clnRect.bottom -= 4;    //4-pixel bottom (resize) border
            clnRect.left += 4;      //4-pixel left (resize) border
            clnRect.right -= 4;     //4-pixel right (resize) border

            //Set the structure back into memory
            System.Runtime.InteropServices.Marshal.StructureToPtr(clnRect, m.LParam, true);
        }

        //Return Zero
        m.Result = IntPtr.Zero;
    }

    //WM_NCPAINT
    private void WmNCPaint(ref Message m)
    {
        //Store HDC
        IntPtr HDC = IntPtr.Zero;
        Graphics gfx = null;

        //Check the WPARAM
        if(m.WParam == (IntPtr)1)
        {
            //For reasons unknown to me, the update region doesn't contain valid data and calling GetDCEx will do nothing.
            //So I call GetWindowDC and exclude the area using System.Drawing.Graphics instead.

            //Graphics Object from HDC
            HDC = GetWindowDC(m.HWnd);
            gfx = Graphics.FromHdc(HDC);

            //Exclude Client Area
            gfx.ExcludeClip(new Rectangle(4, 30, Width - 8, 34));  //Exclude Client Area (GetWindowDC grabs the WHOLE window's graphics handle)
        }
        else
        {
            //Graphics Object from HDC
            HDC = GetDCEx(m.HWnd, m.WParam, DCX_WINDOW | DCX_INTERSECTRGN);
            gfx = Graphics.FromHdc(HDC);
        }

        //Call Paint
        using (PaintEventArgs ncPaintArgs = new PaintEventArgs(gfx, new Rectangle(0, 0, Width, Height)))
            MyForm_NCPaint(this, ncPaintArgs);

        //Return Zero
        m.Result = IntPtr.Zero;
    }

    public MyForm()
    {
        InitializeComponent();
    }

    private void MyForm_NCPaint(object sender, PaintEventArgs e)
    {
        //Clear
        e.Graphics.Clear(Color.Green);
    }
}

Result of above code