This project gives a sample of a control that displays the thumbnails of the JPEG pictures in a selected folder. The control uses the Microsoft GDIPlus library. If you don?t have experience with GDIPlus please read the GDIPlus Common Issues article.
The control is implemented with the class CThumbsListCtrl which is derived from the CListCtrl from MFC. This class can only be dynamically created. If created statically, the attachment of the MFC class to the actual control will happen after the control is created and the OnCreate call will be missed. But OnCreate contains important code and cannot be missed. The class must be created using the LVS_ICON style.
To set the type of files the control should list, use the setFileExt function. It sets the extension of the files that should be accessed from the folders given to the control.
The method showFilesFromFolder updates the information in the control. Id loads the files with specified extension from the folder set by this function.
To get the currently selected file from the control use the getCurSel function. I will return the name of the file in a string.
The thumbnail list uses a file data reader which provides a GDIPlus Bitmap object containing a the preview of a file. The data reader class also provides a NoPrev image. This one should be displayed if the class fails to produce a Bitmap object out of the files. For this thumbnail list a picture data viewer is developed that can generate previews of the picture files that GDIPlus supports. These formats vary in different versions of GDIPlus but the ones that are always supported are BMP, JPEG, GIF and TIFF. After examining the project you might be able to develop your own data viewer class that supports files of your formats. You just have to inherit the CFileDataReader class as shown.
The size of the thumbnails is determined by two constants defined in CThumbsListCtrl.cpp
#define THUMBNAIL_WIDTH 80 #define THUMBNAIL_HEIGHT 62
if you want bigger or smaller thumbnails just change these constants to the values you want.
So here are the development comments on the control.This control should list a set of pictures. The CListControl class provides a rather easy way of listing pictures. The pictures are inserted into a image list (CImageList) which is previously attached to the control with SetImageList. Then the adding item to the list the control displays should be like this InsertItem(item_index, item_text, image_index) where image index is the index of the image in the image list that is attached to the control. So the job comes down to filling the image list with the appropriate images.
This is done in the function showFilesFromFolder. First it lists all files from the folder with the appropriate extension using the SearchDirectory function. Then it gives the file-name to the file data reader which generated a Bitmap object.
In GDIPlus there is a class called Bitmap. This is the class that actually transfers the images used by the control. Using this class the CPicDataReader opens pictures. A Bitmap can be created in many ways, one of which is with its static member FromFile which gets one parameter, the path to the picture. FromFile will create a new Bitmap instance containing the data from the picture file. The file might be any of the formats supported by GDIPlus. FromFile returns a new instance of Bitmap so after it isn?t needed anymore the instance must be deleted to avoid memory leaks. If a problem appears during the picture creation FromFile returns either NULL or a Bitmap with status different from Ok. For example if it returns NULL the problem most likely is that GDIPlus hasn?t been started (check out ?GDIPlus Common Issues to see about starting and shutting down GDIplus). GDIPlus is a Unicode library that can only deal with Unicode strings but there is no need to make Unicode projects to use it. To make a Unicode string out of an ASCII string we use a simple ASCII to Unicode converting routine which does this conversion. So after getting the ASCII string as a parameter we convert it to Unicode and call Bitmap::FromFile to get the picture in the memory. So the implementation of setFile in the picture data reader looks like this:
We check if the status of the bitmap is Ok. If it isn?t, after calling preview we?re going to return the NoPrev bitmap. NoPrev is a static function which gets the bitmap from the resources. NOTE: to use this class you must have a bitmap in the project resources with id IDB_NOPREV.
In the NoPrev function we get a Bitmap object from a handle to a bitmap. To get a Bitmap from a handle to bitmap (HBITMAP) we must use the static method FromHBITMAP which is much like FromFile, only it takes HBITMAP istead of string to create the picture. Except for the HBITMAP parameter the function takes a parameter of type HPALETTE. This must be the palette of the surface that we?re going to draw the picture on. We don?t know the surface in this class so we set the palette to NULL. This should make a palette compatible to the main window of the application. This is safe because we don?t use any special palette functions which could change the desired appearance of the image. The body of the NoPrev function looks like this
Bitmap* CFileDataReader::noPrev() { HBITMAP hbmp = ::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_NOPREV)); ASSERT(hbmp!=NULL); Bitmap* pBmp = Bitmap::FromHBITMAP(hbmp,NULL); ASSERT(pBmp->GetLastStatus() == Ok); return pBmp; }
After getting the picture from the reader we must shrink it to the appropriate thumbnail size. The method GetThumbailImage from the class Bitmap is just what we need. We get the new shrunk bitmap and now we have to insert it in the image list. To do this we must convert it to MFC CBitmap. CBitmap can be created with HBITMAP so we get the handle from the GDIPlus Bitmap with GetHBITMAP then attach it to the MFC CBitmap, then safely place the image in the image list. That?s basically the code description and here is how it is actually implemented:
void CThumbsListCtrl::showFilesFromFolder(const CString& strFold) { CWaitCursor wait; // crete a sand watch cursor if(strFold == m_strLastFold) return; //no folder changed so display the same content m_strLastFold = strFold; //folder changed so empty the list ctrl DeleteAllItems(); std::vector < std::string > arFiles; SearchDirectory(arFiles, (LPCSTR)strFold, (LPCSTR)m_strFileExt, false); if (arFiles.empty()) return; // no files to preview // set the length of the space between thumbnails // you can also calculate and set it based on the length of your list control int nGap = 4; // hold the window update to avoid flicking SetRedraw(FALSE); // reset our image list for (int i=m_iList.GetImageCount()-1; i>=0; i--) m_iList.Remove(i); // remove all items from list view if (GetItemCount() != 0) DeleteAllItems(); // set the size of the image list m_iList.SetImageCount(arFiles.size()); i = 0; CBitmap mfcBmp; //mfc bitmap that we must put in the image list Bitmap* pGdiBmp = NULL; //gdi+ bitmap we get from the readers Bitmap* pThumb = NULL; //thumbnail HBITMAP hBmp; CPoint pt; for(i=0; i<(int)arFiles.size(); i++) { m_pRdr->setFile(arFiles[i].c_str()); pGdiBmp = m_pRdr->preview(); ASSERT(pGdiBmp!=NULL); if(pGdiBmp->GetHeight() == THUMBNAIL_HEIGHT && pGdiBmp->GetWidth() == THUMBNAIL_WIDTH) { pThumb = pGdiBmp; } else { pThumb = (Bitmap*)pGdiBmp->GetThumbnailImage(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT); ASSERT(pThumb!=NULL); } pThumb->GetHBITMAP(Color(255,255,255), &hBmp); mfcBmp.Attach(hBmp); // add bitmap to our image list m_iList.Replace(i, &mfcBmp, NULL); // put item to display // set the image file name as item text CString str = arFiles[i].c_str(); str = str.Right(str.GetLength() - str.ReverseFind('\') - 1); InsertItem(i, str, i); mfcBmp.Detach(); if(pThumb!=pGdiBmp) delete pThumb; //delete it only if it was creted separately delete pGdiBmp; ::DeleteObject(hBmp); pGdiBmp=pThumb=NULL; hBmp=NULL; } SetRedraw(TRUE); SetFocus(); SetItemState(0, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED); }
The sample project allows the user to set a folder, and the control will list all .jpg files that it contains. Just write a folder path in the edit box and press the Change button.
Note:
Before using the GDI Plus, it should be initialized with the GDI Libraries. Initialization has to be done using GdiplusStartup function. This is explained in GDIPlus Common Issues article.
Download the sample Project and Source here.
void CPicDataReader::setFile(const CString& file) { CFileDataReader::setFile(file); m_bSuccess = false; wchar_t* wstrFName = new wchar_t[file.GetLength() + 1]; for (int i=0; i wstrFName[i] = file[i]; wstrFName[i]=0; m_pPicture = Bitmap::FromFile(wstrFName); ASSERT(m_pPicture!=NULL); if (m_pPicture->GetLastStatus() == Ok) { m_bSuccess = true; } delete[] wstrFName; }