Click here to Skip to main content
15,905,068 members
Articles / Desktop Programming / MFC
Article

Slide Show Control

Rate me:
Please Sign up or sign in to vote.
3.50/5 (11 votes)
3 Oct 2007CPOL3 min read 35.2K   1.4K   22   2
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.

C++
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.

C++
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.

C++
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:

C++
#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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
Ali Rafiee has been developing windows applications using C++ since 1991, and he hasn't looked back since. Ali has been a software development consultant for must of his career, but he has finally settled down and has been working for an educational software company since 2000. While he is not working, he is either learning C#, flying airplanes, playing with his daughter, or answering peoples question on newsgroups, he finds that to be a great learning tool for himself.

Ali is also a Microsoft Visual C++ MVP.

Comments and Discussions

 
QuestionCompiler error Pin
showtime3-Nov-12 7:36
showtime3-Nov-12 7:36 
GeneralNice Pin
guyuewuhua16-Aug-09 15:05
guyuewuhua16-Aug-09 15:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.