利用GDI+基于WIN32实现桌面雪花效果(一)

来源:互联网 发布:sdrsharp软件下载 编辑:程序博客网 时间:2024/06/05 10:01

前言:毕业论文写完了,闲着没事干,研究研究这些一直想做的效果,先从桌面飘雪花开始,下面是过程记录。最后给出代码,供大家参考。

效果图:(桌面局部截图)


一、创建空WIN32工程并初始化

1、创建空WIN32工程(snow)

2、设置

项目-》属性-》配置属性-》MFC的使用-》在静态库中使用MFC

3、初始化GDI+和MFC库函数

新建一个Common.h文件,用来存放一些公用的结构体及代码,并在其中初始化GDI+,在其中放下如下代码来初始化GDI+

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <gdiplus.h>  
  2. #pragma comment(lib,"GdiPlus.lib")  
  3. using namespace Gdiplus;    

在stdafx.h中添加如下代码:(添加对MFC类和所有函数库支持)

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <math.h>   
  2.   
  3. #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS  // 某些 CString 构造函数将是显式的  
  4.   
  5. // 关闭 MFC 对某些常见但经常可放心忽略的警告消息的隐藏  
  6. #define _AFX_ALL_WARNINGS  
  7.   
  8. #include <afxwin.h>         // MFC 核心组件和标准组件  
  9. #include <afxext.h>         // MFC 扩展  
  10. #include <afxdisp.h>        // MFC 自动化类  
  11.   
  12. #ifndef _AFX_NO_OLE_SUPPORT  
  13. #include <afxdtctl.h>     // MFC 对 Internet Explorer 4 公共控件的支持  
  14. #endif  
  15. #ifndef _AFX_NO_AFXCMN_SUPPORT  
  16. #include <afxcmn.h>           // MFC 对 Windows 公共控件的支持  
  17. #endif // _AFX_NO_AFXCMN_SUPPORT  

二、新建应用程序入口类(派生自CWinApp)

新建一个类(CSnowApp),该类派生自MFC的CWinApp类,(在头文件右击-》添加-》类)

头文件代码如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #pragma once  
  2.   
  3. #include "Common.h"  
  4.   
  5. // CSnowApp  
  6.   
  7. class CSnowApp : public CWinApp  
  8. {  
  9.     DECLARE_DYNCREATE(CSnowApp)  
  10.   
  11. public:  
  12.     CSnowApp();           // 动态创建所使用的受保护的构造函数  
  13.     virtual ~CSnowApp();  
  14. public:  
  15.     virtual BOOL InitInstance();  
  16.     virtual int ExitInstance();  
  17. protected:  
  18.     DECLARE_MESSAGE_MAP()  
  19. public:  
  20.     ULONG_PTR m_gdiplusToken;    
  21.   
  22.     BOOL RegisterClass(LPCTSTR lpszClassName);  
  23. };  
  24.   
  25. extern CSnowApp theApp;  
这里重写了CWinApp的两个函数,InitInstance()和ExitInstance();

