Technique to Create Dialogs from Images


Click here for larger image

This article describes a technique for creating dialogs with any form you want for them.

In order to define the form of the dialog you only have to create a bitmap with your usual graphics programm (say Photoshop or Microsoft Paint,...). In this bitmap you have to paint the form of your dialog using as many colors as you want (it can be RGB or paletted!), but remembering to choose one color to be interpreted as the only transparent (or only opaque) color. It can be any color you want, but you have to know its RGB-components, because they will be one of the parameters to pass to the BitmapRegion() function, that makes all the hard work for you! The other arguments of this function are the bitmap handle, and a boolean argument which tells the function to consider the passed color as transparent or opaque! What this function returns is a handle to a region object "hRegion", which can, and should, be passed to the member function of CWnd: SetWindowRgn(hRegion), in order to complete the definition of the form of the dialog.

Here are some samples of the use of this funtion:

Sample 1

// hBitmap is the handle to the loaded bitmap
// Here, the transapatent color is black. All other colors 
// in the bitmap will be considered opaque
hRegion=BitmapRegion(hBitmap,RGB(0,0,0));

if(hRegion) 
 SetWindowRgn(hRegion,TRUE); // TRUE=repaint the window! 

Sample 2:

// hBitmap is the handle to the loaded bitmap
// Here, the opaque color is yellow. All other colors in 
// the bitmap will be considered transparent 
// The last argument tells the function to interpret the 
// passed color as opaque. If this argument is not present, 
// the color is the trasnparent color!
hRegion=BitmapRegion(hBitmap,RGB(255,255,0),FALSE);

if(hRegion) 
 SetWindowRgn(hRegion,TRUE); // TRUE=repaint the window! 

This technique provides two ways of using the mentioned function.

One way is to use it for defining the clipping region for the dialog, as explained in the previous code. This function should be called from the OnCreate() message handler of the dialog, where the bitmap has to be loaded (further explanation below!).


Click here for larger image

WITHOUT

With this option the background of the dialog has the plain color of all the dialogs in the system. That is, the dialog has a special boundary-form, but it has the same color and appearance as all standard dialogs. You can place the controls with the Ressource Editor and work with them exactly like you would do with a normal dialog. You only have to pay attention to the position of the controls, because they have to be placed on the opaque areas of the bitmap in order to be visible!

The other way to use this technique is to get a completely owner drawn dialog. Not only the boundaries of the dialog are computed from the bitmap, but also the background image of the dialog! These option needs a bit more code to get done, but it isn't complicated and the effect is really cool (you can use this for the About... dialog of your application!)


Click here for larger image

WITH

With this option the background is filled with the bitmap. The controls (if you put some on the dialog) are drawn over the bitmap, so if you want them not to appear, put them on transparent areas of the dialog, or simply put them away! In the sample project, I use this option with a dialog in which I let no control be shown (in fact there remains only an "Ok" button!) and therefor I have to be able to close the dialog in another manner. The solution is to catch the clicking of the user over a special area of the bitmap. I painted a yellow cross on the bitmap and annotated the coordinates in pixels of the bounding rectangle of the cross. When the user clicks within this area, the dialog is closed with EndDialog(). In order to get the coordinates of the pointer in comparison with those of the mentiones area, the dialog has to have no caption- or title-bar, and it has to have no border! (study the sample project for a closer explanation.)

Another feature implemented in this sample project for the dialog with the bitmap painted on the background, is that the user can dragg the dialog clicking anywhere on it (except on the yellow-cross which closes the dialog!). In order to achieve this effect, You have to override the OnNCHitText() message handler of the dialog and return HTCAPTION everytime the user doesn't click on the yellow-cross. In this case you return HTCLIENT, in order to let the system send the WM_LBUTTONDOWN message to the dialog!

In either case (you want your bitmap to be painted on the background or not) you have to insert the following line in the .cpp file of your dialog implementation:

#include "AnyForm.h"

You have to include the files AnyForm.h and Anyorm.cpp in your project, in order to get things working!

Explanation 1: The bitmap is used only to compute the clipping region of the dialog

