• .net实现客户区延伸至至非客户区 [转]


    有人可能会问,客户区延伸至非客户区到底有什么意义。有些程序在布局上比较紧凑或者希望更美观等,无关紧要的菜单项希望能放到标题栏等非客户区,Form窗体控件本身并没有提供此功能。在这之前,有把窗体FormBorderStyle设为None重新绘制标题栏。还有文章通过调用“User32.dll”中的GetWindowDC函数和ReleaseDC函数来实现在标题栏上添加控件,这种方式虽然完全能在非客户区绘制,但是弊端便是无法在vista和windows7下透明主题时显示非客户绘制的内容,因为在透明主题下Aero会把非客户区从GDI+剥离出来让DirectX进行渲染。

      传统方式(网络收集):

    显示代码
    1 using System.Drawing.Drawing2D;
    2 using System.Runtime.InteropServices;
    3
    4 [DllImport("user32.dll")]
    5 privatestaticextern IntPtr GetWindowDC(IntPtr hWnd);
    6 [DllImport("user32.dll")]
    7 privatestaticexternint ReleaseDC(IntPtr hWnd, IntPtr hDC);
    8
    9 privateconstint WM_NCPAINT =0x0085;
    10 privateconstint WM_NCACTIVATE =0x0086;
    11 privateconstint WM_NCLBUTTONDOWN =0x00A1;
    12 protectedoverridevoid WndProc(ref Message m)
    13 {
    14 base.WndProc(ref m);
    15 Rectangle vRectangle =new Rectangle((Width -75) /2, 3, 75, 25);
    16 switch (m.Msg)
    17 {
    18 case WM_NCPAINT:
    19 case WM_NCACTIVATE:
    20 IntPtr vHandle = GetWindowDC(m.HWnd);
    21 Graphics vGraphics = Graphics.FromHdc(vHandle);
    22 vGraphics.FillRectangle(new LinearGradientBrush(vRectangle,
    23 Color.Pink, Color.Purple, LinearGradientMode.BackwardDiagonal),
    24 vRectangle);
    25
    26 StringFormat vStringFormat =new StringFormat();
    27 vStringFormat.Alignment = StringAlignment.Center;
    28 vStringFormat.LineAlignment = StringAlignment.Center;
    29 vGraphics.DrawString("TitlebarControl", Font, Brushes.BlanchedAlmond,
    30 vRectangle, vStringFormat);
    31
    32 vGraphics.Dispose();
    33 ReleaseDC(m.HWnd, vHandle);
    34 break;
    35 case WM_NCLBUTTONDOWN:
    36 Point vPoint =new Point((int)m.LParam);
    37 vPoint.Offset(-Left, -Top);
    38 if (vRectangle.Contains(vPoint))
    39 MessageBox.Show(vPoint.ToString());
    40 break;
    41 }
    42 }

      下面是windows7下实现,本文将一一讲解,本人能力有限,文中不周全的地方希望大家能够指出,这是对我最大的帮助,谢谢。

      整理了一下,大致分为一下几个步骤:

    1. 扩展客户区
    2. 程序边框显示
    3. 模拟非客户区消息
    4. DwmDefWindowProc处理

    1.扩展客户区

    首先我们先把窗体背景颜色设为黑色。这样我们可以看到,黑色部分便是客户区。

    如果要扩展则需要截获NCCALCSIZE消息,并且返回0,客户区占满整个窗体(包括非客户区),按照微软的说法就是删除了标准的框架,变成了自定义框架。

    具体代码:

    显示代码
    1 protectedoverridevoid WndProc(ref Message m)
    2 {
    3 constint NCCALCSIZE =0x0083;
    4 if (m.Msg == NCCALCSIZE)
    5 {
    6 if (m.WParam != (IntPtr)0)
    7 {
    8 m.Result = (IntPtr)0;
    9 }
    10 }
    11 else
    12 {
    13 base.WndProc(ref m);
    14 }
    15 }

    截获NCCALCSIZE之前和之后,效果如下:

         

    2.程序边框显示

    扩展客户区后,程序的边框也被覆盖,我们需要调用系统桌面管理系统(DWM)的提供的接口来将非客户端框架的边缘扩展到窗口内(这里扩展的是边缘,而不是扩展非客户区),具体为dwmapi.dll中的DwmExtendFrameIntoClientArea函数。(DWM更多信息具体见:http://msdn.microsoft.com/zh-cn/magazine/cc163435.aspx
    这里贴出具体代码供参考:

    显示代码
    1 [DllImport("dwmapi.dll")]
    2 publicexternstaticint DwmIsCompositionEnabled(refint en);
    3
    4 [DllImport("dwmapi.dll")]
    5 publicexternstaticint DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margin);
    6
    7 publicstruct MARGINS
    8 {
    9 publicint m_left;
    10 publicint m_right;
    11 publicint m_top;
    12 publicint m_buttom;
    13 };
    14
    15 ///<summary>
    16 /// 边缘扩展
    17 ///</summary>
    18 ///<param name="form">指定窗体</param>
    19 ///<param name="top">上方区域指定距离</param>
    20 ///<param name="buttom">下方区域指定距离</param>
    21 ///<param name="left">左方区域指定距离</param>
    22 ///<param name="right">右方区域指定距离</param>
    23  publicstaticvoid ApplyAero(Form form, int top, int buttom, int left, int right)
    24 {
    25 int en =0;
    26
    27 MARGINS mg =new MARGINS();
    28 mg.m_buttom = buttom;
    29 mg.m_left = left;
    30 mg.m_right = right;
    31 mg.m_top = top;
    32
    33 if (System.Environment.OSVersion.Version.Major >=6)//判断系统版本
    34   {
    35 DwmIsCompositionEnabled(ref en);
    36 if (en >0)//判断是否已启用windows系统的透明效果
    37   {
    38 DwmExtendFrameIntoClientArea(form.Handle, ref mg);
    39 }
    40 else
    41 {
    42 MessageBox.Show("Desktop Composition is Disabled!");
    43 }
    44 }
    45 else
    46 {
    47 MessageBox.Show("Please run this on Windows7.");
    48 }
    49 }

      在程序启动或初始化时调用上面代码中的ApplyAero()方法,效果如下:

      AeroExpand.ApplyAero(this, 30, 8, 8, 8);

      

    你可以根据实际情况设置你想要多大的边框(注意:边框范围内的区域背景为黑色时,才会显示透明),这时你会发现,只是边框和标题栏出现了,但是他们并不能实现其功能,比如标题栏不能拖动,边缘不能调整大小。那是因为实际上标题栏和边缘实际上是客户区而已。

    3.模拟非客户区消息

    鼠标在客户区移动默认会返回HTCLIENT消息,然后windows做出相应的操作。这里我们可以人为的修改返回的消息,使在客户区的操作被windows认为是在标题栏或者是边缘。

    这里先提供一个方法HitTestNCA(),此方法是根据鼠标在窗体上的位置来返回不同的消息的值:

    显示代码
    1 private IntPtr HitTestNCA()
    2 {
    3 constint HTCLIENT =1;//客户区消息
    4  constint HTCAPTION =2;//标题栏消息
    5  
    6 constint HTLEFT =10;//左边缘消息
    7  constint HTRIGHT =11;//右边缘消息
    8  constint HTTOP =12;//上边缘消息
    9  constint HTTOPLEFT =13;//左上角边缘消息
    10  constint HTTOPRIGHT =14;//右上角边缘消息
    11  constint HTBOTTOM =15;//下边缘消息
    12  constint HTBOTTOMLEFT =16;//左下角边缘消息
    13  constint HTBOTTOMRIGHT =17;//右下角边缘消息
    14  
    15 Point p =new Point(Control.MousePosition.X, Control.MousePosition.Y);//鼠标位置
    16
    17 //下面是判断鼠标处于某处,返回相应的值。
    18 //不要轻易打乱下面顺序,优先级别(高-低):边角-边缘-标题栏
    19  
    20 Rectangle topleft =this.RectangleToScreen(new Rectangle(0, 0, 8, 8));
    21 if (topleft.Contains(p))
    22 returnnew IntPtr(HTTOPLEFT);
    23
    24 Rectangle topright =this.RectangleToScreen(new Rectangle(Width -8, 0, 8, 8));
    25 if (topright.Contains(p))
    26 returnnew IntPtr(HTTOPRIGHT);
    27
    28 Rectangle botleft =this.RectangleToScreen(new Rectangle(0, Height -8, 8, 8));
    29 if (botleft.Contains(p))
    30 returnnew IntPtr(HTBOTTOMLEFT);
    31
    32 Rectangle botright =this.RectangleToScreen(new Rectangle(Width -8, Height -8, 8, 8));
    33 if (botright.Contains(p))
    34 returnnew IntPtr(HTBOTTOMRIGHT);
    35
    36 Rectangle top =this.RectangleToScreen(new Rectangle(0, 0, Width, 8));
    37 if (top.Contains(p))
    38 returnnew IntPtr(HTTOP);
    39
    40 Rectangle left =this.RectangleToScreen(new Rectangle(0, 0, 8, Height));
    41 if (left.Contains(p))
    42 returnnew IntPtr(HTLEFT);
    43
    44 Rectangle right =this.RectangleToScreen(new Rectangle(Width -8, 0, 8, Height));
    45 if (right.Contains(p))
    46 returnnew IntPtr(HTRIGHT);
    47
    48 Rectangle bottom =this.RectangleToScreen(new Rectangle(0, Height -8, Width, 8));
    49 if (bottom.Contains(p))
    50 returnnew IntPtr(HTBOTTOM);
    51
    52 Rectangle cap =this.RectangleToScreen(new Rectangle(0, 8, Width, 22));
    53 if (cap.Contains(p))
    54 returnnew IntPtr(HTCAPTION);
    55
    56 returnnew IntPtr(HTCLIENT);
    57 }

      以上代码具体值可根据需要修改,但要注意鼠标处于边角时,可能也处于边缘范围,所以最好不要打乱上面代码中判断的优先级。

      接下便是需要截获NCHITTEST(鼠标移动或单机的消息)消息,把HitTestNCA()方法返回的值回发给windows,具体代码(WndProc()完整代码

    显示代码
    1 protectedoverridevoid WndProc(ref Message m)
    2 {
    3 constint NCHITTEST =0x84;
    4 constint NCCALCSIZE =0x0083;
    5
    6 if (m.Msg == NCCALCSIZE)
    7 {
    8 if (m.WParam != (IntPtr)0)
    9 {
    10 m.Result = (IntPtr)0;
    11 }
    12 }
    13 elseif (m.Msg == NCHITTEST)
    14 {
    15 m.Result = HitTestNCA();
    16 }
    17 else
    18 {
    19 base.WndProc(ref m);
    20 }
    21 }

      这样我们便在客户区模拟出了默认窗体非客户区的功能。

     4.DwmDefWindowProc处理

      虽然上面几个步骤大致实现了功能,但是我们可以发现,标题栏上面的最小化、最大化、关闭按钮还无法点击,这就需要我们使用DWM中的DwmDefWindowProc函数来进行处理。

       如果要使自定义窗体框架中的按钮可以点击,首先需要发送消息给DwmDefWindowProc处理,然后把处理结果回发给windows,这样才能完成对标题栏默认按钮的处理(http://msdn.microsoft.com/en-us/library/bb688195(v=vs.85).aspx)。

      具体代码(WndProc()完整代码):

    显示代码
    1 [DllImport("dwmapi.dll")]
    2 publicstaticexternint DwmDefWindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, out IntPtr result);
    3
    4 protectedoverridevoid WndProc(ref Message m)
    5 {
    6 IntPtr result;
    7 int dwmHandled = DwmDefWindowProc(m.HWnd, m.Msg, m.WParam, m.LParam, out result);
    8 if (dwmHandled ==1)//判断是否被处理
    9   {
    10 m.Result = result;
    11 return;
    12 }
    13
    14 constint NCHITTEST =0x84;
    15 constint NCCALCSIZE =0x0083;
    16
    17 if (m.Msg == NCCALCSIZE)//截获NCCALCSIZE消息
    18   {
    19 if (m.WParam != (IntPtr)0)
    20 {
    21 m.Result = (IntPtr)0;
    22 }
    23 }
    24 elseif (m.Msg == NCHITTEST)//截获NCHITTEST消息
    25   {
    26 m.Result = HitTestNCA();
    27 }
    28 else
    29 {
    30 base.WndProc(ref m);
    31 }
    32 }

      效果示例:

            

    ------------完整代码下载------------ (VS2010)

    转自:

    https://files.cnblogs.com/WangQ/TitlebarControl.rar

  • 相关阅读:
    JS---案例:大量字符串拼接效果实现
    JS高级---三种创建对象的方式
    松软科技Web课堂:JavaScript HTML DOM 动画
    JS-DOM事件
    JS DOM操作(创建、遍历、获取、操作、删除节点)
    ES6函数的扩展
    月薪20k的web前端开发程序员,他们都会的这6招
    JS高级---识别正则表达式是否匹配
    揭秘webpack plugin
    react-React深入-一等公民-props-onChange
  • 原文地址:https://www.cnblogs.com/saptechnique/p/2251353.html
Copyright © 2020-2023  润新知