The EX08A Example—An ActiveX Control Dialog Container

Now it's time to build an application that uses a Calendar control in a dialog. Here are the steps to create the EX08A example:

  1. Verify that the Calendar control is registered. If the control does not appear in the Visual C++ Gallery's Registered ActiveX Controls page, copy the files MSCal.ocx, MSCal.hlp, and MSCal.cnt to your system directory and register the control by running the REGCOMP program.

  2. Run AppWizard to produce \vcpp32\ex08a\ex08a. Accept all of the default settings but two: select Single Document and deselect Printing And Print Preview. In the AppWizard Step 3 dialog, make sure the ActiveX Controls option is selected, as shown below.

    Click to view at full size.

  3. Install the Calendar control in the EX08A project. Choose Add To Project from Visual C++'s Project menu, and then choose Components And Controls. Choose Registered ActiveX Controls, and then choose Calendar Control 8.0. ClassWizard generates two classes in the EX08A directory, as shown here.

  4. Edit the Calendar control class to handle help messages. Add Calendar.cpp to the following message map code:

    BEGIN_MESSAGE_MAP(CCalendar, CWnd)

    In the same file, add the OnHelpInfo function:

    BOOL CCalendar::OnHelpInfo(HELPINFO* pHelpInfo) 
        // Edit the following string for your system
        ::WinHelp(GetSafeHwnd(), "c:\\winnt\\system32\\mscal.hlp",
                  HELP_FINDER, 0);
        return FALSE;

    In Calendar.h, add the function prototype and declare the message map:

        afx_msg BOOL OnHelpInfo(HELPINFO* pHelpInfo);

    The OnHelpInfo function is called if the user presses the F1 key when the Calendar control has the input focus. We have to add the message map code by hand because ClassWizard doesn't modify generated ActiveX classes.

    The ON_WM_HELPINFO macro maps the WM_HELP message, which is new to Microsoft Windows 95 and Microsoft Windows NT 4.0. You can use ON_WM_HELPINFO in any view or dialog class and then code the handler to activate any help system. Chapter 21 describes the MFC context-sensitive help system, some of which predates the WM_HELP message.

  5. Use the dialog editor to create a new dialog resource. Choose Resource from Visual C++'s Insert menu, and then choose Dialog. The dialog editor assigns the ID IDD_DIALOG1 to the new dialog. Next change the ID to IDD_ACTIVEXDIALOG, change the dialog caption to ActiveX Dialog, and set the dialog's Context Help property (on the More Styles page). Accept the default OK and Cancel buttons with the IDs IDOK and IDCANCEL, and then add the other controls as shown in Figure 8-1. Make the Select Date button the default button. Drag the Calendar control from the control palette. Then set an appropriate tab order. Assign control IDs as shown in the following table.

    Calendar controlIDC_CALENDAR1
    Select Date buttonIDC_SELECTDATE
    Edit controlIDC_DAY
    Edit controlIDC_MONTH
    Edit controlIDC_YEAR
    Next Week buttonIDC_NEXTWEEK

  6. Use ClassWizard to create the CActiveXDialog class. If you run ClassWizard directly from the dialog editor window, it will know that you want to create a CDialog-derived class based on the IDD_ACTIVEXDIALOG template. Simply accept the default options, and name the class CActiveXDialog.

    Click on the ClassWizard Message Maps tab, and then add the message handler functions shown in the table below. To add a message handler function, click on an object ID, click on a message, and click the Add Function button. If the Add Member Function dialog box appears, type the function name and click the OK button.
    Object IDMessageMember Function
    CActiveXDialogWM_INITDIALOGOnInitDialog (virtual function)
    IDC_CALENDAR1NewMonth (event)OnNewMonthCalendar1
    IDOKBN_CLICKEDOnOK (virtual function)

  7. Use ClassWizard to add data members to the CActiveXDialog class. Click on the Member Variables tab, and then add the data members as shown in the illustration below.

    You might think that the ClassWizard ActiveX Events tab is for mapping ActiveX control events in a container. That's not true: it's for ActiveX control developers who are defining events for a control.

    Click to view at full size.

  8. Edit the CActiveXDialog class. Add the m_varValue and m_BackColor data members, and then edit the code for the five handler functions OnInitDialog, OnNewMonthCalendar1, OnSelectDate, OnNextWeek, and OnOK. Figure 8-2 shows all the code for the dialog class, with new code in boldface.


    #include "calendar.h"
    #if !defined(AFX_ACTIVEXDIALOG_H__1917789D_6F24_11D0_8FD9_00C04FC2A0C2__INCLUDED_)
    #define AFX_ACTIVEXDIALOG_H__1917789D_6F24_11D0_8FD9_00C04FC2A0C2__INCLUDED_
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    // ActiveXDialog.h : header file
    // CActiveXDialog dialog
    class CActiveXDialog : public CDialog
    // Construction
        CActiveXDialog(CWnd* pParent = NULL);   // standard constructor
    // Dialog Data
        enum { IDD = IDD_ACTIVEXDIALOG };
        CCalendar    m_calendar;
        short    m_sDay;
        short    m_sMonth;
        short    m_sYear;
        COleVariant m_varValue;
        unsigned long m_BackColor;
    // Overrides
        // ClassWizard generated virtual function overrides
        virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV
                                                         //  support
    // Implementation
        // Generated message map functions
        virtual BOOL OnInitDialog();
        afx_msg void OnNewMonthCalendar1();
        afx_msg void OnSelectDate();
        afx_msg void OnNextWeek();
        virtual void OnOK();
    // Microsoft Visual C++ will insert additional
    //  declarations immediately before the previous line.
    #endif // !defined(AFX_ACTIVEXDIALOG_H__1917789D_6F24_11D0_8FD9_00C04FC2A0C2__INCLUDED_)


    // ActiveXDialog.cpp : implementation file
    #include "stdafx.h"
    #include "ex08a.h"
    #include "ActiveXDialog.h"
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = __FILE__;
    // CActiveXDialog dialog
    CActiveXDialog::CActiveXDialog(CWnd* pParent /*=NULL*/)
        : CDialog(CActiveXDialog::IDD, pParent)
        m_sDay = 0;
        m_sMonth = 0;
        m_sYear = 0;
        m_BackColor = 0x8000000F;
    void CActiveXDialog::DoDataExchange(CDataExchange* pDX)
        DDX_Control(pDX, IDC_CALENDAR1, m_calendar);
        DDX_Text(pDX, IDC_DAY, m_sDay);
        DDX_Text(pDX, IDC_MONTH, m_sMonth);
        DDX_Text(pDX, IDC_YEAR, m_sYear);
    BEGIN_MESSAGE_MAP(CActiveXDialog, CDialog)
    // CActiveXDialog message handlers
    BEGIN_EVENTSINK_MAP(CActiveXDialog, CDialog)
        ON_EVENT(CActiveXDialog, IDC_CALENDAR1, 3 /* NewMonth */, OnNewMonthCalendar1, VTS_NONE)
    BOOL CActiveXDialog::OnInitDialog() 
        m_calendar.SetValue(m_varValue); // no DDX for VARIANTs
        return TRUE;  // return TRUE unless you set the focus to a control
                      // EXCEPTION: OCX Property Pages should return FALSE
    void CActiveXDialog::OnNewMonthCalendar1() 
        AfxMessageBox("EVENT:  CActiveXDialog::OnNewMonthCalendar1");
    void CActiveXDialog::OnSelectDate() 
        CDataExchange dx(this, TRUE);
        DDX_Text(&dx, IDC_DAY, m_sDay);
        DDX_Text(&dx, IDC_MONTH, m_sMonth);
        DDX_Text(&dx, IDC_YEAR, m_sYear);
    void CActiveXDialog::OnNextWeek() 
    void CActiveXDialog::OnOK() 
        m_varValue = m_calendar.GetValue(); // no DDX for VARIANTs

    Figure 8-2. Code for the CActiveXDialog class.

    The OnSelectDate function is called when the user clicks the Select Date button. The function gets the day, month, and year values from the three edit controls and transfers them to the control's properties. ClassWizard can't add DDX code for the BackColor property, so you must add it by hand. In addition, there's no DDX code for VARIANT types, so you must add code to the OnInitDialog and OnOK functions to set and retrieve the date with the control's Value property.

  9. Connect the dialog to the view. Use ClassWizard to map the WM_LBUTTONDOWN message, and then edit the handler function as follows:

    void CEx08aView::OnLButtonDown(UINT nFlags, CPoint point) 
        CActiveXDialog dlg;
        dlg.m_BackColor = RGB(255, 251, 240); // light yellow
        COleDateTime today = COleDateTime::GetCurrentTime();
        dlg.m_varValue = COleDateTime(today.GetYear(), today.GetMonth(),
                                      today.GetDay(), 0, 0, 0);
        if (dlg.DoModal() == IDOK) {
            COleDateTime date(dlg.m_varValue);
            AfxMessageBox(date.Format("%B %d, %Y"));

    The code sets the background color to light yellow and the date to today's date, displays the modal dialog, and reports the date returned by the Calendar control. You'll need to include ActiveXDialog.h in ex08aView.cpp.

  10. Edit the virtual OnDraw function in the file ex08aView.cpp. To prompt the user to press the left mouse button, replace the code in the view class OnDraw function with this single line:

    pDC->TextOut(0, 0, "Press the left mouse button here.");

  11. Build and test the EX08A application. Open the dialog, enter a date in the three edit controls, and then click the Select Date button. Click the Next Week button. Try moving the selected date directly to a new month, and observe the message box that is triggered by the NewMonth event. Watch for the final date in another message box when you click OK. Press the F1 key for help on the Calendar control.

For Win32 Programmers

If you use a text editor to look inside the ex08a.rc file, you might be quite mystified. Here's the entry for the Calendar control in the ActiveX Dialog template:


There's a 32-digit number sequence where the window class name should be. What's going on? Actually, the resource template isn't the one that Windows sees. The CDialog::DoModal function "preprocesses" the resource template before passing it on to the dialog box procedure within Windows. It strips out all the ActiveX controls and creates the dialog window without them. Then it loads the controls (based on their 32-digit identification numbers, called CLSIDs) and activates them in place, causing them to create their own windows in the correct places. The initial values for the properties you set in the dialog editor are stored in binary form inside the project's custom DLGINIT resource.

When the modal dialog runs, the MFC code coordinates the messages sent to the dialog window both by the ordinary controls and by the ActiveX controls. This allows the user to tab between all the controls in the dialog, even though the ActiveX controls are not part of the actual dialog template.

When you call the member functions for the control object, you might think you're calling functions for a child window. The control window is quite far removed, but MFC steps in to make it seem as if you're communicating with a real child window. In ActiveX terminology, the container owns a site, which is not a window. You call functions for the site, and ActiveX and MFC make the connection to the underlying window in the ActiveX control.

The container window is an object of a class derived from CWnd. The control site is also an object of a class derived from CWnd—the ActiveX control wrapper class. That means that the CWnd class has built-in support for both containers and sites.

What you're seeing here is MFC ActiveX control support grafted onto regular Windows. Maybe some future Windows version will have more direct support for ActiveX Controls. As a matter of fact, ActiveX versions of the Windows common controls already exist.