然后自己写了一个窗口类注册函数,完整代码如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. BOOL CSnowApp::RegisterClass(LPCTSTR lpszClassName)  
  2. {  
  3.     WNDCLASS wndcls;  
  4.     memset(&wndcls,0,sizeof(WNDCLASS));  
  5.     wndcls.style=CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW;  
  6.     wndcls.lpfnWndProc=::DefWindowProc;  
  7.     wndcls.hInstance=AfxGetInstanceHandle();  
  8.     wndcls.hIcon=NULL;  
  9.     wndcls.hCursor=::LoadCursor(NULL,IDC_ARROW);  
  10.     wndcls.hbrBackground=(HBRUSH)(COLOR_BTNFACE+1);  
  11.     wndcls.lpszMenuName=NULL;  
  12.     wndcls.lpszClassName=lpszClassName;  
  13.     if(!AfxRegisterClass(&wndcls))  
  14.     {  
  15.         TRACE("Class Registration Failed\n");  
  16.         return FALSE;  
  17.     }  
  18.     return TRUE;  
  19. }  
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. BOOL CSnowApp::InitInstance()  
  2. {  
  3.     // TODO: 在此执行任意逐线程初始化  
  4.   
  5.     //初始化GDI+  
  6.     GdiplusStartupInput gdiplusStartupInput;  
  7.     GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);  
  8.   
  9.     //初始化应用程序  
  10.     CWinApp::InitInstance();  
  11.   
  12.     //注册窗口类  
  13.     if (!RegisterClass(L"CometAnimationUI"))  
  14.     {  
  15.         return FALSE;  
  16.     }  
  17.   
  18.     //创建窗口  
  19.     CSnowWindow snowWnd;  
  20.   
  21.     snowWnd.Create(NULL);  
  22.   
  23.     //将snowWnd作为主窗体HWND,传给m_pMainWnd,供其显示窗体  
  24.     m_pMainWnd=&snowWnd;  
  25.   
  26.     Run();  
  27.   
  28.     return TRUE;  
  29. }  
  30.   
  31. int CSnowApp::ExitInstance()  
  32. {  
  33.     // TODO: 在此执行任意逐线程清理  
  34.     GdiplusShutdown(m_gdiplusToken);  
  35.   
  36.     return CWinApp::ExitInstance();  
  37. }  
这里主要讲解InitInstance()的流程,这是应用程序初始化函数,它首先对GDI+进行初始化,然后注册窗口类,利用CSowWnd类创建窗口,然后将snowWnd作为主窗体HWND,传给m_pMainWnd,供其显示窗体。最后利用Run()函数进入消息循环。流程与WIN32窗体的创建过程一样,只是这里是经过CWinApp类封装过的,所以流程显得不那么明显。

三、雪花窗体显示

这里分析两个问题:

  • 我们并不是将每个雪花创建一个窗口,而是将当前屏幕做为窗体,在它上面画图而已。
  • 雪花的种类是有限的,即原始图片,这里加载了六个,而大家可以看到,整个屏幕的雪花量却是很大的,所以我们要建一个基类完成原始图像加载、窗体刷新等功能。而雪花类则建立在此基类的基础上,完成雪花的下落、移动等功能。

1、定义单个雪花图片对象

该对象包含每个雪花显示所需的所有信息,结构体定义如下:
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. typedef struct tagAnimationImage  
  2. {  
  3.     Gdiplus::Image* pImage;  
  4.     int X;              //图片的X坐标  
  5.     int Y;              //图片的Y坐标  
  6.     int Width;          //图片显示高度  
  7.     int Height;         //图片显示宽度  
  8.     int Angle;          //旋转角度  
  9.     bool firstInit;     //是否初步初始化,用于在初次初始化时设定该雪花是往左走还是往右走还是直线下落  
  10.     int OffsetMode;     //图片的行走方式,向左、向右、向下三种  
  11. } AnimationImage, *PAnimationImage,*LPAnimationImage;  

2、雪花窗体基类CLayeredWnd(派生自CWnd)

CLayeredWnd头文件如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. class CLayeredWnd : public CWnd  
  2. {  
  3.     DECLARE_DYNAMIC(CLayeredWnd)  
  4.   
  5. public:  
  6.     CLayeredWnd();  
  7.     virtual ~CLayeredWnd();  
  8. protected:  
  9.     DECLARE_MESSAGE_MAP()  
  10.   
  11. public:  
  12.     int m_nWidth;  
  13.     int m_nHeight;  
  14.     CArray<LPAnimationImage,LPAnimationImage> m_ImageArray;//图片数组  
  15.     int m_ImageCount;//图片数量  
  16. public:  
  17.     // 载入图片  
  18.     BOOL LoadImage(LPAnimationImage pImage,LPCTSTR lpName);  
  19.     // 添加图片到数组  
  20.     LPAnimationImage AddImage(LPCTSTR lpName);  
  21.     // 释放图片数组  
  22.     void ReleaseImage();  
  23.     // 重新绘制窗口  
  24.     void ReDrawWindow(void);  
  25.     // 虚函数 绘制窗口  
  26.     virtual void OnDrawWindow(Gdiplus::Graphics* pGraphics);  
  27. };  
