Filipe Scur Filipe Scur - 4 months ago 32
C# Question

How to prevent NC_CALCSIZE from resizing my form? C#

I'm creating a custom form (C#/Windows Forms/Vista/Windows7), and overriding WndProc to capture WM_NCPAINT, WM_NCCALCSIZE and WM_NCHITTEST to draw a custom frame. I'm almost done with it but there is a problem that I could not work around myself.

The problem is that NC_CALCSIZE makes my form shrink when I restore it after its maximized. I´ve googled it and found an answer from Bob Powell, and he stated that I neednt to handle NC_CALCSIZE when WPARAM is TRUE. After I've done that, WM_NCPAINT had no effect anymore (it does handle the WM_NCPAINT, but it does not paint the non-client area anymore, only after I invalidate it).

So, resuming, when I handle WM_NCCALCSIZE(WPARAM == TRUE) it shrinks my form, when I dont, it doesnt paint anymore.

Has anyone had this problem before? If more code is needed I can provide it.
Tks.

Here's my WN_CALCSIZE code:

private void WndProcNonClientCalcSize(ref Message m)
{
if (m.WParam == WinAPI.FALSE)
{
this.Log(MethodInfo.GetCurrentMethod(), "FALSE");

WinAPI.NCCALCSIZE_PARAMS csp;
csp = (WinAPI.NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(WinAPI.NCCALCSIZE_PARAMS));
csp.rectProposed.Top += this._nonClientHeight;
csp.rectProposed.Bottom -= this._nonClientBorderThickness;
csp.rectProposed.Left += this._nonClientBorderThickness;
csp.rectProposed.Right -= this._nonClientBorderThickness;
Marshal.StructureToPtr(csp, m.LParam, false);
}
else if (m.WParam == WinAPI.TRUE)
{
this.Log(MethodInfo.GetCurrentMethod(), "TRUE");

WinAPI.NCCALCSIZE_PARAMS csp;
csp = (WinAPI.NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(WinAPI.NCCALCSIZE_PARAMS));
csp.rectProposed.Top += this._nonClientHeight;
csp.rectProposed.Bottom -= this._nonClientBorderThickness;
csp.rectProposed.Left += this._nonClientBorderThickness;
csp.rectProposed.Right -= this._nonClientBorderThickness;
Marshal.StructureToPtr(csp, m.LParam, false);
}

m.Result = WinAPI.TRUE;
}


Here's my WM_NCPAINT code:

private bool WndProcNonClientPaint(ref Message m)
{
this.Log(MethodInfo.GetCurrentMethod(), string.Empty);

this.PaintNonClient(m.HWnd, (IntPtr)m.WParam);
m.Result = WinAPI.TRUE;
return true;
}

private void PaintNonClient(IntPtr hWnd, IntPtr hRgn)
{
WinAPI.RECT windowRect = new WinAPI.RECT();
WinAPI.GetWindowRect(hWnd, ref windowRect);

Rectangle bounds = new Rectangle(0, 0,
windowRect.Right - windowRect.Left,
windowRect.Bottom - windowRect.Top);

if (bounds.Width == 0 || bounds.Height == 0)
return;

Region clipRegion = new Region(bounds);

if (hRgn != (IntPtr)1)
clipRegion = Region.FromHrgn(hRgn);

WinAPI.DCV dcv =
WinAPI.DCV.WINDOW |
WinAPI.DCV.INTERSECTRGN |
WinAPI.DCV.CACHE |
WinAPI.DCV.CLIPSIBLINGS;

IntPtr hDC =
WinAPI.GetDCEx(
hWnd,
hRgn,
dcv);

if (hDC == IntPtr.Zero)
hDC = WinAPI.GetWindowDC(hWnd);

IntPtr compatiblehDC = WinAPI.CreateCompatibleDC(hDC);
IntPtr compatibleBitmap = WinAPI.CreateCompatibleBitmap(hDC, bounds.Width, bounds.Height);

try
{
WinAPI.SelectObject(compatiblehDC, compatibleBitmap);
WinAPI.BitBlt(compatiblehDC, 0, 0, bounds.Width, bounds.Height, hDC, 0, 0, WinAPI.SRCCOPY);

using (Graphics g = Graphics.FromHdc(compatiblehDC))
{
Rectangle outterEdge = new Rectangle(0, 0, this.Width, this.Height);

int x = this._nonClientBorderThickness;
int y = this._nonClientHeight;
int width = this.Width - (this._nonClientBorderThickness * 2);
int height = this.Height - this._nonClientBorderThickness - this._nonClientHeight;

Rectangle innerEdge = new Rectangle(x, y, width, height);

GraphicsPath path = new GraphicsPath();

path.AddRectangle(outterEdge);
path.AddRectangle(innerEdge);

using (SolidBrush brush = new SolidBrush(Color.FromArgb(45, 45, 48)))
g.FillPath(brush, path);

path.Dispose();
}

WinAPI.BitBlt(hDC, 0, 0, bounds.Width, bounds.Height, compatiblehDC, 0, 0, WinAPI.SRCCOPY);
}
finally
{
WinAPI.DeleteObject(compatibleBitmap);
WinAPI.DeleteDC(compatiblehDC);
}
}

