ActiveX
!! Gotchas !! ActiveX Control Gotchas
External
Example MFC ActiveX Control
2008-11-05
Basic Control
- 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.
- In Visual Studio 2005 (or 2008) create a new c++ activeX project (if VS2005, run as administrator under Vista).
- Build it to make sure it compiles and registers fine.
- 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")
- 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)
- Populate the MyControl.inf like this Example MyControl.inf, but make
sure to put the right clsid and name for your control into it.
- Open a tools window for your version of VS (find it under the start menu)
- 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)
- In the tools window copy the MyConrol.inf to the DEBUG directory and create the cab file, see the
CAB creation Example.
- 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
- Make the OnDraw method look like OnDraw that shows HWND
- Build (Make sure you are not using the control in Internet Explorer, if so, then _close_
Internet Explorer)
- Double click on the test.html to see it.
Add a Property
- 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...
- For "Property type:" choose "BSTR", for "Property name:" type "myProp1", then click "Finish".
- Add code to handle the property
- At the end of void CMyControlCtrl::OnmyProp1Changed(void) add: this->Invalidate()
- At the end of OnDraw add: pdc->DrawText(m_myProp1, m_myProp1.GetLength(), new CRect(0, 20, 1000, 1000), DT_NOCLIP);
- At the end of the constructor add: m_myProp1 = "apple";
Add an Event from ActiveX to Javascript
- Go to Class View | MyControl | MyControlCtrl (Note this
is _not_ MyControlLib, then right click for the context menu and choose
Add | Add Event...
- Set "Event name:" to "EventFromControl", click finish
- In OnmyProp1Changed, add:
EventFromControl();
- See below for html for HTML with Event Handler
Add a method that javascript can call
- 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...
- For "Return type:" choose
void,
for "Method name:" type ControlFunction, then click "Finish".
- Makes
CMyControlCtrl::ControlFunction(void) be:
void CMyControlCtrl::ControlFunction(void)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
m_myProp1 = "control function handled";
this->Invalidate();
}
- Add to the html a button to call it by adding to
form this line:
<input type="button" value="ControlFunction" onclick="callControlFunction()" />
- 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
- See this article "How to add OpenGL support to ATL controls" http://www.codeproject.com/atl/atlopengl.asp
- BUT in your controls constructor do this
m_bWindowOnly = true
Otherwise it will never call OnCreate.
Internet Explorer, embedding in a Dialog box
- Make an MFC Dialog based project, make sure that you check the box to support ActiveX controls
- In the dialog editor, right click on the dialog and choose "Insert ActiveX Control".
- The tricky part is that IE is called "Microsoft Web Browser"
- Select the COM tab and choose IE
- Once the control is in place, right click on it and add a variable for it (name it something like m_IE)
- 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
- Add a variable for the property in the Controls .h file
- Initialize the variable in the constructor
- Add property to the Controls Interface in Classview
- Connect the value in the get_PropName and set_PropName methods
- Add the property to the Property Dialog Box, and set the ID of the control for the property
- 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;
- Add setting of the value to the Property Pages Apply method
- Add the property to the ActiveX controls BEGIN_PROP_MAP
PROP_ENTRY("PropName", 1, CLSID_PropPageShortName)
- Use the value whereever needed
Properties
Rules about Properties
- Properties are accessed through
IDispatch (Dual is okay since it does IDispatch).
Insertable makes it available from insert menus (such as the insert control menu in Word).
Windowed only if it always must have its own window (like if it will do OpenGL).
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
- You should already have properties defined on the ActiveX control, this will let them be editted on a PropertyPage
- Make sure your properties where added 'bindable' and with 'requestedit' set See Example Here
- Add a Property Page
Class View | Top level of the ActiveX control | right click | Add | Add Class | ATL | ATL Property Page | Open
- Set the Short Name
- Under Strings Set the title (this will be the name that shows on the Tab for the Property Page), example "&Polygon"
- Erase the Help file entry (I don't know to use it, but maybe it would be good to figure out)
- click Finish
- 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_
- Set the ID's of the fields that corespond to the properties (example: IDC_FooCount)
- Modify the Apply method of the Property Page you added as shown below See Here
- 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
- Make the change event handlers call
SetDirty(TRUE); return 0; the actual handling of the value changes happen in the Apply method
- 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.
- Add a message handler to the Property Page for WM_INITDIALOG and have it initialize the dialogs values of the properties See Here
- Build it and test it in Excel or Word
IDL Property example
Property Page Apply
Property Page Initialize
Embedded Control, howto add properties
- Add the properties Howto add a Property to and ActiveX Control
- Add a property page to set them Howto add a Property page
- In the embedding app, look at the properties on the item, and choose 'Custom' to make the property page pop up
- 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
- ATL Project Names are not more than 12 characters long (Otherwise the control will not appear in Word or Excel insert Control lists) (My guess is this is a typelib name length issue)
Issues
| Issue | Comment |
| Control Doesn't show up in Excel or Word | Make 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
- Open the test Container Tools | ActiveX Control Test Container
- Insert the Control to Test Edit | Insert new control and choose the control you want
- Use Control | Invoke Method to tryout the ActiveX controls interface methods (such as gets and puts)
Howto add a Property to and ActiveX Control
- Add a property Class View | {The Interface} | right click | Add Property
- Add a variable in the control's .h file to hold the properties value, and initialize it if needed.
- Connect the varible to the get_ and set_ methods in the controls .cpp file.
- 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
- 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.
- Before
END_COM_MAP() add this entry COM_INTERFACE_ENTRY(IPersistPropertyBag)
- 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.
- 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
- Create a Visual C++ Projects | ATL Project
- Delete the Proxy Stub Project (needed for DCOM)
- Add an Active X Control to the project Class View | Add Class | ATL | ATL Control
- You can look at it now in IE
- Modify the OnDraw Method in the controls .h file to draw whatever you want
- 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)
- Class must be derived from
IPersistPropertyBagImpl
- COM_MAP must contain
COM_INTERFACE_ENTRY(IPersistPropertyBag)
- PROP_MAP must contain a PROP_ENTRY for the property, the 2nd parameter is the properties id in the .idl file
- The property must exist in the class
- 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
- ?? Ambient Properties are things like a web pages background color
OnAmbientPropertyChange is used to tell when Ambient Properties are changed
Adding Change Listener
- Class must be derived from
IOleControlImpl (see below)
- COM MAP must contain
COM_INTERFACE_ENTRY(IOleControl)
- Add this Method Prototype
STDMETHOD(OnAmbientPropertyChange)(DISPID dispid)
- 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
- Add this to the list of classes your control is derived from
IObjectSafetyImpl as shown below
- Add
COM_INTERFACE_ENTRY(IObjectSafety) to the COM MAP as shown below
- 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>