1. 程式人生 > >利用GDI+基於WIN32實現桌面雪花效果(一)

利用GDI+基於WIN32實現桌面雪花效果(一)

前言:畢業論文寫完了,閒著沒事幹,研究研究這些一直想做的效果,先從桌面飄雪花開始,下面是過程記錄。最後給出程式碼,供大家參考。

效果圖:(桌面區域性截圖)


一、建立空WIN32工程並初始化

1、建立空WIN32工程(snow)

2、設定

專案-》屬性-》配置屬性-》MFC的使用-》在靜態庫中使用MFC

3、初始化GDI+和MFC庫函式

新建一個Common.h檔案,用來存放一些公用的結構體及程式碼,並在其中初始化GDI+,在其中放下如下程式碼來初始化GDI+

#include <gdiplus.h>
#pragma comment(lib,"GdiPlus.lib")
using namespace Gdiplus;  

在stdafx.h中新增如下程式碼:(新增對MFC類和所有函式庫支援)

#include <math.h> 

#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS	// 某些 CString 建構函式將是顯式的

// 關閉 MFC 對某些常見但經常可放心忽略的警告訊息的隱藏
#define _AFX_ALL_WARNINGS

#include <afxwin.h>         // MFC 核心元件和標準組件
#include <afxext.h>         // MFC 擴充套件
#include <afxdisp.h>        // MFC 自動化類

#ifndef _AFX_NO_OLE_SUPPORT
#include <afxdtctl.h>		// MFC 對 Internet Explorer 4 公共控制元件的支援
#endif
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>			// MFC 對 Windows 公共控制元件的支援
#endif // _AFX_NO_AFXCMN_SUPPORT

二、新建應用程式入口類(派生自CWinApp)

新建一個類(CSnowApp),該類派生自MFC的CWinApp類,(在標頭檔案右擊-》新增-》類)

標頭檔案程式碼如下:

#pragma once

#include "Common.h"

// CSnowApp

class CSnowApp : public CWinApp
{
	DECLARE_DYNCREATE(CSnowApp)

public:
	CSnowApp();           // 動態建立所使用的受保護的建構函式
	virtual ~CSnowApp();
public:
	virtual BOOL InitInstance();
	virtual int ExitInstance();
protected:
	DECLARE_MESSAGE_MAP()
public:
	ULONG_PTR m_gdiplusToken;  

	BOOL RegisterClass(LPCTSTR lpszClassName);
};

extern CSnowApp theApp;
這裡重寫了CWinApp的兩個函式,InitInstance()和ExitInstance();

然後自己寫了一個視窗類註冊函式,完整程式碼如下:

BOOL CSnowApp::RegisterClass(LPCTSTR lpszClassName)
{
	WNDCLASS wndcls;
	memset(&wndcls,0,sizeof(WNDCLASS));
	wndcls.style=CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW;
	wndcls.lpfnWndProc=::DefWindowProc;
	wndcls.hInstance=AfxGetInstanceHandle();
	wndcls.hIcon=NULL;
	wndcls.hCursor=::LoadCursor(NULL,IDC_ARROW);
	wndcls.hbrBackground=(HBRUSH)(COLOR_BTNFACE+1);
	wndcls.lpszMenuName=NULL;
	wndcls.lpszClassName=lpszClassName;
	if(!AfxRegisterClass(&wndcls))
	{
		TRACE("Class Registration Failed\n");
		return FALSE;
	}
	return TRUE;
}
BOOL CSnowApp::InitInstance()
{
	// TODO: 在此執行任意逐執行緒初始化

	//初始化GDI+
	GdiplusStartupInput gdiplusStartupInput;
	GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);

	//初始化應用程式
	CWinApp::InitInstance();

	//註冊視窗類
	if (!RegisterClass(L"CometAnimationUI"))
	{
		return FALSE;
	}

	//建立視窗
	CSnowWindow snowWnd;

	snowWnd.Create(NULL);

	//將snowWnd作為主窗體HWND,傳給m_pMainWnd,供其顯示窗體
	m_pMainWnd=&snowWnd;

	Run();

	return TRUE;
}