Answer

Well I couldnt get it to work, it seems the return WM_NCCALCSIZE is more complex than I expected. I simply couldnt understand it, as it didnt do what i expected it to do. I tried to do what this article says but again it was no use:

http://blogs.msdn.com/b/oldnewthing/archive/2003/09/15/54925.aspx

So i googled again and I found this article on CodeProject, describing the same problem I had:

http://www.codeproject.com/Articles/55180/Extending-the-Non-Client-Area-in-Aero

The work around was to listen to WM_SYSCOMMAND and capture SC_RESTORE, setting my form width and height as I Maximized/Restored.

My WM_NCCALCSIZE became this:

    private void WndProcNonClientCalcSize(ref Message m)
    {
        if (m.WParam == WinAPI.FALSE)
        {
            this.Log(MethodInfo.GetCurrentMethod(), "FALSE");

            WinAPI.RECT rect = (WinAPI.RECT)Marshal.PtrToStructure(m.LParam, typeof(WinAPI.RECT));
            rect.Left += this._nonClientBorderThickness;
            rect.Top += this._nonClientHeight;
            rect.Right -= this._nonClientBorderThickness;
            rect.Bottom -= this._nonClientBorderThickness;
            Marshal.StructureToPtr(rect, m.LParam, false);

            m.Result = WinAPI.FALSE;
        }
        else if (m.WParam == WinAPI.TRUE)
        {
            this.Log(MethodInfo.GetCurrentMethod(), "TRUE");

            WinAPI.NCCALCSIZE_PARAMS csp;
            csp = (WinAPI.NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(WinAPI.NCCALCSIZE_PARAMS));

            WinAPI.RECT rectNewClient = csp.rectProposed;

            rectNewClient.Left += this._nonClientBorderThickness;
            rectNewClient.Top += this._nonClientHeight;
            rectNewClient.Right -= this._nonClientBorderThickness;
            rectNewClient.Bottom -= this._nonClientBorderThickness;

            csp.rectProposed = rectNewClient;
            csp.rectBeforeMove = csp.rectProposed;

            Marshal.StructureToPtr(csp, m.LParam, false);

            m.Result = (IntPtr)(WinAPI.NCCALCSIZE_RESULTS.ValidRects);
        }
    }

and my WM_SYSCOMMAND:

    private void WndProcSysCommand(ref Message m)
    {
        UInt32 param;
        if (IntPtr.Size == 4)
            param = (UInt32)(m.WParam.ToInt32());
        else
            param = (UInt32)(m.WParam.ToInt64());

        if ((param & 0xFFF0) == (int)WinAPI.SystemCommands.SC_RESTORE)
        {
            this.Height = this._storedHeight;
            this.Width = this._storedWidth;
        }
        else if (this.WindowState == FormWindowState.Normal)
        {
            this._storedHeight = this.Height;
            this._storedWidth = this.Width;
        }
        base.WndProc(ref m);
    }

It might not be the best solution, but got the work done. If anyone could provide a better solution, I would really apreciate.

Tks, Hans Passant and Tergiver for your attention.