如何做出不规则窗体(100分)

  • 主题发起人 主题发起人 dmsm
  • 开始时间 开始时间
D

dmsm

Unregistered / Unconfirmed
GUEST, unregistred user!
如何做出不规则窗体
请问如何实现像有些播放软件如金山影霸2003那样实现形状截然不同的界面切换,他的skins目录下有好多的
图片,其原理怎样?可否有原代码给例子?或有什么可以改变窗体形状不规则的控件?
谢谢!!! justyear@hotmail.com
 
//以下是椭圆窗体
var
hr:Thandle;
Counta:dword;

begin
// 设置区域句柄
//hr:=CreateEllipticRgn(0,0,Width,Height);
// 设置窗口边界
//setwindowrgn(handle,hr,true);
 
用SUI
或是别的第三方控件,就比较容易实现
 
http://www.eleemedia.com/islet8/codes/download/rgnform_sc.rar
 
我写的一篇没有被发表的文章,也许对你有用。其中的makeregion函数可以根据一幅位图的轮廓形成一个不规则窗口
采用优化算法,速度很快。
===============================================================================
用delphi做一个会在桌面上跳舞的小猪
————兼谈不规则窗口的制作
dodo505
也许大家对office助手都有印象:一个小孙悟空或一个小狗不时地在桌面上耍来耍去,玩弄出各种姿态和花样,用鼠标单击它时还会弹出菜单选项与用户交互。那么它是怎么作出来的呢?本文便向大家介绍如何制作一个象office助手那样的会在桌面上跳舞的小猪。
编程思路:要做一个在桌面上跳舞的小猪,我们可以用位图拷贝函数BitBlt通过各种不同的掩膜组合向桌面上连续绘制不同的透明图片(图片如下图1)这样就会形成小猪在桌面上跳舞的动画。
图1(图片见附件)
但这样做有一个极大的缺点,那就是这样作成的小猪只顾自己跳舞无法与用户交互,也就是说无论我们用鼠标单击或双击小猪,它都没有任何反应,更不会弹出什么菜单选项了。那又该怎么办呢?最直接的解决办法就是使用不规则窗口,即先按照第一张小猪跳舞图片中小猪的轮廓形成一个不规则窗口(如图2),
图2
这时再把该图片放到该不规则窗口中时,则此窗口只显示出小猪来,其余部分自然是透明的(如图3)。
图3
依此类推再处理第二张,形成又一个不规则窗口,然后显示出来,再处理第三张......。如此不停地显示便形成了小猪在桌面上跳舞的动画。由于该动画本身就是一个窗口,所以可以响应各种窗口消息事件如双击、单击等等。
那么现在的关键问题就是如何根据一幅位图的轮廓形成一个不规则窗口,这就要用到几个windows的api函数:CreateRectRgn,CreateEllipticRgn,CombineRgn,SetWindowRgn。其中CreateRectRgn函数用来创建一个矩形区域,CreateEllipticRgn函数用来创建一个圆或椭圆形区域。SetWindowRgn函数用来设置窗口的形状,SetWindowRgn函数的定义如下:
int SetWindowRgn( HWND hWnd, HRGN hRgn, BOOL bRedrawflag);
窗口的形状由参数 hRgn 所标志的区域 (region) 决定。通过创建不同的区域就可以创建不同形状窗口。例如下面的代码,可以产生一个圆形的窗口。(如图4)
图4
var
R : HRgn;
begin
R := CreateEllipticRgn(0,0,300,300);
SetWindowRgn( handle,R , TRUE ) ;
end;

CombineRgn函数可以混合两个区域成为一个区域。其函数原形为
int CombineRgn(

HRGN hrgnDest, // 目标区域的句柄
HRGN hrgnSrc1, // 源区域1的句柄
HRGN hrgnSrc2, // 源区域2的句柄
int fnCombineMode // 两个区域混合的方式
);