成员变量讲解:

m_ImageArray:存储原始图片,即加载的六张雪花图;

m_ImageCount:数组的长度,即存储的原始图片的个数;

成员函数就不讲解功能了,每个函数上面都有标注;

具体实现:

加载图片:LoadImage()

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. BOOL CLayeredWnd::LoadImage(LPAnimationImage pImage,LPCTSTR lpPath)  
  2. {  
  3.     //初始化空间  
  4.     if(pImage->pImage)  
  5.     {  
  6.         delete pImage->pImage;  
  7.         pImage->pImage=NULL;  
  8.     }  
  9.     ZeroMemory(pImage,sizeof(AnimationImage));  
  10.   
  11.     //加载图片  
  12.     pImage->pImage = Gdiplus::Image::FromFile(lpPath);    
  13.     if(!pImage->pImage)  
  14.         return FALSE;  
  15.     pImage->Width=pImage->pImage->GetWidth();  
  16.     pImage->Height=pImage->pImage->GetHeight();  
  17.   
  18.     return TRUE;  
  19. }  
根据给定的地址加载图片,并设计图片的高度和宽度信息。
增加图片:AddImage()

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. LPAnimationImage CLayeredWnd::AddImage(LPCTSTR lpPath)  
  2. {  
  3.     LPAnimationImage pImage=new AnimationImage;  
  4.     ZeroMemory(pImage,sizeof(AnimationImage));  
  5.     LoadImage(pImage,lpPath);  
  6.     m_ImageArray.Add(pImage);  
  7.     m_ImageCount=m_ImageArray.GetCount();  
  8.     return pImage;  
  9. }  
根据给定的路径加载图片,并将其添加到数组中;
重绘窗口:ReDrawWindow

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void CLayeredWnd::ReDrawWindow(void)  
  2. {  
  3.     HDC hDC=::GetDC(m_hWnd);  
  4.     HDC hMemDC=::CreateCompatibleDC(hDC);  
  5.     BITMAPINFO bitmapinfo;  
  6.     bitmapinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);  
  7.     bitmapinfo.bmiHeader.biBitCount = 32;  
  8.     bitmapinfo.bmiHeader.biHeight = m_nHeight;  
  9.     bitmapinfo.bmiHeader.biWidth = m_nWidth;  
  10.     bitmapinfo.bmiHeader.biPlanes = 1;  
  11.     bitmapinfo.bmiHeader.biCompression=BI_RGB;  
  12.     bitmapinfo.bmiHeader.biXPelsPerMeter=0;  
  13.     bitmapinfo.bmiHeader.biYPelsPerMeter=0;  
  14.     bitmapinfo.bmiHeader.biClrUsed=0;  
  15.     bitmapinfo.bmiHeader.biClrImportant=0;  
  16.     bitmapinfo.bmiHeader.biSizeImage = bitmapinfo.bmiHeader.biWidth * bitmapinfo.bmiHeader.biHeight * bitmapinfo.bmiHeader.biBitCount / 8;  
  17.     HBITMAP hBitmap=CreateDIBSection (hMemDC,&bitmapinfo, 0,NULL, 0, 0);  
  18.     HBITMAP hOldBitmap = (HBITMAP)SelectObject (hMemDC,hBitmap);  
  19.     Graphics g(hMemDC);  
  20.     //-------------------------------------------------------------  
  21.     OnDrawWindow(&g);  
  22.     //设置透明窗口-------------------------------------------------  
  23.     CPoint DestPt(0,0);  
  24.     CSize psize(m_nWidth,m_nHeight);  
  25.     BLENDFUNCTION blendFunc32bpp;  
  26.     blendFunc32bpp.AlphaFormat = AC_SRC_ALPHA;  
  27.     blendFunc32bpp.BlendFlags = 0;  
  28.     blendFunc32bpp.BlendOp = AC_SRC_OVER;  
  29.     blendFunc32bpp.SourceConstantAlpha = 255;  
  30.     ::UpdateLayeredWindow(m_hWnd,hDC,NULL,&psize,hMemDC,&DestPt,0,&blendFunc32bpp,ULW_ALPHA);  
  31.     //释放资源-------------------------------------------------  
  32.     ::SelectObject (hMemDC,hOldBitmap);  
  33.     ::DeleteObject(hBitmap);  
  34.     ::DeleteDC(hMemDC);  
  35.     ::ReleaseDC(m_hWnd,hDC);  
  36. }  
