天天看點

CRgn幾種建立方法,不規則窗體

 一、序言

  在絕大多數的Windows應用程式中,其窗體都是使用的正規正矩的矩形窗體,例如我們常用的,“記事本”,“掃雷”,等等。矩形窗體,具有程式設計實作簡單,風格簡潔的優點,是以在普通文檔應用程式和簡單小遊戲中使用足矣。但在某些娛樂遊戲程式中使用就略顯呆闆些了,這時若用不規則窗體替代原先的矩形窗體,将會使這類程式更添情趣。典型的例子有windows 自代的Media Player,新版本的Media Player有個控制台的選項,選中這些面闆,播放器就以選中的面闆形狀出現,這時的播放器比以前版本的Media Player的古老矩形界面要生動有趣的多了。 要實作不規則窗體不是太難,知道了基本原理後,你也可以建立各種有趣的不規則窗體。

二、實作原理

  所有的 Windows 窗體都位于一個稱為“region”中,窗體的大小如果超出“region”的範圍,windows 會自動裁剪超出"region"範圍那部分的窗體,使其不可見。是以,要建立不規則窗體有兩個步驟:第一步就是建立不規則"region".第二步就是将窗體放到建立的“region”中。

  其中第二步很簡單就調用一條語句即可。在SDK中調用API函數SetWindowRgn,該函數原型如下:

int SetWindowRgn( HWND hWnd, HRGN hRgn, BOOL bRedraw );

  其中hWnd為待設定的窗體句柄,hRgn為已經建立的"region"句柄,bRedraw代表是否要重繪窗體。在MFC 中使用視窗類CWnd的成員函數int CWnd::SetWindowRgn(HRGN hRgn, BOOL bRedraw );該函數的參數意義與API中同名函數相同。

  相對與第二步,建立不規則窗體的第一步要複雜許多,并且不規則窗體越複雜,建立其"region"的過程也越複雜。接下去我們将由淺入深地介紹各種建立”region”的方法。

在MFC中"region"對象,由CRgn類實作。CRgn的幾乎每個成員函數都有同名的SDK API函數對應。

三、簡單“region”的建立

  類CRgn建立一個新的"region"的簡單方法有以下幾個成員函數:

  1. BOOL CRgn::CreateRectRgn( int x1, int y1, int x2, int y2 ); 建立矩形的“region”。
  2. BOOL CRgn::CreateEllipticRgn( int x1, int y1, int x2, int y2 ); 建立圓形或橢圓形“region”。
  3. BOOL CRgn::CreateRoundRectRgn( int x1, int y1, int x2, int y2, int x3, int y3 ); 建立圓角矩形“region”。
  4. BOOL CRgn::CreatePolygonRgn( LPPOINT lpPoints, int nCount, int nMode ); 建立多邊形“region”。

  這裡以建立橢圓窗體為例,介紹橢圓窗體建立的方法。在建立橢圓“region”的CreateEllipticRgn函數中,x1,y1指橢圓所在矩形的左上角坐标,x2,y2指該矩形的右下角坐标。

  下面的代碼加入到MFC對話框程式的OnInitDialog函數中,可将該對話框變成橢圓窗體:

  

BOOL CTestDlg::OnInitDialog() {   CDialog::OnInitDialog();   ...

CRgn rgn;
	rgn. CreateEllipticRgn(0,0,200,100);
	SetWindowRgn(rgn,TRUE);
}
           

四、作圖路徑法建立”region”

  使用該方法建立”region”的過程如下:

  第一步繪制所要建立的窗體形狀。

  該步驟中使用到CDC類中的一些成員函數如下:

BOOL CDC::BeginPath( );

  調用該函數後目前裝置環境(DC)開始追蹤繪圖的過程。

int CDC::SetBkMode( int nBkMode );

  設定繪圖時的背景模式,此應用中nBkMode必須取值為TRANSPARENT 。即設定繪圖時背景不發生變化。

BOOL CDC::EndPath( );

  調用該函數後目前裝置環境(DC)結束追蹤繪圖的過程。

  開始繪圖前,先調用BeginPath,然後調用SetBkMode。接下去就可調用CDC的其他繪圖函數作圖,例如Arc,AngleArc,LineTo,MoveTo,RoundRect,,Textout等等。繪圖完畢調用EndPath().

  第二步将繪制的結果轉成”region”.

  此步驟中使用SDK API函數

