Programming with Microsoft Visual C++.NET 6ed [Electronic resources]

George Shepherd, David Kruglinski

نسخه متنی -صفحه : 319/ 78
نمايش فراداده

ActiveX Control Container Programming

MFC and Visual Studio .NET support ActiveX controls both in dialog boxes and as "child windows." To use ActiveX controls, you must understand how a control grants access to properties, and you must understand the interactions between your DDX code and those property values.

Property Access

The ActiveX control developer designates certain properties for access at design time. Those properties are specified in the property pages that the control displays in the dialog editor when you right-click on a control and choose Properties. The calendar control's main property page looks like this:

All the control's properties, including the design-time properties, are accessible at runtime. Some properties, however, might be designated as read-only.

Visual Studio .NET's C++ Wrapper Classes for ActiveX Controls

When you insert an ActiveX control into a project, Visual Studio .NET generates a C++ wrapper class, derived from CWnd, that is tailored to your control's methods and properties. The class has member functions for all properties and methods, and it has constructors that you can use to dynamically create an instance of the control. (Visual Studio .NET also generates wrapper classes for objects used by the control.) Here are a few typical member functions from the file

CCalendar.h that Visual Studio .NET generates for the calendar control:

unsigned long get_BackColor()
{
unsigned long result;
InvokeHelper(DISPID_BACKCOLOR, 
DISPATCH_PROPERTYGET, VT_UI4, (void*)&result, NULL);
return result;
}
void put_BackColor(unsigned long newValue)
{
static BYTE parms[] = VTS_UI4 ;
InvokeHelper(DISPID_BACKCOLOR, DISPATCH_PROPERTYPUT, 
VT_EMPTY, NULL, parms, newValue);
}
short get_Day()
{
short result;
InvokeHelper(0x11, DISPATCH_PROPERTYGET, 
VT_I2, (void*)&result, NULL);
return result;
}
void put_Day(short newValue)
{
static BYTE parms[] = VTS_I2 ;
InvokeHelper(0x11, DISPATCH_PROPERTYPUT, VT_EMPTY, 
NULL, parms, newValue);
}
LPDISPATCH get_DayFont()
{
LPDISPATCH result;
InvokeHelper(0x1, DISPATCH_PROPERTYGET, 
VT_DISPATCH, (void*)&result, NULL);
return result;
}
void put_DayFont(LPDISPATCH newValue)
{
static BYTE parms[] = VTS_DISPATCH ;
InvokeHelper(0x1, DISPATCH_PROPERTYPUT, 
VT_EMPTY, NULL, parms, newValue);
}
unsigned long get_DayFontColor()
{
unsigned long result;
InvokeHelper(0x2, DISPATCH_PROPERTYGET, VT_UI4, 
(void*)&result, NULL);
return result;
}
void put_DayFontColor(unsigned long newValue)
{
static BYTE parms[] = VTS_UI4 ;
InvokeHelper(0x2, DISPATCH_PROPERTYPUT, 
VT_EMPTY, NULL, parms, newValue);
}
short get_DayLength()
{
short result;
InvokeHelper(0x12, DISPATCH_PROPERTYGET, VT_I2, 
(void*)&result, NULL);
return result;
}
void put_DayLength(short newValue)
{
static BYTE parms[] = VTS_I2 ;
InvokeHelper(0x12, DISPATCH_PROPERTYPUT, 
VT_EMPTY, NULL, parms, newValue);
}
short get_FirstDay()
{
short result;
InvokeHelper(0x13, DISPATCH_PROPERTYGET, 
VT_I2, (void*)&result, NULL);
return result;
}
void put_FirstDay(short newValue)
{
static BYTE parms[] = VTS_I2 ;
InvokeHelper(0x13, DISPATCH_PROPERTYPUT, 
VT_EMPTY, NULL, parms, newValue);
}
void NextDay()
{
InvokeHelper(0x16, DISPATCH_METHOD, 
VT_EMPTY, NULL, NULL);   
}
void NextMonth()
{
InvokeHelper(0x17, DISPATCH_METHOD, 
VT_EMPTY, NULL, NULL);
}
void NextWeek()
{
InvokeHelper(0x18, DISPATCH_METHOD, 
VT_EMPTY, NULL, NULL);
}
void NextYear()
{
InvokeHelper(0x19, DISPATCH_METHOD, 
VT_EMPTY, NULL, NULL);
}

