首先,准备两个窗口,窗口A和窗口B,窗口A作为显示窗口,也就是异形窗口,而窗口B作为逻辑窗口,就是你要处理使用的窗口,即主窗口,然后让这两个窗口重叠在一块,也可以说是在窗口B上创建了窗口A,然后通过UpdateLayeredWindow对窗口A实现异形,因为窗口A在窗口B上,那么势必会遮盖住窗口B的控件,然后我们就要对窗口A通过SetWindowRgn进行裁剪,通过镂空出控件的位置从而达到显示出控件。最后划分窗口B的大小,让窗口A显示出异形的模样,如图所示,红色区域也就是窗口B的大小
首先创建一个MFC类,取名CLayeredWindow,基类CWnd,也就是上文提到的窗口A,CLayeredWindow.h代码如下////////////////////////////////////////////////////////////////////////分层窗口class CLayeredWindow : public CWnd{ DECLARE_DYNAMIC(CLayeredWindow) //变量定义protected: CWnd * m_pWndControl; //控件窗口 //函数定义public: //构造函数 CLayeredWindow(); //析构函数 virtual ~CLayeredWindow(); //功能函数public: //创建窗口 VOID CreateLayered(CWnd * pWndControl, CRect & rcWindow); //设置区域 VOID InitLayeredArea(CDC * pDCImage, BYTE cbAlpha, CRect & rcUnLayered, CPoint & PointRound, bool bUnLayeredChild); //设置区域 VOID InitLayeredArea(CImageEx & Image, BYTE cbAlpha, CRect & rcUnLayered, CPoint & PointRound, bool bUnLayeredChild); //消息映射protected: //关闭消息 VOID OnClose(); //焦点消息 VOID OnSetFocus(CWnd * pOldWnd); //静态函数protected: //枚举函数 static BOOL CALLBACK EnumChildProc(HWND hWndChild, LPARAM lParam); DECLARE_MESSAGE_MAP()};//////////////////////////////////////////////////////////////////////CLayeredWindow.cpp代码如下//枚举结构struct tagEnumChildInfo{ CWnd * pWndControl; //控制窗口 CWnd * pWndLayered; //分层窗口 CRgn * pRegionResult; //结果区域}; IMPLEMENT_DYNAMIC(CLayeredWindow, CWnd) CLayeredWindow::CLayeredWindow(){ //设置变量 m_pWndControl=NULL;} CLayeredWindow::~CLayeredWindow(){} BEGIN_MESSAGE_MAP(CLayeredWindow, CWnd)END_MESSAGE_MAP() //创建窗口VOID CLayeredWindow::CreateLayered(CWnd * pWndControl, CRect & rcWindow){ //效验参数 ASSERT((pWndControl!=NULL)&&(pWndControl->m_hWnd!=NULL)); if ((pWndControl==NULL)||(pWndControl->m_hWnd==NULL)) return; //设置变量 m_pWndControl=pWndControl; //创建窗口 CreateEx(WS_EX_LAYERED,TEXT('STATIC'),TEXT(''),0,rcWindow,pWndControl,0L); return;} //设置区域VOID CLayeredWindow::InitLayeredArea(CDC * pDCImage, BYTE cbAlpha, CRect & rcUnLayered, CPoint & PointRound, bool bUnLayeredChild){ //效验参数 ASSERT((pDCImage!=NULL)&&(pDCImage->m_hDC!=NULL)); if ((pDCImage==NULL)||(pDCImage->m_hDC==NULL)) return; //变量定义 BITMAP Bitmap; ZeroMemory(&Bitmap,sizeof(Bitmap)); //获取图像 CBitmap * pBitmap=pDCImage->GetCurrentBitmap(); if (pBitmap!=NULL) pBitmap->GetBitmap(&Bitmap); //获取大小 CSize SizeImage; SizeImage.SetSize(Bitmap.bmWidth,Bitmap.bmHeight); //效验大小 ASSERT((SizeImage.cx>0)&&(SizeImage.cy>0)); if ((SizeImage.cx==0)||(SizeImage.cy==0)) return; //变量定义 BLENDFUNCTION BlendFunction; ZeroMemory(&BlendFunction,sizeof(BlendFunction)); //设置参数 BlendFunction.BlendOp=0; BlendFunction.BlendFlags=0; BlendFunction.AlphaFormat=AC_SRC_ALPHA; BlendFunction.SourceConstantAlpha=cbAlpha; //设置分层 CPoint ImagePoint(0,0); CClientDC ClientDC(this); UpdateLayeredWindow(&ClientDC,NULL,&SizeImage,pDCImage,&ImagePoint,0L,&BlendFunction,ULW_ALPHA); //创建区域 CRgn RegionResult; RegionResult.CreateRectRgn(0,0,SizeImage.cx,SizeImage.cy); //窗口排除 if (bUnLayeredChild==true) { //变量定义 tagEnumChildInfo EnumChildInfo; ZeroMemory(&EnumChildInfo,sizeof(EnumChildInfo)); //设置变量 EnumChildInfo.pWndLayered=this; EnumChildInfo.pWndControl=m_pWndControl; EnumChildInfo.pRegionResult=&RegionResult; //枚举窗口 ASSERT(m_pWndControl->GetSafeHwnd()!=NULL); EnumChildWindows(m_pWndControl->m_hWnd,EnumChildProc,(LPARAM)&EnumChildInfo); } //区域排除 if (rcUnLayered.IsRectEmpty()==FALSE) { //创建区域 CRgn RegionUnLayered; RegionUnLayered.CreateRoundRectRgn(rcUnLayered.left,rcUnLayered.top,rcUnLayered.right+1,rcUnLayered.bottom+1,PointRound.x,PointRound.y); //合并区域 RegionResult.CombineRgn(&RegionResult,&RegionUnLayered,RGN_DIFF); } //设置区域 SetWindowRgn(RegionResult,TRUE); return;} //设置区域VOID CLayeredWindow::InitLayeredArea(CImageEx & Image, BYTE cbAlpha, CRect & rcUnLayered, CPoint & PointRound, bool bUnLayeredChild){ //创建缓冲 CImage ImageBuffer; ImageBuffer.Create(Image.GetWidth(),Image.GetHeight(),32); //绘画界面 CImageDC ImageDC(ImageBuffer); CDC * pBufferDC=CDC::FromHandle(ImageDC); //绘画界面 ASSERT(pBufferDC!=NULL); if (pBufferDC!=NULL) Image.DrawImage(pBufferDC,0,0); //创建分层 InitLayeredArea(pBufferDC,cbAlpha,rcUnLayered,PointRound,bUnLayeredChild); return;} //关闭消息VOID CLayeredWindow::OnClose(){ //投递消息 if (m_pWndControl!=NULL) { m_pWndControl->PostMessage(WM_CLOSE); } return;} //焦点消息VOID CLayeredWindow::OnSetFocus(CWnd * pOldWnd){ //设置焦点 if (m_pWndControl!=NULL) { m_pWndControl->SetFocus(); }} //枚举函数BOOL CALLBACK CLayeredWindow::EnumChildProc(HWND hWndChild, LPARAM lParam){ //获取位置 CRect rcWindow; ::GetWindowRect(hWndChild,&rcWindow); //创建区域 if ((rcWindow.Width()>0)&&(rcWindow.Height()>0)) { //变量定义 ASSERT(lParam!=0L); tagEnumChildInfo * pEnumChildInfo=(tagEnumChildInfo *)lParam; //窗口判断 HWND hWndParent=::GetParent(hWndChild); if (hWndParent!=pEnumChildInfo->pWndControl->GetSafeHwnd()) { return TRUE; } //转换位置 ASSERT(pEnumChildInfo->pWndControl!=NULL); pEnumChildInfo->pWndControl->ScreenToClient(&rcWindow); //创建区域 CRgn RgnChild; RgnChild.CreateRectRgn(rcWindow.left,rcWindow.top,rcWindow.right,rcWindow.bottom); //合并区域 ASSERT(pEnumChildInfo->pRegionResult!=NULL); pEnumChildInfo->pRegionResult->CombineRgn(pEnumChildInfo->pRegionResult,&RgnChild,RGN_DIFF); } return TRUE;}
代码中我们实现了两个最主要的关键函数,创建分层和初始化分层在CreateLayered中,我们创建了一个静态窗口,而且拥有WS_EX_LAYERED属性,这个很重要,这是实现透明的关键;其次InitLayeredArea中,我们根据加载的图片,通过UpdateLayeredWindow实现窗口的异形,可能很多朋友不理解,下面怎么还枚举窗口干啥啊,有什么作用呢,上文中我们说过这个窗口A遮挡了主窗口的控件,那么我们自然就要枚举主窗口的所有控件,从而统计出这些控件的位置,当然光镂空出控件的位置还不够,因为我们需要在主窗口上绘图啊,所以这里我们才会合并区域,如图所示,黑色区域代表异形窗口A,红色区域为主窗口B,绿色为主窗口上面的控件,那么我们通过CombineRgn的RGN_DIFF重新计算区域的大小,这样一来就把异形窗口A镂空出了红色区域显示的部分。
接下来我们在做窗口B,逻辑窗口,创建mfc类,取名CLayeredDialog,基类选择CDialog其实这个类实现起来很简单,无非就是调用窗口A的函数而已,但是有一点不要忘记,别忘记裁剪窗口B,否则是实现不了异形的类声明如下://////////////////////////////////////////////////////////////////////class CLayeredDialog : public CDialog{ DECLARE_DYNAMIC(CLayeredDialog) //变量定义protected: CImageEx m_ImageBack; //背景图片 CLayeredWindow m_LayeredWindow; //分层窗口 //函数定义public: //构造函数 CLayeredDialog(UINT uTemplate, CWnd* pParent = NULL); //析构函数 virtual ~CLayeredDialog(); protected: //控件绑定 virtual void DoDataExchange(CDataExchange* pDX); //绘画消息 virtual VOID OnDrawClientArea(CDC * pDC, INT nWidth, INT nHeight){} //基本函数public: //初始化 void SetLayeredDialog(HINSTANCE hInstance,LPCTSTR lpResource,CSize szSizeLT,CSize szSizeRB); //绘画背景 BOOL OnEraseBkgnd(CDC * pDC); //位置改变 VOID OnWindowPosChanged(WINDOWPOS * lpWndPos); //显示消息 VOID OnShowWindow(BOOL bShow, UINT nStatus); //去除边框 void RemoveBorder(); DECLARE_MESSAGE_MAP()};//////////////////////////////////////////////////////////////////////BEGIN_MESSAGE_MAP(CLayeredDialog, CDialog) ON_WM_ERASEBKGND() ON_WM_SHOWWINDOW() ON_WM_WINDOWPOSCHANGED()END_MESSAGE_MAP() // CLayeredDialog 对话框 IMPLEMENT_DYNAMIC(CLayeredDialog, CDialog)////////////////////////////////////////////////////////////////////////// CLayeredDialog::CLayeredDialog(UINT uTemplate, CWnd* pParent /*=NULL*/): CDialog(uTemplate, pParent){} CLayeredDialog::~CLayeredDialog(){} void CLayeredDialog::DoDataExchange(CDataExchange* pDX){ CDialog::DoDataExchange(pDX);} void CLayeredDialog::SetLayeredDialog( HINSTANCE hInstance,LPCTSTR lpResource,CSize szSizeLT,CSize szSizeRB ){ //设置窗口 RemoveBorder(); //加载资源 m_ImageBack.LoadImage(hInstance,lpResource); //设置大小 CSize SizeWindow(m_ImageBack.GetWidth(),m_ImageBack.GetHeight()); SetWindowPos(NULL,0,0,SizeWindow.cx,SizeWindow.cy,SWP_NOZORDER|SWP_NOMOVE|SWP_NOREDRAW); //获取窗口 CRect rcWindow; GetWindowRect(&rcWindow); //计算位置 CRect rcUnLayered; rcUnLayered.top=szSizeLT.cy; rcUnLayered.left=szSizeLT.cx; rcUnLayered.right=rcWindow.Width()-szSizeRB.cx; rcUnLayered.bottom=rcWindow.Height()-szSizeRB.cy; //设置区域 CRgn RgnWindow; RgnWindow.CreateRoundRectRgn(rcUnLayered.left,rcUnLayered.top,rcUnLayered.right+1,rcUnLayered.bottom+1,0,0); //设置区域 SetWindowRgn(RgnWindow,FALSE); //分层窗口 m_LayeredWindow.CreateLayered(this,rcWindow); m_LayeredWindow.InitLayeredArea(m_ImageBack,200,rcUnLayered,CPoint(0,0),false);} void CLayeredDialog::RemoveBorder(){ DWORD dwStyle = GetStyle(); DWORD dwNewStyle = WS_OVERLAPPED | WS_VISIBLE| WS_SYSMENU |WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_CLIPCHILDREN|WS_CLIPSIBLINGS; dwNewStyle&=dwStyle; SetWindowLong(m_hWnd,GWL_STYLE,dwNewStyle); DWORD dwExStyle = GetExStyle(); DWORD dwNewExStyle = WS_EX_LEFT |WS_EX_LTRREADING |WS_EX_RIGHTSCROLLBAR; dwNewExStyle&=dwExStyle; SetWindowLong(m_hWnd,GWL_EXSTYLE,dwNewExStyle);} //绘画背景BOOL CLayeredDialog::OnEraseBkgnd(CDC * pDC){ //获取位置 CRect rcClient; GetClientRect(&rcClient); //建立缓冲 CImage ImageBuffer; ImageBuffer.Create(rcClient.Width(),rcClient.Height(),32); if ( ImageBuffer.IsNull() ) return TRUE; //创建 DC CImageDC BufferDC(ImageBuffer); CDC * pBufferDC=CDC::FromHandle(BufferDC); //设置缓冲 pBufferDC->SetBkMode(TRANSPARENT); //绘画背景 m_ImageBack.DrawImage(pBufferDC,0,0); OnDrawClientArea(pBufferDC,rcClient.Width(),rcClient.Height()); //绘画界面 pDC->BitBlt(0,0,rcClient.Width(),rcClient.Height(),pBufferDC,0,0,SRCCOPY); return TRUE;} //显示消息VOID CLayeredDialog::OnShowWindow(BOOL bShow, UINT nStatus){ __super::OnShowWindow(bShow, nStatus); //显示分层 if (m_LayeredWindow.m_hWnd!=NULL) { m_LayeredWindow.ShowWindow((bShow==FALSE)?SW_HIDE:SW_SHOW); } return;} //位置改变VOID CLayeredDialog::OnWindowPosChanged(WINDOWPOS * lpWndPos){ __super::OnWindowPosChanging(lpWndPos); //移动分层 if ((m_LayeredWindow.m_hWnd!=NULL)&&(lpWndPos->cx>=0)&&(lpWndPos->cy>0)) { m_LayeredWindow.SetWindowPos(NULL,lpWndPos->x,lpWndPos->y,lpWndPos->cx,lpWndPos->cy,SWP_NOZORDER); } return;}这里主要的函数就一个SetLayeredDialog,通过这里,我们裁剪窗口B,同时创建窗口A,这里的代码很简单,不过多讲解了,这个函数我在后面写了两个参数,通过这两个去裁剪窗口大小,上图所示,第一个代表黑色区域左上角到红色区域的左上角的位置;第二个参数同理。这里我因为做演示用,就实现了一个矩形区域,当然你可以通过CRgn实现一个特殊的多边形,这样实例demo中的开始按钮就可以显示出来了,有兴趣的朋友可以修改一下,就几行代码的事情。关于CImageEx类,是CImage的封装类,大家自行封装一下即可
将主对话框的基类CDialog全部替换成CLayeredDialog,然后在OnInitDialog()中SetLayeredDialog(AfxGetInstanceHandle(),TEXT('VIEW_BACK'),CSize(120,200),CSize(162,145));
当然如果你想绘制图形,你可以重载OnDrawClientArea进行绘制VOID CLayeredDlg::OnDrawClientArea( CDC * pDC, INT nWidth, INT nHeight ){ pDC->TextOut(500,550,TEXT('其实这种窗口实现起来很简单'));}
SetWindowRgn裁剪窗口大小,但是并不代表裁剪掉了原始窗口的尺寸大小,它只是设置了一个窗口的区域.只有被包含在这个区域内的地方才会被重绘,而不包含在区域内的其他区域系统将不会显示.