Properties: Variables of the OCX

Properties of an OCX are essentially variables. They could be of three types. The explanation to follow assumes that the OCX has been written in the MFC and has been generated using the Control and Class Wizards. You may accuse us of double standards but as of now the only convenient method of writing OCXes is to use the Wizards. We have written our own C++ OCX but that  is material for another news-letter.
The Stock Properties are those that deal with standard OCX features such as Caption, BackColor etc. If the OCX is implemented using the MFC then these properties have a default implementation associated with them.
The second type of properties are referred to as Member Variables. These are properties created by the OCX developer. When an OCX is generated using the Wizards, every property of the type Member Variable has a function associated with it. The name of the function is OnNameOfVariableChanged(). This function is called every time the value of the property changes. For example, if we have a property `aa', then OnAaChanged() gets called. Typically, these are properties that do not affect the user interface in any manner.
The Get/Set Properties are those where two functions are generated for each property. The function GetNameOfVariable() is called whenever the user of the OCX needs to know the value of the property. The function SetNameOfVariable() is called when the value is to be changed. For example, a property `ww' of the type Get/Set will have functions GetWw() and SetWw() associated with it. These are properties that normally affect the user-interface in some manner.
When the user accesses the properties, then he is unaware of the type of the property. This distinction in the nature of the properties is created by and matters to only the OCX. The properties of the OCX are also accessed using an IDispatch interface and it's Invoke() function. But before we can access and change the properties of the OCX, let us display them in a dialog.

Program 10

 BOOL MDialog::OnInitDialog()
 {
   CDialog::OnInitDialog();  
   if(z_nID==IDD_DIALOG1)
   {
     
     // Retain all previously existing code  
 
   }                      
   else
   {
     CListBox *z_pListBox;
     PData *z_pNext;
       
     z_pListBox = (CListBox*)GetDlgItem(IDC_LIST1);
     z_pNext = z_pPDFirst;
     while(z_pNext)
     {
      char *z_sTemp = (char*) malloc(100);
     
       if(z_pNext-> z_nPtr)
         sprintf(z_sTemp,"%s*    %s",z_VarType[z_pNext-> z_nPropType],z_pNext-> z_sPropName);
       else
         sprintf(z_sTemp,"%s     %s",z_VarType[z_pNext-> z_nPropType],z_pNext-> z_sPropName);
       z_pListBox-> AddString(z_sTemp);
       z_pNext = z_pNext-> z_pNext;
       free(z_sTemp);
     }
     z_pListBox-> SetCurSel(0);
     return TRUE;
   }                                                                                                       
   return TRUE;
 }                          
                                                                                                                                     
 void MFrameWnd::Properties()
 {
   MDialog z_Dialog(IDD_DIALOG2);
  
   z_Dialog.DoModal();
 }
Choose the sub-option Properties... under Display. The dialog box that opens holds all the properties of the OCX in a list box along with their data types.

Changing property values: A Change of scene

Program 11

 void MDialog::MulProp()
 {
     CAUUID z_Cauuid;
     ISpecifyPropertyPages *z_pPropertyPages;
 
     z_pOleObject-> QueryInterface(IID_ISpecifyPropertyPages,(void**)& z_pPropertyPages);
 
     z_pPropertyPages-> GetPages(& z_Cauuid);
     OleCreatePropertyFrame(0, 10,20,"AruSuv",1,(IUnknown**)& z_pOleObject,z_Cauuid.cElems,z_Cauuid.pElems,1033,0,0);   
     z_pPropertyPages-> Release();
 }  
 
 void MFrameWnd::ChangeProperty()
 {
   MDialog z_Dialog(IDD_DIALOG10); 
   z_Dialog.DoModal();
 }
 
 void MDialog::Accept()
 {
   VARIANTARG z_Varg;
   DISPPARAMS z_DispParam;
   IDispatch *z_pDispatch;
   CString z_sSel;
   PData *z_pPDSearch;
   CEdit *z_pEdit;
   CListBox *z_pListBox;
   int z_nIndex;
   char *z_sVal;
   DISPID z_lDispid;
   
   _fmemset(& z_DispParam,0,sizeof(DISPPARAMS));
   z_pEdit = (CEdit*)GetDlgItem(IDC_EDIT1);
   z_pListBox=(CListBox*)GetDlgItem(IDC_LIST2);
   z_nIndex = z_pListBox-> GetCurSel();
   z_pListBox-> GetText(z_nIndex,z_sSel);
   z_pPDSearch = z_pPDFirst;
   while(z_pPDSearch)
   {
     if(z_sSel.Find(z_pPDSearch-> z_sPropName) == -1)
       z_pPDSearch = z_pPDSearch-> z_pNext; 
     else
       break;
   }      
   z_sVal=(char*)malloc(20);
   z_pEdit-> GetWindowText(z_sVal,20);
   z_Varg.vt = z_pPDSearch-> z_nPropType;
   z_DispParam.cArgs =1 ;
   z_DispParam.cNamedArgs=1;      
   z_lDispid =-3;
   z_DispParam.rgdispidNamedArgs=& z_lDispid; 
   z_DispParam.rgvarg=& z_Varg;
   if(z_pPDSearch-> z_nPropType==VT_I2)
     z_Varg.iVal=atoi(z_sVal); 
   if(z_pPDSearch-> z_nPropType==VT_BOOL)
     z_Varg.bool=atoi(z_sVal);                                                              
   if(z_pPDSearch-> z_nPropType==VT_I4 );
     z_Varg.lVal=atol(z_sVal);                                                            
   if(z_pPDSearch-> z_nPropType==VT_R4)
     z_Varg.fltVal=(float)atof(z_sVal);                                                            
   if(z_pPDSearch-> z_nPropType==VT_BSTR)
   {
     z_Varg.bstrVal=(char*) malloc(strlen(z_sVal) + 1);
     strcpy(z_Varg.bstrVal,z_sVal);
   }
   z_pOleObject-> QueryInterface(IID_IDispatch,(void**)& z_pDispatch);
   z_pDispatch-> Invoke(z_pPDSearch->z_lDispID,IID_NULL,1033,DISPATCH_PROPERTYPUT,& z_DispParam,0,0,0);
   free(z_sVal);  
   z_pDispatch-> Release();
 }
 
 void MDialog ::DispPropPage()
 {
   OleCreatePropertyFrame(0, 10,20,"AruSuv",1,(IUnknown**)& z_pOleObject,1,& z_PropClsid,1033,0,0);
 } 
 
 void MDialog::PropValue()
 {
   DISPPARAMS z_DispParam; 
   CEdit *z_pEdit;
   VARIANT z_Varg;
   IDispatch *z_pDispatch;
   CListBox* z_pListBox;
   int z_nIndex;
   CString z_sSel;
   PData *z_pPDSearch;
   char *z_sDisplay;
   IPerPropertyBrowsing *z_pPerPropBrowsing;
   CButton *z_pButton;
   
   _fmemset(& z_DispParam,0,sizeof(DISPPARAMS));
   z_pEdit = (CEdit*)GetDlgItem(IDC_EDIT1);
   z_pListBox=(CListBox*)GetDlgItem(IDC_LIST2);
   z_nIndex = z_pListBox-> GetCurSel();
   z_pListBox-> GetText(z_nIndex,z_sSel);
   z_pPDSearch = z_pPDFirst;
   while(z_pPDSearch)
   {
     if(z_sSel.Find(z_pPDSearch-> z_sPropName) == -1)
       z_pPDSearch = z_pPDSearch-> z_pNext; 
     else
       break;
   }   
   z_pOleObject-> QueryInterface(IID_IDispatch,(void**)& z_pDispatch);
   z_pDispatch-> Invoke(z_pPDSearch- > z_lDispID,IID_NULL,1033,DISPATCH_PROPERTYGET,& z_DispParam,& z_Varg,0,0);
   z_sDisplay=(char*)malloc(25);
   strcpy(z_sDisplay,"");
   if(z_Varg.vt==VT_I2)
     itoa(z_Varg.iVal,z_sDisplay,10);
   if(z_Varg.vt==VT_BOOL)
     itoa(z_Varg.bool,z_sDisplay,10);
   if(z_Varg.vt==VT_I4)
     ltoa(z_Varg.lVal,z_sDisplay,10);
   if(z_Varg.vt==VT_R4)
     _gcvt(z_Varg.fltVal,3,z_sDisplay);
   if(z_Varg.vt==VT_BSTR)
     strcpy(z_sDisplay,z_Varg.bstrVal);                                                           
   z_pEdit-> SetWindowText(z_sDisplay);
   free(z_sDisplay);          
   z_pOleObject-> QueryInterface(IID_IPerPropertyBrowsing,(void**)& z_pPerPropBrowsing);
   z_pPerPropBrowsing-> MapPropertyToPage(z_pPDSearch-> z_lDispID,& z_PropClsid);
   z_pButton =(CButton*)GetDlgItem(IDC_PROPPAGE);
   if(z_PropClsid!=IID_NULL)
       z_pButton-> ShowWindow(1); 
   else
   z_pButton-> ShowWindow(0); 
   z_pPerPropBrowsing-> Release();
   z_pDispatch-> Release();
 }

