.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 private static extern IntPtr GetWindowDC(IntPtr hWnd);
6 [DllImport( " user32.dll " )]
7 private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
8
9 private const int WM_NCPAINT = 0x0085 ;
10 private const int WM_NCACTIVATE = 0x0086 ;
11 private const int WM_NCLBUTTONDOWN = 0x00A1 ;
12 protected override void 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 protected override void WndProc( ref Message m)
2 {
3 const int 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 public extern static int DwmIsCompositionEnabled( ref int en);
3
4 [DllImport( " dwmapi.dll " )]
5 public extern static int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margin);
6
7 public struct MARGINS
8 {
9 public int m_left;
10 public int m_right;
11 public int m_top;
12 public int 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   public static void 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 const int HTCLIENT = 1 ; // 客户区消息
4   const int HTCAPTION = 2 ; // 标题栏消息
5  
6 const int HTLEFT = 10 ; // 左边缘消息
7   const int HTRIGHT = 11 ; // 右边缘消息
8   const int HTTOP = 12 ; // 上边缘消息
9   const int HTTOPLEFT = 13 ; // 左上角边缘消息
10   const int HTTOPRIGHT = 14 ; // 右上角边缘消息
11   const int HTBOTTOM = 15 ; // 下边缘消息
12   const int HTBOTTOMLEFT = 16 ; // 左下角边缘消息
13   const int 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 return new IntPtr(HTTOPLEFT);
23
24 Rectangle topright = this .RectangleToScreen( new Rectangle(Width - 8 , 0 , 8 , 8 ));
25 if (topright.Contains(p))
26 return new IntPtr(HTTOPRIGHT);
27
28 Rectangle botleft = this .RectangleToScreen( new Rectangle( 0 , Height - 8 , 8 , 8 ));
29 if (botleft.Contains(p))
30 return new IntPtr(HTBOTTOMLEFT);
31
32 Rectangle botright = this .RectangleToScreen( new Rectangle(Width - 8 , Height - 8 , 8 , 8 ));
33 if (botright.Contains(p))
34 return new IntPtr(HTBOTTOMRIGHT);
35
36 Rectangle top = this .RectangleToScreen( new Rectangle( 0 , 0 , Width, 8 ));
37 if (top.Contains(p))
38 return new IntPtr(HTTOP);
39
40 Rectangle left = this .RectangleToScreen( new Rectangle( 0 , 0 , 8 , Height));
41 if (left.Contains(p))
42 return new IntPtr(HTLEFT);
43
44 Rectangle right = this .RectangleToScreen( new Rectangle(Width - 8 , 0 , 8 , Height));
45 if (right.Contains(p))
46 return new IntPtr(HTRIGHT);
47
48 Rectangle bottom = this .RectangleToScreen( new Rectangle( 0 , Height - 8 , Width, 8 ));
49 if (bottom.Contains(p))
50 return new IntPtr(HTBOTTOM);
51
52 Rectangle cap = this .RectangleToScreen( new Rectangle( 0 , 8 , Width, 22 ));
53 if (cap.Contains(p))
54 return new IntPtr(HTCAPTION);
55
56 return new IntPtr(HTCLIENT);
57 }

 

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

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

 

显示代码
1 protected override void WndProc( ref Message m)
2 {
3 const int NCHITTEST = 0x84 ;
4 const int NCCALCSIZE = 0x0083 ;
5
6 if (m.Msg == NCCALCSIZE)
7 {
8 if (m.WParam != (IntPtr) 0 )
9 {
10 m.Result = (IntPtr) 0 ;
11 }
12 }
13 else if (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 public static extern int DwmDefWindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, out IntPtr result);
3
4 protected override void 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 const int NCHITTEST = 0x84 ;
15 const int 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 else if (m.Msg == NCHITTEST) // 截获NCHITTEST消息
25   {
26 m.Result = HitTestNCA();
27 }
28 else
29 {
30 base .WndProc( ref m);
31 }
32 }

  效果示例:

        

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

 

转自:

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值