xcrane 发表于 2014-11-27 22:00:08

用delphi绘制异形窗体

WINDOWS图形界面应用程序都是基于窗口的。在Windows操作系统中,窗口是应用程序与用户之间的界面。微软对窗口的定义是:窗口是屏幕上的一块方形部分,用来显示输出和接受用户的输入。编写基于windows 的GUI程序首先要做的事情之一便是创建一个或多个窗口。我们通常使用CreateWindow和CreateWindowEx这两个API函数创建窗口,此外通过DialogBox,CreateDialog和MessageBox等函数还可以创建特殊用途的窗口(如对话框、消息框等)。
  不管是以上哪个函数,都将无一例外的得到矩形窗口。但是,有时为了实现特殊效果,有时希望使用不规则形状的窗口。而本文就将探讨怎样实现这些异形窗口,如圆形、椭圆形、星形等窗口。

  为了实现不规则形状的窗口,需要用到“区域”(Region)这一概念。在微软Windows操作系统中,区域是指能对其进行填充、绘制、加边框、颜色翻转等操作的长方形、(椭)圆形、多边形,或者这些形状的叠加。

区域的创建

  通过以下API函数,我们可以创建区域,它们是:CreateRectRgn(长方形),CreateRoundRectRgn(圆角长方形),CreateEllipticRgn(圆形和椭圆形),以及CreatePolygonRgn和CreatePolyPolygonRgn。如果调用成功的话,它们都会返回一个指向新建区域的句柄。下面先简单地介绍CreatePolygonRgn和CreateEllipticRgn的用法:
  HRGN CreatePolygonRgn( //创建多边形区域
   CONST POINT *lppt, //指向一个POINT类型的数组
   int cPoints, //数组中元数的个数
   int fnPolyFillMode //多边形填充模式   
   );
  HRGN CreateEllipticRgn( //创建圆形或者椭圆形区域
   int nLeftRect, //(椭)圆外切长方形左上角的X坐标
   int nTopRect, //(椭)圆外切长方形左上角的Y坐标
  int nRightRect, //(椭)圆外切长方形右下角的X坐标
  int nBottomRect //(椭)圆外切长方形右下角的Y坐标
   );
  区域的一个非常重要的性质是:任意两个区域可以进行合成操作,进而生成一个新的区域。


区域的合成

  利用已有的区域可以合成新的区域,这便是函数CombineRgn的作用。该函数的C语言原型声明如下:
  int CombineRgn(
   HRGN hrgnDest, // 指向目的区域
   HRGN hrgnSrc1, // 指向源区域
   HRGN hrgnSrc2, // 指向源区域
   int fnCombineMode // 区域结合模式
   );
  在上述函数中,第二、三个参数为源区域;第一个参数hrgnDest指向的是目的区域,这块区域将用来盛放由hrgnSrc1和hrgnSrc2合成的新区域,所以必须保证hrgnDest所指向的区域在调用函数CombineRgn之前已经存在。
  第四个参数,即fnCombineMode,指明了合成方式,它的取值及含义如表1所示:


合成方式含义


RNG_AND 新区域为两块源区域的相交部分。
RNG_COPY 新区域为第一块源区域的拷贝
RNG_DIFF 新区域为第一块源区域减去与第二块源区域共有的部分
RNG_OR 新区域为两块源区域的并集
RNG_XOR 新区域为两块源区域的非公共部分的并集


区域的使用


  创建或者合成了一定形状的区域之后,并不能看到任何东西。区域要与具体的窗口结合才能起作用。把区域同窗口挂起钩来,要用到名为SetWindowRgn的API函数。它的原型如下:
 int SetWindowRgn(
   HWND hWnd, // 指向窗口区域已被设定好的窗口
   HRGN hRgn, // 指向区域
   BOOL bRedraw // 窗口重画标志
   );
  当成功地调用了此函数后,操作系统将拥有hRgn所指定的那块区域。区域的坐标是相对于窗口的左上角(包括标题区在内)。窗口中只有在区域内的那部分是可见的,对位于区域之外的窗口部分,系统将不予显示。也就是说,窗口表现出来的形状与区域的形状是一样的(当然这还有个条件,那就是窗口不得比区域小)。为了调整窗口的大小,可先用API函数GetRgnBox获取区域的大小(即长与宽),再根据获得的数据(在p2中)来相应地调整窗口的长和宽:
  function GetRgnBox(RGN: HRGN; var p2: TRect): Integer; stdcall;
  有了上面介绍的这些关于区域的知识,就足以实现这些异形窗口了。
  
