MFC ActiveX Controls: Advanced Topics

 

The new home for Visual Studio documentation is Visual Studio 2017 Documentation on docs.microsoft.com.

The latest version of this topic can be found at MFC ActiveX Controls: Advanced Topics.

This article covers advanced topics related to developing ActiveX controls. These include:

Because the ActiveX control classes are part of the class library, you can apply the same procedures and rules for using database classes in a standard MFC application to developing ActiveX controls that use the MFC database classes.

For a general overview of the MFC database classes, see MFC Database Classes (DAO and ODBC). The article introduces both the MFC ODBC classes and the MFC DAO classes and directs you to more details on either.

System_CAPS_ICON_note.jpg Note

As of Visual C++ .NET, the Visual C++ environment and wizards no longer support DAO (although the DAO classes are included and you can still use them). Microsoft recommends that you use OLE DB Templates or ODBC and MFC for new projects. You should only use DAO in maintaining existing applications.

A parameterized property (sometimes called a property array) is a method for exposing a homogeneous collection of values as a single property of the control. For example, you can use a parameterized property to expose an array or a dictionary as a property. In Visual Basic, such a property is accessed using array notation:

        x = o.Array(2, 3) ' gets element of 2D array
        o.Array(2, 3) = 7 ' sets element of 2D array

Use the Add Property Wizard to implement a parameterized property. The Add Property Wizard implements the property by adding a pair of Get/Set functions that allow the control user to access the property using the above notation or in the standard fashion.

Similar to methods and properties, parameterized properties also have a limit to the number of parameters allowed. In the case of parameterized properties, the limit is 15 parameters (with one parameter reserved for storing the property value).

The following procedure adds a parameterized property, called Array, which can be accessed as a two-dimensional array of integers.

To add a parameterized property using the Add Property Wizard

  1. Load your control's project.

  2. In Class View, expand the library node of your control.

  3. Right-click the interface node for your control (the second node of the library node) to open the shortcut menu.

  4. From the shortcut menu, click Add and then click Add Property.

  5. In the Property Name box, type Array.

  6. In the Property Type box, select short.

  7. For Implementation Type, click Get/Set Methods.

  8. In the Get Function and Set Function boxes, type unique names for your Get and Set Functions or accept the default names.

  9. Add a parameter, called row (type short), using the Parameter Name and Parameter Type controls.

  10. Add a second parameter called column (type short).

  11. Click Finish.

Changes Made by the Add Property Wizard

When you add a custom property, the Add Property Wizard makes changes to the control class header (.H) and the implementation (.CPP) files.

The following lines are added to the control class .H file:

   SHORT GetArray(SHORT row, SHORT column);
   void SetArray(SHORT row, SHORT column, SHORT newVal);

This code declares two functions called GetArray and SetArray that allow the user to request a specific row and column when accessing the property.

In addition, the Add Property Wizard adds the following lines to the control dispatch map, located in the control class implementation (.CPP) file:

   DISP_PROPERTY_PARAM_ID(CMyAxUICtrl, "Array", dispidArray, GetArray, SetArray, VT_I2, VTS_I2 VTS_I2)

Finally, the implementations of the GetArray and SetArray functions are added to the end of the .CPP file. In most cases, you will modify the Get function to return the value of the property. The Set function will usually contain code that should execute, either before or after the property changes.

For this property to be useful, you could declare a two-dimensional array member variable in the control class, of type short, to store values for the parameterized property. You could then modify the Get function to return the value stored at the proper row and column, as indicated by the parameters, and modify the Set function to update the value referenced by the row and column parameters.

If error conditions occur in the control, you may need to report the error to the control container. There are two methods for reporting errors, depending on the situation in which the error occurs. If the error occurs within a property's Get or Set function, or within the implementation of an OLE Automation method, the control should call COleControl::ThrowError, which signals to the control user that an error has occurred. If the error occurs at any other time, the control should call COleControl::FireError, which fires a stock Error event.

To indicate the kind of error that has occurred, the control must pass an error code to ThrowError or FireError. An error code is an OLE status code, which has a 32-bit value. When possible, choose an error code from the standard set of codes defined in the OLECTL.H header file. The following table summarizes these codes.

ActiveX Control Error Codes

