当前位置: 首页 > news >正文

.net实现客户区延伸至至非客户区

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

  传统方式(网络收集):

ContractedBlock.gif ExpandedBlockStart.gif 显示代码

   
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,客户区占满整个窗体(包括非客户区),按照微软的说法就是删除了标准的框架,变成了自定义框架。

具体代码:

ContractedBlock.gif ExpandedBlockStart.gif 显示代码

   
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之前和之后,效果如下:

2011031021530362.png     2011031022161423.png

2.程序边框显示

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

ContractedBlock.gif ExpandedBlockStart.gif 显示代码

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

  2011031112380744.png

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

3.模拟非客户区消息

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

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

ContractedBlock.gif ExpandedBlockStart.gif 显示代码

   
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()完整代码

ContractedBlock.gif ExpandedBlockStart.gif 显示代码

   
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()完整代码):

ContractedBlock.gif ExpandedBlockStart.gif 显示代码

   
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 }

  效果示例:

  2011031114464128.png      2011031115224139.png

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

知识共享许可协议
本作品由WangQiang创作,采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。

转载于:https://www.cnblogs.com/WangQ/archive/2011/03/11/client-expand-noclient.html

相关文章:

  • Windows年底再现图片漏洞攻击!
  • Flash视频播放器flowplayer的使用
  • 彻底卸载Virtual Camera虚拟摄像头
  • 解决 PHPExcel 长数字串显示为科学计数
  • 【HIHOCODER 1403】后缀数组一·重复旋律(后缀数组)
  • C#中如何将DataTable中的数据写入Excel
  • 打印机的一些高级设置
  • Qt4--FormLayout
  • 通用服务器桩-Receiver使用说明文档
  • linux ftp 实例
  • 啥活都得干好
  • python框架对比
  • 最快的搭建PXE
  • DBImport v3.0 中文版发布-支持各大数据库数据互导(IT人员必备工具)
  • C语言的第一堂课
  • Angularjs之国际化
  • ES6之路之模块详解
  • Git的一些常用操作
  • GraphQL学习过程应该是这样的
  • HashMap剖析之内部结构
  • Javascript基础之Array数组API
  • javascript数组去重/查找/插入/删除
  • js
  • JS学习笔记——闭包
  • Linux快速复制或删除大量小文件
  • nfs客户端进程变D,延伸linux的lock
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • Python语法速览与机器学习开发环境搭建
  • React-生命周期杂记
  • yii2中session跨域名的问题
  • 闭包--闭包之tab栏切换(四)
  • 从零开始的无人驾驶 1
  • 第十八天-企业应用架构模式-基本模式
  • 基于Android乐音识别(2)
  • 京东美团研发面经
  • 听说你叫Java(二)–Servlet请求
  • 线上 python http server profile 实践
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • Salesforce和SAP Netweaver里数据库表的元数据设计
  • ​linux启动进程的方式
  • # 计算机视觉入门
  • $.ajax()参数及用法
  • (1)Nginx简介和安装教程
  • (Git) gitignore基础使用
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (附源码)springboot教学评价 毕业设计 641310
  • (论文阅读32/100)Flowing convnets for human pose estimation in videos
  • (三分钟)速览传统边缘检测算子
  • (生成器)yield与(迭代器)generator
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • (正则)提取页面里的img标签
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m