比如下面代码混合三个圆形区域形成一个米老鼠形的区域。(如图5)
图5
var
R1,R2,R3 : HRgn;
begin
//分别创建三个圆形区域R1,R2,R3
R1 := CreateEllipticRgn(0,0,60,60);
R2 := CreateEllipticRgn(150,0,210,60);
R3 := CreateEllipticRgn(30,30,180,180);
//混合这三个圆形区域则形成一个米老鼠形区域
combinergn(R1,R1,R2,rgn_or);
combinergn(R1,R1,R3,rgn_or);
//将这个米老鼠形区域赋给本窗口,则形成一个米老鼠形窗口
SetWindowRgn( handle,R1 , TRUE ) ;
end;
由于窗口是米老鼠形状的所以在米老鼠形窗口里放置一个图片框,则图片框里的图片只会显示成米老鼠的形状。(如图6)
图6
有了上面的介绍我们就可以用CreateRectRgn函数和CombineRgn函数通过一种算法来根据一幅位图的轮廓形成一个不规则窗口了。具体方法如下:在窗口中放入一Image图片框,在图片框中加载一幅图片,我们采用一个两重循环,逐行扫描图片中不透明的区域,用CreateRectRgn函数得到一个个连续的不透明的小矩形区域,然后将这些小矩形区域用CombineRgn函数组成一个整个的区域便得到了这个图片轮廓的区域,即按照该图片的轮廓形成了一个不规则区域。再用SetWindowRgn函数将该区域赋给窗口便按照该图片的轮廓形成了一个不规则窗口。
知道了如何根据一幅图片的轮廓形成一个不规则窗口,那么利用不规则窗口连续显示不同的图片以形成动画,就可以制作出我们的在桌面上跳舞的小猪了。(制作出的画面如图7)。
图7
用这种方法制作出来的小猪可以响应我们的鼠标事件,比如本程序中我们用鼠标单击小猪则可以弹出一个弹出菜单,选其中的“退出”选项则可关闭该程序。由于本程序中采用的算法是一行一行地扫描而不是一个点一个点地扫描所以速度很快。
程序源代码如下:
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs,StdCtrls, ExtCtrls, Menus;

type
TForm1 = class(TForm)
Image1: TImage;
Timer1: TTimer;
PopupMenu1: TPopupMenu;
N1: TMenuItem;
N2: TMenuItem;
N3: TMenuItem;
procedure creat(Sender: TObject);
procedure clicked(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure N1Click(Sender: TObject);
private
{ Private declarations }
a:integer;
mousepos:Tpoint;//存放鼠标指针位置
function makeregion(imagebox:Timage):hrgn;
//上面函数的作用是按照imagebox中的位图形状形成一个不规则区域
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}
function Tform1.makeregion(imagebox:Timage):hrgn;
//该函数的作用是按照imagebox中的位图形状形成一个不规则区域
var
lineregion,fullregion:hrgn;//定义区域
x,y,startlinex:integer;//存放位置坐标
transparentcolor:longword;//存放所指定的透明色
infirstregion,inlinea:boolean;
picwidth,picheight:integer; //图象的宽和高
hdc:hwnd;//图象框的句柄

begin
hdc:=imagebox.canvas.handle;//得到图象框的句柄
picwidth:=imagebox.width;//得到图象框的宽和高
picheight:=imagebox.height;
infirstregion:=true;
inlinea:=false;
startlinex:=0;
fullregion:=0;
transparentcolor:=getpixel(hdc,0,0);//指定透明色
//下面的两重循环逐行地扫描不透明色,将连续的不透明色形成一个个小的区域
//然后将这些小区域混合成一个大的不规则区域即位图中不透明区域的形状
//由于是一行行地扫描而不是一个点一个点地扫描所以速度快多了。
for y:=0 to picheight-1 do
begin
for x:=0 to picwidth-1 do
begin
if ((getpixel(hdc,x,y)=transparentcolor) or (x=picwidth)) then
begin
if (inlinea=true) then
begin
inlinea:=false;
//将连续的不透明色组成一个小的矩形区域
lineregion:=createrectrgn(startlinex,y,x,y+1);
if (infirstregion=true)then
begin
fullregion:=lineregion;
infirstregion:=false;
end
else
begin
//混合小的矩形区域到一个大区域,循环结束后,此大区域就是整个不规则窗口
combinergn(fullregion,fullregion,lineregion,rgn_or);
deleteobject(lineregion);
end;
end;
end
else
begin
if (inlinea=false)then
begin
inlinea:=true;
startlinex:=x;
end;
end;
end;
end;
result:= fullregion; //返回所形成的不规则区域
end;

