none
响应WM_NCCALCSIZE后窗体越来越小 RRS feed

  • 问题

  • 我想自绘窗体标题栏,但是皮肤的标题栏高度和系统的不一样,所以要将标题栏的高度加大,我于是响应WM_NCCALCSIZE
    但发现如果不断最大化,还原的话窗体会越来越小,怎么回事?希望有人能解答下,谢谢!

    代码如下:

    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            [DllImport("user32")]
            public static extern int GetSystemMetrics(int nIndex);
            private const int SM_CYCAPTION = 4;
            private const int SM_CXFRAME = 32;
            private const int SM_CYFRAME = 33;
            private const int WM_NCCALCSIZE = 0x83;
    
            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                public int Left;
                public int Top;
                public int Right;
                public int Bottom;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            public struct PWINDOWPOS
            {
                public IntPtr hwnd;
                public IntPtr hwndInsertAfter;
                public int x;
                public int y;
                public int cx;
                public int cy;
                public uint flags;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            public struct NCCALCSIZE_PARAMS
            {
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
                public RECT[] rgrc;
                public PWINDOWPOS lppos;
            }
    
            protected override void WndProc(ref Message m)
            {
                if (m.Msg == WM_NCCALCSIZE && m.WParam != IntPtr.Zero)
                {
                    NCCALCSIZE_PARAMS Params;
                    Params = (NCCALCSIZE_PARAMS)m.GetLParam(typeof(NCCALCSIZE_PARAMS));
                    Params.rgrc[0].Top += 40 - (GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME)); //40为标题栏高度
                    Marshal.StructureToPtr(Params, m.LParam, false);
                }
                base.WndProc(ref m);
            }
        }
    }
    


    2011年9月4日 3:02