The `Change Properties' dialog is displayed when we select `Change Properties...' sub-option under Edit. In this dialog box, select any one of the properties listed. The current value of the property is displayed in the edit control. Next, type in a value in this edit control and click on Accept. This has the effect of passing the value in the edit control to the OCX. This is verified by selecting the property again and this time seeing the new value displayed.
As we select some properties, note that the button `More...'  appears adjacent to the edit control. Click on this button to see the property page associated with that particular property being displayed. The user can change values using this page too. Click on the `All Properties' button. This opens a dialog box where all the property pages of the OCX are displayed. This allows the user to change more than just one property at a time.
Every time we select a new property in the listbox, PropValue() gets executed. In this function, we search the link-list for a match for the selected property. We determine it's ID and use it as the first parameter to Invoke(). Thus, once again Invoke() is used to get the value of the property from the OCX.
The only change made to the Invoke() statement is that we use DISPATCH_PROPERTYGET to indicate that we want the value of the indicated property to be returned to us in the VARIANT structure. A series of if statements use the data type provided in the VARIANT structure to ensure that the value returned will be converted to a string using the appropriate function. This string is then displayed in the edit control.
One way to change the value of the property is to key in the value and then pass it on to the OCX using appropriate functions. However, this technique is not always satisfactory. Consider a case where the user needs to change the font. In such a case, the above mentioned methodology is highly inadequate. This is due to the fact that the specifications for a font  are many; the size, the typeface, the color etc. So, what we need  is a  more intuitive system whereby the user can see the effect of the changes to be made by him. A preview of sorts. This is where property pages make a difference. Rings a bell? We told you all this the first time property pages were introduced.
Each property page is a dialog which provides a better method of changing property values. For example, the property page for fonts provides a listing of the available fonts, an ability to change the size and other such attributes of the selected fonts. It also displays a sample of text in the selected font. This enables the user to see the effect of any changes instantly.
A similar situation arises when we want to change the picture of an OCX. Thus, every time a more interactive system of changing values is required a property page is used. For the oft-used properties such as Color, Font, Picture etc. there are standard property pages defined. These are a Microsoft set guideline to the implementation of property pages. The standard property pages are present in oc25.dll. Thus, this DLL gets loaded whenever these property pages are to be referenced. This DLL handles all the associated responsibility such as informing the OCX of the changed value etc.
For properties specific to an OCX, the writer of the OCX can create his own property pages. If it seems that a property page is a glorified Common Dialog Box; no more, no less; then you are not too far off the mark. The only  difference is that unlike a Common Dialog Box, every property page has to be registered with `reg.dat'. The standard property pages are registered with `reg.dat' at the time of installation of MSVC. The registration of property pages is necessary so that they can be re-used in future by other applications too.
A property may or may not have a property page associated with it. Thus, we have to interrogate the OCX to deduce if there is any property page associated with a property. The interface  IPerPropertyBrowsing is dedicated to handling such requests. We obtain a pointer to this interface from the OCX and then use it's MapPropertyToPage() method.
The OCX uses MapPropertyToPage() to maintain a list of properties and associate property pages. This function is passed the ID of the property as the first  parameter and the address of a CLSID structure as the second. If there is a property page for the property then the OCX fills up the CLSID structure with the CLSID of the property page while otherwise the structure is set to IID_NULL. If the structure holds an IID_NULL then we do not display the button `More...' else we do.
DispPropPage() gets executed when we click on More.... We use an OLE API OleCreatePropertyFrame() to display the standard Properties dialog box. One of the parameters to this function decides the number of property pages to be displayed. Another one states which property page(s) are to be displayed. The second and third parameters are the x and the y co-ordinates of the top-left corner of the dialog with respect to the screen. Next is the title that will be displayed.
Every OCX developer can create property pages. These custom property pages can later be used by others. To access property pages that are have been created by different OCXes, we have to load them into memory. Thus, CoCreateInstance() to load each of them and the pointers obtained are stored in an array.
This is the array passed as the sixth parameter of the function OleCreatePropertyFrame() with the fifth stating the number of elements in the array. The seventh parameter is the number of property pages and the eighth is an array. This array holds the CLSIDs of all property pages that are to be displayed. OleCreatePropertyFrame() uses the CLSIDs to search each the specified OCXes for the property pages. In our case, we search only the OCX that is currently in-place active.
The dialog that is displayed is called the standard Property Frame and will deal with all issues such as informing the OCX of changes in property values, displaying the appropriate page for each property etc. We have duplicated the functionality of the `Apply Now' button with the `Accept' button in our dialog box.
When we click on the button `Accept', Accept() gets executed. In this function, we determine the property selected from the list box and use the link-list to determine it's data type and ID. The DISPPARAMS and the VARIANTARG structures are appropriately initialized as explained before. This time Invoke() is called with DISPATCH_PROPERTYPUT. This will set the value of the property in the OCX to that passed in the DISPPARAMS structure.
The above method is suitable if we were to display a single property page at a time. If the property frame dialog is to hold all the property pages then a different tack is to be used. The OCX will have an implementation of the interface ISpecifyPropertyPages. This interface is dedicated to indicating the set of property pages associated with an OCX.  It  is derived from IUnknown and has just one additional function GetPages().
GetPages() is passed one parameter; the address of a CAUUID structure. This structure holds two elements. One is an array of GUID structures. The OCX will initialize the array to hold the GUIDs of all property pages in use. The second element of CAUUID structure is a unsigned long variable that holds the number of property pages.
MulProp() is executed when we select the All Properties... button. We use GetPages() of ISpecifyPropertyPages to fill a CAUUID structure. The information held in this structure is passed to OleCreatePropertyFrame(). Using this information OleCreatePropertyFrame() now displays all the property pages associated with the OCX.

Events: An enigma unraveled

All Windows programming is event driven. An event is a generic term that is used to describe a happening, anything that an application can sense. A Windows application is notified of the happening with a message sent to it's call-back function. It is upto the user to trap the messages and associate code with them. For example, a mouse click is an event so is a timer message. This is true of the OCX too. The OCX developer will define events and indicate when an event will get triggered. An event may be triggered in different ways:
The code that gets executed when an event occurs is added by the user of the OCX.
Events are best explained using Microsoft Visual Basic as a model. In Visual Basic, consider the case where a button is used within a form. We want a message box to be displayed when we click on the button at run-time. So at design time, we access the Code window for the `clicked' event of the button and add code for a message box to it. The Code window is an user-interface feature provided by Visual Basic to allow the user an intuitive way to add code for individual events. Each event has it's own Code window. The code gets executed at run-time.
It is the user who adds code for more than one event of the control. All this code is saved and executed when the event occurs. Who stores the code that has been added for each event is the moot point. Each control can be used in different applications. Code for the events will vary from application to application. Thus, it is the responsibility of each application, i.e. the container; to save the code.
Since, it is the container that saves the code, it will also be responsible to execute it when the event occurs. The event will always be sensed by the OCX. The OCX will then have to inform the container about the occurrence so that it may execute the code for the event.
Before we go deeper into events, let us display all  available OCX events in a dialog box.

