delphi 可以自定义边框的文本框TSkinNormalEdit思路(QQ2011风格)

需求:

QQ我的资料中基本资料窗体中的文本框:

正常状态下,文本框只有一条看起来只有一个像素的边框,边框的颜色从上到下由深到浅的渐变,当鼠标定位到该文本框时,其边框会变粗,而且边框的颜色加亮显示

如下图所示:

实现思路:
一、准备两个边框素材图片,

一个是正常状态下的边框素材,

比如:

另一个是鼠标进入到文本框内的边框素材

比如:

 

二、需要的参数

首先是边框素材的绘制边距,分为左边距,右边距,上边距,下边距

边框素材根据边距的设置,使用九宫格缩放绘制到界面上

其次是边框的边距,也为左边距,右边距,上边距,下边框

代表的是文本框客户区(即输入区)的大小

默认的,边框素材的绘制边距和边框的边距是一样的

 

三、消息处理

边框属于文本框的非客户区域,在文本框的WM_NCPAINT中绘制

文本框的边框风格有两种:bsNone和bsSingle

bsNone即为无边框样式,不需要绘制边框,而且文本框的大小就是客户区的大小

bsSingle为单边框样式,默认边框为两个像素的宽

如果需要自定义文本框的边框宽度,那么需要处理WM_NCCALCSIZE消息

通过WM_NCCALCSIZE消息,使边框扩展为自己所设置的宽度

例(边框扩展一个像表):

  1. procedure TSkinNormalEdit.WMNCCalcSize(varMessage: TWMNCCalcSize);  
  2. var  
  3.  NCCalcSizeParams: PNCCalcSizeParams;  
  4. begin  
  5.  Inherited;  
  6.   if(BorderStyle=bsNone) then  
  7.  begin  
  8.   end  
  9.  else  
  10.  begin  
  11.    NCCalcSizeParams:=Message.CalcSize_Params;  
  12.    Inc(NCCalcSizeParams.rgrc0.Top,1);  
  13.    Inc(NCCalcSizeParams.rgrc0.Left,1);  
  14.    Dec(NCCalcSizeParams.rgrc0.Right,1);  
  15.    Dec(NCCalcSizeParams.rgrc0.Bottom,1);  
  16.  end;  
  17. end;  


四、边框绘制时机

边框有两种状态,鼠标进入到文本框中和鼠标离开文本框

所以,要判断这两种状态

像一般的从TCustomControl或TGraphicControl继承过来的控件,

我们可以通过Delphi内部的管理消息CM_MOUSEENTER和CM_MOUSELEAVE消息处理

但是文本框包含非客户区(边框)和客户区

CM_MOUSEENTER和CM_MOUSELEAVE消息只是鼠标进入或是离开客户区才会响应

所以接下来要处理如何判断鼠标在非客户区中

非客户区的鼠标移动消息主要有三个

WM_NCMOUSEMOVE:非客户区鼠标移动

WM_NCHITTEST:非客户区鼠标移动在控件的哪个部位(标题栏?边框?边角?客户区等等)

WM_NCMOUSELEAVE:鼠标离开非客户区(但是文本框不触发这个消息)

 

综上,没有一个消息可以简单的标识鼠标是否在控件中

CM_MOUSEENTER可以判断鼠标进入控件的客户区

WM_NCHITTEST可以判断鼠标在控件的非客户区

CM_MOUSELEAVE不能判断鼠标离开控件的客户区

 

所以,用一个定时器加一个判断鼠标在客户区的过程组合

WM_NCHITTEST消息中,判断当鼠标进入第一次非客户区时,响应鼠标进入消息,重绘边框,设置定时器,判断鼠标是否会在100毫秒内离开文本框

CM_MOUSEENTER消息中,鼠标已经进入客户区了,重绘边框

CM_MOUSELEAVE 消息中,只是表明了鼠标离开客户区,所以要启动定时器,每100毫秒判断鼠标是否在文本框中,如果检测到鼠标离开文本框,那么需要响应鼠标离开,重绘边框

 

五、边框绘制

边框为非客户区,在文本框的WM_NCPAINT消息中绘制

比如:

 

    procedure TSkinNormalEdit.WMNCPaint(varMessage: TWMNCPaint);  
var
tmpWindowDC:HDC;
tmpBorderImage:IGPBitmap;
tmpWindowCanvas:TCanvas;
tmpBitmapGraphics:IGPGraphics;
begin
ifSelf.BorderStyle=bsNone then
begin
Inherited;
end
else
begin
tmpWindowDC:=GetWindowDC(Handle);
Try
if tmpWindowDC<>0 then
begin
tmpWindowCanvas:=TCanvas.Create;
Try
tmpWindowCanvas.Handle:=tmpWindowDC;
FParentBackGroundBitmap.SetSize(Width,Height);
if FIsBroderTransparent then
begin
//绘制文本框背景
DrawParentImageDefault(Self,FParentBackGroundBitmap.Canvas.Handle);
end;
//边框为png素材,使用GDI+绘制,需要获取绘制接口,需要引用gdiplus和gdiplushelpers单元
tmpBitmapGraphics:=FParentBackGroundBitmap.Canvas.ToGPGraphics;
//根据鼠标状态判断边框图片
if Self.MouseInClient or CursorInControl then
begin
tmpBorderImage:=Self.FHoverBorderBitmap;
end
else
begin
tmpBorderImage:=Self.FNormalBorderBitmap;
end;
//再绘制背景
if tmpBorderImage<>nil then
begin
//九宫格绘制边框素材
TSkinHelper.StretchDrawImageBorderInRectByMargins(tmpBitmapGraphics,
tmpBorderImage,TGPRect.Create(0,0,Width,Height),
Self.FBorderDrawMargins.Left,
Self.FBorderDrawMargins.Top,
Self.FBorderDrawMargins.Right,
Self.FBorderDrawMargins.Bottom);
end;
//绘制最外的边框
//左边框
BitBlt( tmpWindowDC,0,0,Self.FBorderMargins.Left,Height,
FParentBackGroundBitmap.Canvas.Handle,0,0,SRCCOPY );
//右边框
BitBlt( tmpWindowDC,Width-Self.FBorderMargins.Right,0,Self.FBorderMargins.Right,Height,
FParentBackGroundBitmap.Canvas.Handle,Width-Self.FBorderMargins.Right,0,SRCCOPY );
//上边框
BitBlt( tmpWindowDC,0,0,Width,Self.FBorderMargins.Top,
FParentBackGroundBitmap.Canvas.Handle,0,0,SRCCOPY );
//下边框
BitBlt( tmpWindowDC,0,Height-Self.FBorderMargins.Bottom,Width,Self.FBorderMargins.Bottom,
FParentBackGroundBitmap.Canvas.Handle,0,Height-Self.FBorderMargins.Bottom,SRCCOPY );

Finally
FreeAndNil(tmpWindowCanvas);
End;
end;
Finally
ReleaseDC(Handle,tmpWindowDC);
End;
end;
end;



posted @ 2012-01-12 10:39  许明吉博客  阅读(2733)  评论(1编辑  收藏  举报