这里注意一点:首先是创建一个空白画布,然后调用虚函数OnDrawWindow()来画图,由于下面我们将在CLayeredWnd的基础上派生出CSnowWnd,所以在CSnowWindow我们会重新实现这个绘图函数,在这个函数中实现雪花的下移和平移功能。

最后在整个画布完成以后,利用UpdateLayeredWindow更新到窗体上显示出来。

3、雪花显示窗体类CSnowWindow

头文件内容如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. class CSnowWindow :  
  2.     public CLayeredWnd  
  3. {  
  4. public:  
  5.     CSnowWindow(void);  
  6.     ~CSnowWindow(void);  
  7. private:  
  8.     CArray<LPAnimationImage,LPAnimationImage> m_SnowArray;//雪花图片数组  
  9.     int m_SnowCount;//雪花图片数量  
  10.     int m_RowCount;//每行图片数量  
  11.     int m_AllCount;//可显示图片总数  
  12. public:  
  13.     DECLARE_MESSAGE_MAP()  
  14.     afx_msg void OnTimer(UINT_PTR nIDEvent);  
  15. public:  
  16.     //创建窗体  
  17.     BOOL Create(HWND hWndParent=NULL);  
  18.     //初始化初次显示界面  
  19.     void InitImage();             
  20.     //添加原始图片资源到CLayeredWnd的m_ImageArray中  
  21.     void AddResImg(LPCTSTR lpName,int nAngle);  
  22.     //向m_SnowArray添加雪花图片  
  23.     void AddSnow(int nCount);     
  24.     //下移雪花  
  25.     void DownSnow();              
  26.     //根据上限和下限,随机产生一个数字  
  27.     int GetRndNum(int nMin,int nMax);  
  28. public:  
  29.     //绘制当前界面,重写CLayeredWnd的此虚函数  
  30.     virtual void OnDrawWindow(Gdiplus::Graphics* pGraphics);  
  31. };  