Program 12

 BOOL MDialog::OnInitDialog()
 {
   CDialog::OnInitDialog();  
   if(z_nID==IDD_DIALOG1)
   {
     
     // Retain all previously existing code  
 
   }                      
   else 
   {                                                
     CListBox* z_pListBox;
     FData *z_pFDNext;
 
     z_pListBox = (CListBox*)GetDlgItem(IDC_LIST1);
     z_pFDNext = z_pEDFirst;
     while(z_pFDNext)
     {
       char *z_sTemp = (char*) malloc(50);
       sprintf(z_sTemp,"%s              %s(",z_VarType[z_pFDNext->z_nRType],z_pFDNext->z_sFName);
       if(z_pFDNext->z_nNumParams)
         for(int z_nParams = 0; z_nParams < z_pFDNext->z_nNumParams;  z_nParams++)
         {
           if(z_nParams)
             strcat(z_sTemp,",");
           strcat(z_sTemp,z_VarType[z_pFDNext->z_pPTypes[z_nParams]]);
           strcat(z_sTemp," ");
           strcat(z_sTemp,z_pFDNext->z_sPName[z_nParams]);
       }
       strcat(z_sTemp,")");
       z_pListBox->AddString(z_sTemp);
       free(z_sTemp);
       z_pFDNext = z_pFDNext->z_pNext;
     }                   
     z_pListBox->SetCurSel(0);
     return TRUE;
   }                                                                                                                                                          
   return TRUE;
 }  
 
 void MFrameWnd::Events()
 {
   MDialog z_Dialog(IDD_DIALOG4);
  
   z_Dialog.DoModal();
 }
In OnInitDialog(), we use the link-list and string concatenation to populate the listbox. The dialog box will be displayed when we select Events... sub-option under Display.
Now that we know all the events available for the OCX, let us add code and see that the code gets executed when the event occurs.