ErrorDescription
CTL_E_ILLEGALFUNCTIONCALLIllegal function call
CTL_E_OVERFLOWOverflow
CTL_E_OUTOFMEMORYOut of memory
CTL_E_DIVISIONBYZERODivision by zero
CTL_E_OUTOFSTRINGSPACEOut of string space
CTL_E_OUTOFSTACKSPACEOut of stack space
CTL_E_BADFILENAMEORNUMBERBad file name or number
CTL_E_FILENOTFOUNDFile not found
CTL_E_BADFILEMODEBad file mode
CTL_E_FILEALREADYOPENFile already open
CTL_E_DEVICEIOERRORDevice I/O error
CTL_E_FILEALREADYEXISTSFile already exists
CTL_E_BADRECORDLENGTHBad record length
CTL_E_DISKFULLDisk full
CTL_E_BADRECORDNUMBERBad record number
CTL_E_BADFILENAMEBad file name
CTL_E_TOOMANYFILESToo many files
CTL_E_DEVICEUNAVAILABLEDevice unavailable
CTL_E_PERMISSIONDENIEDPermission denied
CTL_E_DISKNOTREADYDisk not ready
CTL_E_PATHFILEACCESSERRORPath/file access error
CTL_E_PATHNOTFOUNDPath not found
CTL_E_INVALIDPATTERNSTRINGInvalid pattern string
CTL_E_INVALIDUSEOFNULLInvalid use of NULL
CTL_E_INVALIDFILEFORMATInvalid file format
CTL_E_INVALIDPROPERTYVALUEInvalid property value
CTL_E_INVALIDPROPERTYARRAYINDEXInvalid property array index
CTL_E_SETNOTSUPPORTEDATRUNTIMESet not supported at run time
CTL_E_SETNOTSUPPORTEDSet not supported (read-only property)
CTL_E_NEEDPROPERTYARRAYINDEXNeed property array index
CTL_E_SETNOTPERMITTEDSet not permitted
CTL_E_GETNOTSUPPORTEDATRUNTIMEGet not supported at run time
CTL_E_GETNOTSUPPORTEDGet not supported (write-only property)
CTL_E_PROPERTYNOTFOUNDProperty not found
CTL_E_INVALIDCLIPBOARDFORMATInvalid clipboard format
CTL_E_INVALIDPICTUREInvalid picture
CTL_E_PRINTERERRORPrinter error
CTL_E_CANTSAVEFILETOTEMPCan't save file to TEMP
CTL_E_SEARCHTEXTNOTFOUNDSearch text not found
CTL_E_REPLACEMENTSTOOLONGReplacements too long

If necessary, use the CUSTOM_CTL_SCODE macro to define a custom error code for a condition that is not covered by one of the standard codes. The parameter for this macro should be an integer between 1000 and 32767, inclusive. For example:

#define MYCTL_E_SPECIALERROR CUSTOM_CTL_SCODE(1000)

If you are creating an ActiveX control to replace an existing VBX control, define your ActiveX control error codes with the same numeric values the VBX control uses to ensure that the error codes are compatible.

In some cases you may want to handle certain keystroke combinations in a special way; for example, insert a new line when the ENTER key is pressed in a multiline text box control or move between a group of edit controls when a directional key ID pressed.

If the base class of your ActiveX control is COleControl, you can override CWnd::PreTranslateMessage to handle messages before the container processes them. When using this technique, always return TRUE if you handle the message in your override of PreTranslateMessage.

The following code example demonstrates a possible way of handling any messages related to the directional keys.

BOOL CMyAxUICtrl::PreTranslateMessage(MSG* pMsg)
{
   BOOL bHandleNow = FALSE;

   switch (pMsg->message)
   {
      case WM_KEYDOWN:
         switch (pMsg->wParam)
         {
         case VK_UP:
         case VK_DOWN:
         case VK_LEFT:
         case VK_RIGHT:
            bHandleNow = TRUE;
            break;
         }
         if (bHandleNow)
         {
            OnKeyDown((UINT)pMsg->wParam, LOWORD(pMsg->lParam), HIWORD(pMsg->lParam));
         }
         break;
   }
   return bHandleNow;
}

For more information on handling keyboard interfaces for an ActiveX control, see the ActiveX SDK documentation.

You can create dialog controls that have no user interface and are invisible at run time. If you add an invisible at run time ActiveX control to a dialog box and use CWnd::GetDlgItem to access the control, the control will not work correctly. Instead, you should use one of the following techniques to obtain an object that represents the control:

  • Using the Add Member Variable Wizard, select Control Variable and then select the control's ID. Enter a member variable name and select the control's wrapper class as the Control Type.

    -or-

  • Declare a local variable and subclass as the dialog item. Insert code that resembles the following (CMyCtrl is the wrapper class, IDC_MYCTRL1 is the control's ID):

       CCirc myCirc;
       myCirc.SubclassDlgItem(IDC_CIRCCTRL2, this);
       // ... use myCirc ...
       myCirc.UnsubclassWindow();
    

MFC ActiveX Controls

Show: