VC++
Tool: MFC GDI Plus thumbnail CListCtrl,
BMP File, Jpeg file, Gif File, Tiff file
|
|
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:
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;
}
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 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
|