由于每个变量和函数都对应有标注,这里就不再叙述了,下面看具体实现:
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void CSnowWindow::InitImage()  
  2. {  
  3.     AddResImg(_T("C:\\Snow01.png"),0);  
  4.     AddResImg(_T("C:\\Snow02.png"),0);  
  5.     AddResImg(_T("C:\\Snow03.png"),-1);  
  6.     AddResImg(_T("C:\\Snow04.png"),-1);  
  7.     AddResImg(_T("C:\\Snow05.png"),0);  
  8.     AddResImg(_T("C:\\Snow06.png"),0);  
  9.     AddResImg(_T("C:\\Snow07.png"),0);  
  10.     AddResImg(_T("C:\\Snow08.png"),0);  
  11.     AddResImg(_T("C:\\Snow09.png"),0);  
  12.     AddResImg(_T("C:\\Snow10.png"),0);  
  13.     AddResImg(_T("C:\\Snow11.png"),0);  
  14.     AddResImg(_T("C:\\Snow12.png"),0);  
  15.   
  16.   
  17.     m_RowCount=m_nWidth/(m_ImageArray[0]->Width+20);  
  18.     m_AllCount=m_RowCount*(m_nHeight/(m_ImageArray[0]->Height+20));  
  19.     AddSnow(m_RowCount);  
  20. }  
  21. //添加要显示的雪花图片到CLayeredWnd的m_ImageArray中  
  22. void CSnowWindow::AddResImg(LPCTSTR lpName,int nAngle)  
  23. {  
  24.     LPAnimationImage pImage=AddImage(lpName);  
  25.     pImage->Angle=nAngle;  
  26. }  
  27. //添加雪花图片  
  28. void CSnowWindow::AddSnow(int nCount)  
  29. {  
  30.     for(int i=0;i<nCount;i++)  
  31.     {  
  32.         //随机取一张图片  
  33.         int nIndex=GetRndNum(0,m_ImageCount-1);  
  34.         LPAnimationImage pImage=new AnimationImage;  
  35.         LPAnimationImage pSrcImage=m_ImageArray[nIndex];  
  36.         CopyMemory(pImage,pSrcImage,sizeof(AnimationImage));  
  37.   
  38.         //随机设置图片的初始位置  
  39.         pImage->X=GetRndNum(0,m_nWidth);  
  40.         pImage->Y=0-GetRndNum(pImage->Height,pImage->Height*2);  
  41.   
  42.         //随机缩放图片  
  43.         float f=(float)GetRndNum(50,100);  
  44.         f=f/(float)100;  
  45.         pImage->Width=(int)((float)pImage->Width*f);  
  46.         pImage->Height=(int)((float)pImage->Height*f);  
  47.   
  48.         pImage->firstInit=true;//初始化为TRUE,此参数用来判定是否首次初始化OffsetMode参数  
  49.   
  50.         m_SnowArray.Add(pImage);  
  51.   
  52.     }  
  53.     m_SnowCount=m_SnowArray.GetCount();  
  54. }  
讲解:
1、AddResImg()实现根据图片路径将图片加载到CLayeredWnd的m_ImageArray中

2、AddSnow()实现随机从m_ImageArray中取一张图片,并对其初始化,最后将其添加到m_SnowArray中。

3、InitImage()就是实现了初次显示的雪花图片的初始化工作,AddSnow(m_RowCount);将每行所具有的雪花个数的图片,以第一行显示出来。

在初次顶端雪花初始化完成以后,就要定时将雪花图片移动(下移和平移)

1、设置定时器

由于是定时刷新当前界面以使雪花下移,所以必定要设定定时器,并对WM_TIMER消息进行处理,下面看对WM_TIMER消息处理的代码:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void CSnowWindow::OnTimer(UINT_PTR nIDEvent)  
  2. {  
  3.     // TODO: 在此添加消息处理程序代码和/或调用默认值  
  4.     CLayeredWnd::OnTimer(nIDEvent);  
  5.     DownSnow();  
  6. }  
从这里可以看到,每次产生的定时脉冲所做的事情就是将雪花下移。下面看DownSnow()的代码:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //下移雪花图片  
  2. void CSnowWindow::DownSnow()  
  3. {  
  4.     int nTop=25;  
  5.     for(int i=m_SnowCount-1;i>=0;i--)  
  6.     {  
  7.         LPAnimationImage pImage=m_SnowArray[i];  
  8.         pImage->Y+=5;//下移  
  9.         if(pImage->Y>m_nHeight)//超出屏幕高度  
  10.         {  
  11.             m_SnowArray.RemoveAt(i);//移除这张图片  
  12.             delete pImage;  
  13.             continue;//转到下一次循环  
  14.         }  
  15.         //-------------  
  16.         if(pImage->Y<nTop)  
  17.             nTop=pImage->Y;//找到当前最后一个雪花离屏幕顶部的距离  
  18.         //横向移动   
  19.         if (pImage->firstInit)  
  20.         {  
  21.             pImage->firstInit=false;  
  22.             pImage->OffsetMode=GetRndNum(1,3);  
  23.         }  
  24.         switch (pImage->OffsetMode)  
  25.         {  
  26.         case 1:  
  27.             pImage->X--;  
  28.             break;  
  29.         case 2:  
  30.             pImage->X++;  
  31.             break;  
  32.         }  
  33.     }  
  34.     m_SnowCount=m_SnowArray.GetCount();  
  35.     int nCount=m_AllCount-m_SnowCount;  
  36.     if(nCount>0 && nTop>20)//设定每行的竖向间隔,这里设定每20个像素显示一行雪花  
  37.     {  
  38.         if(nCount>m_RowCount)  
  39.             nCount=m_RowCount;  
  40.         AddSnow(nCount);  
  41.     }  
  42.     ReDrawWindow();  
  43. }  