Program 13

 void MFrameWnd::ObjCreate(CLSID z_TempClsid)
 {
   IClassFactory *z_pClassFactory;
   char *z_sName;
   CMenu *z_pMenu,*z_pAddMenu,*z_pPopMenu,*z_pSubMenu;
   IConnectionPointContainer *z_pCPContain;
   IConnectionPoint *z_pCPoint;
   ULONG z_lCookie; 
     
   z_pOleClientSite = new MOleClientSite;
   z_pInPlaceFrame = new MOleInPlaceFrame;
   z_pOleInPlaceSite = new MOleInPlaceSite;
   z_pDispatch =  new MDispatch;
   CoGetClassObject(z_TempClsid,1,0,IID_IClassFactory,(void**)& z_pClassFactory);
   z_pClassFactory-> CreateInstance(0,IID_IOleObject,(void**)& z_pOleObject);
   z_pOleObject->SetClientSite((IOleClientSite*)z_pOleClientSite);
   z_pOleObject->DoVerb(OLEIVERB_SHOW,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
   z_nActive = TRUE;
   z_pMenu = GetMenu();
   z_pSubMenu = z_pMenu->GetSubMenu(1);
   z_pAddMenu = new CMenu;
   z_pAddMenu->LoadMenu(IDR_MENU2);
   z_pPopMenu = z_pAddMenu->GetSubMenu(0);
   z_sName = (char*) malloc(10);
   z_pOleObject->GetUserType(0,& z_sName);
   z_pSubMenu->AppendMenu(MF_ENABLED|MF_STRING|MF_POPUP,(UINT)z_pPopMenu->m_hMenu,z_sName);
   free(z_sName);
   
   InitArray();                       
   z_lCookie=10; 
   z_pOleObject->
 QueryInterface(IID_IConnectionPointContainer,(void**)& z_pCPContain);
   z_pCPContain->FindConnectionPoint(z_ClsidEvents,& z_pCPoint);                                                                                                
   z_pCPoint->Advise((IUnknown*)z_pDispatch,& z_lCookie);
   z_pCPoint->Release();                               
   z_pCPContain->Release();
 }
 
 void MDialog::Save()
 { 
   CData *z_pTemp;   
   FData *z_pEDSearch;
   CString z_sSel;     
   CListBox* z_pListBox;
   BOOL z_nExists;
   int z_nSel, z_nCount;    
   CEdit *z_pEdit;
     
   z_pListBox = CListBox*)GetDlgItem(IDC_LIST1);                 
   z_nSel = z_pListBox->GetCurSel();
   z_pTemp = 0;
   z_pListBox->GetText(z_nSel,z_sSel);
   z_nExists = FALSE;
   z_pEDSearch = z_pEDFirst;
   while(z_pEDSearch)
   {
     if(z_sSel.Find(z_pEDSearch->z_sFName) == -1)
       z_pEDSearch = z_pEDSearch->z_pNext;    
     else   
       break;  
   }
   z_pEdit = (CEdit*)GetDlgItem(IDC_EDIT1);
   z_nCount = z_pEdit->GetLineCount();
   if(!z_pCDFirst)
   {
     z_pCDFirst = new CData;
     z_pCDPrev = z_pCDFirst;  
     z_pCDPrev->z_pNext = 0;
   }                                                
   else
   {
     CData *z_pNext;
     
     z_pNext= z_pCDFirst;
     while(z_pNext)
     {
       if(z_pNext->z_lDispID == z_pEDSearch->z_lDispID) 
       {
         z_nExists = TRUE;
         break;
       }  
       else
         z_pNext = z_pNext->z_pNext;        
     }
     if(!z_nExists)
     {
       z_pNext = new CData;
       z_pCDPrev->z_pNext = z_pNext;
       z_pCDPrev = z_pNext;
       z_pCDPrev->z_pNext = 0;
     }
     else                     
     {
       z_pTemp = z_pCDPrev;
       z_pCDPrev = z_pNext;
     }
   }
   z_pCDPrev->z_sFunction = (char**)malloc( z_nCount * sizeof(char*));
   for(int z_nTemp = 0; z_nTempz_sFunction[z_nTemp] = (char*)malloc(100);
     z_nCopied = z_pEdit->GetLine(z_nTemp,z_pCDPrev->z_sFunction[z_nTemp],100);
     *(*(z_pCDPrev->z_sFunction + z_nTemp) + z_nCopied) = '\0';
   }
   z_pCDPrev->z_lDispID = z_pEDSearch->z_lDispID;
   z_pCDPrev->z_nCount = z_nCount;
   if(z_nExists)
     z_pCDPrev = z_pTemp;
  }
   
   void MDialog::Test1()
   {
     Test();
   } 
   
   void MDialog::Test(BOOL z_nFlag,CData *z_pCDSearch)
   {
     CEdit *z_pEdit;   
     int z_nCount;   
           
     if(!z_nFlag)                               
     {
       z_pEdit = (CEdit*)GetDlgItem(IDC_EDIT1);
       z_nCount = z_pEdit->GetLineCount();
     }                                           
     else
       z_nCount=z_pCDSearch->z_nCount;
     for( int z_nTemp = 0; z_nTemp < z_nCount; z_nTemp++)
    {
      char *z_sStore;
      int z_nCopied;                         
 
      z_sStore =(char*)malloc(100);
      if(!z_nFlag)
     {
       z_nCopied = z_pEdit->GetLine(z_nTemp,z_sStore,100);
       z_sStore[z_nCopied]='\0'; 
     }                                                 
     else
       strcpy(z_sStore,z_pCDSearch->z_sFunction[z_nTemp]);
     if(*z_sStore == '0')                           
     {
       z_sStore++;                            
       ::MessageBox(0,"Windows API","Function!",0);
     } 
     else if(*z_sStore =='1')
     {
       z_sStore ++;
       FuncExec(z_sStore);
     }     
     else   
       ::MessageBox(0,"Unrecognisable Command","Error!",0);
       free(z_sStore); 
   }
 }                                                                                                                         
 
 void MFrameWnd::Execute()
 {
   z_pDialog = new MDialog(IDD_DIALOG7);
   z_pDialog->DoModal();
 }
                                                
 void* _export _cdecl MDispatch::Invoke(DISPID dispidMember,REFIID,LCID,unsigned short,DISPPARAMS  *pdispparams,VARIANT  * pvarResult,EXCEPINFO*,unsigned int*) 
 { 
   if(dispidMember != -705  & &   dispidMember != -709 & & dispidMember != -706
   & &  dispidMember != -501 & &  dispidMember != -701 & &  dispidMember != -710 )
     {
       CData *z_pCDSearch = z_pCDFirst;
        while(z_pCDSearch)
        {                                
         if(z_pCDSearch->z_lDispID == dispidMember)
           break;
         else
         z_pCDSearch = z_pCDSearch->z_pNext;         
        }
       if(z_pCDSearch)
         z_pDialog->Test(1,z_pCDSearch);
     }                      
     return 0;
 }
 
 void* _export _cdecl MDispatch::QueryInterface (REFIID , void  ** ppvObj)
 {
   *ppvObj = this;
   return 0; 
 }
 
 void* _export _cdecl MOleClientSite::QueryInterface ( REFIID riid  , void **ppvObj )
 {
   if ( riid == IID_IOleInPlaceSite)
   {
     *ppvObj = z_pOleInPlaceSite;
     return 0;
   }
   if ( riid == IID_IDispatch)
   {
     *ppvObj = z_pDispatch;
     return 0;
   }
   return ResultFromScode(E_NOTIMPL);
 } 
