Introduction
Since my previous article was published, I received many user inquiries about how to improve the program. This article demonstrates how to create snow flakes that fall on your desktop, by using the draw functions directly in the desktop context. When the application starts, it creates an array of snow flakes and starts the timers for each flake. Manipulation of the RedrawWindow
function allows the drawing of flakes behind and over the desktop icons, making the application more impressive.
General Steps
For better randomizing, the start coordinate, timer interval, flake position (over or behind the desktop icons) and size should be pre-defined as shown below:
srand((unsigned)time(NULL));
for(int i=0; i<nArray; i++)
{
nTimer = rand()*70/RAND_MAX+10;
arTimers.Add(nTimer);
nPosX = rand()*rcWorkArea.Width()/RAND_MAX;
arPositions.Add(nPosX);
nOver = rand()*100/RAND_MAX;
if(nOver>50)
arOverIcons.Add(1);
else
arOverIcons.Add(0);
nBig = rand()*100/RAND_MAX;
if(nBig<50)
arBigFlakes.Add(1);
else
arBigFlakes.Add(0);
}
Then, create the flake and start the timer:
...
CFlake* pFlake = new CFlake(nPosX, nBig, nOver);
m_arFlakes.Add(pFlake);
SetTimer(i+1,nTimer,0);
...
When the timer event occurs, move the appropriate flake on the desktop:
void CMainFrame::OnTimer(UINT nIDEvent)
{
if(nIDEvent >= 0 && nIDEvent <= (UINT)m_arFlakes.GetSize())
{
CFlake* pFlake = m_arFlakes.GetAt(nIDEvent-1);
if(pFlake)
pFlake->Move();
}
}
As shown above, class CFlake
(Flake
in C# code) is responsible for the desktop drawing.
Unfortunately, Windows Vista does not allow the use of the RedrawWindow
function with the RDW_NOERASE
flag for the desktop Window correctly. So, you cannot draw the flakes behind the icons in this case.
Most of the snow fall algorithm was taken from my previous article, except the drawing functions:
HDC hDC = GetDC(m_hWndDesktop);
if(hDC)
{
RECT rc;
rc.left = m_nCurrentX;
rc.top = m_nCurrentY;
rc.right = m_nCurrentX+15;
rc.bottom = m_nCurrentY+15;
int nTestHeight = m_nScreenHeight - 15;
if(m_nCurrentY<nTestHeight)
RedrawWindow(m_hWndDesktop, &rc, NULL, RDW_INVALIDATE
| RDW_ERASE | RDW_UPDATENOW );
m_nCurrentY += 3;
m_nCounter++;
if(m_nCounter == 15)
{
if((rand()*10/RAND_MAX)>5) m_nIncrement = 1;
else m_nIncrement = -1;
m_nCounter = 0;
}
m_nCurrentX += m_nIncrement;
if(m_nCurrentY>m_nScreenHeight)
{
m_nCurrentY = 0;
m_nCurrentX = abs(rand()*m_nScreenWidth/RAND_MAX);
if(abs(rand()*100/RAND_MAX)>50)
m_bIsBigFlake = TRUE;
else
m_bIsBigFlake = FALSE;
}
int storedDC = SaveDC(hDC);
rc.left = m_nCurrentX;
rc.top = m_nCurrentY;
rc.right = m_nCurrentX+15;
rc.bottom = m_nCurrentY+15;
HPEN pOldPen = (HPEN)SelectObject(hDC, m_hFlakePen);
if(m_bIsBigFlake)
{
MoveToEx(hDC, m_nCurrentX+7, m_nCurrentY, 0);
LineTo(hDC, m_nCurrentX+7, m_nCurrentY+15);
...
}
else
{
...
}
if(!m_bIsVista)
{
if(!m_bOverIcons && m_nCurrentY<m_nScreenHeight)
RedrawWindow(m_hWndDesktop, &rc, NULL, RDW_INVALIDATE
| RDW_NOERASE | RDW_UPDATENOW);
}
SelectObject(hDC, pOldPen);
RestoreDC(hDC, storedDC);
ReleaseDC(m_hWndDesktop, hDC);
}
Finally, when the application closes, clean up the desktop in the CFlake
destructor (the current flake on screen position), and in the CMainFrame::OnClose
function (snow drift at the bottom of the desktop):
CFlake::~CFlake()
{
...
RedrawWindow(m_hWndDesktop, &rc, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
}
void CMainFrame::OnClose()
{
...
if(hWndDesktop)
{
CRect rcWorkArea;
SystemParametersInfo(SPI_GETWORKAREA,0,(LPVOID)&rcWorkArea,0);
rcWorkArea.top = rcWorkArea.bottom - 16;
::RedrawWindow(hWndDesktop, &rcWorkArea, NULL, RDW_INVALIDATE | RDW_ERASE |
RDW_UPDATENOW );
}
}
Known Problems
- Application does not work properly when the Active Desktop is enabled.
- Drawing the snowflakes behind the icons with this code under Windows Vista is not implemented.
Any improvements, comments or suggestions to this code are welcome.