透明皮肤控件设计系列(二):皮肤窗口初级篇

33 篇文章 0 订阅
5 篇文章 0 订阅
Windows将窗口分为客户区和非客户区,例如对于标准的Windows窗口,标题栏和边框都属于非客户区,又称为NC区。对于客户区的绘制,应用程序会收到WM_PAINT消息,而非客户区,对应的消息是WM_NCPAINT。要实现皮肤窗口,需要三个步骤:

第一步:定义非客户区的大小。

要自定义非客户区的大小,程序就要响应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。

待续。。。。。。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值