MFC GDI Plus thumbnail CListCtrl

Started by thiruvasagamani, Sep 13, 2008, 03:30 PM

Previous topic - Next topic

thiruvasagamani

     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.
Thiruvasakamani Karnan