答案

  • 我找了一下Win32 API貌似也没提供改变caption部分高度的方法, 所以只有这样了.

    >> 我想写一个换肤库,如果在客户区里画的话控件的坐标就全要改动了,这可不是一个好方法...
    将所有页面控件放到一个和client等大的Panel里面, 这些控件坐标就不用变了, 只需要将Panel的位置上移10(因为你现在是将client下移了40-30 = 10).
    Panel的Border就按默认的, 不会显示出来.
    panel1.Size = new Size(this.ClientSize.Width, this.ClientSize.Height + 10);
    panel1.Location = new Point(0, -10);


    Leo Liu [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    • 已标记为答案 610384825 2011年9月9日 14:09
    • 取消答案标记 610384825 2011年9月11日 0:53
    • 已标记为答案 610384825 2011年9月12日 5:08
    2011年9月9日 2:29
    版主

全部回复

  • 没人能帮个忙吗?
    2011年9月5日 11:24
  • Hi 610384825,

    首先请看一下MSDN文档对NCCALCSIZE_PARAMS结构的解释(这篇文档没有中文版本): http://msdn.microsoft.com/en-us/library/ms632606(v=vs.85).aspx.
    其中对于rgrc成员的解释, 它是一个RECT[3]类型, 其中首个元素是移动或者改变大小之后的新窗口坐标, 第二个元素是移动或者改变大小之前窗口的坐标.
    然后先将代码修改为如下之后运行, 这里我显示了一下窗口相关的一些数据:
            protected override void WndProc(ref Message m)
            {
                     if (m.Msg == WM_NCCALCSIZE && m.WParam != IntPtr.Zero)
                     {
                              NCCALCSIZE_PARAMS Params;
                              Params = (NCCALCSIZE_PARAMS)m.GetLParam(typeof(NCCALCSIZE_PARAMS));
                              Params.rgrc[0].Top += 40 - (GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME));
                              label1.Text = "NEW WND| Top:" + Params.rgrc[0].Top + " | Bottom:" + Params.rgrc[0].Bottom + " | Height:" + (Params.rgrc[0].Bottom - Params.rgrc[0].Top);
                              this.Text = " | Height:" + (Params.rgrc[0].Bottom - Params.rgrc[0].Top);
                              label2.Text = "OLD WND| Top:" + Params.rgrc[1].Top + " | Bottom:" + Params.rgrc[1].Bottom + " | Height:" + (Params.rgrc[1].Bottom - Params.rgrc[1].Top);
                              label3.Text = "Caption Height:" + GetSystemMetrics(SM_CYCAPTION) + " | Vertical Border Height:" + GetSystemMetrics(SM_CYFRAME);
                              Marshal.StructureToPtr(Params, m.LParam, false);
                     }
                     base.WndProc(ref m);
            }
    


    在代码里面获取到标题栏和水平边框的高度之后相加, 即(GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME))得到的数值, 在我这里是30, 所以实际上每次改变之后新窗口的Top坐标实际上都是+10了. 而窗口的高度是用Bottom减去Top的, 所以每次最大化又还原之后新窗口高度实际上都减少了10, 这个我用this.Text = " | Height:" + (Params.rgrc[0].Bottom - Params.rgrc[0].Top);将窗口高度显示在了窗口标题中, 你可以不断最大化又还原, 之后看到每次新窗口高度的变化.


    你可以看看这个帖子, 里面是和你完全一样的问题, 楼主最后给出了demo的链接, 你看看能不能下载到, 我这里是微软内网, 没法打开:
    http://topic.csdn.net/t/20050309/16/3837593.html.

    祝你快乐每一天,

    Leo Liu [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.


    -------------------------------------------------
    This response contains links reference to a third party World Wide Web site. Microsoft is providing this information as a convenience to you.
    Microsoft does not control these sites and has not tested any software or information found on these sites; therefore, Microsoft cannot make any representations regarding the quality, safety, or suitability of any software or information found there.
    There are inherent dangers in the use of any software found on the Internet, and Microsoft cautions you to make sure that you completely understand the risk before retrieving any software from the Internet.
    2011年9月6日 4:45
    版主
  • 你好,

    那篇MSDN的文章我看过了,好像没有什么帮助

    我看了(Params.rgrc[0].Bottom - Params.rgrc[0].Top),的确是每次都减少了10

    但是这是为什么呢?如果我只响应WM_NCCALCSIZE消息一次,那大小变了之后就没用了

    那CSDN最后的DEMO打不开,我还是不知道错在哪,希望你能给出具体的代码或者指出错误的地方

    非常感谢你的回复

    2011年9月6日 13:53
  • >>  我看了(Params.rgrc[0].Bottom - Params.rgrc[0].Top),的确是每次都减少了10, 但是这是为什么呢?
    GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME)结果是10, 所以实际上是Params.rgrc[0].Top += 10, Top和Bottom都是计算的从屏幕顶端到窗体顶边和底边的像素值, 每次新窗口Top属性+10, 而Bottom不变, 所以窗体区域高度-10.

    >> 如果我只响应WM_NCCALCSIZE消息一次,那大小变了之后就没用了
    你的意思是只需要响应WM_NCCALCSIZE消息一次吗, 也就是WinForm程序运行起来之后就不需要改变大小了吗? 这样的话可以设定FormBorderStyle为FixedToolWindow.

    如果不是此需求, 则最简单的办法, 同时设定新窗口的Bottom属性:
    Params.rgrc[0].Bottom += 40 - (GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME));

    Leo Liu [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    2011年9月6日 15:06
    版主
  • 你好,

    Params.rgrc[0].Bottom += 40 - (GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME));

    这句加了之后的确不会变小了,但是底部的边框不见了,这个怎么解决呢?

    2011年9月7日 11:32
  • Hi 610384825,

    这本来就是个变通方案, 你可以设定FormBorderStyle为FixedDialog或者FixedSingle来降低盖住边框的视觉影响.
    不然就放弃这种办法,直接在Client区域内用一块图片和Caption部分的图片拼合来实现这个效果了.
    Leo Liu [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    2011年9月8日 2:47
    版主
  • 你好

    不能通过减小客户区来实现吗?

    2011年9月8日 10:55
  • 窗口大小是随着客户区域大小改变来变化的, 减小Client区域, 窗口大小会跟着变化, 现在这个变通方案也只是将Client区域跟整个窗口错位了一下.


    Leo Liu [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    2011年9月8日 11:59
    版主
  • 那么,除了把FormBorderStyle设为None就没有别办法了吗?

    我想写一个换肤库,如果在客户区里画的话控件的坐标就全要改动了,这可不是一个好方法...

    2011年9月8日 14:18
  • 我找了一下Win32 API貌似也没提供改变caption部分高度的方法, 所以只有这样了.

    >> 我想写一个换肤库,如果在客户区里画的话控件的坐标就全要改动了,这可不是一个好方法...
    将所有页面控件放到一个和client等大的Panel里面, 这些控件坐标就不用变了, 只需要将Panel的位置上移10(因为你现在是将client下移了40-30 = 10).
    Panel的Border就按默认的, 不会显示出来.
    panel1.Size = new Size(this.ClientSize.Width, this.ClientSize.Height + 10);
    panel1.Location = new Point(0, -10);


    Leo Liu [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    • 已标记为答案 610384825 2011年9月9日 14:09
    • 取消答案标记 610384825 2011年9月11日 0:53
    • 已标记为答案 610384825 2011年9月12日 5:08
    2011年9月9日 2:29
    版主
  • 这是个好方法,不过还是有很多不足,比如控件的Parent变了,BackgroundImage也变了

    不过还是先这样凑合吧..

    • 已标记为答案 610384825 2011年9月9日 14:09
    • 取消答案标记 610384825 2011年9月9日 14:09
    2011年9月9日 14:09
  • 你好,我突然发现,如果屏蔽SetBoundsCore就不会变小了,怎么回事?
    2011年9月11日 0:53
  • Hi 610384825,

    好吧, 还是你犀利, 呵呵.
    那现在就是去掉设定Params.rgrc[0].Bottom的这一行代码, 然后重写SetBoundsCore方法如下:

            protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
            {
                // Set a fixed height and width for the control.
                base.SetBoundsCore(x, y, width, height + 10, specified);
            }
    

    给接收到的高度+10.

    祝你中秋快乐,


    Leo Liu [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    2011年9月12日 5:19
    版主
  • 这个问题主要是由于在Form内部还原窗口时,调用了一个函数:SizeFromClientSize造成的。还原时,SetBoundsCore函数使用SizeFromClientSize函数的返回值进行设置。因此,可以重载SetBoundsCore函数以达到效果

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

    2015年3月24日 13:46