透明皮肤控件设计系列(二):皮肤窗口初级篇
2013-08-12 13:23
369 查看
Windows将窗口分为客户区和非客户区,例如对于标准的Windows窗口,标题栏和边框都属于非客户区,又称为NC区。对于客户区的绘制,应用程序会收到WM_PAINT消息,而非客户区,对应的消息是WM_NCPAINT。要实现皮肤窗口,需要三个步骤:
第一步:定义非客户区的大小。
要自定义非客户区的大小,程序就要响应WM_NCCALCSIZE消息。假设我们的标题高度为60(像素,下同),边框为10,那么对应的代码应该类似这样:
效果如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202008/30/fb813743b33f712eb07f0f9839df5b4f)
可以看到,原来的标题栏还在,下面有块白色区域,其实这两者加起来就是新定义的xTitleHeight。
第二步:绘制非客户区。
我们定义一个绘制非客户区的函数:
然后在系统触发WM_NCPAINT消息时调用它:
现在程序运行后效果如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202008/30/77d3e4e8dcb80e3ca704c63564c4125d)
但是如果你切换到其它窗口,再切换回来,发现窗口变成了这样:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202008/30/65722e22ed69d58bbcc0dd73d5ec877c)
原来我们还要拦截WM_NCACTIVATE消息:
另外,当我们在Taskbar里点我们的窗体时,会激发active消息,在这个消息中,默认是会画非客户区的,所以也处理:
如果我们要画自定义的标题按钮,修改DrawTitle函数即可,这个后面我们再来处理。
第三步:绘制客户区。
绘制客户区,可以重载处理WM_PAINT消息画在客户区画布,也可以重载WM_ERASEBKGND擦除背景时画在背景,也可以两者都重载。
我们定义一个函数DrawClient:
重载处理WM_PAINT消息:
重载处理WM_ERASEBKGND消息:
注意:假如处理了WM_PAINT消息,那么对于窗口上放置的从TGraphicControl继承下来的无句柄控件将无法显示,因为窗口原来的WM_PAINT过程会轮询控件,发现是没有句柄的将会通知其重绘。
程序运行效果如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202008/30/7cb5b29356297c3d3219be8e7d35c681)
经过上面的学习,我们已经可以制作皮肤窗口了:只要在DrawTitle和DrawClient函数里面将绘制颜色的代码换成绘制图片即可。但是别着急,一个成熟的控件,很多细节是需要处理的。要是说界面编程有技巧,那么就是这些细节的地方。
一:标题的细节处理:
细心的朋友如果把鼠标移动到原来系统有按钮的地方,按下去,然后移动到其它区域再松开,会发现系统本来的按钮又出现了:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202008/30/d563e4b97df7d5d85381d76a40b4eb80)
这是因为我们现在窗口默认的BorderStyle是bsSizeable,有两种方法解决:
方法1:在窗口创建的时候去掉按钮:
但是这样一来,双击标题区,窗口就不会自动最大化和恢复了,幸运的是边框还是可以改变大小。要恢复标题栏功能,方法是处理WM_NCLBUTTONDBLCLK消息:
方法2:直接将BorderStyle设置为bsNone:
设置为bsNone后,标题区和边框功能都消失了,解决方法是处理WM_NCHITTEST消息,同时处理WM_NCLBUTTONDBLCLK:
本文使用的是方法2。
待续。。。。。。
第一步:定义非客户区的大小。
要自定义非客户区的大小,程序就要响应WM_NCCALCSIZE消息。假设我们的标题高度为60(像素,下同),边框为10,那么对应的代码应该类似这样:
const xTitleHeight: Integer = 50; // 标题栏的高度 xFramWidth: Integer = 10; // 左、右、下边框的厚度 procedure TForm1.WMNCCALCSIZE(var Message: TWMNCCALCSIZE); begin with TWMNCCALCSIZE(Message).CalcSize_Params^.rgrc[0] do begin Inc(Left, xFramWidth); Inc(Top, xTitleHeight); Dec(Right, xFramWidth); Dec(Bottom, xFramWidth); end; Message.Result := 0; end;
效果如下:
可以看到,原来的标题栏还在,下面有块白色区域,其实这两者加起来就是新定义的xTitleHeight。
第二步:绘制非客户区。
我们定义一个绘制非客户区的函数:
procedure TForm1.DrawTitle; var DC: HDC; C: TCanvas; begin DC := GetWindowDC(Handle); C := TControlCanvas.Create; C.Handle := DC; try C.Brush.Color := clRed; C.FillRect(Rect(0, 0, Width, xTitleHeight)); // 标题区域 C.Brush.Color := clBlue; C.FillRect(Rect(0, xTitleHeight, xFramWidth, Height - xFramWidth)); // 左边框 C.Brush.Color := clGreen; C.FillRect(Rect(Width – xFramWidth, xTitleHeight, Width, Height – xFramWidth)); // 右边框 C.Brush.Color := clYellow; C.FillRect(Rect(0, Height – xFramWidth, Width, Height)); // 下边框 finally C.Handle := 0; C.Free; ReleaseDC(Handle, DC); end; end;
然后在系统触发WM_NCPAINT消息时调用它:
procedure TForm1.WMNCPaint(var Message: TWMNCPaint); begin DrawTitle; end;
现在程序运行后效果如下:
但是如果你切换到其它窗口,再切换回来,发现窗口变成了这样:
原来我们还要拦截WM_NCACTIVATE消息:
procedure TForm1.WMNCPaint(var Message: TWMNCPaint); begin DrawTitle; Message.Result := 1; end;
另外,当我们在Taskbar里点我们的窗体时,会激发active消息,在这个消息中,默认是会画非客户区的,所以也处理:
procedure TForm1.WMActivate(var Message: TWMActivate); begin inherited; DrawTitle; end;
如果我们要画自定义的标题按钮,修改DrawTitle函数即可,这个后面我们再来处理。
第三步:绘制客户区。
绘制客户区,可以重载处理WM_PAINT消息画在客户区画布,也可以重载WM_ERASEBKGND擦除背景时画在背景,也可以两者都重载。
我们定义一个函数DrawClient:
procedure TForm1.DrawClient(DC: HDC); var C: TCanvas; begin C := TControlCanvas.Create; C.Handle := DC; try C.Brush.Color := clDkGray; C.FillRect(ClientRect); finally C.Handle := 0; C.Free; end; end;
重载处理WM_PAINT消息:
procedure TForm1.WMPaint(var Message: TWMPaint); var DC: HDC; PS: TPaintStruct; begin DC := Message.DC; if DC = 0 then DC := BeginPaint(Handle, PS); DrawClient(DC); if DC = 0 then EndPaint(Handle, PS); Message.Result := 1; end;
重载处理WM_ERASEBKGND消息:
procedure TForm1.WMEraseBkgnd(var Message: TWMEraseBkgnd); var DC: HDC; begin DC := Message.DC; if DC <> 0 then DrawClient(DC); Message.Result := 1; end;
注意:假如处理了WM_PAINT消息,那么对于窗口上放置的从TGraphicControl继承下来的无句柄控件将无法显示,因为窗口原来的WM_PAINT过程会轮询控件,发现是没有句柄的将会通知其重绘。
程序运行效果如下:
经过上面的学习,我们已经可以制作皮肤窗口了:只要在DrawTitle和DrawClient函数里面将绘制颜色的代码换成绘制图片即可。但是别着急,一个成熟的控件,很多细节是需要处理的。要是说界面编程有技巧,那么就是这些细节的地方。
一:标题的细节处理:
细心的朋友如果把鼠标移动到原来系统有按钮的地方,按下去,然后移动到其它区域再松开,会发现系统本来的按钮又出现了:
这是因为我们现在窗口默认的BorderStyle是bsSizeable,有两种方法解决:
方法1:在窗口创建的时候去掉按钮:
procedure TForm1.FormCreate(Sender: TObject); begin BorderIcons:=[]; end;
但是这样一来,双击标题区,窗口就不会自动最大化和恢复了,幸运的是边框还是可以改变大小。要恢复标题栏功能,方法是处理WM_NCLBUTTONDBLCLK消息:
procedure TForm1.WMNCLBUTTONDBLCLK(var Message: TWMNCLButtonDblClk); var P: TPoint; Style: DWORD; begin inherited; P := Point(Message.XCursor – Left, Message.YCursor – Top); if PtInRect(Rect(0, 0, Width, xTitleHeight), P) then begin Style := GetWindowLong(Handle, GWL_STYLE); if Style and WS_MAXIMIZE > 0 then SendMessage(Handle, WM_SYSCOMMAND, SC_RESTORE, 0) else SendMessage(Handle, WM_SYSCOMMAND, SC_MAXIMIZE, 0); end; end;
方法2:直接将BorderStyle设置为bsNone:
procedure TForm1.FormCreate(Sender: TObject); begin BorderStyle:=bsNone; end;
设置为bsNone后,标题区和边框功能都消失了,解决方法是处理WM_NCHITTEST消息,同时处理WM_NCLBUTTONDBLCLK:
procedure TForm1.WMNCLBUTTONDBLCLK(var Message: TWMNCLButtonDblClk); var P: TPoint; Style: DWORD; begin inherited; P := Point(Message.XCursor – Left, Message.YCursor – Top); if PtInRect(Rect(0, 0, Width, xTitleHeight), P) then begin Style := GetWindowLong(Handle, GWL_STYLE); if Style and WS_MAXIMIZE > 0 then SendMessage(Handle, WM_SYSCOMMAND, SC_RESTORE, 0) else SendMessage(Handle, WM_SYSCOMMAND, SC_MAXIMIZE, 0); end; end;
procedure TForm1.WMNCHitTest(var Message: TWMNCHITTEST);
const
w = 5;
var
P: TPoint;
begin
(*
P.X := Message.XPos;
P.Y := Message.YPos + GetTitleHeight;
Windows.ScreenToClient(Self.Handle, P);
*)
P := Point(Message.XPos – Left, Message.YPos – Top);
if PtInRect(Rect(0, 0, w, w), P) then
Message.Result := HTTOPLEFT // 左上角
else if PtInRect(Rect(w, 0, Width – w, w), P) then
Message.Result := HTTOP // 上边
else if PtInRect(Rect(Width – w, 0, Width, w), P) then
Message.Result := HTTOPRIGHT // 右上角
else if PtInRect(Rect(Width – w, w, Width, Height – w), P) then
Message.Result := HTRIGHT // 右边
else if PtInRect(Rect(Width – w, Height – w, Width, Height), P) then
Message.Result := HTBOTTOMRIGHT // 右下角
else if PtInRect(Rect(w, Height – w, Width – w, Height), P) then
Message.Result := HTBOTTOM // 下边
else if PtInRect(Rect(0, Height – w, w, Height), P) then
Message.Result := HTBOTTOMLEFT // 左下角
else if PtInRect(Rect(0, w, w, Height – w), P) then
Message.Result := HTLEFT // 左边
else if PtInRect(Rect(0, 0, Width, xTitleHeight), P) then
Message.Result := HTCAPTION // 标题栏
else
inherited;
end;
本文使用的是方法2。
待续。。。。。。
相关文章推荐
- 透明皮肤控件设计系列(三):皮肤窗口进阶篇
- 透明皮肤控件设计系列(四):皮肤窗口完结篇
- 透明皮肤控件设计系列(五):透明控件
- 透明皮肤控件设计系列(一):前言
- QT 实现子控件的透明,可以实现主窗口设置背景皮肤
- (原创)自已实现服务器控件系列 之 设计时可用鼠标拖动大小的Label控件
- windowsSDK利用子窗口控件设置窗口透明
- C#基础系列:开发自己的窗体设计器(在容器上拖动鼠标增加控件)
- 为什么控件的有些属性在代码中可用,但是在设计界面的属性窗口中就没有
- BBS 设计思路系列 ---- 抛弃多皮肤机制,只提供一套皮肤机制。
- QT入门:怎么写无边框(标题栏)的窗口、透明窗口、圆角控件
- [Visual C++系列]窗口控件 - 4.4 Scroll Bar
- 在应用了皮肤的程序中制作透明的文本编辑控件(如:TcxMemo)
- 浏览器扩展系列————透明浏览器窗口的实现
- C#基础系列:开发自己的窗体设计器(实现控件的拖动)
- Windows编程中的子窗口控件设计
- [Visual C++系列]窗口控件 - 4.5 List Box/Check List Box
- 为触屏手机而设计系列1——拇指操作的“热区/死角”与“控件尺寸”
- [Visual C++系列]窗口控件 - 4.5 List Box/Check List Box
- 快速入门系列零:玛德界面库的来由、窗口、皮肤、项目管理等基本概念