隨著Microsoft憑借Windows在操作系統上取得的巨大成績,Windows用戶界面也日益成為業界標准。統一的界面給廣大用戶對應用軟件的學習與使用帶來了很大方便。但每天都面對同一副面孔,日久天長難免會產生一些厭倦,想開發一些"離經叛道"的應用程序,如果能夠一改Windows千篇一律的"標准"界面,一定會給用戶帶來一種清新的感覺。標准Windows應用程序窗口一般為帶有標題欄的淺灰色矩形外觀,因而"異形"對話框/窗口也主要是顏色與外形上動手腳。本實例實現了一個"精靈"窗口,程序編譯運行後的界面效果如圖一所示:
圖一、疊加在Visual C++開發工具上的透明"精靈"窗體
一、實現方法
一般情況下,實現異型窗體主要是作兩方面的工作,一是改變背景顏色,二是改變窗口外形。改變窗口背景顏色是最簡單的改變Windows應用程序外觀的方法,根據Windows創建與管理機理,一般有兩種方法。一種是處理WM_CTLCOLOR消息,需要重畫窗口或對話(或對話的子控件)時,Windows向應用程序發送消息WM_CTLCOLOR,應用程序處理WM_CTLCOLOR消息並返回一個用來繪畫窗體背景的刷子句柄。另外一種是響應Windows的WM_ERASEBKGND消息,Windows向窗口發送一個WM_ERASEBKGND消息通知該窗口擦除背景,可以使用VC++的ClassWizard重載該消息的缺省處理程序來擦除背景(實際是用刷子畫),並返回TRUE以防止Windows擦除窗口。對於改變窗體的外形,可以通過使用新的SDK函數SetWindowRgn(),將繪畫和鼠標消息限定在窗口的一個指定的區域,因此實際上是使窗口成為指定的不規則形狀(區域形狀)。"區域"是Windows GDI中一種強有力的機制,區域是設備上的一塊空間,可以是任意形狀,復雜的區域可以由各個小區域組合而成。Windows內含的區域創建函數有CreateRectRgn()、CreatePolyRgn()、CreatePolygonRgn()、CreateRoundRectRgn()和CreateEllipticRgn(),再通過CombineRgn()來組合區域,即可得到復雜形狀的區域,獲得復雜形狀的窗口外形。
通過上面的方法雖然可以得到"異形"窗口,但感覺顏色單調,外形也不夠"COOL",能否獲得更酷的"異形"窗口呢?回答是肯定的。下面就介紹利用位圖和蒙板創建"異形"窗口的方法。本實例實現的"精靈"效果就是通過這種方法實現的。
利用位圖創建異形窗體的原理是根據象素的顏色來進行"扣像"處理,對所有非指定顏色象素區域進行區域組合。利用這一技術,實際上就是實現對話框/窗口的位圖背景,並且對指定的顏色區域進行透明處理。下面就以透明位圖為背景的窗體為例來說明:
首先用繪圖軟件如PhotoShop繪制編輯一幅擬做程序背景用的圖片以及該圖片相應的掩模圖片,用BMP格式保存,下一步是用Visual C++的資源編輯器引入該背景圖片和掩模圖片文件,設置其ID為IDB_SHOW和IDB_MASK。需要說明的是,雖然Visual C++集成開發環境的資源編輯器只能編輯不超過16色的位圖,但完全我們可以以真彩色方式存儲,不必理會Visual C++的警告。
有了上述的工作,剩下的核心工作就是根據背景位圖和掩模位圖來確定最終顯示的位圖區域,也就是說,"扣除"的區域將以透明效果顯示。下面的代碼實現了這一功能:
/////////////////////////////////////////////////////////////////////////////
// 獲得窗體矩形
CRect rectWnd;
this->GetWindowRect(rectWnd);
// 讀取"掩模"位圖資源
CBitmap myBitmap,*pOldBitmap;
myBitmap.LoadBitmap(nMaskId);
// 創建"內存一致"設備
CDC memDC;
memDC.CreateCompatibleDC(pDC);
// 選擇繪圖設備
pOldBitmap = memDC.SelectObject(&myBitmap);
// 創建窗體的初始區域
CRgn rgnWnd,rgnTemp;
rgnWnd.CreateRectRgn(0,0,rectWnd.Width(),rectWnd.Height());
int nWidth,nHeight;
COLORREF color;
//下面的兩層循環為檢查背景位圖象素顏色,進行透明區域處理;
//當象素顏色為指定的透明值時,即將該點從區域中剪裁掉。
for (nWidth = 0;nWidth <= rectWnd.Width()-1;nWidth++)
{
for (nHeight = 0;nHeight <= rectWnd.Height();nHeight++)
{
color = memDC.GetPixel(nWidth,nHeight);
// 當象素是白色時,去掉該點
if (color == RGB(255,255,255))
{
//象素顏色為指定的透明色,創建透明"微區域"
rgnTemp.CreateRectRgn(nWidth,nHeight,nWidth+1,nHeight+1);
//"扣像",從完整的區域中"扣除"透明的"微區域"
rgnWnd.CombineRgn(&rgnWnd,&rgnTemp,RGN_XOR);
//刪除剛創建的透明"微區域",釋放系統資源
rgnTemp.DeleteObject();
}
}
}
memDC.SelectObject(pOldBitmap);
SetWindowRgn((HRGN)rgnWnd,TRUE); //用最終設定窗口的顯示區域為指定區域
為了最終顯示透明效果的窗體,還需要重置系統默認的背景擦除操作,即添加WM_ERASEBKGND消息處理過程,在其中實現背景位圖的顯示功能,這一步可以借助ClassWizard來實現。
最後需要注意的是,為了使異形窗體應用程序能夠正常地脫動和退出,需要處理窗體的WM_NCHITTEST、WM_CHAR等消息,這部分內容本書中其它實例已經作過介紹,這裡就不再贅述了,感性趣的讀者朋友可以參考相關實例。
二、編程步驟
1、 啟動Visual C++6.0,生成一個Win32應用程序,將該程序命名為"transparent";
2、 使用Class Wizard在應用程序中添加CMyWnd類,其基類選擇CWnd;
3、 向應用程序的項目中添加背景位圖和掩模位圖,其ID分別設置為IDB_SHOW、IDB_MASK;
4、 添加代碼,編譯運行程序。
三、程序代碼
//////////////////////////////////////////////////////////////// MyWnd.h : header file
#if !defined(AFX_MYWND_H__A761190E_CDF2_4A56_8848_AEE5AC7AD160__INCLUDED_)
#define AFX_MYWND_H__A761190E_CDF2_4A56_8848_AEE5AC7AD160__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// CMyWnd window
class CMyWnd : public CWnd
{
// Construction
public:
CMyWnd();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMyWnd)
//}}AFX_VIRTUAL
// Implementation
public:
void Initialize(LPCTSTR lpszName,CRect &rectWnd,UINT nMaskId,UINT nShowId);
void Display(CDC *pDC,UINT nMaskId);
virtual ~CMyWnd();
// Generated message map functions
protected:
//{{AFX_MSG(CMyWnd)
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg UINT OnNcHitTest(CPoint point);
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
UINT m_nBitmapId;
};
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_MYWND_H__A761190E_CDF2_4A56_8848_AEE5AC7AD160__INCLUDED_)
////////////////////////////////////////////////////////// MyWnd.cpp : implementation file
#include "stdafx.h"
#include "transparent.h"
#include "MyWnd.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
// CMyWnd
CMyWnd::CMyWnd()
{}
CMyWnd::~CMyWnd()
{}
BEGIN_MESSAGE_MAP(CMyWnd, CWnd)
//{{AFX_MSG_MAP(CMyWnd)
ON_WM_ERASEBKGND()
ON_WM_NCHITTEST()
ON_WM_CHAR()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
BOOL CMyWnd::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
CRect rectWnd;
this->GetWindowRect(&rectWnd);
CDC memDC;
CBitmap myBitmap,*pOldBitmap;
myBitmap.LoadBitmap(m_nBitmapId);
memDC.CreateCompatibleDC(pDC);
pOldBitmap = memDC.SelectObject(&myBitmap);
pDC->BitBlt(0,0,rectWnd.Width(),rectWnd.Height(), &memDC,0,0,SRCCOPY);
// 將設備還原
memDC.SelectObject(pOldBitmap );
return TRUE;
}
UINT CMyWnd::OnNcHitTest(CPoint point)
{
// TODO: Add your message handler code here and/or call default
UINT nHitTest = CWnd :: OnNcHitTest(point) ;
return (nHitTest == HTCLIENT) ? HTCAPTION : nHitTest ;
}
void CMyWnd::Display(CDC *pDC, UINT nMaskId)
{
// 獲得窗體矩形
CRect rectWnd;
this->GetWindowRect(rectWnd);
// 讀取位圖資源
CBitmap myBitmap,*pOldBitmap;
myBitmap.LoadBitmap(nMaskId);
// 創建"內存一致"設備
CDC memDC;
memDC.CreateCompatibleDC(pDC);
//選擇繪圖設備
pOldBitmap = memDC.SelectObject(&myBitmap);
// 創建窗體的初始區域
CRgn rgnWnd,rgnTemp;
rgnWnd.CreateRectRgn(0,0,rectWnd.Width(),rectWnd.Height());
int nWidth,nHeight;
COLORREF color;
for (nWidth = 0;nWidth <= rectWnd.Width()-1;nWidth++)
{
for (nHeight = 0;nHeight <= rectWnd.Height();nHeight++)
{
color = memDC.GetPixel(nWidth,nHeight);
// 當象素是白色時,去掉該點
if (color == RGB(255,255,255))
{
rgnTemp.CreateRectRgn(nWidth,nHeight,nWidth+1,nHeight+1);
rgnWnd.CombineRgn(&rgnWnd,&rgnTemp,RGN_XOR);
rgnTemp.DeleteObject();
}
}
}
memDC.SelectObject(pOldBitmap);
SetWindowRgn((HRGN)rgnWnd,TRUE);
}
void CMyWnd::Initialize(LPCTSTR lpszName, CRect &rectWnd, UINT nMaskId, UINT nShowId)
{
this->CreateEx(0,AfxRegisterWndClass(0),lpszName,WS_POPUP
| WS_SYSMENU,rectWnd,NULL,NULL,NULL);
this->Display(GetWindowDC(),nMaskId);
m_nBitmapId = nShowId;
}
void CMyWnd::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
if(nChar==VK_ESCAPE)
{
PostMessage(WM_QUIT);
}
CWnd::OnChar(nChar, nRepCnt, nFlags);
}
////////////////////////////////////////////////////////////
BOOL CTransparentApp::InitInstance()
{
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
m_pMainWnd = &m_wndMain;// m_wndMain是CmyWnd類成員變量;
CRect rectWnd(500, 300, 580, 380);
m_wndMain.Initialize(_T("loveghost"),rectWnd,IDB_MASK,IDB_SHOW);
m_wndMain.ShowWindow(SW_SHOW); // 窗體創建完畢,顯示之;
return TRUE; // 返回非零值表示要繼續處理消息
}
四、小結
這種異形窗口的創建方法適應於所有的基於CWnd類的派生窗口,采用這一方法,可以創建出任何形狀的"異形"窗體。