Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Slide Show Control

0.00/5 (No votes)
3 Oct 2007 1  
Use this control to insert a Slide Show into your MFC application.

Screenshot - SlideShow.jpg

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;
   //Start looking for files in the given directory

   m_Ret = m_Finder.FindFile(Dir+_T("\\*.*"));
   //while there are files 

   while (m_Ret)
   {
      m_Ret = m_Finder.FindNextFile();
      if (!m_Finder.IsDirectory())
      {
         Image *TempImage;
         //Try and load the file

         TempImage = Image::FromFile(m_Finder.GetFilePath());
         //Just because we got an Image pointer back doesn't mean

         //that an image was loaded. So check the Status

         if (TempImage && TempImage->GetLastStatus() == Ok)
         {
            //delete the old image

            delete m_CurImage;
            m_CurImage = TempImage;
            //Cause a WM_PAINT

            Invalidate();
            UpdateWindow();
            //and break out of the loop

            break;
         }
         //if an image was not loaded

         else if (TempImage)
         {
            delete TempImage;
         }
      }
   }
   //set a timer so that we can load the next image

   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;
            //Try and load the file

            TempImage = Image::FromFile(m_Finder.GetFilePath());
            //Just because we got an Image pointer back doesn't mean

            //that an image was loaded. So check the Status

            if (TempImage && TempImage->GetLastStatus() == Ok)
            {
               //delete the old image

               delete m_CurImage;
               m_CurImage = TempImage;
               //Cause a WM_PAINT

               Invalidate();
               UpdateWindow();
               //and break out of the loop

               break;
            }
            //if an image was not loaded

            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); // device context for painting


   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 we have encountered a directory

      if (Finder.IsDirectory())
      {
         //and it's not a . or a ..

         if (!Finder.IsDots())
         {
            //do whatever it is that we are doing to this directory

            LookAtAllFiles(Finder.GetFilePath());
         }
      }
      // it must be a file

      else
      {
          //here we do something with that file

      }
   }
}

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here