Once the OCX has been chosen for display, select the Executing Events... sub-option under Edit menu-option. The dialog contains a list-box that holds the  names of the events. Select any one event. The edit control will be used to add code for this event.
The code for an OCX event might consist not only of the OCX methods but also of functions from the Windows API. When the code is being executed, each function name will be compared against the names of the OCX functions for a match. If no match is found, then the Windows API is searched for a function of this name. If the function does not fall into either of the categories, then an error is flagged.
Implementing this feature without adding considerable amounts of code is not feasible. To overcome this hurdle and to enhance understanding, we have designed a unique method. We have assumed that all OCX methods will be prefaced with 1. These functions will get executed. All those lines of code that are prefaced with 0 are considered to be a part of the Windows API. Any other prefix for a line of code will be flagged as an error.
Thus, if aaa() is an OCX method and if the code that is entered in the edit control is:
  
   1aaa()
0bbb()
2ccc()
ddd()
then aaa() will be executed. bbb() will bring up a dialog that states that it is a part of the Windows API. We do realise that bbb() is not a part of the API. It is only deemed so by the prefix assigned to it. It is only an illustrative example. Real-life containers will have robust error-checks to verify validity of the function. The functions ccc() and ddd() will flag an error. Click on `Test' to see the code being executed. This is equivalent to the Preview feature that most products such as PowerBuilder support. Click on `Save' to save the code. The code will be associated with the event that was highlighted in the list-box at the time of clicking on `Save'.
We assume for ease of explanation that the code is added to the `clicked' event. Close the dialog  box. Click on the OCX. The code saved for the event gets executed. Similarly, if the code was added to the `timer' event, then every time a WM_TIMER message was sent to the OCX, it would get executed. Consider this about an OCX, for methods, all details are decided by the OCX. This means that not only does the OCX decide the name of the method and it's parameters but also the code within the method. The user of the OCX just calls this method with the requisite number and type of parameters.
The container uses the IDispatch of the OCX to execute methods. The container obtains the IDispatch pointer by calling QueryInterface() in the OCX with IID_IDispatch. This IDispatch pointer is said to be an incoming pointer for the container. Any pointer obtained by calling the QueryInterface() with the appropriate IID is called an incoming pointer and the interface it points to is called as the incoming interface.
Events fall into a very special category. Let's explain what we mean by special. The OCX defines the events that it supports. It also senses the occurance of the event. It is the responsibility of the OCX to inform the container that an event has occured. But it is the user of the OCX who writes the code for the event while it is the container that saves and executes this code.
Since the container executes the event code, the IDispatch that handles events has to be implemented in it. This means that the container implements an interface for the OCX. The container then passes this IDispatch pointer to the OCX. This pointer is called an outgoing pointer and the IDispatch interface implemented in the container is called the outgoing interface.
The OCX can support multiple outgoing interfaces. As of now, outgoing interfaces are used only for events. But as ever Microsoft has an eye on the future. It has developed a system to handle outgoing pointers. The OCX has one IConnectionPoint interface implemented for each outgoing pointer. Since, there can be multiple outgoing pointers the OCX implements an IConnectionPointContainer interface. This interface manages the different IConnectionPoint interfaces. For reasons best known to Microsoft, the OCX can obtain a pointer to the container's IDispatch interface through a convoluted mechanism.
First the container uses QueryInterface() to obtain a pointer to the interface IConnectionPointContainer in the OCX. We then have to use the FindConnectionPoint() method of this interface to obtain a pointer to an IConnectionPoint interface. The first parameter to this function is the uuid of the events type info while the second parameter is the address of the IConnectionPoint interface.
Next, we execute the the method Advise() of IConnectionPoint. This function expects an IUnknown pointer as it's first parameter. We have passed it the MOleClientSite pointer. The OCX on recieving this pointer calls the MOleClientSite method QueryInterface() with the uuid of the events. We, the container, are expected to return our IDispatch pointer at this point.
At long last, the OCX recieves the container's IDispatch pointer. You must be wondering why one has to go all the way to the moon and back so that the OCX may get the container's IDispatch pointer. Well join the club, we are equally mystified!
The structure CData is used to store the code added for an event. It also stores the ID of the event. The code for each  event is stored in a separate structure all of which are a part of the final link-list. z_pEDFirst stores the address of the first element of the list. The link-list is initialized by Save(). Test() will merely parse each line of code. If the pre-fix is 1, then it passes on the handling to FuncExec(), otherwise it will display an appropriate message box.
When an event occurs in the OCX, it calls the function Invoke() of the container's IDispatch. The OCX passes the ID of the event as the first parameter to Invoke(). It is this ID that is used to identify the event that has been invoked. In Invoke(), we use the ID to search the link-list for a match. We, then, access the code stored in the link-list and execute it.
Once again, events is something that we have figured out. The manual is conspicuously silent on the how's, when's and wherefore's of the event implemetation. If you have a copy of Microsoft OLE Control Developer's Kit: User's Guide & Reference from the The Six Volume Documentation Collection For Microsoft Visual C++ Version 2.0 for Win32, then read pages 325-326 for an overview of incoming and outgoing pointers. Drop us a line if you survive the ordeal.