int CSnowApp::ExitInstance()
{
	// TODO: 在此執行任意逐執行緒清理
	GdiplusShutdown(m_gdiplusToken);

	return CWinApp::ExitInstance();
}
這裡主要講解InitInstance()的流程,這是應用程式初始化函式,它首先對GDI+進行初始化,然後註冊視窗類,利用CSowWnd類建立視窗,然後將snowWnd作為主窗體HWND,傳給m_pMainWnd,供其顯示窗體。最後利用Run()函式進入訊息迴圈。流程與WIN32窗體的建立過程一樣,只是這裡是經過CWinApp類封裝過的,所以流程顯得不那麼明顯。

三、雪花窗體顯示

這裡分析兩個問題:

  • 我們並不是將每個雪花建立一個視窗,而是將當前螢幕做為窗體,在它上面畫圖而已。
  • 雪花的種類是有限的,即原始圖片,這裡載入了六個,而大家可以看到,整個螢幕的雪花量卻是很大的,所以我們要建一個基類完成原始影象載入、窗體重新整理等功能。而雪花類則建立在此基類的基礎上,完成雪花的下落、移動等功能。

1、定義單個雪花圖片物件

該物件包含每個雪花顯示所需的所有資訊,結構體定義如下:
typedef struct tagAnimationImage
{
	Gdiplus::Image* pImage;
	int X;				//圖片的X座標
	int Y;				//圖片的Y座標
	int Width;			//圖片顯示高度
	int Height;			//圖片顯示寬度
	int Angle;			//旋轉角度
	bool firstInit;		//是否初步初始化,用於在初次初始化時設定該雪花是往左走還是往右走還是直線下落
	int OffsetMode;		//圖片的行走方式,向左、向右、向下三種
} AnimationImage, *PAnimationImage,*LPAnimationImage;

2、雪花窗體基類CLayeredWnd(派生自CWnd)

CLayeredWnd標頭檔案如下:

class CLayeredWnd : public CWnd
{
	DECLARE_DYNAMIC(CLayeredWnd)

public:
	CLayeredWnd();
	virtual ~CLayeredWnd();
protected:
	DECLARE_MESSAGE_MAP()

public:
	int m_nWidth;
	int m_nHeight;
	CArray<LPAnimationImage,LPAnimationImage> m_ImageArray;//圖片陣列
	int m_ImageCount;//圖片數量
public:
	// 載入圖片
	BOOL LoadImage(LPAnimationImage pImage,LPCTSTR lpName);
	// 新增圖片到陣列
	LPAnimationImage AddImage(LPCTSTR lpName);
	// 釋放圖片陣列
	void ReleaseImage();
	// 重新繪製視窗
	void ReDrawWindow(void);
	// 虛擬函式 繪製視窗
	virtual void OnDrawWindow(Gdiplus::Graphics* pGraphics);
};
成員變數講解:

m_ImageArray:儲存原始圖片,即載入的六張雪花圖;

m_ImageCount:陣列的長度,即儲存的原始圖片的個數;

成員函式就不講解功能了,每個函式上面都有標註;

具體實現:

載入圖片:LoadImage()

