By Ranjan Banerji.
A tutorial on how to create connection points using VC++ and
ATL
Introduction
The purpose
of this article is
to provide an introductory tutorial to implement
connection points in VC++
using ATL. For
deeper understanding of connection points I suggest you
read Code Project/COM
/ DCOM - Connection
Points And Asynchronous calls - Part I. From a
simplistic point of view I tend to treat Connection
Points as an event handling mechanism for COM.
So if you created an ActiveX
control, a button for example and you wanted the click
event to be handled by the container of the control and
not the control itself you would use connection points.
You may, however, go beyond just handling windows event
and create those of your own. I have found that creating
connection points in VC++
6.0 to be a little confusing and not very obvious. I did
these a couple of years ago while creating some controls
and then recently had to use connection points again and
just could not remember how I implemented them the first
time. This tutorial is designed to help figure out how
to create connection points using VC++.
The
Project
Some of
what is here will be a repeat of what you have seen in
other articles. I will be as brief as possible and will
assume that you know how to create an ATL
DLL and a control within it. Lets start with
creating an ATL COM
DLL using the VC++
wizards calling the project Connection. Then
lets create a composite control. You can create any
control or object. A composite control simply allows me
to provide for a good example. I am not showing how to
create these as I am assuming that you know. If you do
not know how to do so you should read some of the other
articles posted here. Name this control ConnCtl. While
creating the control select the attribute tab and then
select the Support Connection Points check box. Then
click OK.
VC++ This will
automatically take you to the resource editor to place
controls and build your composite control. For starters
lets place a button on this control. You can either
double click on the button or right click on CConCtl
in the ClassView and then select to Add a Windows
Message Handler. We add a handler to handle the clicking
of a the button. This is done no differently from how we
would add a windows message handler in a regular windows
application using VC++.
In the message handling function lets add a message box
to show that we handled the click.
LRESULT CConnCtl::OnClickedButton(WORD wNotifyCode, WORD wID,
HWND hWndCtl, BOOL& bHandled) {
::MessageBox( hWndCtl, "Button was clicked"",
"Handled in Control", MB_OK" );
return 0;
}
Build the
project and test the control. When you click on the
button you will see the message box we added. So now we
have an ActiveX
control that has a button and when you click the button
you get our message box. Fantastic! You can use this
control in any COM
container. So where do the connection points come in?
Now suppose
you wanted to trap the button click event in the
container. Or to be more precise if you wanted the
control to inform the container that the button has been
clicked. This is when you need to implement connection
points.
To do so
first go to class view and right click on _IconnCtlEvents
and add a method that will represent the
interface that your control will provide its container
with the information that the button has been clicked.
Lets call it OnControlButtonClicked
. And
let's pass a BSTR
. You could pass anything.
I guess you should have arguments that are pertinent to
button clicks. Well figure it out.
Now here
comes the tricky part. I always goof up here and tend o
forget what needs to be done to create a connection
point. You need to compile the project. I always forget
to do this and then wonder why the following steps do
not work. Now in class view right click on CConCtl
and then select "Implement Connection
Point". You will be shown a dialog box.
Check IConnCtlEvents
and click OK. Now VC++
implements connection points for you. In class view you
will see that CProxy_IConnCtlEvents< class
T>
is created and it provides Fire_OnControlButtonClicked(BSTR
someStr).
Next, to make connection points work,
that is for your code to let its container know that the
button has been clicked we add Fire_OnControlButtonClicked(BSTR
someStr)
to the Windows message handling function
created earlier.
LRESULT CConnCtl::OnClickedButton(WORD wNotifyCode,
WORD wID, HWND hWndCtl, BOOL& bHandled) {
::MessageBox( hWndCtl, "Button was clicked",
"Handled in Control", MB_OK );
Fire_OnControlButtonClicked( CComBSTR(
"This message has been fired to the container" ) );
return 0;
}
Now compile
the project and if everything goes as designed you
should get a few compile errors saying something along
the lines that IID__IConnCtlEvents
could
not be found. If that is the case make IID__IconnCtlEvents
in ConnCTL.h as shown below. I have
always wondered if this is a problem on my installation
of VC++ or a VC++
bug.
BEGIN_CONNECTION_POINT_MAP(CConnCtl)
CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink)
CONNECTION_POINT_ENTRY(DIID__IConnCtlEvents)
END_CONNECTION_POINT_MAP()
You now
have a control that handles the windows click button
event and passes that event on as defined by you (This
is very cool. You the developer get to decide how this
event is fired and what information is sent to the
container). If you place this control in a VB form you
will be able to catch the button click event fired by ConnCtl
as follows:
Private Sub ConnCtl1_OnControlButtonClicked( ByVal someStr As String )
'Now the container can do further processing once the button is clicked.
End Sub
Only
Windows Messages?
Are
connection points used just to pass on Windows Messages?
No. Connection Points are a way for a COM
object, server or control to provide an interface via
which other objects, applications etc can receive
notifications of events. For a more detailed explanation
of how connection points work please refer to Code
Project/COM / DCOM - Connection Points And Asynchronous
calls - Part I and read ATL
Internals by Brent Rector and Chris Sells.
So lets
suppose that not only did we want to let the control
container know when we click the button but also when we
clicked it for the 10th time. The process to set this up
is very simple. Once again in class view right click on _IconnCtlEvents
and select the option to add a method. Let's call
this one OnTenthClick
. Once again compile
the project. Then right-click on CConCtl
and
then select "Implement Connection Point".
Follow the steps mentioned above. Now you have the
ability to provide the container with an interface that
will notify the container when the user has clicked 10
times. Well not really. All you have done is create an
interface that has that name. Now you must count the
clicks and when you reach ten you must call Fire_OnTenthClick().
LRESULT CConnCtl::OnClickedButton(WORD wNotifyCode,
WORD wID, HWND hWndCtl, BOOL& bHandled) {
::MessageBox( hWndCtl, "Button was clicked",
"Handled in Control", MB_OK );
Fire_OnControlButtonClicked( CComBSTR(
"This message has been fired to the container" ) );
ClickCounter(); //Do stuff with how many times
//the button has been clicked
return 0;
}
void CConnCtl::ClickCounter() {
_clickCount++;
if( 10 == _clickCount ) {
Fire_OnTenthClick( CComBSTR( "Button has been clicked 10 times" ) );
}
}
Conclusion
Connection
Points are a great mechanism to handle events and are
very easy to implement in VC++
using ATL (except
for some non-intuitive steps). Please note that
connection points can be created for any COM
object and not just controls.