procedure TForm1.creat(Sender: TObject);
begin
//窗体式样为无边框
form1.BorderStyle:=bsNone;
//初始化a
a:=0;
end;
procedure TForm1.clicked(Sender: TObject);
begin
//得到当前鼠标位置
getcursorpos(mousepos);
//弹出菜单
popupmenu1.Popup(mousepos.x,mousepos.y);
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
aa:hrgn;//用于存储得到的区域
picture:string;//存储要显示的位图文件的文件名
begin
image1.AutoSize:=true;//按位图尺寸自动调整大小
a:=a+1;//每经过一个计时器事件,就换一幅位图
if a<8 then
begin
picture:='pig'+inttostr(a)+'.bmp' ;
image1.Picture.LoadFromFile(picture) ;
end
else
a:=0;
//按照image1中的位图形状形成一个不规则区域
aa:=makeregion(image1);
//将形成的不规则区域赋给本窗体以形成一个不规则窗体
setwindowrgn(handle,aa,true);
end;
procedure TForm1.N1Click(Sender: TObject);
begin
//单击窗体,在弹出的菜单中选“退出”时关闭本程序
application.Terminate;
end;

end.
以上程序在Delphi6,WindowsMe下调试通过。
==================================================================
 
有无更快的算法
 
要做不规则窗体简单,但是要响应上面的鼠标消息就有点困难。
 
http://www.delphibbs.com/delphibbs/dispq.asp?lid=2428370
 
建立任意图片形状窗口的方法