BOOL CLayeredWnd::LoadImage(LPAnimationImage pImage,LPCTSTR lpPath)
{
	//初始化空間
	if(pImage->pImage)
	{
		delete pImage->pImage;
		pImage->pImage=NULL;
	}
	ZeroMemory(pImage,sizeof(AnimationImage));

	//載入圖片
	pImage->pImage = Gdiplus::Image::FromFile(lpPath);  
	if(!pImage->pImage)
		return FALSE;
	pImage->Width=pImage->pImage->GetWidth();
	pImage->Height=pImage->pImage->GetHeight();

	return TRUE;
}
根據給定的地址載入圖片,並設計圖片的高度和寬度資訊。
增加圖片:AddImage()
LPAnimationImage CLayeredWnd::AddImage(LPCTSTR lpPath)
{
	LPAnimationImage pImage=new AnimationImage;
	ZeroMemory(pImage,sizeof(AnimationImage));
	LoadImage(pImage,lpPath);
	m_ImageArray.Add(pImage);
	m_ImageCount=m_ImageArray.GetCount();
	return pImage;
}
根據給定的路徑載入圖片,並將其新增到陣列中;
重繪視窗:ReDrawWindow
void CLayeredWnd::ReDrawWindow(void)
{
	HDC hDC=::GetDC(m_hWnd);
	HDC hMemDC=::CreateCompatibleDC(hDC);
	BITMAPINFO bitmapinfo;
	bitmapinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bitmapinfo.bmiHeader.biBitCount = 32;
	bitmapinfo.bmiHeader.biHeight = m_nHeight;
	bitmapinfo.bmiHeader.biWidth = m_nWidth;
	bitmapinfo.bmiHeader.biPlanes = 1;
	bitmapinfo.bmiHeader.biCompression=BI_RGB;
	bitmapinfo.bmiHeader.biXPelsPerMeter=0;
	bitmapinfo.bmiHeader.biYPelsPerMeter=0;
	bitmapinfo.bmiHeader.biClrUsed=0;
	bitmapinfo.bmiHeader.biClrImportant=0;
	bitmapinfo.bmiHeader.biSizeImage = bitmapinfo.bmiHeader.biWidth * bitmapinfo.bmiHeader.biHeight * bitmapinfo.bmiHeader.biBitCount / 8;
	HBITMAP hBitmap=CreateDIBSection (hMemDC,&bitmapinfo, 0,NULL, 0, 0);
	HBITMAP hOldBitmap = (HBITMAP)SelectObject (hMemDC,hBitmap);
	Graphics g(hMemDC);
	//-------------------------------------------------------------
	OnDrawWindow(&g);
	//設定透明視窗-------------------------------------------------
	CPoint DestPt(0,0);
	CSize psize(m_nWidth,m_nHeight);
	BLENDFUNCTION blendFunc32bpp;
	blendFunc32bpp.AlphaFormat = AC_SRC_ALPHA;
	blendFunc32bpp.BlendFlags = 0;
	blendFunc32bpp.BlendOp = AC_SRC_OVER;
	blendFunc32bpp.SourceConstantAlpha = 255;
	::UpdateLayeredWindow(m_hWnd,hDC,NULL,&psize,hMemDC,&DestPt,0,&blendFunc32bpp,ULW_ALPHA);
	//釋放資源-------------------------------------------------
	::SelectObject (hMemDC,hOldBitmap);
	::DeleteObject(hBitmap);
	::DeleteDC(hMemDC);
	::ReleaseDC(m_hWnd,hDC);
}
這裡注意一點:首先是建立一個空白畫布,然後呼叫虛擬函式OnDrawWindow()來畫圖,由於下面我們將在CLayeredWnd的基礎上派生出CSnowWnd,所以在CSnowWindow我們會重新實現這個繪圖函式,在這個函式中實現雪花的下移和平移功能。

最後在整個畫布完成以後,利用UpdateLayeredWindow更新到窗體上顯示出來。

3、雪花顯示窗體類CSnowWindow

標頭檔案內容如下:

