C++Builder中不規(guī)則窗體的快速顯示 2000-09-07· 陶志才·yesky 不規(guī)則窗體的應(yīng)用增加軟件的吸引力 傳統(tǒng)的WINDOWS應(yīng)用軟件界面給人的感覺總是千篇一律的方方正正的窗體,看的時(shí)間長了難免會(huì)有些厭煩,總是希望能見到些不同一般的軟件界面。如今,相當(dāng)數(shù)量的商業(yè)軟件在提供優(yōu)秀而強(qiáng)大的功能的同時(shí),軟件的界面也是做得越來越漂亮,比如《超級解霸2000》中的界面插件,使用過的人一定對其華麗的外觀充滿好感。作為一個(gè)編程愛好者,如果自己寫出的軟件也擁有類似的界面,也許會(huì)吸引更多目光的注視。那么,我們現(xiàn)在就開始動(dòng)手制作自己的漂亮界面吧。 技術(shù)內(nèi)幕 要想在自己的程序中加入不規(guī)則窗體的應(yīng)用,你首先要熟悉幾個(gè)WINDOWS API函數(shù)的使用,它們是:橢圓形(或圓形)區(qū)域創(chuàng)建函數(shù)CreateEllipticRgn 、多邊形區(qū)域創(chuàng)建函數(shù)CreatePolygonRgn、 矩形區(qū)域創(chuàng)建函數(shù)CreateRectRgn、 帶圓角的矩形區(qū)域創(chuàng)建函數(shù)CreateRoundRectRgn。你可以用這些函數(shù)創(chuàng)建不同類型的窗體區(qū)域,也可以用WINDOWS API函數(shù)CombineRgn將幾個(gè)簡單區(qū)域組合成一個(gè)復(fù)雜區(qū)域。 下一步要做的就是將已經(jīng)創(chuàng)建好的區(qū)域顯示在屏幕上,同樣也是使用WINDOWS API 函數(shù)來實(shí)現(xiàn),這次用到的是SetWindowRgn函數(shù)。 WINDOWS API 函數(shù)在Borland C++ Builder 頭文件中均已定義,在應(yīng)用程序中使用這些API函數(shù)就象使用C++的普通庫函數(shù)一樣。 準(zhǔn)備工作 為你的程序準(zhǔn)備一幅背景圖片,推薦方法是: 在PhotoShop中打開圖片后使用磁性套索工具選取你所需要的圖象輪廓——復(fù)制——新建文件(背景使用白色)——粘貼——另存文件(PSD文件)——用ACDSee等看圖軟件將保存的PSD文件轉(zhuǎn)換為BMP文件face.bmp備用。如下圖: 程序中引用圖片 打開Borland C++ Builder,在窗體上放置一個(gè)Image控件Image1,其Picture暫為空;在窗體上放置一個(gè)Popup菜單,編輯菜單項(xiàng)增加“Close”項(xiàng)(添加程序代碼使得激活彈出菜單時(shí)即可關(guān)閉應(yīng)用程序)。程序中做如下處理: void __fastcall TForm1::FormCreate(TObject *Sender) { < 。 < 。 < 。 Image1-> Picture-> LoadFromFile( ".\\face.bmp "); Width=Image1-> Width; Height=Image1-> Height; Repaint(); < 。 < 。 < 。 } 此時(shí),窗體的大小已能跟隨所用圖片的大小而改變,但仍舊是傳統(tǒng)的WINDOWS界面,要想顯示成具有圖片輪廓的窗體外形,就需要使用前文介紹的WINDOWS API函數(shù)將不需要顯示的部分摳去。 摳像方法一 這是一種非常簡單的方法,采用對圖片逐行掃描的方式,將圖片像素點(diǎn)為白色的部分摳去,使用的方法是:在像素點(diǎn)附近產(chǎn)生一個(gè)包含幾個(gè)像素點(diǎn)的矩形,與原圖片采用異或方式摳去,程序如下: HRGN tepRgn; for(y=0;y <Image1-> Height;y++) for(x=0;x <Image1-> Width;x++) if(Image1-> Canvas-> Pixels[x][y]==clWhite) { < tepRgn=CreateRectRgn(x,y,x+1,y+1); CombineRgn(WndRgn,WndRgn,tepRgn,RGN_XOR); DeleteObject(tepRgn); } 這種方法的優(yōu)點(diǎn)是處理比較簡單,缺點(diǎn)是處理速度太慢,尤其是在處理大幅圖片時(shí),往往要4~5秒的時(shí)間才能將窗體顯示出來。因此產(chǎn)生了通過另外的途徑快速勾勒圖片輪廓的想法。 摳像方法二 這次我們采用另一個(gè)WINDOWS API函數(shù)CreatePolygonRgn(多邊形區(qū)域),使用這個(gè)函數(shù)時(shí)需為它準(zhǔn)備圖片輪廓的坐標(biāo)點(diǎn)數(shù)組及坐標(biāo)點(diǎn)個(gè)數(shù),也是通過對圖片逐行掃描的方式,找到白色像素點(diǎn)與非白色像素點(diǎn)的分界點(diǎn),將該點(diǎn)的坐標(biāo)存入數(shù)組中,然后用CreatePolygonRgn函數(shù)一次就可以把圖片外圍的不用部分摳去,從而省去大量的處理時(shí)間。程序如下: register int x,y; int l,r; POINT *a; bool lb,rb; HRGN WndRgn,TempRgn,; if((a=(POINT *)malloc(800*2*(sizeof(POINT))))==NULL) { ShowMessage( "申請內(nèi)存失??! "); exit(0); } l=0;r=Image1-> Height*2-1; WndRgn=CreateRectRgn(0,0,Image1-> Width,Image1-> Height); for(y=0;y <Image1-> Height;y++) { lb=true; for(x=0;x <Image1-> Width;x++) if(Image1-> Canvas-> Pixels[x][y]!=clWhite) { a[l].x=x; a[l].y=y; lb=false; break; } if(lb) a[l]=a[l-1]; l++; rb=true; for(x=Image1-> Width-1;x> =0;x--) if(Image1-> Canvas-> Pixels[x][y]!=clWhite) { a[r].x=x; a[r].y=y; rb=false; break; } if(rb) a[r]=a[r+1]; r--; } TempRgn=CreatePolygonRgn(a,Image1-> Height*2,ALTERNATE); CombineRgn(WndRgn,WndRgn,TempRgn,RGN_AND); DeleteObject(TempRgn); < free(a); 程序中對每一像素行都從左右兩個(gè)方向分別掃描,找到兩邊的分界點(diǎn)存入數(shù)組。 不過這個(gè)方法也存在一些缺陷,那就是圖片的內(nèi)凹部分輪廓并未表現(xiàn)出來。從下圖中可以看出: 最終解決方案 考慮到既不增加算法的復(fù)雜度,又可大幅度縮短不規(guī)則窗體的創(chuàng)建速度,因此采用綜合以上兩種方案,達(dá)到我們應(yīng)用的目的,程序中首先應(yīng)用方法二對圖片雙向掃描,產(chǎn)生輪廓坐標(biāo)點(diǎn)數(shù)組,然后在圖片輪廓內(nèi)應(yīng)用方法一將內(nèi)凹部分摳去,最后才用多邊形區(qū)域創(chuàng)建函數(shù)摳去圖片外圍部分。程序如下: void __fastcall TForm1::FormCreate(TObject *Sender) { register int x,y; int l,r; POINT *a; bool lb,rb; HRGN WndRgn,TempRgn,tepRgn; Width=800;Height=600; if((a=(POINT *)malloc(800*4*(sizeof(POINT))))==NULL) { ShowMessage( "申請內(nèi)存失??! "); exit(0); } Image1-> Picture-> LoadFromFile( ".\\face.bmp "); Width=Image1-> Width; Height=Image1-> Height; Repaint(); l=0;r=Image1-> Height*2-1; WndRgn=CreateRectRgn(0,0,Image1-> Width,Image1-> Height); < //應(yīng)用方法二產(chǎn)生輪廓坐標(biāo)點(diǎn)數(shù)組 for(y=0;y <Image1-> Height;y++) { lb=true; for(x=0;x <Image1-> Width;x++) if(Image1-> Canvas-> Pixels[x][y]!=clWhite) { a[l].x=x+1; a[l].y=y; lb=false; break; } if(lb) a[l]=a[l-1]; l++; rb=true; for(x=Image1-> Width-1;x> =0;x--) if(Image1-> Canvas-> Pixels[x][y]!=clWhite) { a[r].x=x; a[r].y=y; rb=false; break; } if(rb) a[r]=a[r+1]; r--; } //應(yīng)用方法一摳去圖片內(nèi)凹部分 r=Image1-> Height*2-1; for(y=0;y <Image1-> Height;y++){ for(x=a[y].x;x <a[r].x;x++) if(Image1-> Canvas-> Pixels[x][y]==clWhite) { < tepRgn=CreateRectRgn(x,y,x+1,y+1); CombineRgn(WndRgn,WndRgn,tepRgn,RGN_XOR); DeleteObject(tepRgn); } r--; } //將圖片外圍部分摳去 TempRgn=CreatePolygonRgn(a,Image1-> Height*2,ALTERNATE); CombineRgn(WndRgn,WndRgn,TempRgn,RGN_AND); DeleteObject(TempRgn); free(a); //顯示不規(guī)則窗體 SetWindowRgn(Handle,WndRgn,true); SetWindowPos(Handle,HWND_TOP,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE); } 至此,一個(gè)漂亮的程序界面就出現(xiàn)在你的屏幕上了。見下圖: 以上程序在Celeron466、WIN98SE和WIN2000、C++ Builder5.0下調(diào)試通過。 如有疑問,請致函 taozc@sina.com.cn 。 回復(fù)人:dbxmcf(刀板蝦米) (2001-6-5 17:44:00) 得0分 哇,我記得就一句話可以解決的 void __fastcall TForm1::OnCreate(TObject *Sender) { Canvas-> Brush-> Style=bsClear;//幫助上有的 //將Form1的 BorderStyle變?yōu)閎sNone,當(dāng)然運(yùn)行還是有一點(diǎn)問題的 //I 'm not sure whether it solves your problem } 其實(shí)很多問題VCL已經(jīng)幫我們做好了,不需要過多技巧的 回復(fù)人:wjzhuang(程序豬) (2001-6-5 17:53:00) 得0分 是的,我記得有三種方法可以解決,但上面的是最好的. 其他方法有各種問題存在. 方法1: 響應(yīng)OnCreate事件 Form1-> Brush-> Style=bsClear; Form1-> borderStyle=bsNone; 不足: 當(dāng)窗體最小化后再恢復(fù)時(shí),窗體會(huì)有陰影 方法2: 就是上面的方法. 方法3: 不好意思,我忘了. 回復(fù)人:wangxd(東東) (2001-6-5 22:02:00) 得0分 那我就提供方法3把 頭文件: void virtual __fastcall OnWMEraseBkgnd(TWMEraseBkgnd &Msg); void virtual __fastcall OnWMNCHitTest(TWMNCHitTest &Msg); BEGIN_MESSAGE_MAP MESSAGE_HANDLER(WM_ERASEBKGND,TWMEraseBkgnd,OnWMEraseBkgnd) MESSAGE_HANDLER(WM_NCHITTEST,TWMNCHitTest,OnWMNCHitTest) END_MESSAGE_MAP(TForm) cpp文件: void __fastcall TForm1::OnWMEraseBkgnd(TWMEraseBkgnd &Msg) { this-> Brush-> Style=bsClear; Msg.Result=true; } void __fastcall TForm1::OnWMNCHitTest(TWMNCHitTest &Msg) { TForm::Dispatch(&Msg); if (Msg.Result==HTCLIENT) Msg.Result=HTCAPTION; } 回復(fù)人:luhongjun(過江項(xiàng)羽) (2001-6-5 22:45:00) 得0分 這才是最好的方法. void __fastcall TForm1::Button1Click(TObject *Sender) { TRect *rctClient,*rctFrame; HRGN hClient,hFrame; POINT *lpTL,*lpBR; rctFrame=new TRect; rctClient=new TRect; lpTL=new POINT; lpBR=new POINT; GetWindowRect(Form1-> Handle,rctFrame); ::GetClientRect(Form1-> Handle,rctClient); lpTL-> x=rctFrame-> Left;lpTL-> y=rctFrame-> Top; lpBR-> x=rctFrame-> Right;lpBR-> y=rctFrame-> Bottom; ::ScreenToClient(Form1-> Handle,lpTL); ::ScreenToClient(Form1-> Handle,lpBR); rctFrame-> Left=lpTL-> x;rctFrame-> Top=lpTL-> y; rctFrame-> Right=lpBR-> x;rctFrame-> Bottom=lpBR-> y; rctClient-> Left=abs(rctFrame-> Left); rctClient-> Top=abs(rctFrame-> Top); rctClient-> Right=rctClient-> Right+ abs(rctFrame-> Left); rctClient-> Bottom=rctClient-> Bottom+ abs(rctFrame-> Top); rctFrame-> Right=rctFrame-> Right+ abs(rctFrame-> Left); rctFrame-> Bottom=rctFrame-> Bottom+ abs(rctFrame-> top); rctFrame-> Top=0;rctFrame-> Left=0; hClient=CreateRectRgn(rctClient-> Left,rctClient-> Top, rctClient-> Right,rctClient-> Bottom); hFrame=CreateRectRgn(rctFrame-> left,rctFrame-> Top, rctFrame-> Right,rctFrame-> Bottom); CombineRgn(hFrame,hClient,hFrame,RGN_XOR); SetWindowRgn(Form1-> Handle,hFrame,true); delete rctFrame; delete rctClient; delete lpTL,lpBR; } |