This option is shown in the previous photo called "Without".

In this case the code is rather simple. You have to override the OnCreate() message handler of the dialog, and insert a few lines of code, in order to obtain something like this:

int CMyDlgWithout::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
 if (CDialog::OnCreate(lpCreateStruct) == -1)
  return -1;

 // In this case, we don't need to store the loaded bitmap 
 // for later use, because we don't put it on the background. 
 // We use it only to compute the clipping region and after 
 // the region is set on the window, we can free the loaded 
 // bitmap. That's the reason whiy in this case there is no 
 // need of member varibles, memory contexts,...

 HBITMAP hBmp;
 HRGN	hRegion;

 // The name of the bitmap to load (if not in the same 
 // directory as the application, provide full path!)
 // If the bitmap is to be load from the ressources, the name 
 // stands for the ressource-string identification
 char	*name="BitmapWithout.bmp";

 // If you want to load the bitmap from file 
 hBmp=(HBITMAP)LoadImage(lpCreateStruct->hInstance,
                         name,
                         IMAGE_BITMAP,
                         0,
                         0,
                         LR_LOADFROMFILE 
                         | LR_CREATEDIBSECTION);

 // If you want to load the bitmap from the resources 
 // Uncomment the next line, and comment the previous 
 // "LoadImage()" line!
 // hImage=(HBITMAP) LoadImage(lpCreateStruct->hInstance,
                               name,
                               IMAGE_BITMAP,
                               0,
                               0,
                               LR_LOADFROMFILE 
                               | LR_CREATEDIBSECTION);
	
 // Let's make this function do the hard work. You pass it 
 // the handle of the bitmap, the color in the bitmap to be 
 // transparent, and a third boolean parameter that tells 
 // the function whether the passed color is to be 
 // interpreted as the transparent color or as the opaque 
 // color. 
 //
 // A sample where the red color is interpreted as the 
 // opaque color...
 //
 // hRegion=BitmapRgn(hBmp,0x00FF0000,RGB(255,0,0),FALSE);
 // In this sample, the transparent color is black!
 hRegion=BitmapRegion(hBmp,RGB(0,0,0));

 // If there was no problem getting the region, we make 
 // it the current clipping region of the dialog's window
 if(hRegion) 
  SetWindowRgn(hRegion,TRUE);		

 // After this, because in this case we don't need to store 
 // the bitmap anymore, we can free the ressources used by 
 // it. We also don't need here to select the bitmap on the 
 // memory context. In fact, we don't need any memory 
 // context!!! So:

 // Delete the bitmap the we loaded at the beginning
 if(hBmp) 
  DeleteObject(hBmp);

 // And in this case, that's all folks!!!

 return 0;
}

Explanation 2: The bitmap is used to compute the clipping region of the dialog, and to be painted on the background of the dialog

This option is shown in the previous photo called "With".

This case is a little more complicated, but it can be better understood through the code of the sample project. At first, you have to insert a few member variables in the dialog-class that controls your dialog. The class definition would look similar to this:

class CMyDlgWith : public CDialog 
{
...

// Implementation
protected:
 HBITMAP    hBmp;		
 HBITMAP    hPrevBmp;	
 HDC        hMemDC;
 HRGN       hRegion;
 BITMAP     bmInfo;
...

};

You have to override the message handlers for the following messages: WM_CREATE, WM_DESTROY and WM_ERASEBKGND. The code for these functions would look like this:

int CMyDlgWith::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
 if (CDialog::OnCreate(lpCreateStruct) == -1)
  return -1;
	
 // The name of the bitmap to load (if not in the same 
 // directory as the application, provide full path!)
 // If the bitmap is to be load from the ressources, 
 // the name stands for the ressource-string identification
 char	*name="BitmapWith.bmp";

 // If you want to load the bitmap from file 
 hBmp=(HBITMAP)LoadImage(lpCreateStruct->hInstance,
                         name,
                         IMAGE_BITMAP,
                         0,
                         0,
                         LR_LOADFROMFILE 
                         | LR_CREATEDIBSECTION);

 // If you want to load the bitmap from the resources 
 // Uncomment the next line, and comment the previous 
 // "LoadImage()" line!
 // hImage=(HBITMAP)LoadImage(lpCreateStruct->hInstance,
                              name,
                              IMAGE_BITMAP,
                              0,
                              0,
                              LR_LOADFROMFILE 
                              | LR_CREATEDIBSECTION);

 // Let's make this function do the hard work. You pass it 
 // the handle of the bitmap, the color in the bitmap to be 
 // transparent, and a third boolean parameter that tells 
 // the function whether the passed color is to be 
 // interpreted as the transparent color or as the opaque 
 // color In this sample, the transparent color is black!
 hRegion=BitmapRegion(hBmp,RGB(0,0,0));

 // If there was no problem getting the region, we make it 
 // the current clipping region of the dialog's window
 if(hRegion) 
 SetWindowRgn(hRegion,TRUE);		

 // We ask the bitmap for its size...
 GetObject(hBmp,sizeof(bmInfo),&bmInfo);

 // At last, we create a display-compatible memory context!
 hMemDC=CreateCompatibleDC(NULL);

 hPrevBmp=(HBITMAP)SelectObject(hMemDC,hBmp);

 return 0;
}

void CMyDlgWith::OnDestroy()
{
 CDialog::OnDestroy();

 // Simply select the previous bitmap on the memory context...
 SelectObject(hMemDC,hPrevBmp);	

 // ... and delete the bitmap the we loaded at the beginning
 if(hBmp) 
 DeleteObject(hBmp);
}

BOOL CMyDlgWith::OnEraseBkgnd(CDC* pDC)
{
 // If you only want a dialog with a special border-form, 
 // but you don't want a bitmap to be drawn on its background 
 // surface, then comment the next two lines, and uncomment 
 // the last line in this function!
 BitBlt(pDC->m_hDC,
        0, 0,
        bmInfo.bmWidth,
        bmInfo.bmHeight,
        hMemDC,
        0, 0,
        SRCCOPY);
 return FALSE;

 // return CDialog::OnEraseBkgnd(pDC);
}

The last thing to do, is to provide the dialog a way to get closed by the user. As I mentioned before, the best thing is to draw a special figure anywhere on the bitmap and to catch the clicking of the mouse in this area, by catching the WM_LBUTTONDOWN message of the dialog. Another feature can be easily implemented: permit 'click and drag' the dialog clicking anywhere on it! This can be achieved by catching the WM_NCHITTEST message. The following code shows how this is implemented:

void CMyDlgWith::OnLButtonDown(UINT nFlags, CPoint point)
{
 // If the user clicks on the 'x'-mark we close the dialog!
 // The coordinates of the rectangle around the 'x'-mark 
 // are in pixels and client-coordinates! 
 // That's the reason why the dialog ressource has to have 
 // no border and no caption bar.
 if(point.x>333 && point.x<354 && point.y>54 && point.y<77)
  EndDialog(0);
	
 CDialog::OnLButtonDown(nFlags, point);
}

UINT CMyDlgWith::OnNcHitTest(CPoint point) 
{
 ScreenToClient(&point);

 // Because "point" is passed in screen coordinates 
 // (Windows knows why?!) we have to convert it to client 
 // coordinates, in order to compare it with the bounding 
 // rectangle around the 'x'-mark. If the point lies within, 
 // then we return a hit on a control or some other client 
 // area (so that the OnLButtonDown-message can be sent, and
 // afterwards close the dialog!), but in all other cases, 
 // we return the information as if the user always would 
 // have clicked on the caption bar. These permitts the user 
 // to drag and move the dialog clicking anywhere on it 
 // expect the rectangle we considere here!
 if(point.x>333 && point.x<354 && point.y>54 && point.y<77)
  return HTCLIENT;
 else
  return HTCAPTION;
}

Demo project The demo project shows the two ways of using this technique. The source code has comments in order to get better explained.

Source code The source code comprises only the two following files, that you have to include in your project: AnyForm.h and AnyForm.cpp.

Downloads

Download source - 4 Kb
Download demo project - 115 Kb