Dynamic creation and placement of ActiveX controls



Environment: VC6 SP4, NT4

Contents

Why do we need the ability of dynamic ActiveX control creation and placement ?

Most of the applications that use ActiveX controls, do so by embedding them at design time. In this article I have demonstrated how simple it is to create and place Active X controls at run time. I have used CWnd::CreateControl method of MFC.

Dynamic placement of ActiveX controls throws open the possibility for an application window to acquire different 'look and feel' at run time. You could think of an software application, which would be nothing more than a simple MFC created frame work, capable of loading into itself ActiveX controls written by Visual Basic developers. Such a paradigm offers the scope for customization as well.

Think of an imaginary software that should have the capability of displaying different image files like .jpg, gif, .bmp, etc. Further, individual image viewer ActiveX controls for each of the formats are also made available to us. How should you proceed? A simple approach could be to embed an instance of each of the controls on the main window, and then make visible any one of the control as per file format requirements.

Think of a software design, where in you provide a basic executable that contains a frame work and nothing else. The framework does nothing more than providing a menu, toolbar and some client space. You will let the framework application dynamicaly instantiate and load ActiveX controls into it's client space. The ActiveX controls in this case are the image viewers. While distributing my multi format image viewer product, I would also distribute the individual ActiveX controls, responsible for displaying each of the file formats (.jpg , .gif, .mpg, ..etc.)

Further, my installation package would create a registry list, containing the ActiveX control CLSID/ProgId responsible for each of the file format.

When an user, selects an image file , my product will determine the progid/clsid of the ActiveX control from the registry and instantiate that control within the application frame work, and the display the image file. The requirement from the ActiveX Control's side should be to implement a well published method/property through which I can pass on the file name, that is to be displayed by the ActiveX Control.

In this article I have created a very basic MFC dialog application, that loads an ActiveX control when supplied with a prog id.
top

Create a simple MFC dialog based application

Create a new MFC Dialog based EXE application. Make sure that the "Enable ActiveX Controls" option is checked.

top

Dynamic ActiveX Control creation

The class CWnd provides a method CreateControl that makes ActiveX control creation very much like a normal window. Add a private member variable (name it m_ControlWrapper), of type CWnd, in the project's main dialog class.

  class CYourApplicationDialog
    {
    .
    .
    .
  private:
    CWnd m_ControlWrapper;
    .
    .
    };

The member m_ControlWrapper will be the wrapper for our dynamically instantiated ActiveX control.Provide a menu bar to the dialog window.

Provide a handler for the "Insert ActiveX control" menu item.

void CYourApplicationDialog::OnFileInsertcontrol() 
  {
  // TODO: Add your command handler code here

  //
  // You may prompt  the user for the progid of the 
  // ActiveX control. For the sake of simplicity, I 
  // have hard coded the prog id. The downloadable 
  // source code provides a dialog box to key in the 
  // desired progid

  CString strControlProgid;
  strControlProgid="mscal.calendar.7"; //calendar control


  //
  // Because we are not a MDI, we make sure to destroy
  // any earlier instances.
  //
  OnDestroycontrol();


  //
  // We will make the ActiveX control occupy the entire
  // client area
  //
  RECT rc;
  ::GetClientRect(m_hWnd,&rc);


  //
  //create the ActiveX control with the given prog id
  //

  BOOL bStat=FALSE;
  bStat=
  m_ControlWrapper.CreateControl (
                          strControlProgid,
                          "",
                          WS_VISIBLE, 
                          rc,
                          this,
                          5000,
                          NULL,
                          FALSE,
                          NULL);

  if (bStat == FALSE)
     {
     ::MessageBox (m_hWnd,"Error!!",
                   "Could not place control",
                   MB_OK);
     return;
     }

  ::EnableMenuItem(
              ::GetMenu(m_hWnd),
              ID_DESTROYCONTROL,
              MF_ENABLED | MF_BYCOMMAND);


  //
  //display the prog id in the window's title bar
  //
  ::SetWindowText (m_hWnd,strControlProgid );

  }
top

Destroying the ActiveX Control

To safely unload the control we wil have to invoke CWnd::DestroyWindow. The code for control destruction is as follows:

void CYourApplicationDialog::OnDestroycontrol() 
  {
  // TODO: Add your command handler code here

  //
  //Use the method CWnd::DestroyWindow() 
  //
  m_ControlWrapper.DestroyWindow();

  ::EnableMenuItem(
              ::GetMenu(m_hWnd),
              ID_DESTROYCONTROL,
              MF_GRAYED | MF_DISABLED | MF_BYCOMMAND);

  ::SetWindowText (m_hWnd,"ActiveX Container");
  }
top

Resizing the ActiveX Control

We will dynamicaly maintain the size of our control equal to that of the client area of the main window. To achieve this, the method IOleObject::SetExtent should be used. But unlike Win32 functions, the dimensions need to be in twips.

(1 twip ~ 1/1440 inch).

We will override the OnSize message handler of the main window. Here we will obtain the new client rectangle extents, convert the dimensions into twips, and then invoke the IOleObject::SetExtent method.

Use the CWnd method GetControlUnknown() to get the IUnknown interface of the ActiveX control COM object. Using the IUnknown, query for the interface IOleObject In this example, I have used the _com_ptr_t smart interface pointer class. VC++ provides such wrappers for all standard OLE interfaces. Given an interface IMyInterface, the following single statement

_COM_SMARTPTR_TYPEDEF(IMyInterface,
                      __uuidof(IMyInterface));

will provide a smart interface pointer wrapper with the name IMyInterfacePtr. Do not forget to include the header file comdef.h .
void CYourApplicationDialog::OnSize(UINT nType,
                                         int cx, 
                                         int cy) 
  {
  CDialog::OnSize(nType, cx, cy);

  // TODO: Add your message handler code here
  //
  //update the control's dimensions, 
  //

  //
  //make sure that we have a valid embedded control
  //
  if (m_ControlWrapper.GetControlUnknown() == NULL) return;

  IOleObjectPtr pOleObj(m_ControlWrapper.GetControlUnknown ());
  if (pOleObj != NULL) 
     {

     RECT rc;
     ::GetClientRect (m_hWnd, &rc);

     CSize size(rc.right , rc.bottom );
     CClientDC dc(NULL);
     //
     //convert from pixel to twips.
     //
     dc.DPtoHIMETRIC(&size);


     pOleObj->SetExtent (1,(SIZEL*)&size);
     pOleObj->Update();//this does not seem to be working
     ::RedrawWindow(m_hWnd,NULL,NULL,RDW_ALLCHILDREN | 
                                     RDW_UPDATENOW | 
                                     RDW_INVALIDATE);
     }

  }

top

Downloads

The following binaries have been provided with this article:
  • Exe_client.exe:This is the main application, whose screen shots have been provided above
  • Sample1.ocx:Sample OCX created using Visual Basic. (progid=CodeGuru.Sample1)
  • Sample2.ocx:Sample OCX created using Visual Basic.(progid=CodeGuru.Sample2)

Download demo project - 91 Kb
Download source - 187 Kb

top