桌面下雪程序的编写

时间:2022-05-14 05:00:24
一. 综述
考虑到雪花将会很多,并且每个雪花都有自己的行为路径,统一处理比较麻烦,因此自定义一个类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. 代码下载地址
点击打开链接