Ambient Properties: Container's charter

So far, we have accessed and changed information that is a part of the OCX. The container too has properties that can be passed to the OCX. These are called Ambient Properties. As far as the OCX is concerned, these are read-only properties. The OCX can read the values of these properties but cannot change them. There are fourteen ambient properties provided to us.
Why would the OCX want to know of the containers properties? Well, consider the case of AmbientBackColor. This is the back color of the container. The OCX can use this value to maintain color co-ordination. Support for ambient properties is implemented by the container. It has nothing to do with the OCX. Even if the container does nothing about the ambient properties, the operation of the OCX is in no way affected.
In  the following example, we have used just two of the standard ambient properties:

Program 14

 BOOL MDialog::OnInitDialog()
 {
   CDialog::OnInitDialog();  
   if(z_nID==IDD_DIALOG1)
   {
     
     // Retain all previously existing code  
 
   }                      
   else 
     {
       CEdit *z_pEdit;
       char *z_sDisplay;
       
       z_sDisplay = (char*)malloc(10); 
       z_pEdit=(CEdit*)GetDlgItem(IDC_EDIT1);
       ltoa(z_lSelColor,z_sDisplay,10);
       z_pEdit->SetWindowText(z_sDisplay);
       free(z_sDisplay);
       if(z_nUIDead)
         CheckRadioButton(IDC_RADIO1,IDC_RADIO2,IDC_RADIO1);
       else
         CheckRadioButton(IDC_RADIO1,IDC_RADIO2,IDC_RADIO2);
       return TRUE;  
     }                                                                                                                                                  
   return TRUE;
 }  
 
 void MFrameWnd::OnPaint()
 {
   if(z_pOleObject)
   {                           
       
     SIZEL z_sizel;
     CPaintDC z_DC(this);
      
     z_sizel.cx = XformWidthInPixelsToHimetric(z_DC.m_hDC,(z_pTracker->m_rect.right - z_pTracker->m_rect.left));
     z_sizel.cy  = XformWidthInPixelsToHimetric(z_DC.m_hDC,(z_pTracker->m_rect.bottom - z_pTracker->m_rect.top));
     if(z_nUIDead & & !z_nActive)
     {
       z_pTracker->m_nStyle  =   CRectTracker::resizeInside|CRectTracker::solidLine;
       OleDraw(z_pOleObject,DVASPECT_CONTENT,z_DC.m_hDC,& z_Rect);
     }                                                                         
     else
       z_pTracker->m_nStyle = CRectTracker::solidLine;
       z_pTracker->Draw(& z_DC);     
       ReleaseDC(& z_DC);
       z_pOleObject->SetExtent(DVASPECT_CONTENT,& z_sizel);
   }
   CFrameWnd::OnPaint();
 }                                    
 
 void MFrameWnd::OnLButtonDown(UINT z_nFlags,CPoint z_Point)
{                     
   z_nActive = FALSE;
   z_pOleObject->DoVerb(OLEIVERB_HIDE,0,(IOleClientSite*)z_pOleClientSite,- 1,m_hWnd,& z_Rect);
 }                                                                                                            
   
 void MFrameWnd::OnLButtonDblClk(UINT z_nFlags,CPoint z_Point)
 {
   if(!z_nUIDead & & !z_nActive & & z_Rect.PtInRect(z_Point))                                                            
   {
     z_nActive = TRUE;
     z_pOleObject->DoVerb(OLEIVERB_SHOW,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
   }
 }                                                                                                              
 
 void MFrameWnd::OnMouseMove(UINT z_nFlags,CPoint z_Point)
 {                               
   int z_nDraw = 0;
 
   if(!z_nUIDead)
   {
     if(!z_nActive & & (z_nFlags & MK_LBUTTON) & & z_Rect.PtInRect(z_Point))      {
       z_nDraw = 1;
       z_pTracker->Track(this,z_Point);
     }                                                      
     if(z_nDraw)      
     {
       z_Rect = z_pTracker->m_rect;
       Invalidate();
     } 
   }
 }    
 
 void MFrameWnd::AmbientProperties()
 {
   MDialog z_Dialog(IDD_DIALOG11);
 
   z_Dialog.DoModal();
 }
 
 void MDialog::UIDead()
 {
   int z_nChecked;
   IOleControl *z_pOleControl;                                                                             
   
   z_nChecked = GetCheckedRadioButton(IDC_RADIO1,IDC_RADIO2);
   if(z_nChecked==IDC_RADIO1)
   {
     z_pOleObject->DoVerb(OLEIVERB_HIDE,0,(IOleClientSite*)z_pOleClientSite,-1,z_hFWnd,& z_Rect);
     z_nUIDead = TRUE;
     z_nActive = FALSE;
     Invalidate();
   }                      
   else 
     z_nUIDead = FALSE;
   z_pOleObject->QueryInterface(IID_IOleControl,(void**)& z_pOleControl);
   z_pOleControl->OnAmbientPropertyChange(DISPID_AMBIENT_UIDEAD);
   z_pOleControl->Release();
 }
 
 void MDialog::AmbBackColor()
 {
     CColorDialog *z_pColorDialog;
     char *z_sDisplay;                                                         
     CEdit *z_pEdit;
     IOleControl *z_pOleControl;
     
     z_pColorDialog = new CColorDialog;
     z_pColorDialog->SetCurrentColor(z_lSelColor);
     z_pColorDialog->DoModal();
     z_lSelColor = z_pColorDialog->GetColor();
     z_sDisplay = (char*)malloc(10);    
     ltoa(z_lSelColor,z_sDisplay,10);
     z_pEdit = (CEdit*)GetDlgItem(IDC_EDIT1);
     z_pEdit->SetWindowText(z_sDisplay);  
     free(z_sDisplay);
     z_pOleObject->QueryInterface(IID_IOleControl,(void**)& z_pOleControl);
     z_pOleControl->OnAmbientPropertyChange(DISPID_AMBIENT_BACKCOLOR);
 }

void*   _export   _cdecl   MDispatch::Invoke(DISPID   dispidMember,REFIID,LCID,unsigned   short,DISPPARAMS  *pdispparams,VARIANT *pvarResult,EXCEPINFO *,unsigned  int*) 
 { 
   if(dispidMember == -701 || dispidMember == -501 )
   {                             
     pvarResult->vt=VT_I4;
     pvarResult->lVal=m_pFrameWnd->m_nSelColor;
   }                           
   else if(dispidMember == -710)
   {                             
     pvarResult->vt=VT_BOOL;
     pvarResult->bool=m_pFrameWnd->m_nUIDead;
   }
   else  if(dispidMember  != -705  & &  dispidMember  !=  -709  & & dispidMember  != -706)
   {
     CData *m_pCDSearch = m_pCDFirst;
     while(m_pCDSearch)
     {
       if(m_pCDSearch->m_lDispID == dispidMember)
         break;
       else
         m_pCDSearch = m_pCDSearch->m_pNext;
     }
     if(m_pCDSearch)
       m_pDialog->Test(1,m_pCDSearch);
   }
   return 0;
 }     
Select the  menu-option Ambient Properties... under Edit. This will display a dialog with two ambient properties; Ambient BackColor and UIDead; listed in it. Select the button `More...'. This opens the Color dialog box. This dialog box is a part of the Windows' Common Dialog Boxes. Select a color and click on `OK'. A numeric value representing the new color is displayed in the edit control.
For UIDead, select TRUE. Click on `OK' to close the dialog box. Now try and move the OCX around in the container or double-click on it to activate it. None of our efforts are met with success. This is because we have set the UIDead ambient property to TRUE. Use the `Ambient Properties' dialog box to set this ambient property back to FALSE and everything works just as before. AmbBackColor() is executed when we click on the button `More...'. In this function, we use the class CColorDialog that is a part of the MFC to display the Color dialog box. The function SetCurrentColor() will ensure that this dialog displays the current value of Ambient  BackColor as that selected on display. The DoModal() function terminates when we close this dialog box. GetColor() is used to retrieve a number corresponding to the current selected color in the dialog. This number is displayed in the edit control. The value of the color is stored in z_lSelColor.
The OCX is notified about changes in the container's ambient properties via it's IOleControl interface. The container uses the OnAmbientPropertyChange() function of this interface to notify the OCX. This function merely notifies the OCX of the change. The OCX then calls Invoke() in the container's IDispatch implementation to determine the  new  value.  Invoke() uses the VARIANT structure passed to it by the OCX to return the new value.
UIDead() is called when we select either of the radio-buttons. In this function, depending on whether TRUE or FALSE is selected, we appropriately set a boolean variable z_nUIDead. If TRUE is selected, then the OCX is hidden using DoVerb().
If the ambient property UIDead is TRUE, then z_nUIDead is also TRUE. OnMouseMove(), OnPaint(), OnLButtonDblClk() have been re-written such that as long as this variable is TRUE there is no moving of the OCX, no re-activating it or triggering of events. In OnInitDialog(), we use the values stored in z_lSelColor and z_nUIDead to display the value of the color and selecting the appropriate radio-button.
All the work in handling the ambient properties is done by the container. Doesn't that make sense, since they are the container's properties anyway?

Continue