Program examples compiled using Visual Studio/C++ .Net 2003 compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this tutorial are listed below. Don’t forget to read Tenouk’s small disclaimer. The supplementary note for this tutorial is the .NET early stage development.
Step 5: Adding an Event
In this step, you will add a ClickIn and a ClickOut event to your ATL control. You will fire the ClickIn event if the user clicks within the polygon and fire ClickOut if the user clicks outside. The tasks to add an event are as follows:
Adding the ClickIn and ClickOut Methods
When you created the ATL control in step 2, you selected the Connection points check box. This created the _IPolyCtlEvents interface in the Polygon.idl file. Note that the interface name starts with an underscore. This is a convention to indicate that the interface is an internal interface. Thus, programs that allow you to browse COM objects can choose not to display the interface to the user. Also note that selecting Connection points added the following line in the Polygon.idl file to indicate that _IPolyCtlEvents is the default source interface:
[default, source] dispinterface _IPolyCtlEvents;
The source attribute indicates that the control is the source of the notifications, so it will call this interface on the container.
Now add the ClickIn and ClickOut methods to the _IPolyCtlEvents interface.
To add the ClickIn and ClickOut methods
Figure 20: Adding methods to Polygon.
Figure 21: Adding ClickIn method through Add Method Wizard.
Figure 22: Adding ClickOut method through Add Method Wizard.
Check the Polygon.idl file to see that the code was added to the _IPolyCtlEvents dispinterface. The _IPolyCtlEvents dispinterface in your Polygon.idl file should now look like this:
[id(1), helpstring("method ClickIn")] void ClickIn([in]LONG x, [in] LONG y);
[id(2), helpstring("method ClickOut")] void ClickOut([in] LONG x, [in] LONG y);
The ClickIn and ClickOut methods take the x and y coordinates of the clicked point as parameters.
Generating the Type Library
Generate the type library at this point, because the Connection Point Wizard will use it to obtain the information it needs to construct a connection point interface and a connection point container interface for your control.
To generate the type library
Rebuild your project or,
Right-click the Polygon.idl file in Solution Explorer and click Compile on the shortcut menu.
Figure 23: Compiling the IDL file.
This will create the Polygon.tlb file, which is your type library. The Polygon.tlb file is not visible from Solution Explorer, because it is a binary file and cannot be viewed or edited directly.
Figure 24: The generated TLB (type library) file.
Implementing the Connection Point Interfaces
Implement a connection point interface and a connection point container interface for your control. In COM, events are implemented through the mechanism of connection points. To receive events from a COM object, a container establishes an advisory connection to the connection point that the COM object implements. Because a COM object can have multiple connection points, the COM object also implements a connection point container interface. Through this interface, the container can determine which connection points are supported.
The interface that implements a connection point is called IConnectionPoint, and the interface that implements a connection point container is called IConnectionPointContainer.
To help implement IConnectionPoint, you will use the Implement Connection Point Wizard. This wizard generates the IConnectionPoint interface by reading your type library and implementing a function for each event that can be fired.
To use the Implement Connection Point Wizard
Figure 25: Adding Connection Point to ATL control.
Figure 26: Selecting and adding a Connection Point.
If you look at the generated _IPolyCtlEvents_CP.h file in Solution Explorer, you will see that it has a class called CProxy_IPolyCtlEvents that derives from IConnectionPointImpl. _IPolyCtlEvents_CP.h also defines the two methods Fire_ClickIn() and Fire_ClickOut(), which take the two coordinate parameters. You call these methods when you want to fire an event from your control.
The wizard also added CProxy_PolyEvents and IConnectionPointContainerImpl to your control's multiple inheritance list. The wizard also exposed IConnectionPointContainer for you by adding appropriate entries to the COM map.
You are finished implementing the code to support events. Now, add some code to fire the events at the appropriate moment. Remember, you are going to fire a ClickIn or ClickOut event when the user clicks the left mouse button in the control. To find out when the user clicks the button, add a handler for the WM_LBUTTONDOWN message.
To add a handler for the WM_LBUTTONDOWN message
Figure 27: Adding WM_LBUTTONDOWN, a Windows message handler through Properties window.
Figure 28: Selecting and adding WM_LBUTTONDOWN.
Next, modify the handler.
To modify the OnLButtonDown() method
Change the code which comprises the OnLButtonDown() method in PolyCtl.cpp (deleting any code placed by the wizard) so that it looks like this:
LRESULT CPolyCtl::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
WORD xPos = LOWORD(lParam); // horizontal position of cursor
WORD yPos = HIWORD(lParam); // vertical position of cursor
// Create a region from our list of points
hRgn = CreatePolygonRgn(&m_arrPoint, m_nSides, WINDING);
// If the clicked point is in our polygon then fire the ClickIn
// event otherwise we fire the ClickOut event
if (PtInRegion(hRgn, xPos, yPos))
// Delete the region that we created
This code makes use of the points calculated in the OnDraw() function to create a region that detects the user's mouse clicks with the call to PtInRegion().
The uMsg parameter is the ID of the Windows message being handled. This allows you to have one function that handles a range of messages. The wParam and the lParam parameters are the standard values for the message being handled. The parameter bHandled allows you to specify whether the function handled the message or not. By default, the value is set to TRUE to indicate that the function handled the message, but you can set it to FALSE. This will cause ATL to continue looking for another message handler function to send the message to.
Building and Testing the Control
Now try out your events. Build the control and start the ActiveX Control Test Container again. This time, view the event log window. To route events to the output window, click Logging from the Options menu and select Log to output window.
Figure 29: Setting the Logging Options of the ActiveX Control Test Container.
Insert the control and try clicking in the window. Note that ClickIn is fired if you click within the filled polygon, and ClickOut is fired when you click outside of it.
Figure 30: Polygon in action.
Next, you will add a property page.
Step 6: Adding a Property Page
Property pages are implemented as separate COM objects, which allow them to be shared if required. In this step, you will do the following tasks to add a property page to the control:
Creating the Property Page Resource.
Adding Code to Create and Manage the Property Page.
Adding the Property Page to the Control.
Creating the Property Page Resource
To add a property page to your control, use the ATL Add Class Wizard.
To add a Property Page
Figure 31: Adding a new class for ATL control’s property page.
Figure 32: Adding an ATL Property Page.
Figure 33: ATL Property Page Wizard, Names page.
Figure 34: ATL Property Page Wizard, Strings page.
The Title of the property page is the string that appears in the tab for that page. The Doc string is a description that a property frame uses to put in a status line or tool tip. Note that the standard property frame currently does not use this string, so you can leave it with the default contents. You will not going to generate a Help file at the moment, so delete the entry in that text box.
The following three files are created:
Contains the C++ class CPolyProp, which implements the property page.
Includes the PolyProp.h file.
The registry script that registers the property page object.
The following code changes are also made:
The new property page is added to the object entry map in Polygon.cpp.
The PolyProp class is added to the Polygon.idl file.
The new registry script file PolyProp.rgs is added to the project resource.
A dialog box template is added to the project resource for the property page.
The property strings that you specified are added to the resource string table.
Now add the fields that you want to appear on the property page.
To add fields to the Property Page
Figure 35: Modifying the Static control property.
Figure 36: Property page template, adding an Edit control.
Figure 37: Modifying the Edit control property.
This completes the process of creating the property page resource.
Adding Code to Create and Manage the Property Page
Now that you have created the property page resource, you need to write the implementation code. First, enable the CPolyProp class to set the number of sides in your object when the Apply button is pressed.
To modify the Apply function to set the number of sides
Change the Apply() function in PolyProp.h as follows:
for (UINT i = 0; i < m_nObjects; i++)
CComQIPtr<IPolyCtl, &IID_IPolyCtl> pPoly(m_ppUnk[i]);
short nSides = (short)GetDlgItemInt(IDC_SIDES);
MessageBox(OLE2T(strError), _T("Error"), MB_ICONEXCLAMATION);
m_bDirty = FALSE;
A property page can have more than one client attached to it at a time, so the Apply() function loops around and calls put_Sides() on each client with the value retrieved from the edit box. You are using the CComQIPtr class, which performs the QueryInterface() on each object to obtain the IPolyCtl interface from the IUnknown interface (stored in the m_ppUnk array).
The code now checks that setting the Sides property actually worked. If it fails, the code displays a message box displaying error details from the IErrorInfo interface. Typically, a container asks an object for the ISupportErrorInfo interface and calls InterfaceSupportsErrorInfo() first, to determine whether the object supports setting error information. You can skip this task.
CComPtr helps you by automatically handling the reference counting, so you do not need to call Release() on the interface. CComBSTR helps you with BSTR processing, so you do not have to perform the final SysFreeString() call. You also use one of the various string conversion classes, so you can convert the BSTR if necessary (this is why the USES_CONVERSION macro is at the start of the function).
You also need to set the property page's dirty flag to indicate that the Apply button should be enabled. This occurs when the user changes the value in the Sides edit box.
To handle the Apply button
Figure 38: An event Properties page.
Figure 39: Adding an event to CPolyProp class.
Next, you will modify the handler.
To modify the OnEnChangeSides() method
Add the following code in Polyprop.cpp to the OnEnChangeSides() method (deleting any code that the wizard put there):
LRESULT CPolyProp::OnEnChangeSides(WORD /*wNotifyCode*/,
WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
OnEnChangeSides() will be called when a WM_COMMAND message is sent with the EN_CHANGE notification for the IDC_SIDES control. OnEnChangeSides() then calls SetDirty() and passes TRUE to indicate the property page is now dirty and the Apply button should be enabled.
Adding the Property Page to the Control
The ATL Add Class Wizard and the ATL Property Page Wizard do not add the property page to your control for you automatically, because there could be multiple controls in your project. You will need to add an entry to the control's property map.
To add the property page
Open PolyCtl.h and add this line to the property map:
PROP_ENTRY("Sides", 1, CLSID_PolyProp)
The control's property map now looks like this:
PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
PROP_ENTRY("FillColor", DISPID_FILLCOLOR, CLSID_StockColorPage)
PROP_ENTRY("Sides", 1, CLSID_PolyProp)
// Example entries
// PROP_ENTRY("Property Description", dispid, clsid)
You could have added a PROP_PAGE macro with the CLSID of your property page, but if you use the PROP_ENTRY macro as shown, the Sides property value is also saved when the control is saved.
The three parameters to the macro are the property description, the DISPID of the property, and the CLSID of the property page that has the property on it. This is useful if, for example, you load the control into Visual Basic and set the number of Sides at design time. Because the number of Sides is saved, when you reload your Visual Basic project, the number of Sides will be restored.
Building and Testing the Control
Now build that control and insert it into ActiveX Control Test Container. In Test Container, on the Edit menu, click PolyCtl Class Object. The property page appears; click the Polygon tab. (If you clean your solution before re-building, you need to re-insert the PolyCtl Class object using Edit Insert New Control menu).
The Apply button is initially disabled. Start typing a value in the Sides box and the Apply button will become enabled. After you have finished entering the value, click the Apply button. The control display changes, and the Apply button is again disabled. Try entering an invalid value. You will see a message box containing the error description that you set from the put_Sides function.
Figure 40: Invoking Polygon’s property page in ActiveX Control Test Container.
Figure 41: Changing Sides’s value through property page.
Next, you will put your control on a Web page.
Step 7: Putting the Control on a Web Page
Your control is now finished. To see your control work in a real-world situation, put it on a Web page. An HTML file that contains the control was created when you defined your control. Open the PolyCtl.htm file from Solution Explorer and you can see your control on a Web page.
Figure 42: Polygon, an ATL control in web page.
In this step, you will script the Web page to respond to events. You will also modify the control to let Internet Explorer know that the control is safe for scripting.
Scripting the Web Page
The control does not do anything yet, so change the Web page to respond to the events that you send.
To script the Web page
Open PolyCtl.htm and select HTML view. Add the lines in bold to the HTML code that makes up the page.
Figure 43: The HTML view.
<TITLE>ATL 3.0 test page for object PolyCtl</TITLE>
<OBJECT ID="PolyCtl" <CLASSID="CLSID:4CBBC676-507F-11D0-B98B-000000000000">>
Sub PolyCtl_ClickIn(x, y)
PolyCtl.Sides = PolyCtl.Sides + 1
Sub PolyCtl_ClickOut(x, y)
PolyCtl.Sides = PolyCtl.Sides - 1
Figure 44: Adding VBScript codes to the HTML.
You have added some VBScript code that gets the Sides property from the control and increases the number of sides by one if you click inside the control. If you click outside the control, you reduce the number of sides by one.
Indicating that the Control Is Safe for Scripting
You can view the Web page with the control in Internet Explorer or, more conveniently, use the Web browser view built into Visual C++ .NET. To see your control in the Web browser view, right-click PolyCtl.htm, and click View in Browser.
Figure 45: Viewing the ATL control in Browser.
Figure 46: Polygon, an ATL control seen in Browser.
Based on your current Internet Explorer security settings, you may receive a Security Alert dialog box stating that the control may not be safe to script and could potentially do damage. You can try single click inside the green polygon. The following Security alert dialog box will be displayed.
Figure 47: Internet Explorer Security Alert dialog.
Just click Yes. Then click again inside the green polygon. Finally, click outside the polygon (inside the circle). Can you see the action?
Other typical example, if you had a control that displayed a file but also had a Delete method that deleted a file, it would be safe if you just viewed it on a page. It would be not safe to script, however, because someone could call the Delete method.
Security Note: For this tutorial, you can change your security settings in Internet Explorer to run ActiveX controls that are not marked as safe. In Control Panel, click Internet Properties and click Security to change the appropriate settings. When you have completed the tutorial, change your security settings back to their original state.
You can programmatically alert Internet Explorer that it does not need to display the Security Alert dialog box for this particular control. You can do this with the IObjectSafety interface, and ATL supplies an implementation of this interface in the class IObjectSafetyImpl. To add the interface to your control, add IObjectSafetyImpl to your list of inherited classes and add an entry for it in your COM map.
To add IObjectSafetyImpl to the control
public IObjectSafetyImpl<CPolyCtl, INTERFACESAFE_FOR_UNTRUSTED_CALLER>
Building and Testing the Control
Build the control. Once the build has finished, open PolyCtl.htm in browser view again. This time, the Web page should be displayed directly without the Safety Alert dialog box. Click inside the polygon; the number of sides increases by one. Click outside the polygon (in the circle) to reduce the number of sides. If you try to reduce the number of sides below three, you will see the error message that you set. Here is the control after one click:
Figure 48: Polygon in IE Browser without the Security Alert dialog prompt.
If you fail to build or re-build the control with the following error, close your Visual Studio/C++ .Net and delete the Debug directory in the polygon project directory. Then re-open Polygon and re-build.
cannot open file 'Debug/Polygon.dll'
This concludes the ATL tutorial.
Further reading and digging:
MSDN MFC 9.0 class library online documentation - latest version.
DCOM at MSDN.
COM+ at MSDN.
COM at MSDN.