class CSnowWindow :
	public CLayeredWnd
{
public:
	CSnowWindow(void);
	~CSnowWindow(void);
private:
	CArray<LPAnimationImage,LPAnimationImage> m_SnowArray;//雪花圖片陣列
	int m_SnowCount;//雪花圖片數量
	int m_RowCount;//每行圖片數量
	int m_AllCount;//可顯示圖片總數
public:
	DECLARE_MESSAGE_MAP()
	afx_msg void OnTimer(UINT_PTR nIDEvent);
public:
	//建立窗體
	BOOL Create(HWND hWndParent=NULL);
	//初始化初次顯示介面
	void InitImage();			
	//新增原始圖片資源到CLayeredWnd的m_ImageArray中
	void AddResImg(LPCTSTR lpName,int nAngle);
	//向m_SnowArray新增雪花圖片
	void AddSnow(int nCount);	
	//下移雪花
	void DownSnow();			
	//根據上限和下限,隨機產生一個數字
	int GetRndNum(int nMin,int nMax);
public:
	//繪製當前介面,重寫CLayeredWnd的此虛擬函式
	virtual void OnDrawWindow(Gdiplus::Graphics* pGraphics);
};
由於每個變數和函式都對應有標註,這裡就不再敘述了,下面看具體實現:
void CSnowWindow::InitImage()
{
	AddResImg(_T("C:\\Snow01.png"),0);
	AddResImg(_T("C:\\Snow02.png"),0);
	AddResImg(_T("C:\\Snow03.png"),-1);
	AddResImg(_T("C:\\Snow04.png"),-1);
	AddResImg(_T("C:\\Snow05.png"),0);
	AddResImg(_T("C:\\Snow06.png"),0);
	AddResImg(_T("C:\\Snow07.png"),0);
	AddResImg(_T("C:\\Snow08.png"),0);
	AddResImg(_T("C:\\Snow09.png"),0);
	AddResImg(_T("C:\\Snow10.png"),0);
	AddResImg(_T("C:\\Snow11.png"),0);
	AddResImg(_T("C:\\Snow12.png"),0);


	m_RowCount=m_nWidth/(m_ImageArray[0]->Width+20);
	m_AllCount=m_RowCount*(m_nHeight/(m_ImageArray[0]->Height+20));
	AddSnow(m_RowCount);
}
//新增要顯示的雪花圖片到CLayeredWnd的m_ImageArray中
void CSnowWindow::AddResImg(LPCTSTR lpName,int nAngle)
{
	LPAnimationImage pImage=AddImage(lpName);
	pImage->Angle=nAngle;
}
//新增雪花圖片
void CSnowWindow::AddSnow(int nCount)
{
	for(int i=0;i<nCount;i++)
	{
		//隨機取一張圖片
		int nIndex=GetRndNum(0,m_ImageCount-1);
		LPAnimationImage pImage=new AnimationImage;
		LPAnimationImage pSrcImage=m_ImageArray[nIndex];
		CopyMemory(pImage,pSrcImage,sizeof(AnimationImage));

		//隨機設定圖片的初始位置
		pImage->X=GetRndNum(0,m_nWidth);
		pImage->Y=0-GetRndNum(pImage->Height,pImage->Height*2);

		//隨機縮放圖片
		float f=(float)GetRndNum(50,100);
		f=f/(float)100;
		pImage->Width=(int)((float)pImage->Width*f);
		pImage->Height=(int)((float)pImage->Height*f);

		pImage->firstInit=true;//初始化為TRUE,此引數用來判定是否首次初始化OffsetMode引數

		m_SnowArray.Add(pImage);

	}
	m_SnowCount=m_SnowArray.GetCount();
}
講解:
1、AddResImg()實現根據圖片路徑將圖片載入到CLayeredWnd的m_ImageArray中

2、AddSnow()實現隨機從m_ImageArray中取一張圖片,並對其初始化,最後將其新增到m_SnowArray中。

3、InitImage()就是實現了初次顯示的雪花圖片的初始化工作,AddSnow(m_RowCount);將每行所具有的雪花個數的圖片,以第一行顯示出來。

在初次頂端雪花初始化完成以後,就要定時將雪花圖片移動(下移和平移)

1、設定定時器

由於是定時重新整理當前介面以使雪花下移,所以必定要設定定時器,並對WM_TIMER訊息進行處理,下面看對WM_TIMER訊息處理的程式碼:

