Control Event Handling  
 

In languages like Visual Basic, using the events for a control simply involves adding code for the desired event. Many of the details, such as connecting the control's event interface to the container, are largely invisible to the programmer. However, when using a control in Visual C++, some extra work does need to be done. This section will cover two basic methods; one is specific to CWnd derived controls which are placed in a dialog, the other approach uses a CCmdTarget derived class to handle event notifications.

To create an event handler for a control that has been placed on a dialog form, open the form in the resource editor, right click the control and select Events. This will open a dialog that lists the available events for the control. Selecting one of the events adds the event to the dialog class with a name like OnProgressFtpClient1. In the implementation for the dialog class, a section of code will be added that looks like this:

BEGIN_EVENTSINK_MAP(CExampleDlg, CDialog)
  //{{AFX_EVENTSINK_MAP(CExampleDlg)
  ON_EVENT(CExampleDlg, IDC_FTPCLIENT1, 4, OnProgressFtpClient1,
           VTS_VARIANT VTS_VARIANT VTS_VARIANT)
  //}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

This is the event sink map which is used to map a function in the dialog class to the control's event dispatch interface. The ON_EVENT macro defines the event sink with the dialog class name, the control ID, the dispatch ID for the event, the event handler function and then the parameters that are passed to the event. The three VTS_VARIANT macros specify that the event handler has three VARIANT arguments. All of this code is automatically generated with one ON_EVENT for each control event that was selected. Of the two approaches, this is the simplest but it depends on the fact that the control has been placed on a dialog.

A more general purpose way to implement event handling is to derive a class from the CCmdTarget class which will act as the event sink for the control. First, edit the StdAfx.h header file to include afxctl.h. Next, create a new class for the project called CEventSink. It should be derived from CCmdTarget with Automation support enabled. However, do not make it creatable by type ID. A dialog may be displayed that it was unable to edit the object definition (ODL) file for the product. Since your project may not have one, this is only a warning and can be ignored.

Open the EventSink.cpp implementation file and look towards the end of the file where there is a section that looks something like this:

BEGIN_INTERFACE_MAP(CEventSink, CCmdTarget)
    INTERFACE_PART(CEventSink, IID_IEventSink, Dispatch)
END_INTERFACE_MAP()

This maps the CEventSink class to the event interface. This needs to be changed so that it is mapped to the control's IFtpClientEvents interface. Change the second argument of the INTERFACE_PART macro to the value DIID__IFtpClientEvents. This section should now look like:

BEGIN_INTERFACE_MAP(CEventSink, CCmdTarget)
    INTERFACE_PART(CEventSink, DIID__IFtpClientEvents, Dispatch)
END_INTERFACE_MAP()

Next, decide what event handlers should be implemented for the control. This example will implement all of them, but it isn't necessary if they aren't actually going to be used by the application. The event handlers will be protected member functions of the CEventSink class. They will be defined in EventSink.h and implemented in EventSink.cpp:

void OnCancel();
void OnCommand(VARIANT& varResultCode, VARIANT& varResultString);
void OnError(VARIANT& varError, VARIANT& varDescription);
void OnProgress(VARIANT& varFileName, VARIANT& varFileSize, VARIANT& varBytesCopied,
                VARIANT& varPercent);
void OnTimeout();

Once the event handler functions have been implemented, they need to be added to the dispatch map. Look for the BEGIN_DISPATCH_MAP section in EventSink.cpp and add the definitions for the events:

DISP_FUNCTION_ID(CEventSink,"OnCancel",1,OnCancel,VT_EMPTY,VTS_NONE)
DISP_FUNCTION_ID(CEventSink,"OnCommand",2,OnCommand,
                 VT_EMPTY,VTS_VARIANT VTS_VARIANT)
DISP_FUNCTION_ID(CEventSink,"OnError",3,OnError,
                 VT_EMPTY,VTS_VARIANT VTS_VARIANT)
DISP_FUNCTION_ID(CEventSink,"OnProgress",4,OnProgress,
                 VT_EMPTY,VTS_VARIANT VTS_VARIANT VTS_VARIANT VTS_VARIANT)
DISP_FUNCTION_ID(CEventSink,"OnTimeout",5,OnTimeout,VT_EMPTY,VTS_NONE)

These declarations are similar to those used with the ON_EVENT macros in the previous example. The VT_EMPTY type specifies that the event handler does not return a value. VTS_VARIANT specifies a VARIANT argument. VTS_NONE specifies that the event doesn't have any arguments.

With the event handlers implemented, the next step is to connect them to the control. Include the EventSink.h header file in the module where the control is being used and create two new member variables for the class:

DWORD m_dwEventSink;
CEventSink* m_pEventSink;

Next, an instance of the CEventSink class needs to be created and then the sink dispatch interface needs to be connected to the control using the AfxConnectionAdvise function:

// Create an instance of the event sink class
m_pEventSink = new CEventSink();

// Get a pointer to the sink IDispatch interface
LPUNKNOWN pUnknownSink = m_pEventSink->GetIDispatch(FALSE);

// Connect the event source to the sink
AfxConnectionAdvise(m_pIFtpClient,
                    DIID__IFtpClientEvents,
                    pUnknownSink,
                    FALSE,
                    &m_dwEventSink);

The last thing that needs to be done is to disconnect the event sink from the control when it is no longer needed. This is done by calling AfxConnectionUnadvise, typically right before an instance of the control is deleted:

if (m_pEventSink)
{
    LPUNKNOWN pUnknownSink = m_pEventSink->GetIDispatch(FALSE);

    AfxConnectionUnadvise(m_pIFtpClient,
                          DIID__IFtpClientEvents,
                          pUnknownSink,
                          FALSE,
                          m_dwEventSink);

    delete m_pEventSink;
    m_pEventSink = NULL;
    m_dwEventSink = 0;
}

With this code, the application is now wired to receive event notifications from the control. Keep in mind that because the instance of the CEventSink class was created on the heap, failure to destroy the sink will cause a memory leak in the application.