2

我正在创建一个自定义表单(C#/Windows Forms/Vista/Windows7),并覆盖 WndProc 以捕获 WM_NCPAINT、WM_NCCALCSIZE 和 WM_NCHITTEST 以绘制自定义框架。我几乎完成了它,但有一个我无法解决的问题。

问题是 NC_CALCSIZE 使我的表单在最大化后恢复时缩小。我用谷歌搜索并找到了 Bob Powell 的答案,他说当 WPARAM 为 TRUE 时我不需要处理 NC_CALCSIZE。在我这样做之后,WM_NCPAINT 不再起作用(它确实处理了 WM_NCPAINT,但它不再绘制非客户区,只有在我使其无效之后)。

因此,恢复,当我处理 WM_NCCALCSIZE(WPARAM == TRUE) 时,它会缩小我的表单,当我不处理时,它不会再绘制了。

以前有人遇到过这个问题吗?如果需要更多代码,我可以提供。谢了。

这是我的 WN_CALCSIZE 代码:

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;
    }

这是我的 WM_NCPAINT 代码:

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);
        }
    }
4

4 回答 4

2

好吧,我无法让它工作,看来返回 WM_NCCALCSIZE 比我预期的要复杂。我根本无法理解它,因为它没有做我期望它做的事情。我试图按照这篇文章所说的去做,但同样没有用:

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

所以我再次用谷歌搜索,我在 CodeProject 上找到了这篇文章,描述了我遇到的同样问题:

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

解决方法是听 WM_SYSCOMMAND 并捕获 SC_RESTORE,将我的表单宽度和高度设置为最大化/恢复。

我的 WM_NCCALCSIZE 变成了这样:

    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);
        }
    }

和我的 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);
    }

这可能不是最好的解决方案,但完成了工作。如果有人能提供更好的解决方案,我真的很感激。

Tks,Hans Passant 和 Tergiver,请注意。

于 2012-09-17T23:42:52.793 回答
2

这不是问题WM_NCCALCSIZE。处理时WM_NCCALCSISE,.NET 调用RestoreWindowBoundsIfNecessary()方法,因此窗口的大小发生了变化(请参阅使用 SetWindowPlacement 更改窗口的恢复位置不适用于每个窗口)。您可以在表单最小化时手动缓存表单的大小并在恢复时重置,或者不调用基本WndProc()方法,该方法调用RestoreWindowBoundsIfNecessary()

于 2013-01-07T11:19:35.647 回答
0

我得到了不同的结果,实际上我想获得公众对此解决方案的意见:

protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
    width += BORDER_LEFT_WIDTH + BORDER_RIGHT_WIDTH;
    height += BORDER_TOP_HEIGHT + TITLEBAR_HEIGHT + BORDER_BOTTOM_HEIGHT;
    base.SetBoundsCore(x, y, width, height, specified);
}

实际上,这抵消了我在处理类似问题时遇到的收缩。

于 2014-12-28T17:16:15.840 回答
0

看起来这个问题有更优雅的解决方案,虽然仍然很老套。请参阅使用 DWM 自定义窗口框架:如何正确处理 WM_NCCALCSIZE

不同之处在于,这里的解决方案要么改变窗口大小导致重绘/闪烁,要么人为地缩小边界,而上面帖子中的解决方案只是说你的窗口没有标题/边框,因此窗口和客户区的大小是相等的。

顺便说一句,如果您希望客户区占据所有窗口大小 WndProcNonClientCalcSize 可以简化为

private void WndProcNonClientCalcSize(ref Message m)
{
  m.Result = IntPtr.Zero;
}
于 2016-08-04T04:09:02.350 回答