MFC GDI Plus Picture Box

   This project gives a sample of a control that displays pictures on the dialog. The control uses the Microsoft GDIPlus library. It displays pictures given either by handle or a by path to a file. If you don?t have experience with GDIPlus please read the ?GDIPlus Common Issues? article.

   The control is implemented in the class CPictureBox which is derived from the CStatic class from MFC. The picture box can be created either statically (from a resource – as it is shown in the project) or dynamically (with the Create method of the parent class). When created the following flag must be set: SS_SIMPLE.

   To set the picture of the control call the setPicture method. It receives a parameter that is either HBITMAP or CString. After calling this method, in order for the change to take effect you should redraw the control using the Invalidate method.

   When the picture is shown on the control there are 3 styles for how it is displayed. To change these styles call the setPictureStyle method with one of these constants:
PSTYLE_NORMAL – the picture is displayed at real size in the top left corner of the control

   You can place any controls on the picture box so you can make a background for your dialog using this control.

CPictureBox Implementation – MFC GDI Plus 

   So here are the comments about the implementation of the class. In GDI Plus there is a class called Image. This is the class that actually holds the image for the control described.

   An Image 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 Image instance containing the data from the picture file. The file might be any of the formats supported by GDI Plus. These formats vary in different versions of GDIPlus but the ones that are always supported are BMP, JPEG, GIF and TIFF. FromFile returns a new instance of Image 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 an Image 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 the method ascii2unicode which does this conversion. So after getting the ASCII string as a parameter we convert it to Unicode and call Image::FromFile to get the picture in the memory. So the first implementation of setPicture looks like this:


void CPictureBox::setPicture(CString strFileName)
{
    if (m_pImage!=NULL)
       delete m_pImage;
    wchar_t* wstrFName = new wchar_t[strFileName.GetLength()+1];
    ascii2unicode(strFileName, wstrFName);
    ASSERT(wstrFName!=NULL);
    m_pImage = Image::FromFile(wstrFName); 
    delete [] wstrFName; wstrFName = NULL;
    if ((m_pImage==NULL) || (m_pImage->GetLastStatus()!=Ok)) 
	{
		// make it transparent
		ModifyStyleEx(0, WS_EX_TRANSPARENT, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
	} 
	else 
	{
		ModifyStyleEx(WS_EX_TRANSPARENT, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
    } 
}

The last code in the function makes the control transparent if a picture is set. So if the picture is smaller than the control, any pictures or controls behind it wont be hidden.

To get an Image from a handle to bitmap (HBITMAP) we must use the Bitmap class from GDIPlus. It has a static method FromHBITMAP which is much like FromFile, only it takes HBITMAP istead of string to create the picture. Bitmap is a class derived from Image. So we can give the member pointer to Image the value return from FromHBITMAP which is a pointer to Bitmap. 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. So we must give the palette of the device context of the control. So the other version of setPicture looks like this:


void CPictureBox::setPicture(HBITMAP hbm)
{
	if (m_pImage!=NULL)
	  delete m_pImage;
	CPalette pal;
	pal.CreateHalftonePalette(GetDC());
	m_pImage = Bitmap::FromHBITMAP(hbm, (HPALETTE)pal);
	if ((m_pImage==NULL) || (m_pImage->GetLastStatus()!=Ok)) {
	// make it transparent
	ModifyStyleEx(0, WS_EX_TRANSPARENT, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
	} else {
	ModifyStyleEx(WS_EX_TRANSPARENT, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
	} //else
}

Again we make the control transparent if a picture is set.

A GDIPlus object can only be drawn on a GDIPlus surface. Luckily the Graphics class has a constructor which attaches it to an existing device context (HDC). Also the Graphics class has a method called DrawImage which draws an object from type Image on the surface. The DrawImage function has a lot of ways to be called but the most common are DrawImage(Image*, int, int) which draws the image in real size with a given top-left corner and DrawImage(Image*, int, int, int, int) which draws the image with a given top-left corner with given height and width. So we attach a Graphics object to the control?s DC and then draw the image on it, depending on the style that is selected. Here is the sample code:


switch(m_pictureStyle) 
{
	case PSTYLE_NORMAL:
	Graphics(dc.GetSafeHdc()).DrawImage(m_pImage,left,top);
	break;
	case PSTYLE_CENTER:
	top = rect.Height()/2 - (m_pImage->GetHeight())/2;
	left = rect.Width()/2 - (m_pImage->GetWidth())/2;
	Graphics(dc.GetSafeHdc()).DrawImage(m_pImage,left,top);
	break;
	case PSTYLE_STRETCH:
	Graphics(dc.GetSafeHdc()).DrawImage(m_pImage,top,left,rect.Width(),rect.Height());
	break;
	default:
	ASSERT(false);
}

   This code is positioned in the OnPaint function of the control. OnPaint is called by Windows each time you call Invalidate. That?s why in order for your changes to take effect you must call Invalidate. Only then the control is redrawn.

When you call Invalidate the control is updated and redrawn but if there are controls over it (for example it is a background picture of a dialog and all controls are on it) they don?t get redrawn, because the is nobody to call the function Invalidate for them. So at the end of OnPaint there is some code which checks all controls on the dialog that must be drawn on top the picture box, and redraws them.

In the sample project a picture is set that is in the executable as a resource with HBITMAP. The program allows you manually to set pictures on the picture box from your hard drive. And allows you to change the style of the image in the control.

The Sample Project Files can be downloaded from here.

PSTYLE_CENTER – the picture is centered in the control
PSTYLE_STRETCH – the picture size is changed so it would fit the control (Warning: you might lose the aspect ratio of the picture if you display it this way)
Again use Invalidate to make the change take effect.