异形窗口示例程序


  笔者用Delphi编写了一个示例程序,它实现了5种形状的窗口(图1中列出了其中四种)。希望该示例程序能有助于读者加深对区域的理解和运用。
  以下是程序的主单元代码:
  //Create windows in special shapes.
  //Press CTRL+S to exit this program.
  unit Shapes;
  interface
  uses Windows, Messages, SysUtils, Classes, Graphics,Controls, Forms, Dialogs, ExtCtrls, StdCtrls;
  const HOTKEYID=1;
  type
  TShape = class(TForm)
   Timer1: TTimer;
   procedure FormCreate(Sender: TObject);
   procedure FormDestroy(Sender: TObject);
   procedure Timer1Timer(Sender: TObject);
  private
   procedure WMHOTKEY(var Msg:TWMHotKey);message WM_HOTKEY;
  end;
var
   Tick:Integer;
   Shape: TShape;
   Rgn, Rgn1:THandle;
   XX,YY:Integer;
  Triangle:array of TPoint =((x:30;y:0),(x:0;y:60),(x:60;y:60)); //三角形
   Diamond:array of TPoint =((x:30;y:0),(x:60;y:50),(x:30;y:100),(x:0;y:50)); //棱形
  Star:arrayof TPoint=((x:34;y:0), (x:0;y:73),(x:75;y:28),(x:3;y:28),(x:64;y:73));   //五角星
  implementation
  {$R *.DFM}
  procedure TShape.FormCreate(Sender: TObject);
  begin
   Timer1.Interval:=99;
//将Application窗口的扩展风格重新设为WS_EX_TOOLWINDOW,这样程序运行时就不会在任务栏上出现一个小图标。
   BorderStyle:=bsNone;
   Timer1Timer(self);
   SetWindowLong(Application.Handle,GWL_ExStyle,WS_EX_ToolWindow);
  //为窗口注册Ctr+S 热键
   RegisterHotKey(Handle,HOTKEYID,MOD_CONTROL,ord('S'));
  end;
  procedure TShape.FormDestroy(Sender: TObject);
  begin
  DeleteObject(Rgn); //删除句柄
  UnregisterHotKey(Handle,HOTKEYID);
  end;
  procedure TShape.WMHOTKEY(var Msg:TWMHotKey);
  begin
  //检测到Ctrl+S 热键时退出程序
 if (Msg.HotKey=HOTKEYID) then
Close
  else
inherited;
  end;
  procedure TShape.Timer1Timer(Sender: TObject);
  var
Rect:TRect;
   cl:Integer;
  begin
  Randomize;
  cl:=Random($2FFFFFFF); //颜色代码
  Tick:=Tick mod 300;
  //每隔一定时间变换一次形状
  case Tick of
  0://五星形
   begin
   Rgn:=CreatePolygonRgn(Star,5,0);
   SetWindowRgn(Handle,Rgn,TRUE);
   GetRgnBox(Rgn,Rect);
   Width:=Rect.Right-Rect.Left;
   Height:=Rect.Bottom-Rect.Top;
   Color:=cl;
   end;
  60://圆形
   begin
   Rgn:=CreateEllipticRgn(0,0,60,60);
   SetWindowRgn(Handle,Rgn,TRUE);
   GetRgnBox(Rgn,Rect);
   Width:=Rect.Right-Rect.Left;
   Height:=Rect.Bottom-Rect.Top;
   Color:=cl;
   end;
  120://圆环形
   begin
   Rgn:=CreateEllipticRgn(0,0,60,60);
   Rgn1:=CreateEllipticRgn(15,15,45,45);
   CombineRgn(Rgn,Rgn,Rgn1,RGN_XOR);
   DeleteObject(Rgn1);
   SetWindowRgn(Handle,Rgn,TRUE);
   GetRgnBox(Rgn,Rect);
   Width:=Rect.Right-Rect.Left;
   Height:=Rect.Bottom-Rect.Top;
   Color:=cl;
   end;
  180://棱形
  begin
   Rgn:=CreatePolygonRgn(Diamond,4,0);
   SetWindowRgn(Handle,Rgn,TRUE);
   GetRgnBox(Rgn,Rect);
   Width:=Rect.Right-Rect.Left;
   Height:=Rect.Bottom-Rect.Top;
   Color:=cl;
  end;
  240://三角形
  begin
   Rgn:=CreatePolygonRgn(Triangle,3,0);
   SetWindowRgn(Handle,Rgn,TRUE);
   GetRgnBox(Rgn,Rect);
   Width:=Rect.Right-Rect.Left;
   Height:=Rect.Bottom-Rect.Top;
   Color:=cl;
  end;
  end;//case
  inc(Tick);
  if Left>Screen.Width-Width then
XX:=-Random(20)
  else if Left<0 then
XX:=Random(20);
  if Top>Screen.Height-Height then
YY:=-Random(20)
  else if Top<0 then
YY:=Random(20);
  if (XX>-3)and(XX<3) then
XX:=15;
  if (YY>-3)and(YY<3) then
YY:=10;
  //移动窗口
  SetWindowPos(Handle,HWND_TOPMOST,Left+XX,Top+YY,Width,Height,SWP_NOACTIVATE);
 end;
end.
 




MLK 发表于 2014-12-3 19:22:16

真有趣
页: [1]
查看完整版本: 用delphi绘制异形窗体