作者:晨晨
问题提出/摘要:
现在有许多Delphi方面的书都有讲如何建立圆形、椭圆形、星形等等非标准形状的窗口,不过实用性并不大。因为如果是一个不规则图形的话,你要怎么才能画好呢?就算可以那又要用多少时间呢?所以啦,我介绍下面的建立任意图片形状窗口的方法!只是可惜是别人发明的非我原创 :(

回答:
下面的程序段是一个外国人的原算法!(我最早看到算法,可他是不是抄的我就不知道了)原来是一个控件,不过在我的电脑上不能用。由万重大侠改写了并且发布在他的网站上:
http://mantousoft.51.net 注释是我写的。
function Tform1.CreateRegion(wMask: TBitmap; wColor: TColor; hControl: THandle): HRGN;
var
dc, dc_c : HDC;
Rgn, TempRgn : HRGN;
X, Y, BeginY : Integer;
line : boolean;
color : TColor;
begin {代码风格不统一,因为有些是照抄那个外国人的。
dc := GetWindowDC(hControl);
dc_c := CreateCompatibleDC(dc);
SelectObject(dc_c, wMask.Handle);
BeginY := 0;{这句可以不要,有了可以避免编译器警告。}
Rgn := CreateRectRgn(0, 0, 0, 0); {先初始化一个空的区域给Rgn。}
for X := 0 to wMask.Width - 1 do
begin
line := False;
for Y := 0 to wMask.Height - 1 do
begin
color := GetPixel(dc_c, X, Y);
if not (color = wColor) then
begin
if not line then
begin
line := True;
BeginY := Y;
end;
end;
if (color = wColor) or (Y = wMask.Height - 1) then
begin
if line then
begin
line := False;
TempRgn := CreateRectRgn(X, BeginY, X + 1, Y);
CombineRgn(Rgn, Rgn, TempRgn, RGN_OR);
{把图形以连续得线段为单位生成区域,并且合并到总的区域中}
end;
end;
end;
end;
ReleaseDC(hControl, dc);
DeleteObject(dc);
Result := Rgn;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
w1 : TBitmap;
w2 : TColor;
rgn : HRGN;
begin
w1 := TBitmap.Create;
w1.Assign(image1.Picture.Bitmap);
w2 := w1.Canvas.Pixels[0, 0];
rgn := CreateRegion(w1, w2, Handle);
if rgn <> 0 then
begin
SetWindowRgn(Handle, rgn, true);
end;
w1.Free;
end;

不过后来看到了罗云彬(http://asm.yeah.net)大侠给出了不同的算法,我以为更好些,源程序可是用100%的汇编写成的(厉害吧!)我改为了Delphi的样子!首先解释两个函数。
1、原形:function CreateRectRgn(p1, p2, p3, p4: Integer): HRGN; stdcall;创建一个由点p1,p2和p3,p4描述的矩形区域;
2、原形:function CombineRgn(p1, p2, p3: HRGN; p4: Integer): Integer; stdcall;把p2和p3区域合并为区域p1,p4是合并方式,p4取值如下:RGN_AND 交集合并,RGN_COPY拷贝p2的内容,RGN_DIFF合并p2和p3不相交的地方,RGN_OR并集合并,RGN_XOR不同时在p2和p3中的部分。
下面给出完整源程序:
function Tform1.CreateRegion(wMask: TBitmap; wColor: TColor; hControl: THandle): HRGN;
var
dc, dc_c : HDC;
rgn : HRGN;
x, y : integer;
coord : TPoint;
line : boolean;
color : TColor;
begin
dc := GetWindowDC(hControl);
dc_c := CreateCompatibleDC(dc);
{创建一个与特定设备场景一致的内存设备场景}
SelectObject(dc_c, wMask.Handle);
{把位图选入内存设备场景这样才可以操作!}
BeginPath(dc);
{启动一个路径分支。
在这个命令后执行的GDI绘图命令会自动成为路径的一部分。
对线段的连接会结合到一起。并且设备场景中任何现成的路径都会被清除。}
for x := 0 to wMask.Width - 1 do
begin
line := false;
for y := 0 to wMask.Height - 1 do
begin
color := GetPixel(dc_c, x, y);
if not (color = wColor) then
begin
if not line then
{如果这个不同颜色的线段开始了当然就不记录了!}
begin
line := true; {这是记录下这一列连续的不是背景颜色的颜色段的第一个位子。}
coord.x := x; {其实这个X是不用的,因为coord.x是恒等于x的。但尊重源作者,保留!}
coord.y := y;
end;
end;
if (color = wColor) or (y = wMask.Height - 1) then {如果这个颜色段完了或者到了图片底部}
begin
if line then
begin
line := false;
MoveToEx(dc, coord.x, coord.y, nil); {就把画线的起点移到这个列线段不同颜色的开始}
LineTo(dc, coord.x, y);
LineTo(dc, coord.x + 1, y);
LineTo(dc, coord.x + 1, coord.y); {上面三个LineTo()语句就是画一个2×y的矩形。
一定要这样才可以画上每个点!并且连在一起的如果你查查Win32 SDK手册你还可以用Rectangle(dc, coord.x, coord.y, x + 2, y)等其他的画图函数代替从MoveToEx()开始的四个语句!也是一样的,只要记住要把点画完而且要重叠地画才可以把路径连在一起!}
CloseFigure(dc); {描绘到一个路径时,关闭当前打开的图形。万重大侠说不可以少,我不理解,但是多了不错。}
end;
end;
end;
end;
EndPath(dc); {结束画路径}
rgn := PathToRegion(dc); {连接路径为区域的函数。}
ReleaseDC(hControl, dc); {释放资源,公式化的必须使用。}
Result := rgn;
end;

看一下这些资料吧
 
不用这么麻烦,用ABC控件,一下子搞定。
 
后退
顶部