void CSnowWindow::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: 在此新增訊息處理程式程式碼和/或呼叫預設值
	CLayeredWnd::OnTimer(nIDEvent);
	DownSnow();
}
從這裡可以看到,每次產生的定時脈衝所做的事情就是將雪花下移。下面看DownSnow()的程式碼:
//下移雪花圖片
void CSnowWindow::DownSnow()
{
	int nTop=25;
	for(int i=m_SnowCount-1;i>=0;i--)
	{
		LPAnimationImage pImage=m_SnowArray[i];
		pImage->Y+=5;//下移
		if(pImage->Y>m_nHeight)//超出螢幕高度
		{
			m_SnowArray.RemoveAt(i);//移除這張圖片
			delete pImage;
			continue;//轉到下一次迴圈
		}
		//-------------
		if(pImage->Y<nTop)
			nTop=pImage->Y;//找到當前最後一個雪花離螢幕頂部的距離
		//橫向移動 
		if (pImage->firstInit)
		{
			pImage->firstInit=false;
			pImage->OffsetMode=GetRndNum(1,3);
		}
		switch (pImage->OffsetMode)
		{
		case 1:
			pImage->X--;
			break;
		case 2:
			pImage->X++;
			break;
		}
	}
	m_SnowCount=m_SnowArray.GetCount();
	int nCount=m_AllCount-m_SnowCount;
	if(nCount>0 && nTop>20)//設定每行的豎向間隔,這裡設定每20個畫素顯示一行雪花
	{
		if(nCount>m_RowCount)
			nCount=m_RowCount;
		AddSnow(nCount);
	}
	ReDrawWindow();
}
講解:
1、for迴圈中對每個雪花進行平移和下移。

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

注意這裡有幾句程式碼:

首先定義了一個距離,25個畫素。

int nTop=25;
這句處在FOR迴圈內,由於pImage->Y每次都會遞加5,所以到最後就會出現一種情況,其它雪花縱座標都大於25,只有一個雪花的縱座標小於25,所以這句的目的就是查詢最後一個仍小於nTop的值。由於Y值每次遞加5,所以最後一個Y值肯定落在20-25之間。
		if(pImage->Y<nTop)
			nTop=pImage->Y;//找到當前最後一個雪花離螢幕頂部的距離

由於當雪花落在螢幕邊界外後,會將雪花從陣列中刪除,而且隨著雪花的下落,螢幕上還要繼續落雪花,新產生的行所具有的要求是:

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

這裡使用int nCount=m_AllCount-m_SnowCount;nCount=m_RowCount;來設定每行要顯示的雪花數量。

(2)、每行雪花的行距

上面說了利用nTop來找到最後一個雪花離螢幕的距離(20-25),所以當nTop>20的時候,說明是時候顯示下一行了,這時才可以顯示,不然就擠在一塊了。

	m_SnowCount=m_SnowArray.GetCount();
	int nCount=m_AllCount-m_SnowCount;
	if(nCount>0 && nTop>20)//設定每行的豎向間隔,這裡設定每20個畫素顯示一行雪花
	{
		if(nCount>m_RowCount)
			nCount=m_RowCount;
		AddSnow(nCount);
	}

3、呼叫CLayeredWnd的ReDrawWindow()

由於在ReDrawWindow中呼叫了虛擬函式OnDrawWindow(),根據虛擬函式性質,會呼叫CSnowWindow重寫的OnDrawWindow函式,看OnDrawWindow的實現:

// 虛擬函式 繪製視窗
void CSnowWindow::OnDrawWindow(Gdiplus::Graphics* pGraphics)
{
	for(int i=0;i<m_SnowCount;i++)
	{
		LPAnimationImage pImage=m_SnowArray[i];

		pGraphics->DrawImage(pImage->pImage,pImage->X,pImage->Y,pImage->Width,pImage->Height);

	}
}
實現方法就是將每一個雪花圖片重新根據新座標繪製出來,僅而而已。

到這裡,本文雪花飄落相關的東東已經介紹完成了,下篇在此基礎上完成雪花在下落過程中實現旋轉的功能。

相關網頁: