Click here to Skip to main content
Click here to Skip to main content

Creating Custom Controls

, 11 May 2000
Rate this:
Please Sign up or sign in to vote.
An introduction to creating custom controls using MFC
  • Download source files - 21 Kb
  • Sample Image - CustomControl.jpg

    Introduction

    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 CListCtrl 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 class (CWnd). In this example we'll create a custom control for displaying bitmaps, and we'll call this class CBitmapViewer. Obviously there is already the 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 WM_PAINT and WM_ERASEBKGND 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 of PreSubclassWindow.

    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 SetBitmap prototype:

    class CBitmapViewer : public CWnd
    {
    // Construction
    public:
        CBitmapViewer();
    
    // Attributes
    public:
        BOOL SetBitmap(UINT nIDResource);
    
        ...
        
    protected:
        CBitmap m_Bitmap;
    };

    In your CBitmapViewer implementation file add the following code for your SetBitmap method, and your WM_PAINT and WM_ERASEBKGND message handlers:

    void CBitmapViewer::OnPaint() 
    {
        // Draw the bitmap - if we have one.
        if (m_Bitmap.GetSafeHandle() != NULL)
        {
            CPaintDC dc(this); // device context for painting
    
            // Create memory DC
            CDC MemDC;
            if (!MemDC.CreateCompatibleDC(&dc))
                return;
    
            // Get Size of Display area
            CRect rect;
            GetClientRect(rect);
    
            // Get size of bitmap
            BITMAP bm;
            m_Bitmap.GetBitmap(&bm);
            
            // Draw the bitmap
            CBitmap* pOldBitmap = (CBitmap*) MemDC.SelectObject(&m_Bitmap);
            dc.StretchBlt(0, 0, rect.Width(), rect.Height(), 
                          &MemDC, 
                          0, 0, bm.bmWidth, bm.bmHeight, 
                          SRCCOPY);
            MemDC.SelectObject(pOldBitmap);      
        }
        
        // Do not call CWnd::OnPaint() for painting messages
    }
    
    BOOL CBitmapViewer::OnEraseBkgnd(CDC* pDC) 
    {
        // If we have an image then don't perform any erasing, since the OnPaint
        // function will simply draw over the background
        if (m_Bitmap.GetSafeHandle() != NULL)
            return TRUE;
        
        // Obviously we don't have a bitmap - let the base class deal with it.
        return CWnd::OnEraseBkgnd(pDC);
    }
    
    BOOL CBitmapViewer::SetBitmap(UINT nIDResource)
    {
        return m_Bitmap.LoadBitmap(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 CWnd::Create to create the control dynamically. For instance, in your dialog's OnInitDialog you could have the following code:

    // CBitmapViewer m_Viewer; - declared in dialog class header
    
    m_Viewer.Create(NULL, _T(""), WS_VISIBLE, CRect(0,0,100,100), this, 1);
    m_Viewer.SetBitmap(IDB_BITMAP1);
    

    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 information.

    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")  // Window class name
    
    CBitmapViewer::CBitmapViewer()
    {
        RegisterWindowClass();
    }
    
    BOOL CBitmapViewer::RegisterWindowClass()
    {
        WNDCLASS wndcls;
        HINSTANCE hInst = AfxGetInstanceHandle();
    
        if (!(::GetClassInfo(hInst, BITMAPVIEWER_CLASSNAME, &wndcls)))
        {
            // otherwise we need to register a new class
            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;
    
            if (!AfxRegisterClass(&wndcls))
            {
                AfxThrowResourceException();
                return FALSE;
            }
        }
    
        return TRUE;
    }
    

    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 /*=WS_VISIBLE*/)
    {
        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 of type CBitmapViewer in your dialog class (say, m_Viewer) and in your dialog's DoDataExchange add the following

    void CCustomControlDemoDlg::DoDataExchange(CDataExchange* pDX)
    {
        CDialog::DoDataExchange(pDX);
        //{{AFX_DATA_MAP(CCustomControlDemoDlg)
        DDX_Control(pDX, IDC_CUSTOM1, m_Viewer);
        //}}AFX_DATA_MAP
    }

    DDX_Control links the member variable m_Viewer with the control with ID 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 then the 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.

    License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

    About the Author

    Chris Maunder
    Founder CodeProject
    Canada Canada
    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.
    Follow on   Twitter   Google+   LinkedIn

    Comments and Discussions

     
    QuestionCreating Custom Control for CView PinmemberMember 78946015-Jun-14 18:49 
    QuestionI'm a litle confused (nothing new) PinmemberH.Brydon28-Jun-13 17:06 
    QuestionThank you Pinmemberkill1421-Nov-12 15:26 
    GeneralMy vote of 1 Pinmemberhoseinhero2-Sep-12 7:05 
    QuestionThanks. PinmemberMarty Barringer7-Jul-12 12:52 
    QuestionHow to add bitmap from other loaction Pinmembersayit2shoaib23-May-12 17:35 
    QuestionWhat if i have to handle more then one control? Pinmemberamarasat22-Jun-11 5:59 
    GeneralSolution for AfxGetInstanceHandle() language DLL failure PinmemberScott Crawford10-Mar-11 11:20 
    GeneralRe: Solution for AfxGetInstanceHandle() language DLL failure PinmemberMithun Kadam22-Feb-12 23:04 
    GeneralQuestion Pinmemberthready4-Mar-10 16:45 
    GeneralRe: Question Pinmemberthready4-Mar-10 17:39 
    QuestionHow to develop DLL for class which is used for custom control Pinmembergsheladia7-Jun-09 19:55 
    AnswerRe: How to develop DLL for class which is used for custom control Pinmemberthready4-Mar-10 13:41 
    GeneralError when closing Dialog PinmemberBryster20-Jan-09 2:40 
    GeneralRe: Error when closing Dialog Pinmemberthready4-Mar-10 13:43 
    QuestionHow to use custom control in VC6? Pinmemberpoyu3215-Oct-08 17:15 
    QuestionProject Type? Pinmemberrioch21-Aug-08 1:12 
    AnswerRe: Project Type? PinadminChris Maunder21-Aug-08 3:59 
    GeneralRe: Project Type? Pinmemberrioch21-Aug-08 4:07 
    GeneralRe: Project Type? PinadminChris Maunder21-Aug-08 4:09 
    GeneralCan't get the custom control to work Pinmembertimmyg76015-Jun-08 14:27 
    GeneralRe: Can't get the custom control to work Pinmembertimmyg76018-Jun-08 15:53 
    GeneralMisundersood what your said Pinmemberladiode4-Jul-07 20:32 
    GeneralCustom Control in MS VS 2005 Pinmemberthe_anonymous13-Jan-07 23:30 
    GeneralRe: Custom Control in MS VS 2005 PinmemberRamon9117-May-07 22:09 
    GeneralAbout the function "Create()" Pinmemberwzh198312218-Dec-06 23:19 
    GeneralHint for flicker-free drawing [modified] PinmemberDave Calkins24-Aug-06 13:56 
    GeneralClass name vs class name.... Pinmemberkjthompson23-Feb-06 9:42 
    GeneralRe: Class name vs class name.... PinmemberMr.Prakash5-Mar-07 20:50 
    QuestionCustom Control and RUNTIME_CLASS? Pinmembernprogrammer26-Oct-05 10:30 
    Generalerror when trying to use a CView wndow as a custom control on dialog. PinmemberWarChildWTS29-Aug-05 19:43 
    GeneralMinor bug in RegisterWindowClass() method. Pinmemberarmstrom10-Jan-05 11:04 
    GeneralRe: Minor bug in RegisterWindowClass() method. PinmemberMoak29-Mar-08 15:36 
    QuestionHow to cram a dialog in an custom control? PinmemberT.T.H.21-Oct-04 23:47 
    AnswerRe: How to cram a dialog in an custom control? PinmemberT.T.H.6-Apr-05 0:57 
    GeneralPics on this thread won't show in non-IE browsers PinsussAnonymous29-Jun-04 16:51 
    GeneralRe: Pics on this thread won't show in non-IE browsers PinsussAnonymous29-Jun-04 16:54 
    GeneralModeless Dialog PinmemberEnzo Scordio25-May-04 1:31 
    GeneralWM_MOUSEWHEEL and KEYDOWN PinmemberTomazZ12-Mar-04 1:21 
    GeneralRe: WM_MOUSEWHEEL and KEYDOWN PinmemberTomazZ12-Mar-04 1:34 
    GeneralCPU usage PinsussConfused-216-Feb-04 6:11 
    GeneralCPU usage PinsussConfused-116-Feb-04 6:04 
    QuestionHow can I provide my custom control with a 3D border? PinmemberErnesto Moscoso9-Jan-04 6:56 
    AnswerRe: How can I provide my custom control with a 3D border? PinmemberPeter Moonen9-Jan-04 22:47 
    GeneralHow can I reduce the size of the Client Area? PinmemberErnesto Moscoso12-Jan-04 2:27 
    Generalchange bitmap Pinmembercraigsmith00713-Dec-03 5:22 
    GeneralRe: change bitmap PinmemberPeter Moonen3-Jan-04 11:31 
    GeneralNeed Help PinsussAnonymous4-Dec-03 16:49 
    GeneralRe: Need Help PinmemberPeter Moonen3-Jan-04 11:34 
    QuestionHow can easily pass a Custom Control to ActiveX PinmemberZeroCoolLP11-Nov-03 11:51 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

    | Advertise | Privacy | Mobile
    Web02 | 2.8.140709.1 | Last Updated 12 May 2000
    Article Copyright 2000 by Chris Maunder
    Everything else Copyright © CodeProject, 1999-2014
    Terms of Service
    Layout: fixed | fluid