HRGN PathToRegion( HDC hdc );

  Hdc為作圖DC的句柄, CDC類中的m_hDC成員變量可做此參數傳入。示例,将下面代碼加入某個按鈕單擊事件中,可以将目前窗體變為字元串”hello”的形狀

  

void CTestDlg::OnTest() {

HRGN wndRgn;
	CClientDC dc(this);
	CFont mFont;
	
	if (dc.m_hDC!=NULL)
	{
		VERIFY(mFont.CreateFont(
			200, 50, 0, 0, FW_HEAVY, TRUE, FALSE,
			0, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
			CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
			DEFAULT_PITCH|FF_SWISS, "宋體"));
		//開始記錄窗體輪廓路徑
		dc.BeginPath();
		//設定背景為透明模式,這句話是必須有的。
		dc.SetBkMode(TRANSPARENT);
		
		CFont * pOldFont;
		pOldFont = dc.SelectObject( &mFont );
		dc.TextOut(0, 0, "Hello");
		
		//結束記錄窗體輪廓路徑
		dc.SelectObject( pOldFont );
		dc.EndPath();
		
		//把所記錄的路徑轉化為窗體輪廓句柄
		wndRgn = ::PathToRegion(dc.m_hDC);
		//賦予窗體指定的輪廓形狀
		this->SetWindowRgn(wndRgn, TRUE);
	}
           

}

CClientDC是CDC的派生類,故此該類具有所有CDC類的成員變量和成員函數。

  圖二 hello形狀的窗體效果圖

五、根據圖像建立”region”

  此法建立不規則窗體比較複雜。首先準備一張含有目标窗體形狀的圖檔,設定透明色即将圖檔中部不屬于窗體形狀的部分,标記成同一種顔色,例如藍色RGB(0,0,255).程式運作後先裝入圖檔。然後逐個掃描圖檔的每個像素,如這個像素不屬于透明色,則在相應位置建立一個隻含一個像素的“region”然後将這些小”region ”合并起來組成一個任意形狀的”region”.這裡将使用到CRgn的一個成員函數 :

int CRgn::CombineRgn( CRgn* pRgn1, CRgn* pRgn2, int nCombineMode );

  其中pRgn1,pRgn2為要合并的兩個“region”,nCombineMode為合并的方式,此應用中取RGN_OR,即兩”region”全部合并去處重複部分。代碼實作如下:

void CIrregularDlg::SetupRegion(CDC *pDC, CBitmap &cBitmap, COLORREF TransColor)
{
	CDC memDC;
	
	memDC.CreateCompatibleDC(pDC);
	CBitmap *pOldMemBmp=NULL;
	pOldMemBmp=memDC.SelectObject(&cBitmap);

	CRgn wndRgn;
	//建立總的窗體區域,初始region為0
	wndRgn.CreateRectRgn(0,0,0,0);

	BITMAP bit;
	cBitmap.GetBitmap (&bit);//取得位圖參數,這裡要用到位圖的長和寬
	int y;
	for(y=0; y<bit.bmHeight; y++)
	{
		CRgn rgnTemp; //儲存臨時region
		int iX = 0;
		do
		{
			//跳過透明色找到下一個非透明色的點.
			while (iX < bit.bmWidth && memDC.GetPixel(iX, y) == TransColor)
				iX++;
			int iLeftX = iX; //記住這個起始點

			//尋找下個透明色的點
			while (iX < bit.bmWidth && memDC.GetPixel(iX, y) != TransColor)
				++iX;

			//建立一個包含起點與重點間高為1像素的臨時“region”
			rgnTemp.CreateRectRgn(iLeftX, y, iX, y+1);
			wndRgn.CombineRgn(&wndRgn, &rgnTemp, RGN_OR);
			
			//删除臨時"region",否則下次建立時和出錯
			rgnTemp.DeleteObject();
		}while(iX < bit.bmWidth);
	}

	SetWindowRgn(wndRgn,TRUE);
	SetForegroundWindow();
}
           

 筆者:memDC.CreateCompatibleDC(NULL);也是可行的,不知道會不會有什麼不良情況,但可以省個參數!

繼續閱讀