You don't have to concern yourself too much with the code inside these functions, but you can match up the first parameter of each InvokeHelper function with the dispatch ID for the corresponding property or method in the calendar control property list. As you can see, properties always have separate put_ and get_ functions. To call a method, you simply call the corresponding function. For example, to call the NextDay method from a dialog class function, you write code such as this:

m_calendar.NextDay();

In this case, m_calendar is an object of class CCalendar, the wrapper class for the calendar control.

MFC Application Wizard Support for ActiveX Controls

When the ActiveX Controls option (the default) is selected in the MFC Application Wizard, the wizard inserts the following line in your application class InitInstance member function:

AfxEnableControlContainer();

It also inserts the following line in the project's

StdAfx.h file:

#include <afxdisp.h>

If you decide to add ActiveX controls to an existing project that doesn't include the two lines above, you can simply add the lines.

The Add Class Wizard and the Container Dialog Box

If you've used the dialog editor to generate a dialog template, you know that you can use the Add Class Wizard to generate a C++ class for the dialog window. If your template contains one or more ActiveX controls, you can use the Add Member Variable Wizard to add data members and the Class View's Properties window to add event handler functions.

Dialog Class Data Members vs. Wrapper Class Usage

Chapter 8. The CDialog::OnInitDialog function calls CWnd::UpdateData(FALSE) to read the dialog class data members, and the CDialog::OnOK function calls UpdateData(TRUE) to write the members. Suppose you add a data member for each ActiveX control property and you need to get the Value property value in a button handler. If you call UpdateData(FALSE) in the button handler, it will read all the property values from all the dialog's controls—clearly a waste of time. It's more effective to avoid using a data member and to call the wrapper class get_ function instead. To call that function, you must first tell Visual Studio .NET to add a wrapper class object data member.

Suppose you have a calendar wrapper class CCalendar and you have an m_calendar data member in your dialog class. If you want to get the Value property, you do it like this:

COleVariant var = m_calendar.get_Value();
Note

The VARIANT type and COleVariant class are described in Chapter 23.

Now consider another case: You want to set the day to the 5th of the month before the control is displayed. To do this by hand, you add a dialog class data member m_sCalDay that corresponds to the control's short integer Day property. Then you add the following line to the DoDataExchange function:

DDX_OCShort(pDX, IDC_CALENDAR1, 0x11, m_sCalDay);

The third parameter is the Day property's integer index (its DispID), which you can find in the get_Day and put_Day functions generated by Visual Studio .NET for the control. Here's how you construct and display the dialog box:

CMyDialog dlg;
dlg.m_sCalDay = 5;
dlg.DoModal();

The DDX code takes care of setting the property value from the data member before the control is displayed. No other programming is needed. As you'd expect, the DDX code sets the data member from the property value when the user clicks the OK button.

Note

Even when Visual Studio .NET correctly detects a control's properties, it can't always generate data members for all of them. In particular, no DDX functions exist for VARIANT properties such as the calendar's Value property. You have to use the wrapper class for these properties.

Mapping ActiveX Control Events

The Class View's Properties window lets you map ActiveX control events in the same way that you map Windows messages and command messages from controls. If a dialog class contains one or more ActiveX controls, the code wizards available from the Properties window add and maintain an event sink map that connects mapped events to their handler functions. You can see the code in

ActiveXDialog.h and

ActiveXDialog.cpp later in this chapter.

Note

ActiveX controls have the annoying habit of firing events before your program is ready for them. If your event handler uses windows or pointers to C++ objects, it should verify the validity of those entities before using them.

Locking ActiveX Controls in Memory

Normally, an ActiveX control remains mapped in your process as long as its parent dialog box is active. That means it must be reloaded each time the user opens a modal dialog box. The reloads are usually quicker than the initial load because of disk caching, but you can lock the control into memory for better performance. To do so, add the following line in the overridden OnInitDialog function after the base class call:

AfxOleLockControl(m_calendar.GetClsid());

The ActiveX control remains mapped until your program exits or until you call the AfxOleUnlockControl function.