讲解:
1、for循环中对每个雪花进行平移和下移。

2、如何产生下一行雪花。

注意这里有几句代码:

首先定义了一个距离,25个像素。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="color:#006600;">int nTop=25;</span>  
这句处在FOR循环内,由于pImage->Y每次都会递加5,所以到最后就会出现一种情况,其它雪花纵坐标都大于25,只有一个雪花的纵坐标小于25,所以这句的目的就是查找最后一个仍小于nTop的值。由于Y值每次递加5,所以最后一个Y值肯定落在20-25之间。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if(pImage->Y<nTop)  
  2.     nTop=pImage->Y;//找到当前最后一个雪花离屏幕顶部的距离  

由于当雪花落在屏幕边界外后,会将雪花从数组中删除,而且随着雪花的下落,屏幕上还要继续落雪花,新产生的行所具有的要求是:

(1)、数量不能超,而且不能少

这里使用int nCount=m_AllCount-m_SnowCount;nCount=m_RowCount;来设定每行要显示的雪花数量。

(2)、每行雪花的行距

上面说了利用nTop来找到最后一个雪花离屏幕的距离(20-25),所以当nTop>20的时候,说明是时候显示下一行了,这时才可以显示,不然就挤在一块了。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. m_SnowCount=m_SnowArray.GetCount();  
  2. int nCount=m_AllCount-m_SnowCount;  
  3. if(nCount>0 && nTop>20)//设定每行的竖向间隔,这里设定每20个像素显示一行雪花  
  4. {  
  5.     if(nCount>m_RowCount)  
  6.         nCount=m_RowCount;  
  7.     AddSnow(nCount);  
  8. }  

3、调用CLayeredWnd的ReDrawWindow()

由于在ReDrawWindow中调用了虚函数OnDrawWindow(),根据虚函数性质,会调用CSnowWindow重写的OnDrawWindow函数,看OnDrawWindow的实现:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // 虚函数 绘制窗口  
  2. void CSnowWindow::OnDrawWindow(Gdiplus::Graphics* pGraphics)  
  3. {  
  4.     for(int i=0;i<m_SnowCount;i++)  
  5.     {  
  6.         LPAnimationImage pImage=m_SnowArray[i];  
  7.   
  8.         pGraphics->DrawImage(pImage->pImage,pImage->X,pImage->Y,pImage->Width,pImage->Height);  
  9.   
  10.     }  
  11. }  
实现方法就是将每一个雪花图片重新根据新坐标绘制出来,仅而而已。

到这里,本文雪花飘落相关的东东已经介绍完成了,下篇在此基础上完成雪花在下落过程中实现旋转的功能。


源码来啦:http://download.csdn.net/detail/harvic880925/6999829


请大家尊重原创,转载请标明处出,谢谢!!!本文出处:http://blog.csdn.net/harvic880925/article/details/20565125





相关网页:

《深入解析MFC -- CWinApp》:http://blog.sina.com.cn/s/blog_660ca10d0100lg0z.html 

《MFC中的CWnd类与Windows窗体的关系》:http://blog.tianya.cn/blogger/post_read.asp?BlogID=4361806&PostID=44293548

《桌面动画-雪花和兔子-源码分享》http://blog.csdn.net/cometnet/article/details/17332699 (感谢本文作者,参考了他的源码)

0 0
原创粉丝点击