HOW TO: Handle Events for Excel by Using Visual C++ .NET (309301)
The information in this article applies to:
- Microsoft Excel 2000
- Microsoft Excel 2002
- Microsoft Office Excel 2003
- Microsoft Visual C++ .NET (2003)
- Microsoft Visual C++ .NET (2002)
This article was previously published under Q309301 For a Microsoft C# .NET version of this article, see
302815. For a Microsoft Visual
Basic .NET version of this article, see
302814. IN THIS TASKNote Microsoft Visual C++ .NET (2002) supports both the managed code
model that is provided by the Microsoft .NET Framework and the unmanaged native
Microsoft Windows code model. The information in this article applies only to
unmanaged Visual C++ code. SUMMARY This step-by-step article describes how to handle Excel
events from an Automation client that is developed with Visual C++ .NET and
Microsoft Foundation Classes (MFC).
back to the top
Overview of Event Handling Visual C++ .NET uses the traditional IConnectionPoint Component Object Model (COM) interface to handle COM events. When
you use IConnectionPoint, you must have a class that implements the sink interface for the
connection point to which you want to attach. This sample provides the CAppEventListener class to fulfill this requirement.
back to the top
Create the Visual C++ .NET Automation Client- Follow the steps in the "Create an Automation Client"
section of the following Microsoft Knowledge Base article to create a basic
Automation client:
307473 HOWTO: Use a Type Library for Office Automation from Visual C++ .NET
In step 1, type
MFCExcelEvents for the name of the project.
In step 3, change the ID of the first button to ID_STARTSINK and the caption to Start Event Sink. Add a second button, and then change the ID of the second button
to ID_STOPSINK and the caption to Stop Event Sink.
In step 4, select the following Excel interfaces:
- _Application
- _Workbook
- Range
- Workbooks
- On the Project menu, click Add Class. Select the Generic C++ class in the list of templates, and then click Open.
- In the Generic C++ Class Wizard dialog box, type CAppEventListener for the
class name, type IDispatch for the base class, and then
click Finish.
- Replace all of the code in Appeventlistener.h with the
following:
#pragma once
#include "oaidl.h"
#include "CApplication.h"
#include "CWorkbooks.h"
#include "CWorkbook.h"
#include "CRange.h"
//00024413-0000-0000-C000-000000000046
const IID IID_ApplicationEvents =
{0x00024413,0x0000,0x0000,{0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
class CAppEventListener : public IDispatch
{
protected:
int m_refCount;
IConnectionPoint* m_pConnectionPoint;
DWORD m_dwConnection;
public:
//Constructor.
CAppEventListener();
//Destructor.
~CAppEventListener();
/***** IUnknown Methods *****/
STDMETHODIMP QueryInterface(REFIID riid, void ** ppvObj);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
/***** IDispatch Methods *****/
STDMETHODIMP GetTypeInfoCount(UINT *iTInfo);
STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid,
ITypeInfo **ppTInfo);
STDMETHODIMP GetIDsOfNames(REFIID riid,
OLECHAR **rgszNames,
UINT cNames, LCID lcid,
DISPID *rgDispId);
STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS* pDispParams,
VARIANT* pVarResult, EXCEPINFO* pExcepInfo,
UINT* puArgErr);
/**** Click Handler *****/
STDMETHODIMP HandleBeforeWorkbookClose( IDispatch* xlBook,
VARIANT_BOOL* fCancel );
STDMETHODIMP HandleSheetChange( IDispatch* xlSheet, IDispatch* xlRange);
/**** Attach/Detach from event source *****/
STDMETHODIMP AttachToSource( IUnknown* pEventSource );
STDMETHODIMP DetachFromSource();
};
Note The value for the IID_IApplicationEvents variable is derived from the Excel object library. For
information about how to verify the universally unique identifier (UUID) for a
set of Excel events, see the "Excel Application Events" section. - Replace all of the code in Appeventlistener.cpp with the
following code:
#include "stdafx.h"
#include "AppEventListener.h"
//Constructor.
CAppEventListener::CAppEventListener() :
m_pConnectionPoint(NULL),
m_dwConnection(0)
{
m_refCount = 0;
}
//Destructor.
CAppEventListener::~CAppEventListener()
{}
/******************************************************************************
* IUnknown Interfaces -- All COM objects must implement, either
* directly or indirectly, the IUnknown interface.
******************************************************************************/
/******************************************************************************
* QueryInterface -- Determines if this component supports the
* requested interface, places a pointer to that interface in ppvObj if it is
* available, and returns S_OK. If not, sets ppvObj to NULL and returns
* E_NOINTERFACE.
******************************************************************************/
STDMETHODIMP CAppEventListener::QueryInterface(REFIID riid, void ** ppvObj)
{
if (riid == IID_IUnknown){
*ppvObj = static_cast<IUnknown*>(this);
}
else if (riid == IID_IDispatch){
*ppvObj = static_cast<IDispatch*>(this);
}
else if (riid == IID_ApplicationEvents){
*ppvObj = static_cast<IDispatch*>(this);
}
else{
*ppvObj = NULL;
return E_NOINTERFACE;
}
static_cast<IUnknown*>(*ppvObj)->AddRef();
return S_OK;
}
/******************************************************************************
* AddRef() -- In order to allow an object to delete itself when
* it is no longer needed, it is necessary to maintain a count of all
* references to this object. When a new reference is created, this function
* increments the count.
******************************************************************************/
STDMETHODIMP_(ULONG) CAppEventListener::AddRef()
{
return ++m_refCount;
}
/******************************************************************************
* Release() -- When a reference to this object is removed, this
* function decrements the reference count. If the reference count is 0, then
* this function deletes this object and returns 0.
******************************************************************************/
STDMETHODIMP_(ULONG) CAppEventListener::Release()
{
m_refCount--;
if (m_refCount == 0)
{
delete this;
return 0;
}
return m_refCount;
}
/******************************************************************************
* IDispatch Interface -- This interface allows this class to be used as an
* automation server, allowing its functions to be called by other COM
* objects.
******************************************************************************/
/******************************************************************************
* GetTypeInfoCount -- This function determines if the class supports type
* information interfaces or not. It places 1 in iTInfo if the class supports
* type information and 0 if it does not.
******************************************************************************/
STDMETHODIMP CAppEventListener::GetTypeInfoCount(UINT *iTInfo)
{
*iTInfo = 0;
return S_OK;
}
/******************************************************************************
* GetTypeInfo -- Returns the type information for the class. For classes
* that do not support type information, this function returns E_NOTIMPL;
******************************************************************************/
STDMETHODIMP CAppEventListener::GetTypeInfo(UINT iTInfo, LCID lcid,
ITypeInfo **ppTInfo)
{
return E_NOTIMPL;
}
/******************************************************************************
* GetIDsOfNames -- Takes an array of strings and returns an array of DISPIDs
* that correspond to the methods or properties indicated. If the name is not
* recognized, returns DISP_E_UNKNOWNNAME.
******************************************************************************/
STDMETHODIMP CAppEventListener::GetIDsOfNames(REFIID riid,
OLECHAR **rgszNames,
UINT cNames, LCID lcid,
DISPID *rgDispId)
{
return E_NOTIMPL;
}
/******************************************************************************
* Invoke -- Takes a dispid and uses it to call another of this class's
* methods. Returns S_OK if the call was successful.
******************************************************************************/
STDMETHODIMP CAppEventListener::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS* pDispParams,
VARIANT* pVarResult, EXCEPINFO* pExcepInfo,
UINT* puArgErr)
{
switch(dispIdMember){
case 0x00622:
if(pDispParams->cArgs !=2)
return E_INVALIDARG;
else
{
if(pDispParams->rgvarg[1].vt & VT_BYREF)
{
HandleBeforeWorkbookClose( // Call the function.
*(pDispParams->rgvarg[1].ppdispVal),
pDispParams->rgvarg[0].pboolVal);
}
else
{
HandleBeforeWorkbookClose( // Call the function.
(pDispParams->rgvarg[1].pdispVal),
pDispParams->rgvarg[0].pboolVal);
}
}
case 0x0061c:
{
if(pDispParams->rgvarg[1].vt & VT_BYREF)
{
HandleSheetChange( // Call the function.
*(pDispParams->rgvarg[1].ppdispVal),
*(pDispParams->rgvarg[0].ppdispVal));
}
else
{
HandleSheetChange( // Call the function.
pDispParams->rgvarg[1].pdispVal,
pDispParams->rgvarg[0].pdispVal);
}
}
break;
}
return S_OK;
}
/******************************************************************************
* HandleBeforeWorkbookClose -- This method processes the BeforeWorkbookClose
* event for the application attached to this event handler.
******************************************************************************/
STDMETHODIMP CAppEventListener::HandleBeforeWorkbookClose( IDispatch* xlBook,
VARIANT_BOOL* fCancel )
{
OutputDebugString("HandleBeforeWorkbookClose\n");
HRESULT hr = S_OK;
return hr;
}
/******************************************************************************
* HandleSheetChange -- This method processes the SheetChange event for the
* application attached to this event handler.
******************************************************************************/
STDMETHODIMP CAppEventListener::HandleSheetChange( IDispatch* xlSheet,
IDispatch* xlRange)
{
OutputDebugString("HandleSheetChange\n");
HRESULT hr = S_OK;
return hr;
}
/******************************************************************************
* AttachToSource -- This method attaches to an event source.
******************************************************************************/
STDMETHODIMP CAppEventListener::AttachToSource( IUnknown* pEventSource )
{
HRESULT hr = S_OK;
IConnectionPointContainer* pCPC = NULL;
hr = pEventSource->QueryInterface( IID_IConnectionPointContainer,
(void**)&pCPC );
if (SUCCEEDED(hr)){
hr = pCPC->FindConnectionPoint( IID_ApplicationEvents,
&m_pConnectionPoint );
if (SUCCEEDED(hr)){
hr = m_pConnectionPoint->Advise( this, &m_dwConnection );
}
pCPC->Release();
}
return hr;
}
/******************************************************************************
* DetachFromSource -- This method detaches from an event source.
******************************************************************************/
STDMETHODIMP CAppEventListener::DetachFromSource()
{
HRESULT hr = S_OK;
if (m_pConnectionPoint != NULL){
m_pConnectionPoint->Unadvise( m_dwConnection );
m_pConnectionPoint = NULL;
}
return hr;
}
- Double-click the ID_STARTSINK control on your dialog box and add the following code to CMFCExcelEventsDlg::OnBnClickedStartsink:
COleVariant
covTrue((short)TRUE),
covFalse((short)FALSE),
covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
void CMFCExcelEventsDlg::OnBnClickedStartsink()
{
CWorkbooks xlBooks;
CWorkbook xlBook;
try{
//Start Excel and create a new workbook.
if (m_ExcelApplication.CreateDispatch( "Excel.Application" ) == 0)
{
AfxMessageBox( "Can't launch Excel!" );
return;
}
xlBooks = m_ExcelApplication.get_Workbooks();
xlBook = xlBooks.Add( covOptional );
//Add an event handler for the Application object.
m_pAppEventListener = new CAppEventListener();
m_pAppEventListener->AddRef();
m_pAppEventListener->AttachToSource( m_ExcelApplication.m_lpDispatch );
//Make Excel visible and give the user control.
m_ExcelApplication.put_Visible(true);
m_ExcelApplication.put_UserControl(true);
}
catch(COleException *e)
{
e->ReportError();
e->Delete();
return;
}
}
- Double-click the ID_STOPSINK control on your dialog box and add the following code to CMFCExcelEventsDlg::OnBnClickedStopsink:
void CMFCExcelEventsDlg::OnBnClickedStopsink()
{
try{
if (m_pAppEventListener != NULL){
m_pAppEventListener->DetachFromSource();
m_pAppEventListener->Release();
m_pAppEventListener = NULL;
}
m_ExcelApplication.Quit();
m_ExcelApplication.ReleaseDispatch();
}
catch(COleException *e)
{
e->ReportError();
e->Delete();
return;
}
}
- Append the following code to CMFCExcelEventsDlg::OnInitDialog in Mfcexceleventsdlg.cpp:
m_pAppEventListener = NULL;
- Add the following #include to Mfcexceleventsdlg.h:
#include "AppEventListener.h"
- In the Mfcexceleventsdlg.h file, add the following to the
list of protected variables in the //Implementation section of CMFCExcelEventsDlg:
CApplication m_ExcelApplication;
CAppEventListener* m_pAppEventListener;
back to the top
Test the Sample- Press CTRL+ALT+O to display the Output window.
- Press F5 to build and run the program.
Note If you receive a compile error, see the "Troubleshooting"
section. - On the form, click Use Start Event Sink. The program starts Excel and creates a new workbook.
- Add any data to cells on the worksheets. Examine the Output
window in Microsoft Visual Studio to verify that the event handlers are called.
- Click Close on the File menu to close the workbook. Click No to close without saving changes. Verify that the WorkbookBeforeClose event was received in the Output window in Visual
Studio.
- On the form, click Stop Event Sink. This disconnects the CAppEventListener event sink and closes Excel.
- Close the form to end the debug session.
back to the top
Troubleshooting
back to the top
Excel Application Events The following steps describe how to verify the universally unique
identifier (UUID) for a set of Excel events.
- On the Tools menu in Visual Studio .NET, click OLE/COM Object Viewer.
- Expand the Type Libraries node.
- Double-click Microsoft Excel your version Object Library in the type libraries list.
- When the ITypeLib Viewer opens with the Excel object
library displayed, expand the node for coClass Application. Under the coClass Application node, you see ApplicationEvents.
- Double-click ApplicationEvents.
- In the right pane of the ITypeLib Viewer, locate the
following UUID:
uuid(00024413-0000-0000-C000-000000000046) This UUID corresponds to the IID_ApplicationEvents variable that you declared in Appeventlistener.h.
back to the top
REFERENCES For more information, see the following Microsoft Developer
Network (MSDN) Web site: For more information about creating sink interfaces, and
simplifying the connection process, see the following Knowledge Base article:
181845 HOWTO: Create a Sink Interface in MFC-Based COM Client
For more information and a general example of
connection points, see the Connpts.exe sample that is described in the
following Knowledge Base article:
152087 SAMPLE: Connpts.exe Implements Connection Points in MFC Apps
back to the top
Modification Type: | Major | Last Reviewed: | 1/19/2006 |
---|
Keywords: | kbOfficeDev kbAutomation kbHOWTOmaster KB309301 kbAudDeveloper |
---|
|