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:
BEGIN_MESSAGE_MAP(CCalendar, CWnd) ON_WM_HELPINFO() END_MESSAGE_MAP()
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:
protected: afx_msg BOOL OnHelpInfo(HELPINFO* pHelpInfo); DECLARE_MESSAGE_MAP()
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.
Control | ID |
Calendar control | IDC_CALENDAR1 |
Select Date button | IDC_SELECTDATE |
Edit control | IDC_DAY |
Edit control | IDC_MONTH |
Edit control | IDC_YEAR |
Next Week button | IDC_NEXTWEEK |
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 ID | Message | Member Function |
CActiveXDialog | WM_INITDIALOG | OnInitDialog (virtual function) |
IDC_CALENDAR1 | NewMonth (event) | OnNewMonthCalendar1 |
IDC_SELECTDATE | BN_CLICKED | OnSelectDate |
IDC_NEXTWEEK | BN_CLICKED | OnNextWeek |
IDOK | BN_CLICKED | OnOK (virtual function) |
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.
ACTIVEXDIALOG.H //{{AFX_INCLUDES() #include "calendar.h" //}}AFX_INCLUDES #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 public: CActiveXDialog(CWnd* pParent = NULL); // standard constructor // Dialog Data //{{AFX_DATA(CActiveXDialog) enum { IDD = IDD_ACTIVEXDIALOG }; CCalendar m_calendar; short m_sDay; short m_sMonth; short m_sYear; //}}AFX_DATA COleVariant m_varValue; unsigned long m_BackColor; // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CActiveXDialog) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV // support //}}AFX_VIRTUAL // Implementation protected: // Generated message map functions //{{AFX_MSG(CActiveXDialog) virtual BOOL OnInitDialog(); afx_msg void OnNewMonthCalendar1(); afx_msg void OnSelectDate(); afx_msg void OnNextWeek(); virtual void OnOK(); DECLARE_EVENTSINK_MAP() //}}AFX_MSG DECLARE_MESSAGE_MAP() }; //{{AFX_INSERT_LOCATION}} // 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 // 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__; #endif ////////////////////////////////////////////////////////////////////// // CActiveXDialog dialog CActiveXDialog::CActiveXDialog(CWnd* pParent /*=NULL*/) : CDialog(CActiveXDialog::IDD, pParent) { //{{AFX_DATA_INIT(CActiveXDialog) m_sDay = 0; m_sMonth = 0; m_sYear = 0; //}}AFX_DATA_INIT m_BackColor = 0x8000000F; } void CActiveXDialog::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CActiveXDialog) 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); //}}AFX_DATA_MAP DDX_OCColor(pDX, IDC_CALENDAR1, DISPID_BACKCOLOR, m_BackColor); } BEGIN_MESSAGE_MAP(CActiveXDialog, CDialog) //{{AFX_MSG_MAP(CActiveXDialog) ON_BN_CLICKED(IDC_SELECTDATE, OnSelectDate) ON_BN_CLICKED(IDC_NEXTWEEK, OnNextWeek) //}}AFX_MSG_MAP END_MESSAGE_MAP() ////////////////////////////////////////////////////////////////////// // CActiveXDialog message handlers BEGIN_EVENTSINK_MAP(CActiveXDialog, CDialog) //{{AFX_EVENTSINK_MAP(CActiveXDialog) ON_EVENT(CActiveXDialog, IDC_CALENDAR1, 3 /* NewMonth */, OnNewMonthCalendar1, VTS_NONE) //}}AFX_EVENTSINK_MAP END_EVENTSINK_MAP() BOOL CActiveXDialog::OnInitDialog() { CDialog::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); m_calendar.SetDay(m_sDay); m_calendar.SetMonth(m_sMonth); m_calendar.SetYear(m_sYear); } void CActiveXDialog::OnNextWeek() { m_calendar.NextWeek(); } void CActiveXDialog::OnOK() { CDialog::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.
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.
pDC->TextOut(0, 0, "Press the left mouse button here.");
For Win32 ProgrammersIf 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:
CONTROL "",IDC_CALENDAR1, "{8E27C92B-1264-101C-8A2F-040224009C02}", WS_TABSTOP,7,7,217,113There'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 CWndthe 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.