Introduction
Someone was asking about a Slide Show control, I did a quick search and didn't find one, so I wrote a sample to show how this could be done. At first, I used CImage
to load images from the image files, but quickly noticed that quality of the images were degraded. So, I switched to GDI+, and it was prefect.
To accomplish this, I created a class that inherits from CStatic
and called it CSlideShowCtrl
; this way, the user can place a CStatic
control on their dialog box and attach it to this class. This class overwrites a couple of CStatic
methods, which are OnPaint
and OnTimer
. There are also two other methods to Start
and Stop
the slide show.
The Start
method takes a Directory
and a Delay
as parameters. The Directory
is where the files are located, and the Delay
is the number of milliseconds to pause between images.
Now, in order to continuously loop through the images in the directory, there is a CFileFind
member and a m_Ret
member in the CSlideShowCtrl
class which gets inialized in the Start
method, and once CFileFind
finds a file, it starts a timer for the Delay
interval, and the OnTimer
method calls the FindNextFile
method of the CFileFind
class. If when the timer is hit, m_Ret
is false
, it will reinitialize the file search by calling FindFile
. If the CFileFind
object wasn't part of the class definition, then it would have been very difficult to find the next file, and only show the files once in each iteration.
void CSlideShowCtrl::Start(CString Dir,UINT Interval)
{
m_Dir = Dir;
m_Ret = m_Finder.FindFile(Dir+_T("\\*.*"));
while (m_Ret)
{
m_Ret = m_Finder.FindNextFile();
if (!m_Finder.IsDirectory())
{
Image *TempImage;
TempImage = Image::FromFile(m_Finder.GetFilePath());
if (TempImage && TempImage->GetLastStatus() == Ok)
{
delete m_CurImage;
m_CurImage = TempImage;
Invalidate();
UpdateWindow();
break;
}
else if (TempImage)
{
delete TempImage;
}
}
}
SetTimer(100,Interval,NULL);
}
void CSlideShowCtrl::OnTimer(UINT nIDEvent)
{
if (nIDEvent == 100)
{
if (!m_Ret)
{
m_Ret = m_Finder.FindFile(m_Dir+_T("\\*.*"));
}
while (m_Ret)
{
m_Ret = m_Finder.FindNextFile();
if (!m_Finder.IsDirectory())
{
Image *TempImage;
TempImage = Image::FromFile(m_Finder.GetFilePath());
if (TempImage && TempImage->GetLastStatus() == Ok)
{
delete m_CurImage;
m_CurImage = TempImage;
Invalidate();
UpdateWindow();
break;
}
else if (TempImage)
{
delete TempImage;
}
}
}
}
CStatic::OnTimer(nIDEvent);
}
The OnPaint
method's only job is to draw the current Gdiplus::Image
object. It also draws the images to fit within the Static controls client area while keeping the correct aspect ratio. Since it has to paint a background for the unused portion of the control, it uses a double buffering technique to keep the image from flickering.
void CSlideShowCtrl::OnPaint()
{
CPaintDC dc(this);
CRect Rect;
GetClientRect(&Rect);
CDC MemDC;
MemDC.CreateCompatibleDC(&dc);
CBitmap Bmp;
Bmp.CreateCompatibleBitmap(&dc,Rect.Width(),Rect.Height());
int SavedDC = MemDC.SaveDC();
MemDC.SelectObject(&Bmp);
MemDC.FillSolidRect(Rect,RGB(127,127,127));
if (m_CurImage != NULL)
{
double Ratio = 1.0;
if (m_CurImage->GetWidth() > Rect.Width())
{
Ratio = (double)Rect.Width() / (double)m_CurImage->GetWidth();
}
if (m_CurImage->GetHeight() > Rect.Height())
{
double Temp = Rect.Height() / (double)m_CurImage->GetHeight();
if (Temp < Ratio)
{
Ratio = Temp;
}
}
int X = (int)(Rect.Width() - (m_CurImage->GetWidth()*Ratio)) / 2;
int Y = (int)(Rect.Height() - (m_CurImage->GetHeight()*Ratio)) / 2;
Graphics TheDC(MemDC.GetSafeHdc());
TheDC.DrawImage(m_CurImage,X,Y,m_CurImage->GetWidth()*Ratio,
m_CurImage->GetHeight()*Ratio);
}
dc.BitBlt(0,0,Rect.Width(),Rect.Height(),&MemDC,0,0,SRCCOPY);
MemDC.RestoreDC(SavedDC);
}
Here is a little explanation of the individual techniques used to accomplish this task. The heart of this control is the CFileFind
class. This handy class is what allows us to traverse the files in a directory. It is basically a wrapper class for the FindFirstFile
and FindNextFile
API functions.
The directory traversal usually consists of some sort of loop. A while
loop is very useful in this scenario. It is relatively simply and straightforward to use. One thing to point out here is that if necessary, it is common practice to use recursion in order to traverse into child directories.
void LookAtAllFiles(CString Path)
{
CFileFind Finder;
BOOL Ret = Finder.FindFile(Path+_T("\\*.*"));
While (Ret)
{
Ret = Finder.FindNextFile();
if (Finder.IsDirectory())
{
if (!Finder.IsDots())
{
LookAtAllFiles(Finder.GetFilePath());
}
}
else
{
}
}
}
Simple enough. There is a small problem here for the Slide Show control. We need to know how the CFileFind
class is doing between calls to OnTimer
. To accomplish this, the return value that is used as part of the loop (m_Ret
) and the CFileFind
object (m_Finder
) are both part of the class. This easily solves the problem of finding the next file when it's time to switch images. Well, the reason the Slide Show control uses timers instead of maybe running in a loop and drawing each item is that, it needs to give the control back to the calling message pump. If the code was to go into a loop and simply draw each image as it came across them, then the application wouldn't be able to process any more messages and the application would appear locked up.
Using the code
In order to use the CSlideShowCtrl
, insert a Static control on a dialog box. Give the Static control a different ID than IDC_STATIC
, for example, IDC_SLIDESHOWCTRL
. Attach a variable to the Static control by right-clicking on the control and selecting Add Variable. Once a variable is attached to the control, open the dialog box's header file and change the variable's type from CStatic
to CSlideShowCtrl
; don't forget to include the header file for the CSlideShowCtrl
. At this point, you can simply call the CSlideShowCtrl::Start
method from your dialog code to start the slide show. Since this control uses GDI+, put the following include in your Stdafx.h:
#include <gdiplus.h>
using namespace Gdiplus;
You will also need to call the GdiplusStartup
and GDIplusShutdown
functions in your application's initialization and clean up routines.