桌面下雪程式的編寫

mkdym發表於2011-11-29
一. 綜述
考慮到雪花將會很多,並且每個雪花都有自己的行為路徑,統一處理比較麻煩,因此自定義一個類CSnowflake,它所呈現的主要介面有兩個:下落和“死亡”判斷。下落路徑由雪花物件自身處理,主框架中只是採用定時器來控制其下落。當然,雪花落到螢幕底後就相當於“死亡”了,為了保持活動雪花總數大致不變,我又開啟了一個定時器,用來產生雪花。在使用者互動上我做了一個托盤,可以顯示提示,右鍵彈出選單。還有一個小問題——程式執行之後即隱藏介面,自己試了許多方法,也在網上差了許多,最後還是在訊息WM_WINDOWPOSCHANGING響應中新增lpwndpos->flags&=SWP_HIDEWINDOW並且去掉MFC生成的程式碼這個方法來的徹底。
二. 程式顯示
1. 雪花
 
2. 托盤

 


三. 雪花snowflake類
主要描述其下落方法。
BOOL CSnowflake::Down()
{
	if (bDie)
		return FALSE;


	CRect rtNewLocation;
	srand((UINT)time(NULL));//隨機種子


	if (rand()%2)
		rtNewLocation.left=rtLocation.left+rand()%10;
	else
		rtNewLocation.left=rtLocation.left-rand()%5;


	rtNewLocation.right=rtNewLocation.left+rtLocation.Width();
	rtNewLocation.top=rtLocation.top+iSpeed;
	rtNewLocation.bottom=rtNewLocation.top+rtLocation.Height();


	if (rtNewLocation.bottom>=rtDesktop.bottom)//超出繪製螢幕
	{
		bDie=TRUE;//設定死亡標誌
		return FALSE;
	}
	else//下落
	{
		//擦除原雪花
		RedrawWindow(hwndDesktop,&rtLocation,NULL,RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW);


		HDC hDesktopDC=GetDC(hwndDesktop);
		CDC desktopDC;
		desktopDC.Attach(hDesktopDC);//桌面視窗DC


		CBitmap bmp;
		switch(bmpID)
		{
		case 0:
			bmp.LoadBitmap(IDB_BITMAP1);
			break;
		case 1:
			bmp.LoadBitmap(IDB_BITMAP2);
			break;
		case 2:
			bmp.LoadBitmap(IDB_BITMAP3);
			break;
		case 3:
			bmp.LoadBitmap(IDB_BITMAP4);
			break;
		default:
			break;
		}


		//重繪原矩形區域
		CDC bmpDC;
		bmpDC.CreateCompatibleDC(&desktopDC);


		CBitmap *poldbmp=bmpDC.SelectObject(&bmp);
		desktopDC.TransparentBlt(rtNewLocation.left,rtNewLocation.top,rtNewLocation.Width(),rtNewLocation.Height(),
			&bmpDC,0,0,rtNewLocation.Width(),rtNewLocation.Height(),RGB(0,0,0));//將底色白色設為透明


		bmpDC.SelectObject(poldbmp);


		desktopDC.Detach();
		ReleaseDC(hwndDesktop,hDesktopDC);


		rtLocation=rtNewLocation;//賦新位置
		return TRUE;
	}
}




說明:
其中的hwndDesktop是在建構函式中使用以下程式碼獲得的
HWND hProgMan=::FindWindowW(L"ProgMan",NULL);
if(hProgMan)
{
	HWND hShellDefView=::FindWindowEx(hProgMan,NULL,L"SHELLDLL_DefView",NULL);
	if(hShellDefView)
		hwndDesktop=::FindWindowEx(hShellDefView,NULL,L"SysListView32",L"FolderView");
}
if (hwndDesktop==NULL)
	bDie=TRUE;


其中的rtLocation指的是雪花當前矩形位置,rtDesktop指的是繪製螢幕矩形範圍。


點陣圖我畫了四個,隨機選擇一個。
四. 主對話方塊中的處理
1. 定時器處理
void CSnow2Dlg::OnTimer(UINT_PTR nIDEvent)
{
	switch(nIDEvent)
	{
	case 1://控制雪花下落
		{
			if(WAIT_TIMEOUT==WaitForSingleObject(m_handleEvent,100))
				break;
			ResetEvent(m_handleEvent);


			std::vector<CSnowflake> tempflakes;
			for (std::vector<CSnowflake>::iterator iter=snowflakes.begin();iter!=snowflakes.end();++iter)
			{
				if (iter->IsDie()==FALSE)
				{
					iter->Down();
					tempflakes.push_back(*iter);
				}
			}


			snowflakes.clear();
			snowflakes=tempflakes;


			SetEvent(m_handleEvent);
		}
		break;
	case 2://判斷雪花死亡狀態,產生新雪花
		{
			if(WAIT_TIMEOUT==WaitForSingleObject(m_handleEvent,100))
				break;
			ResetEvent(m_handleEvent);


			if (snowflakes.size()<MAX_COUNT_FLAKES)
			{
				srand(static_cast<UINT>(time(NULL)));
				static int count=1;
				for (int i=0;i!=count;++i)
				{
					CSnowflake flake(rand()%MAX_BMP_COUNT,15,15,rand()%m_iDesktopWidth+1,rand()%5+2,
						CRect(0,0,m_iDesktopWidth,m_iDesktopHeight));
					snowflakes.push_back(flake);
				}
				++count;
				if (count>10)
					count=10;
			}


			SetEvent(m_handleEvent);
		}
		break;
	default:
		break;
	}


	CDialog::OnTimer(nIDEvent);
}


說明:此處有一std::vector<CSnowflake>型別的snowflakes成員變數,這個儲存了當前所有活動雪花,若雪花已“死”,將會被移除出此向量,這樣“死亡”的雪花就可在螢幕工作列積累。然而在兩個定時器中都會訪問這個向量,於是為了防止訪問衝突,設定了一個同步事件m_handleEvent。


2. 托盤處理
托盤的新增是在OnInitDialog中的:
m_nid.cbSize=sizeof(NOTIFYICONDATA);
m_nid.hWnd=this->m_hWnd;
m_nid.uID=IDR_MAINFRAME;
m_nid.uFlags=NIF_ICON|NIF_MESSAGE|NIF_TIP|NIF_INFO;
m_nid.uCallbackMessage=UM_TRAY;//自定義的訊息名稱
m_nid.hIcon=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDR_MAINFRAME));
wcscpy_s(m_nid.szTip,L"桌面下雪程式");//資訊提示條
wcscpy_s(m_nid.szInfo,L"哦,下雪了");//資訊提示條
wcscpy_s(m_nid.szInfoTitle,L"桌面下雪程式提示");//資訊提示條
m_nid.dwInfoFlags=NIIF_INFO;
Shell_NotifyIcon(NIM_ADD,&m_nid);//在托盤區新增圖示


對其圖示的訊息處理函式為:
LRESULT CSnow2Dlg::OnTray(WPARAM wParam,LPARAM lParam)
{
	if(wParam!=IDR_MAINFRAME) 
		return 1;


	switch(lParam) 
	{
	case WM_RBUTTONDOWN:
		{
			CPoint pos;
			GetCursorPos(&pos);//得到滑鼠位置 
			CMenu menu;
			menu.LoadMenuW(IDR_TRAYMENU);
			CMenu *psubmenu=menu.GetSubMenu(0);
			SetForegroundWindow(); //使在選單外點選時選單消失
			psubmenu->TrackPopupMenu(TPM_LEFTALIGN,pos.x,pos.y,this);//確定彈出式選單的位置
		} 
		break;
	default:
		break;
	} 


	return 0;
}


此處有一個右鍵彈出選單。選單的命令響應就不列出了。


五. 結束語
1. Bug
點選托盤選單時,雪花會停止下落。
2. 說明
本程式只適於靜態桌面環境下。
3. 奮鬥無止境
4. 程式碼下載地址
點選開啟連結

相關文章