Download source files - 21 Kb
In a previous article
I demonstrated subclassing a windows common control in order to modify its behaviour or
extend its functionality. Sometimes you can only push the windows common controls so far.
An example I came across was the common issue of needing a grid control to display and
edit tabular data. I subclassed a
and extended it to allow subitem editing, multiline cells, sort-on-click headers and
a myriad of other features. However, deep down it was still a list control and there came
a point where I seemed to be writing more code to stop the control performing actions than I
was to actually make it do something.
I needed to start from scratch, working from a base class that provided only the
functionality I needed without any of the features (or liabilities) that I didn't
need. Enter the custom control.
Creating a Custom Control class
Writing a custom control is very similar to subclassing a windows common control.
You derive a new class from an existing class and override the functionality of the
base class in order to make it do what you want.
In this case we'll be deriving a class from
CWnd, since this class provides the minimum
functionality we need, without too much overhead.
The first step in creating a custom control is to derive your class from your chosen base
CWnd). In this example we'll create a custom control for displaying
bitmaps, and we'll call this class
CBitmapViewer. Obviously there is already
CStatic class that already displays bitmaps, but the example is only meant
to demonstrate the possibilities available to the adventurous programmer.
To your class you should add handlers for the
messages. I've also added an override for
PreSubclassWindow in case you wish to
perform any initialisation that requires the window to have been created. See my
previous article for a discussion
The aim of this control is to display bitmaps, so we'll a method to set the bitmap and call it
SetBitmap. We're not only talented, us programmers, but extremely imaginative as well.
The internal code for the control is unimportant to this discussion but is included for completeness.
Add a member variable of type
CBitmap to the class, as well as the
class CBitmapViewer : public CWnd
BOOL SetBitmap(UINT nIDResource);
CBitmapViewer implementation file add the following code for your
method, and your
WM_ERASEBKGND message handlers:
if (m_Bitmap.GetSafeHandle() != NULL)
CBitmap* pOldBitmap = (CBitmap*) MemDC.SelectObject(&m_Bitmap);
dc.StretchBlt(0, 0, rect.Width(), rect.Height(),
0, 0, bm.bmWidth, bm.bmHeight,
BOOL CBitmapViewer::OnEraseBkgnd(CDC* pDC)
if (m_Bitmap.GetSafeHandle() != NULL)
BOOL CBitmapViewer::SetBitmap(UINT nIDResource)
Making the class a Custom Control
So far we have a class that allows us to load and display a bitmap - but as yet we have no
way of actually using this class. We have two choices in creating the control - either
dynamically by calling
Create or via a dialog template created using the Visual
Studio resource editor.
Since our class is derived from
CWnd we can use
create the control dynamically. For instance, in your dialog's
could have the following code:
m_Viewer.Create(NULL, _T(""), WS_VISIBLE, CRect(0,0,100,100), this, 1);
where m_Viewer is an object of type
CBitmapViewer that is declared
in your dialogs header, and
IDB_BITMAP1 is the ID of a bitmap resource. The
control will be created and the bitmap will display.
However, what if we wished to place the control in a dialog template using the Visual
Studio resource editor? For this we need to register a Windows Class name using the
AfxRegisterClass function. Registering a class allows us to specify the background
color, the cursor, and the style. See
AfxRegisterWndClass in the docs for more
For this example we'll register a simple class and call it "MFCBitmapViewerCtrl". We only
need to register the control once, and a neat place to do this is in the constructor of the
class we are writing
#define BITMAPVIEWER_CLASSNAME _T("MFCBitmapViewerCtrl")
HINSTANCE hInst = AfxGetInstanceHandle();
if (!(::GetClassInfo(hInst, BITMAPVIEWER_CLASSNAME, &wndcls)))
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpfnWndProc = ::DefWindowProc;
wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
wndcls.hInstance = hInst;
wndcls.hIcon = NULL;
wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
wndcls.hbrBackground = (HBRUSH) (COLOR_3DFACE + 1);
wndcls.lpszMenuName = NULL;
wndcls.lpszClassName = BITMAPVIEWER_CLASSNAME;
In our example of creating the control dynamically, we should now change the creation call to
m_Viewer.Create(_T("MFCBitmapViewerCtrl"), _T(""), WS_VISIBLE, CRect(0,0,100,100), this, 1);
This will ensure the correct window styles, cursors and colors are used in the control. It's probably
worthwhile writing a new
Create function for your custom control so that users don't
have to remember the window class name. For example:
BOOL CBitmapViewer::Create(CWnd* pParentWnd, const RECT& rect, UINT nID, DWORD dwStyle )
return CWnd::Create(BITMAPVIEWER_CLASSNAME, _T(""), dwStyle, rect, pParentWnd, nID);
To use the custom control in a dialog resource, simply create a custom control on the dialog
resource as you would any other control
and then in the control's properties, specify the class name as "MFCBitmapViewerCtrl"
The final step is to link up a member variable with the control. Simply declare an object
CBitmapViewer in your dialog class (say, m_Viewer) and in your
DoDataExchange add the following
void CCustomControlDemoDlg::DoDataExchange(CDataExchange* pDX)
DDX_Control(pDX, IDC_CUSTOM1, m_Viewer);
DDX_Control links the member variable m_Viewer with the control with
IDC_CUSTOM1 by calling
SubclassWindow. Creating a custom
control in your dialog resource with the class name "MFCBitmapViewerCtrl" creates a window
that behaves as your
CBitmapViewer::RegisterWindowClass has specified, and
DDX_Control call links your
CBitmapViewer object with
this pre-prepared window.
Compile your project, run the application, and be amazed. You've just created a custom control.
Chris is the Co-founder, Administrator, Architect, Chief Editor and Shameless Hack who wrote and runs The Code Project. He's been programming since 1988 while pretending to be, in various guises, an astrophysicist, mathematician, physicist, hydrologist, geomorphologist, defence intelligence researcher and then, when all that got a bit rough on the nerves, a web developer. He is a Microsoft Visual C++ MVP both globally and for Canada locally.
His programming experience includes C/C++, C#, SQL, MFC, ASP, ASP.NET, and far, far too much FORTRAN. He has worked on PocketPCs, AIX mainframes, Sun workstations, and a CRAY YMP C90 behemoth but finds notebooks take up less desk space.
He dodges, he weaves, and he never gets enough sleep. He is kind to small animals.
Chris was born and bred in Australia but splits his time between Toronto and Melbourne, depending on the weather. For relaxation he is into road cycling, snowboarding, rock climbing, and storm chasing.