Home   Notes 

ActiveX

!! Gotchas !! ActiveX Control Gotchas

External


Example MFC ActiveX Control

2008-11-05

Basic Control

  1. The ActiveX control I make here will be named "MyControl", replace instances of "MyControl" in these steps with whatever name you decide to give yours.
  2. In Visual Studio 2005 (or 2008) create a new c++ activeX project (if VS2005, run as administrator under Vista).
  3. Build it to make sure it compiles and registers fine.
  4. Find your controls clsid, which can be found in the projects one and only .idl file, it is the last GUID in the file. (for me it was: "32F9288B-C5EB-48C9-B8D7-B278F3E1C916")
  5. Add a new file to your project named MyControl.inf (I right click on the project say 'add existing file', then in the file chooser window, right click on a folder, choose 'explore', then in the explorer window that comes up navigate up one directory to get back to where the projects files are, then right click to add a new text file, which I rename in this case to MyControl.inf, then go back to the file add window, make sure file type to add is 'all' and then add the file I just made)
  6. Populate the MyControl.inf like this Example MyControl.inf, but make sure to put the right clsid and name for your control into it.
  7. Open a tools window for your version of VS (find it under the start menu)
  8. In the tools window goto the DEBUG directory that has MyControl.ocx (It is odd, you will see 2 directories named after your control and 2 DEBUG directories, make sure you goto the right one)
  9. In the tools window copy the MyConrol.inf to the DEBUG directory and create the cab file, see the CAB creation Example.
  10. Make an html page to show MyControl (see below ActiveX HTML version 1), make sure to put the real CLSID in the html (see above for where to find it.

Show the HWND

  1. Make the OnDraw method look like OnDraw that shows HWND
  2. Build (Make sure you are not using the control in Internet Explorer, if so, then _close_ Internet Explorer)
  3. Double click on the test.html to see it.

Add a Property

  1. Go to Class View | MyControl | MyControlLib | _DMyControl (Note this does not end in "Events" and it is not the one named "MyControl"), then right click for the context menu and choose Add | Add Property...
  2. For "Property type:" choose "BSTR", for "Property name:" type "myProp1", then click "Finish".
  3. Add code to handle the property
    1. At the end of void CMyControlCtrl::OnmyProp1Changed(void) add: this->Invalidate()
    2. At the end of OnDraw add: pdc->DrawText(m_myProp1, m_myProp1.GetLength(), new CRect(0, 20, 1000, 1000), DT_NOCLIP);
    3. At the end of the constructor add: m_myProp1 = "apple";

Add an Event from ActiveX to Javascript

  1. Go to Class View | MyControl | MyControlCtrl (Note this is _not_ MyControlLib, then right click for the context menu and choose Add | Add Event...
  2. Set "Event name:" to "EventFromControl", click finish
  3. In OnmyProp1Changed, add: EventFromControl();
  4. See below for html for HTML with Event Handler

Add a method that javascript can call

  1. Go to Class View | MyControl | MyControlLib | _DMyControl (Note this does not end in "Events" and it is not the one named "MyControl"), then right click for the context menu and choose Add | Add Method...
  2. For "Return type:" choose void, for "Method name:" type ControlFunction, then click "Finish".
  3. Makes CMyControlCtrl::ControlFunction(void) be:
    void CMyControlCtrl::ControlFunction(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); m_myProp1 = "control function handled"; this->Invalidate(); }
  4. Add to the html a button to call it by adding to form this line:
    <input type="button" value="ControlFunction" onclick="callControlFunction()" />
  5. Add this to the script block at the top:
    function callControlFunction() { var element = document.getElementById("MyControl"); element.ControlFunction(); }

Example MyControl.inf

[version] signature=$Chicago$ AdvancedINF=1.0 [Add.Code] MyControl.ocx=MyControl.ocx [delme005.ocx] file=thiscab clsid={32F9288B-C5EB-48C9-B8D7-B278F3E1C916} FileVersion=1,0,0,1 RegisterServer=yes

CAB creation Example

Setting environment for using Microsoft Visual Studio 2005 x86 tools. C:\Program Files\Microsoft Visual Studio 8\VC>cd "C:\work\MyControl\debug" C:\work\MyControl\debug>copy ..\MyControl\MyControl.inf . 1 file(s) copied. C:\work\MyControl\debug>cabarc -s 6144 n MyControl.cab MyControl.ocx MyControl.inf Microsoft (R) Cabinet Tool - Version 5.00.2134.1 Copyright (C) Microsoft Corp. 1981-1999. Creating new cabinet 'MyControl.cab' with compression 'MSZIP': -- adding MyControl.ocx -- adding MyControl.inf Completed successfully C:\work\MyControl\debug>

ActiveX HTML version 1

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>ActiveX Test</title></head> <body> <object id="MyControl" width="250" height="200" classid="CLSID:32F9288B-C5EB-48C9-B8D7-B278F3E1C916" codebase="file:///../debug/MyControl.cab#version=1,0,0,1" > </object> </body> </html>

OnDraw that shows HWND

void CMyControlCtrl::OnDraw( CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) { if (!pdc) return; // TODO: Replace the following code with your own drawing code. pdc->FillRect(rcBounds, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH))); pdc->Ellipse(rcBounds); HWND hWnd = (HWND)(this->GetHwnd()); TCHAR buf[100]; int count = _stprintf(buf, _T("hWnd is %d"), (int)hWnd); pdc->DrawText(buf, count, new CRect(0, 0, 1000, 1000), DT_NOCLIP); }

HTML with Event Handler

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>ActiveX Test</title> <script type="text/javascript"> function handleOnLoad() { var element = document.getElementById("MyControl"); if (element.addEventListener) { element.addEventListener('EventFromControl', eventFromControlHandler, false); } else if (element.attachEvent) { element.attachEvent("EventFromControl", eventFromControlHandler); } } function eventFromControlHandler(e) { e = e || window.event; alert("Handling EventFromControl"); } function manipulateProperty(newVal) { var oldval = MyControl.myProp1; alert("Original Value = " + oldval); MyControl.myProp1 = newVal; } </script> </head> <body onload="handleOnLoad()"> <form action=""> <input type="button" value="orange" onclick="manipulateProperty('orange')" /> <input type="button" value="apple" onclick="manipulateProperty('apple')" /> </form> <object id="MyControl" width="250" height="200" classid="CLSID:8D16A88C-3DE6-46C5-82A1-E08BFDF553B5" codebase="file:///../debug/MyControl.cab#version=1,0,0,1" > </object> </body> </html>

OpenGL support, How to add it to an ActiveX Control

  1. See this article "How to add OpenGL support to ATL controls" http://www.codeproject.com/atl/atlopengl.asp
  2. BUT in your controls constructor do this
    m_bWindowOnly = true
    Otherwise it will never call OnCreate.


Internet Explorer, embedding in a Dialog box

  1. Make an MFC Dialog based project, make sure that you check the box to support ActiveX controls
  2. In the dialog editor, right click on the dialog and choose "Insert ActiveX Control".
  3. The tricky part is that IE is called "Microsoft Web Browser"
  4. Select the COM tab and choose IE
  5. Once the control is in place, right click on it and add a variable for it (name it something like m_IE)
  6. In OnInit you can have it load a page by doing something like this:
    m_IE.Navigate( "www.google.com", NULL, NULL, NULL, NULL);

Howto add another Property

Checklist

  1. Add a variable for the property in the Controls .h file
  2. Initialize the variable in the constructor
  3. Add property to the Controls Interface in Classview
  4. Connect the value in the get_PropName and set_PropName methods
  5. Add the property to the Property Dialog Box, and set the ID of the control for the property
  6. In Classview add an event handler to the property page for the control you added
    If textfield: EN_CHANGE
    If radio button BN_CLICKED
    then make it do this:
    SetDirty(TRUE); return 0;
  7. Add setting of the value to the Property Pages Apply method
  8. Add the property to the ActiveX controls BEGIN_PROP_MAP
    PROP_ENTRY("PropName", 1, CLSID_PropPageShortName)
  9. Use the value whereever needed

Properties

Rules about Properties


Stock Properties

The definitive list can be found at in olectl.h, which as I write this is at:
C:\Program Files\Microsoft Visual Studio.NET\Vc7\PlatformSDK\include\olectl.h


Howto add a Property page

This is for ATL projects

  1. You should already have properties defined on the ActiveX control, this will let them be editted on a PropertyPage
  2. Make sure your properties where added 'bindable' and with 'requestedit' set See Example Here
  3. Add a Property Page
    Class View | Top level of the ActiveX control | right click | Add | Add Class | ATL | ATL Property Page | Open
  4. Set the Short Name
  5. Under Strings Set the title (this will be the name that shows on the Tab for the Property Page), example "&Polygon"
  6. Erase the Help file entry (I don't know to use it, but maybe it would be good to figure out)
  7. click Finish
  8. Edit the Property Page using resource editor, it is a dialog in your project, and is named after the Property Page ATL control you added, but with a prefix of IDD_
  9. Set the ID's of the fields that corespond to the properties (example: IDC_FooCount)
  10. Modify the Apply method of the Property Page you added as shown below See Here
  11. Add handling of changes to the fields that are properties Class View | Select the Property Page class | Properties | Events, expand the options pertaining to each control you care about, add a handler to its EN_CHANGE event
  12. Make the change event handlers call
    SetDirty(TRUE); return 0; the actual handling of the value changes happen in the Apply method
  13. Add the Property Page to the ActiveX control. Add Something like
    PROP_ENTRY("PropName", 1, CLSID_PropPageShortName) to the property map in the controls .h file (find it by looking for
    BEGIN_PROP_MAP(CControlName) Here is the function footprint
    PROP_ENTRY( zDescriptOfProp, idlFileIdNumberOfTheProperty, CLSID_shortNameOfPropertyPage There appeares to be another way of doing this using
    PROP_PAGE macro, but I haven't investigated that method.
  14. Add a message handler to the Property Page for WM_INITDIALOG and have it initialize the dialogs values of the properties See Here
  15. Build it and test it in Excel or Word

IDL Property example

Property Page Apply

Property Page Initialize


Embedded Control, howto add properties

  1. Add the properties Howto add a Property to and ActiveX Control
  2. Add a property page to set them Howto add a Property page
  3. In the embedding app, look at the properties on the item, and choose 'Custom' to make the property page pop up
  4. There appears to be a way to use IPerPropertyBrowsing to put the properties on the property page and not need to pop up the custom property page

ActiveX Control Gotchas

Make Sure

Issues

IssueComment
Control Doesn't show up in Excel or WordMake sure the ATL Project Name does not contain more than 12 characters (My guess is this is a typelib name length issue)

Howto Test an ActiveX control using ActiveX Control Test Container

  1. Open the test Container Tools | ActiveX Control Test Container
  2. Insert the Control to Test Edit | Insert new control and choose the control you want
  3. Use Control | Invoke Method to tryout the ActiveX controls interface methods (such as gets and puts)

Howto add a Property to and ActiveX Control

  1. Add a property Class View | {The Interface} | right click | Add Property
  2. Add a variable in the control's .h file to hold the properties value, and initialize it if needed.
  3. Connect the varible to the get_ and set_ methods in the controls .cpp file.
  4. In the Property Map, just Before
    END_PROP_MAP()
    add an entry for the new property like:
    PROP_ENTRY("Text", 1, CLSID_ProppedControl)
    But use your properties name instead of "Text" and your properties id (as see in the idl file) instead of 1
    Also, use your controls class id instead of CLSID_ProppedControl
  5. Add something like this (but with your Classes type in the parenthesis) to the classes your control class inherits from
    public IPersistPropertyBagImpl<CProppedControl>
    This says that it handles the type of persistant properties used by I.E.
  6. Before END_COM_MAP() add this entry COM_INTERFACE_ENTRY(IPersistPropertyBag)
  7. Set the value in a web page with something like this
    <param name="Text" value="Get off'a my cloud!"> inbetween <OBJECT ...> and </OBJECT> and of course replacing "Text" with the name used for your property above.
  8. To mark the control safe add this to the classes your control inherits from
    public IObjectSafetyImpl<cproppedcontrol, INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA> and add this COM_INTERFACE_ENTRY(IObjectSafety) before END_COM_MAP()

Note that IPersistPropertyBag is used by I.E, and IPersistStream is used by ??? and IPersistStorage is used by?


Howto make a Drawn ActiveX Control

  1. Create a Visual C++ Projects | ATL Project
  2. Delete the Proxy Stub Project (needed for DCOM)
  3. Add an Active X Control to the project Class View | Add Class | ATL | ATL Control
  4. You can look at it now in IE
  5. Modify the OnDraw Method in the controls .h file to draw whatever you want
  6. Call the method FireViewChange(); to make the control redraw itself.

Java, Embedding in

http://www.codeproject.com/java/javacom.asp?df=100&forumid=651&exp=0&select=516112

Persist, HTML | OBJECT | PARAM (Read only)

Note: The default implimentation of IPersistPropertyBagImpl seems to work fine. No need to override it. (But there was a note somewhere about a bug in it)

  1. Class must be derived from IPersistPropertyBagImpl
  2. COM_MAP must contain COM_INTERFACE_ENTRY(IPersistPropertyBag)
  3. PROP_MAP must contain a PROP_ENTRY for the property, the 2nd parameter is the properties id in the .idl file
  4. The property must exist in the class
  5. The properties Set method is called after the class is constructed
class ATL_NO_VTABLE CPolyCtl : 
   
	public IPersistPropertyBagImpl<CPolyCtl>
{
private:
    LONG	m_testLongProp;
   
   public:
   
BEGIN_COM_MAP(CPolyCtl)
   
	COM_INTERFACE_ENTRY(IPersistPropertyBag)
END_COM_MAP()
   
BEGIN_PROP_MAP(CPolyCtl)
   
	PROP_ENTRY("TestLongProp", 2, CLSID_PolyProp)
END_PROP_MAP()   
   
   STDMETHOD(put_TestLongProp)(LONG newVal);   
}

STDMETHODIMP CPolyCtl::put_TestLongProp(LONG newVal)
{
    m_testLongProp = newVal;
    return S_OK;
}

Persist


Ambient Properties

Info

Adding Change Listener

  1. Class must be derived from IOleControlImpl (see below)
  2. COM MAP must contain COM_INTERFACE_ENTRY(IOleControl)
  3. Add this Method Prototype STDMETHOD(OnAmbientPropertyChange)(DISPID dispid)
  4. Add the Method
class ATL_NO_VTABLE CShapeCtl :
   
   public IOleControlImpl<CShapeCtl>
{
   
   BEGIN_COM_MAP(CShapeCtl)
   
   COM_INTERFACE_ENTRY(IOleControl)
   
   STDMETHOD(OnAmbientPropertyChange)(DISPID dispid);
   
}

STDMETHODIMP CShapeCtl::OnAmbientPropertyChange (DISPID dispid)
{
   OLE_COLOR backcolor;
   
   switch(dispid)
   {
      case DISPID_AMBIENT_BACKCOLOR:
          GetAmbientBackColor( &backcolor);
          // do something with it:
          SetBackColorToMatch( backcolor);
          // redraw the control:
          FireViewChange();
          break;
       
   }
}

Safe for Scripting, Making the ActiveX control

  1. Add this to the list of classes your control is derived from IObjectSafetyImpl as shown below
  2. Add COM_INTERFACE_ENTRY(IObjectSafety) to the COM MAP as shown below
  3. Optionally or this into it INTERFACESAFE_FOR_UNTRUSTED_DATA

Note: This requires the header atlctl.h

#include <atlctl.h>
class ATL_NO_VTABLE SceneGraphXControl : 
	// Lots of other classes here
	public IObjectSafetyImpl<SceneGraphXControl, INTERFACESAFE_FOR_UNTRUSTED_CALLER>
{
	// class defined here including something like:

BEGIN_COM_MAP(SceneGraphXControl)
	// lots of other entries here
	COM_INTERFACE_ENTRY(IObjectSafety)
END_COM_MAP()

}

Using in HTML

<html>

<script language="JavaScript">
function HandleTheClick() {
  window.document.SceneGraphXControl.ProcessCommand("Testing");
}
</script>

<object
  id="SceneGraphXControl"
  height="600"
  width="600"
  data="data:application/x-oleobject;base64,LuyFlL11C0mNHCeKs/XkbgAHAAADPgAAAz4AAA=="
  classid="CLSID:9485EC2E-75BD-490B-8D1C-278AB3F5E46E"
  viewastext
></object>

<form name="FormName" id="Form1">
	<input type="BUTTON" value="Test Me" onclick="HandleTheClick()">
</form>

</html>