YOU CAN CODE!

 

With The Case Of UCanCode.net  Release The Power OF  Visual C++ !   HomeProducts | PurchaseSupport | Downloads  
Download Evaluation
Pricing & Purchase?
E-XD++Visual C++/ MFC Products
Overview
Features Tour 
Electronic Form Solution
Visualization & HMI Solution
Power system HMI Solution
CAD Drawing and Printing Solution

Bar code labeling Solution
Workflow Solution

Coal industry HMI Solution
Instrumentation Gauge Solution

Report Printing Solution
Graphical modeling Solution
GIS mapping solution

Visio graphics solution
Industrial control SCADA &HMI Solution
BPM business process Solution

Industrial monitoring Solution
Flowchart and diagramming Solution
Organization Diagram Solution

Graphic editor Source Code
UML drawing editor Source Code
Map Diagramming Solution

Architectural Graphic Drawing Solution
Request Evaluation
Purchase
ActiveX COM Products
Overview
Download
Purchase
Technical Support
  General Q & A
Discussion Board
Contact Us

Links

Get Ready to Unleash the Power of UCanCode .NET

 


UCanCode Software focuses on general application software development. We provide complete solution for developers. No matter you want to develop a simple database workflow application, or an large flow/diagram based system, our product will provide a complete solution for you. Our product had been used by hundreds of top companies around the world!

"100% source code provided! Free you from not daring to use components because of unable to master the key technology of components!"


VC++ MFC Tutorial: ActiveX Control Example

 

Navin Kohli

This article is somewhat like an extension to POLYGON tutorial provided in Microsoft’s online documentation. So I would not be explaining the basic steps involved to create the project and adding control to the project. I would only explain the steps where I diverted form POLYGON tutorial. To completely follow the example code of this article we will have to do the following steps.

  1. Name your project as ActiveXCtl

  2. Name the control as ShapeCtl.

  3. Since our control is circular and we will fill it with different kinds of fill patterns, we will add two custom properties to control, Radius and FillPattern. To do so follow the steps to add Sides property in POLYGON tutorial.

  4. POLYGON tutorial has only one event added to control. Following the same method we will add three methods to our control. Right click on CShapeCtl class in class view and add windows message handlers for WM_LBUTTONDOWN, WM_LBUTTONDBLCLK and WM_MOUSEMOVE. As is clear from these messages that our control will fire events for click, double click and mouse move on the control. The following functions will be added to CShapeCtl.h file

       LRESULT OnLButtonDblClk(UINT uMsg, WPARAM wParam,
                               LPARAM lParam, BOOL& bHandled);
       LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam,
                             LPARAM lParam, BOOL& bHandled);
       LRESULT OnMouseMove(UINT uMsg, WPARAM wParam,
                           LPARAM lParam, BOOL& bHandled);

 

  1. We will see the implementation a little later.

  2. In the second step of POLYGON tutorial where control is configured, from the Stock Properties tab add all the stock properties to control. It is not necessary that you have to add all of them. Since for my learning experience I wanted to see how all of these get implemented, I added all of these. Therefore to look your code similar to the example code, add all the stock properties.

  3. Follow step 6 of POLYGON tutorial to add property pages to control. We will add two pages and name these as ShapeProp and ApperanceProp. Add the controls to these pages as show in the figure

    Figure 1

     

    Figure 2

  4. We will not add any MFC support to our project because we won’t be using it.

Add the necessary controls and corresponding variables to the property page classes to manipulate the values corresponding to these controls. Since at the start of this article I told you that I didn’t add any MFC support to the project, therefore we will not be making use of CPropertPage or other MFC classes for these property page. We will use Win32 API calls to manipulate all the controls. I know it sucks after using the MFC for our applications. If you don’t want to go my way then you add the MFC support to this control and make use of all the control classes. Here is part of our code that initializes the controls.

LRESULT CAppearanceProp::OnInitDialog(UINT uMsg, WPARAM wParam,
                                      LPARAM lParam,
                                      BOOL& bHandled)
{
   USES_CONVERSION;
   // Let's set some ranges for controls.
   if ((m_hBorderWidthEdit = GetDlgItem (IDC_BORDERWIDTH_EDIT))
      != NULL)
   {
      ::SendMessage (m_hBorderWidthEdit, UDM_SETRANGE, 0,
                     MAKELONG (10, 0));
   }

   if ((m_hBorderWidthSpin = GetDlgItem (IDC_BORDERWIDTH_SPIN))
      != NULL)
   {
      ::SendMessage (m_hBorderWidthSpin, UDM_SETRANGE, 0,
                     MAKELONG (10, 0));
   }
   m_hAppearanceCombo    = GetDlgItem (IDC_APPEARANCE_COMBO);
   m_hFillStyleCombo     = GetDlgItem (IDC_FILLSTYLE_COMBO);
   m_hFillPatternCombo   = GetDlgItem (IDC_PATTERN_COMBO);
   m_hDrawStyleCombo     = GetDlgItem (IDC_DRAWSTYLE_COMBO);
   m_hEnableCheck        = GetDlgItem (IDC_ENABLE_CHECK);
   m_hBorderVisibleCheck = GetDlgItem (IDC_BORDERVISIBLE_CHECK);
   m_hTabStopCheck       = GetDlgItem (IDC_TABSTOP_CHECK);
   .
   .

All the control variables have been defines as HWND.

Control Implementation

Now we have done enough work to prepare skeleton structure for our control. Compile the project and test it by inserting it in ActiveX Control Test Container provided in the Tool menu of VC++ IDE. Ofcourse you will see nothing else but a rectangle with the label "ATL 2.0". Now we will fill the function bodies to draw the control the way we want it to be. Before I start discussing these functions, here is one thing that I did to the source code files. Object wizard puts most of the function bodies in header file. To separate the interface definitions and their implementation I moved all the function implementations to source files. Therefore if you see any difference between your files and the example code files, this could be the reason.

OnDraw ()

An OnDraw method is automatically added to your control class when you create your control with the ATL Object Wizard. POLYGON tutorial has put code for drawing simple polygon. We will add code to draw circular control, but a fancy one. Our control implements circle filled with fill patterns and you can change the font to one of your choice. Here is part of code in OnDraw function that fills the control with pattern you specify

switch (m_nFillPattern)
{
case fillBDiagonal:
   hBrush = CreateHatchBrush (HS_BDIAGONAL, colorFill);
   break;

case fillCrossHatch:
   hBrush = CreateHatchBrush (HS_CROSS, colorFill);
   break;
case fillDiagXHatch:
   hBrush = CreateHatchBrush (HS_DIAGCROSS, colorFill);
   break;
case fillFDiagonal:
   hBrush = CreateHatchBrush (HS_FDIAGONAL, colorFill);
   break;
case fillHorizontal:
   hBrush = CreateHatchBrush (HS_HORIZONTAL, colorFill);
   break;
case fillVertical:
   hBrush = CreateHatchBrush (HS_VERTICAL, colorFill);
   break;
}

The font property is implemented through IFontDisp interface. So we will make use of it to get, change and release the font object handle.

CComQIPtr<IFont, &IID_IFont> pFont (m_pFont);
if (pFont != NULL)
{
   pFont->get_hFont (&hFont);
   pFont->AddRefHfont (hFont);
   hOldFont = (HFONT)SelectObject (hdc, hFont);
}

And as a good Win32 programming practice, do delete al the GDI objects like HPEN, HBRUSH, HFONT, etc. after these have been used. And don’t forget to restore the font, pen and brush in the device context before you exit the function.

if (pFont != NULL)
{
   pFont->ReleaseHfont (hFont);
}

SelectObject (hdc, hOldFont);
SelectObject (hdc, hOldBrush);
DeleteObject (hBrush);
SelectObject (hdc, hOldPen);
DeleteObject (hPen);

Ambient Properties Change

If you want your control to respond to any change in the ambient properties of the container that is hosting the control, we will have to implement the OnAmbientPropertyChange method of IOleControl interface. Our control has already has IObjectControl in the inheritance list. If its not, then add it to the list of classes from which our control is derived from. And add corresponding entry in the BEGIN_COM_MAP. In the header file you need to add the following lines for this

class ATL_NO_VTABLE CShapeCtl :
   .
   public IOleControlImpl<CShapeCtl>,
   .
   .

   BEGIN_COM_MAP(CShapeCtl)
   .
   COM_INTERFACE_ENTRY(IOleControl)
   .

Add the following interface definition to CShapeCtl.h file.

// IOleControl
   STDMETHOD(OnAmbientPropertyChange)(DISPID dispid);
   .

And in the source file we will handle the property we want to respond to. In the example code I have handled the change in Background color.

STDMETHODIMP CShapeCtl::OnAmbientPropertyChange (DISPID dispid)
{
   // It is up to the control to decide which ambient property it
   // wants to respond to.
   // NOTE: In this control only change to background color is
   // being acknowledged. If you want to respond to any other
   // ambient property, add the appropriate code at the appropriate
   // place in switch statement.

   switch (dispid)
   {
      case DISPID_AMBIENT_BACKCOLOR:
      // Get the ambient back color and change the view of control.

      if (SUCCEEDED (GetAmbientBackColor (m_clrBackColor)))
      {
         FireViewChange ();
      }
      break;

      case DISPID_AMBIENT_DISPLAYNAME:
      case DISPID_AMBIENT_FONT:
      .

Control Redraw

Whenever some control property changes you would like to redraw the control. Call FireViewChange method of CComControl class. The following code shows how the control handles the change in radius.

STDMETHODIMP CShapeCtl::put_Radius(short newVal)
{
   if (newVal <= 0)
   {
      return (Error (_T ("Radius value can't be zero!")));
   }
   else if (newVal > RectHt || newVal > RectWd)
   {
      return (Error (_T ("Radius can't exceed control's extents!")));
   }

   m_nRadius = newVal;

   // Change the control's view.

   FireViewChange ();

   return S_OK;
}

Event Firing

While creating control we added three methods to handle mouse button down and move events. For firing event for mouse click we check the location where the click took place. If its inside the control region, we fire ClickIn event otherwise we fire ClickOut event.

LRESULT CShapeCtl::OnLButtonDown(UINT uMsg, WPARAM wParam,
                                 LPARAM lParam, BOOL& bHandled)
{
   HRGN rgn;
   WORD xPos = LOWORD (lParam);
   WORD yPos = HIWORD (lParam);

   // Create the elliptic region to determine if the click point was
   // inside or outside the control.

   rgn = CreateEllipticRgn((CenterPt.x - m_nRadius),
                           (CenterPt.y - m_nRadius),
                           (CenterPt.x + m_nRadius),
                           (CenterPt.y + m_nRadius));
   // If the click was inside control, then fire ClickIn; otherwise,
   // fire ClickOut.
   if (PtInRegion (rgn, xPos, yPos))
   {
      Fire_ClickIn (xPos, yPos);
   }
   else
   {
      Fire_ClickOut (xPos, yPos);
   }
   // Deleted the created region object.
   DeleteObject (rgn);
   return 0;
}

Use CreateEllipticRgn API call with the radius of control and then use PtInRegion API call to check if the click was inside this region or not. Don’t forget to delete the created region object.

Control Persistence

Here is the most important part of COM objects. What do we do with the control we have created? How should I save the properties of this control? How will the container, which uses this control, reload the control in the state it was saved? And you know this is the part that is not explained in most of the examples published in books. And this was the part which took me couple of weeks to get the things straight. There are different types of persistence interfaces provided in COM to accomplish this tucancode.net. But the interfaces which are of main interest to ActiveX controls are IPersistStream, IPersistStorage, and IPersistPropertyBag. A container implements only one type of persistence interface. But if you want to make your control useable by any kind of container, implement each one of these interfaces. Why do we have to implement each one on the interface? And this was the same question I ucancode.neted myself when I was experimenting with this control and trying to insert it in different kinds on containers. Here are couple of hints to decide which interface will be used by the container.

  • VB 6.0 needs only IPersistStreamInit or IPersistStream. And when VB6.0 saves the control in a form, it uses IPersistPropertyBag interface to save and load the control.

  • IE 4.0 will only use IPersistProprtyBag to load the control.

  • VisualInterDev uses IPersistPropertyBag to save the state of the control

  • MS Office applications use IPersistStorage interface to save and load the control

  • When you insert the control in VC++ dialog box, it uses IPersistStreamInit interface to save and load the Control State.

If you look in the resource file of the Container App project, you will see the following code

///////////////////////////////////////////////////////////////////
//
// Dialog Info
//

IDD_CONTAINERAPP_DIALOG DLGINIT
BEGIN
    IDC_SHAPECTL, 0x376, 129, 0
    0x0000, 0x0000, 0x0060, 0x8080, 0x0000, 0x0080, 0x0080, 0xcdcd,
    0xcdcd, 0xcdcd, 0xcdcd, 0x00ff, 0x0000, 0xcdcd, 0xcdcd, 0xffff,
    0xffff, 0xcdcd, 0xcdcd, 0x0000, 0x0000, 0x0008, 0x0000, 0xffff,
    0xffff, 0x0002, 0x0000, 0x0018, 0x8000, 0xcdcd, 0xcdcd, 0x0002,
    0x0000, 0x0022, 0x0000, 0x004e, 0x0061, 0x0076, 0x0065, 0x0065,
    0x006e, 0x0027, 0x0073, 0x0020, 0x0043, 0x006f, 0x006e, 0x0074,
    0x0072, 0x006f, 0x006c, 0x0000, 0x0001, 0x0600, 0x02bc, 0xd4c0,
    0x0001, 0x530e, 0x7263, 0x7069, 0x2074, 0x544d, 0x4220, 0x6c6f,
    "\144"
    0
END

This is how IPersistStreamInit interface saved the control sate. Next time you load the dialog box, the Control State will be loaded from this stream.

All the persistence interfaces have same methods, but with different argument types, to save and load the control state. For implementation of these three persistence interfaces, make sure that we have these in the inheritance list and the PROP_MAP_ENTRY. If not, then add these.

class ATL_NO_VTABLE CShapeCtl :
.
public IPersistStreamInitImpl<CShapeCtl>,
.
public IPersistStorageImpl<CShapeCtl>,
,
public IPersistPropertyBagImpl<CShapeCtl>,
.

In the property map add following entries if not present.

BEGIN_COM_MAP(CShapeCtl)
   .
   COM_INTERFACE_ENTRY(IPersistStreamInit)
   COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
   .
   COM_INTERFACE_ENTRY(IPersistPropertyBag)
   .
   COM_INTERFACE_ENTRY(IPersistStorage)
   .
END_COM_MAP()

And add the following interface definitions to CShapeCtl.h.

// IPersistStreamImpl
STDMETHOD(Load)(IStream *pStream);
STDMETHOD(Save)(IStream *pStream, BOOL fClearDirty);
STDMETHOD(GetSizeMax)(ULARGE_INTEGER* pcbSize);

// IPersistStorageImpl
STDMETHOD(Load)(IStorage *Storage);
STDMETHOD(Save)(IStorage *pStorage, BOOL fSameAsLoad);

// IPersistPropertBag
STDMETHOD(Load)(IPropertyBag *pPropBag, IErrorLog* pErrorLog);
STDMETHOD(Save)(IPropertyBag *pPropBag, BOOL fClearDirty,
                BOOL fSaveAllProperties);

Now we will have to provide the interface implementation in CShapeCtl.cpp file. I wouldn’t be going into details of implementation of each interface. I have tried to explain each and every detail of these interfaces in the CShapeCtl.cpp file While implementing these interfaces make sure that data is loaded in the same order in which it was saved. And you use the same stream and storage names. IPersistPropertyBag persistence has some problems. The default ATL implementation persist data in property bag if its of VARIANT type. So we will need some method to save user-defined data types. Therefore the simplest way would be to convert custom data types to one of the types defined in VARIANT type. And then save it in the property bag. And while loading reconvert it to user defined data type. For more explanation see "Professional ATL COM Programming", Wrox Press Ltd, by Dr. Richard Grimes. The code used in the example has been taken from this book.

void CShapeCtl::Insert (BSTR *pStream, void *pData, DWORD size)
{
   CComBSTR bstr;
   bstr.Attach (*pStream);

   LPBYTE ptr = static_cast<LPBYTE>(pData);
   TCHAR strTemp[3] = {0};

   for (DWORD j = 0; j < size; ++j)
   {
      BYTE ch = *ptr;
      strTemp[1] = (ch & 0xf) > 9 ? '7'+ (ch & 0xf) : '0' +
                   (ch & 0xf);
      ch = ch >> 4;

      strTemp[0] = (ch & 0xf) > 9 ? '7'+ (ch & 0xf) : '0' +
                   (ch & 0xf);
      ptr++;

      bstr += strTemp;
   }

   *pStream = bstr.Detach ();
}

void CShapeCtl::Extract (LPCWSTR *pStream, void *pData, DWORD size)
{
   LPCWSTR str = *pStream;
   LPBYTE ptr = static_cast<LPBYTE>(pData);

   for (DWORD j = 0; j < size; ++j)
   {
      BYTE ch = (str[0] >= L'A') ? str[0] - L'7' : str[0] - L'0';
      ch *= 16;
      ch += (str[1] >= L'A') ? str[1] - L'7' : str[1] - L'0';
      *ptr = ch;
      ptr++;
      str += 2;
   }

   *pStream = str;
}

This code converts your data type to string type and save as VARIANT and while reloading convert from string to user defined type. For more explanation see the comments in the code.

Control Category and Safety

If you insert this control in a web page and load the page, a warning message warning about unsafe ActiveX control gets displayed. It only gets displayed if you click OK. So we should mark our controls safe or unsafe so that when o container wants to load it, it knows about it. There are two ways of doing this. First way is to include the following category map in your control. I didn’t use this method. Instead I added the component category creation code in ActiveXCtl.cpp. During the registration of control, category gets created and registered in the registry. And when control is unregistered, this category is removed form the system registry. This way you can define your own control category types. Look in DllRegisterServer and DllUnregisterServer function bodies for implementation.

BEGIN_CATEGORY_MAP (CshapeCtl)
   IMPLEMENT_CATEGORY(CATID_SafeForScripting)
   IMPLEMENT_CATEGORY(CATID_SafeForInitializing)
END_CATEGORY_MAP ()

Second way is to Implement IObjectSafety interface. I have implemented this interface too. There was no need to implement both methods, but I did it for sake of getting experience of implementation of these methods. Add the following interface definition in the inheritance list, property map.

class ATL_NO_VTABLE CShapeCtl :

.
   public IObjectSafetyImpl<CShapeCtl,
          INTERFACESAFE_FOR_UNTRUSTED_CALLER |
          INTERFACESAFE_FOR_UNTRUSTED_DATA>
.

BEGIN_COM_MAP(CShapeCtl)
.
   COM_INTERFACE_ENTRY(IObjectSafety)
.
END_COM_MAP()

Add these interface definitions to CShapeCtl.h file

// IObjectSafety
STDMETHOD(GetInterfaceSafetyOptions)
         (REFIID riid, DWORD *pdwSupportedOptions,
          DWORD *pdwEnabledOptions);
STDMETHOD(SetInterfaceSafetyOptions)
         (REFIID riid, DWORD dwOptionSetMucancode.net,
          DWORD dwEnabledOptions);

Look in code for implementation details of the interface.

Testing Control

Figure 3

 I have included ContainerApp which is a simple dialog based MFC application. In the dialog box right click and choose Insert Activex control option. Select ShapeCtl control. Right click on the control and manipulate the control properties from the property pages we added.

To include this control in web page use any of the editor or use the ActiveXPage.html file included in the ComApps folder.

References:

  1. Professional ATL COM Programming, Wrox Press Ltd, by Dr. Richard Grimes.

  2. Active Template Library, IDG Books Worldwide Inc, by Tom Armstrong

  3. The Essence of COM with ActiveX, Prentice Hall, by David S. Platt

  4. Inside OLE second Ed., Microsoft Press, by Kraig Brockscmidt

  5. POLYGON tutuorial supplied with VC++ 6.0

  6. Microsft knowledgebase articles

Compilation

The example code has been compiled with VC++ 6.0 (SP-1) on Windows NT 4.0 (SP-4). If you compile the code, you will get 9 warnings. These warnings are there because I didn’t implement the dispatch interfaces for font, picture and mouse icon property of the control. I didn’t do this to keep the implementation of control as simple as I could. See Microsoft Knowledgebase Article ID : Q166472 for details on their implementation.

Downloads

activex_tut1.zip - Download source code -- 58 Kb
activex_tut2.zip - Download Container Application -- 31 Kb

 

Copyright ?1998-2024 UCanCode.Net Software , all rights reserved.
Other product and company names herein may be the trademarks of their respective owners.

Please direct your questions or comments to webmaster@ucancode.net