Use Office Applications as ActiveX Document Servers
An ActiveX document container can contain a number of Office documents, but only one document can be displayed at a time. When the Office document is activated, the menu for the Office document's server application merges into the container's menu. An ActiveX document container automatically activates the document being displayed. This varies from conventional OLE document embedding. Conventional embedding or linking requires the end-user to activate the document before the menus are merged.When a "new" worksheet is embedded in the container it is treated as an ActiveX object. No end-user action is required to merge the Excel menu with the container's menu.
Create an MFC Container Application that Automates an ActiveX Document
To generate the sample application that automates an embedded Excel worksheet, follow these steps:- Start Microsoft Visual Studio .NET. On the File menu, point to New, and then click Project. Under Project types, click Visual C++ Projects, and select the MFC Application template. Name the project AutomateEmbed. Save the project to your C:\...root folder.
- In the MFC Application Wizard, follow these steps:
- Click Application Type and then select Single Document.
- Click Compound Document Support and then select Container.
- Check Active document container.
- Click Finish to accept the remaining default settings.
- Add interfaces from the Excel object library. To do this,
follow these steps:
- On the Project menu, click Add Class.
- From the list of templates, select MFC Class From TypeLib, and then click Open. The Add Class From Typelib Wizard appears.
- In the list of available type libraries, locate Microsoft Excel version Object Library, where version is 9.0 for Excel 2000 or 10.0 for Excel 2002.
- Add the following interfaces: _ApplicationClick Finish.
_Workbook
_Worksheet
Range
Worksheets
- In the Solution AutomateEmbed area in Solution Explorer, you will see a tree view that includes the following:Source FilesExpand the Resource Files node, and double-click AutomateEmbed.RC to open it.
Header Files
Resource Files - Double-click Menu to see two menus: IDR_CNTR_INPLACE and IDR_MAINFRAME.
- Double-click IDR_CNTR_INPLACE. A graphic Menu Designer window opens, showing the File menu. Near the bottom of the File menu is a blank CommandBarButton that contains the legend Type Here. Type AutomateExcel as the caption.
- Right-click the newly-captioned CommandBarButton, and then
click Add Event Handler to run the Event Handler Wizard. In the
wizard, set the following values: The Handler description will say "Called after menu item or command button has been chosen".
Set this: To this: --------------------------------------------------------- Command Name ID_FILE_AUTOMATEEXCEL Message Type Command Function Handler Name OnFileAutomateExcel Class List CAutomateEmbedView
- Click Add and Edit to insert the skeleton handler into the code for the CAutomateEmbedView.cpp file.
- In Solution Explorer, double-click the AutomateEmbedView.cpp to open the file in the code window.
- Type or paste the following code at the top of the file:
// AutomateEmbedView.cpp : implementation of the CAutomateEmbedView class // #include "stdafx.h" #include "AutomateEmbed.h" #include "AutomateEmbedDoc.h" #include "CntrItem.h" #include "AutomateEmbedView.h" #include "CWorkbook.h" #include "CWorksheet.h" #include "CWorksheets.h" #include "CRange.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // CAutomateEmbedView
- Add a new public member function to CAutomateEmbedView in
the AutomateEmbedView.h file:
HRESULT GetDocIDispatch( LPDISPATCH* ppDisp );
- At the bottom of the AutomateEmbedView.cpp file, replace
the skeleton message handler for CAutomateEmbedView::OnFileAutomateExcel with
the following code:
// CAutomateEmbedView message handlers void CAutomateEmbedView::OnFileAutomateExcel() { // Query for the IDispatch pointer for the embedded object. // In this case it is Excel worksheet. LPDISPATCH lpDisp; HRESULT hr = GetDocIDispatch(&lpDisp); // Your own new function. // If you got an IDispatch, then use it to Automate Excel if(SUCCEEDED(hr)) { CWorkbook oBook; CWorksheets oSheets; CWorksheet oSheet; CRange oRange; // Set_Workbook oBook to use lpDisp, the IDispatch* of the // embedded/Embedded workbook. oBook.AttachDispatch(lpDisp); // Then, get the first worksheet in the workbook. oSheets = oBook.get_Worksheets(); oSheet = oSheets.get_Item(COleVariant((long)1)); // Get the Range object corresponding to Cell A1. oRange = oSheet.get_Range(COleVariant(TEXT("A1")), COleVariant(TEXT("A1"))); // Fill the range with the string "Hello World". oRange.put_Value(COleVariant((long)DISP_E_PARAMNOTFOUND, VT_ERROR), COleVariant(TEXT("Hello World"))); //NOTE: If you are automating Excel 2000 the Range.SetValue requires only one // argument. The first parameter in the Excel 2002 syntax in the line above is for the data type, // and is optional. It is not permitted by Excel 2000 or earlier versions of Excel. } // End if } // End of method /***************************************************************************** * * * GetDocIDispatch - This method determines if the document is embedded * * or linked, and acquires an IDispatch pointer to the embedded/linked * * document's server application for use in Automation. * * The document must be activated for this method to succeed. * * * * Parameters: ppDisp = The address of an LPDISPATCH to be filled with * * the IDispatch pointer of the embedded/linked document's server. * * * * Returns: S_OK if successful, otherwise an HRESULT reporting the error. * * * *****************************************************************************/ HRESULT CAutomateEmbedView::GetDocIDispatch(LPDISPATCH* ppDisp) { //HRESULT hr = S_OK; HRESULT hr = E_UNEXPECTED; // If no document then return no ppDisp. IOleLink* lpLink = NULL; IMoniker* lpMoniker = NULL; IRunningObjectTable* lpROT = NULL; IUnknown* lpUnk = NULL; if(!m_pSelection) { return hr; } // First, try to get an IOleLink interface from the document. // If successful, this indicates that the document is linked as // opposed to embedded. hr = m_pSelection->m_lpObject->QueryInterface(IID_IOleLink, (void**)&lpLink); if(SUCCEEDED(hr)) { // Get the moniker of the source document for this link. // You need this to find the ActiveX Document Server. hr = lpLink->GetSourceMoniker(&lpMoniker); if(SUCCEEDED(hr)) { // For linked documents, search the Running Object Table // for the relevant server. Do this through the // IRunningObjectTable interfce, which you can get through // an API call. hr = GetRunningObjectTable(0,&lpROT); if(SUCCEEDED(hr)) { // Search the Running Object Table for the ActiveX // Document Server of this document. You'll get back an // IUnknown pointer to the server. hr = lpROT->GetObject( lpMoniker, &lpUnk ); if(SUCCEEDED(hr)) { // Finally, get the IDispatch pointer from the // IUnknown pointer. hr = lpUnk->QueryInterface(IID_IDispatch, (void**)ppDisp); } } } } else { // If that fails, try for a direct IDispatch pointer. This // indicates that the document is embedded, not linked. hr = m_pSelection->m_lpObject->QueryInterface(IID_IDispatch, (void**)ppDisp); } // Clean up interface pointers you may have acquired along the way. if(lpLink) lpLink->Release(); if(lpMoniker) lpMoniker->Release(); if(lpROT) lpROT->Release(); if(lpUnk) lpUnk->Release(); return hr; }
- Compile and run the application. If you receive compiler errors, see the "Troubleshooting" section.
- On the Container form click Edit and then click Insert Object.
- In the Insert New Object list box, select a new Excel Worksheet. The empty Excel Worksheet appears in the container and the Excel menu merges with the menu of the container.
- From the container's File menu, click AutomateExcel. The string "Hello World" appears in cell A1.
- On the File menu, click New to clear the worksheet. Do not save the worksheet.
- In the new document, insert an existing Excel Workbook (Create from File).
- On the File menu, click AutomateExcel. "Hello World" appears in cell A1 of the worksheet.
Troubleshooting
- If you add class wrappers for the Excel object library by
using the File option in the Add Class From TypeLib Wizard, and browse the
object library, you may receive an error message. To avoid this problem, type
the full path and file name for the object library instead of browsing for the
file.
For more information, click the following article number to view the article in the Microsoft Knowledge Base:
311408 BUG: 'Read-only' warning when adding an MFC class from a type library
- If you receive the following error message when you build
your sample application, change "Variant DialogBox" in CRange.h to "Variant
_DialogBox":For more information about this error message, click the following article number to view the article in the Microsoft Knowledge Base:Warning C4003: Not enough actual parameters for macro 'DialogBoxA'.311407 BUG: MFC class wizard does not resolve naming conflicts between Windows APIs and COM interface methods
- If you receive a compiler error in Excel.tlh when you build by using Visual C++ 2005 or Visual C++ 2008:
- Run "Find in Files" (CTRL+SHIFT+F), and then search for "#import." Comment or delete every line where the EXCEL.EXE import library has been imported by using #import. There will be approximately five instances in the five header files that are created by the type library import wizard.
- Open the Stdafx.h source file. After each #include statement, add the following import statements together with the auto_rename modifier to import the Excel type library and its dependencies.
// mso.dll #import "libid:{2df8d04c-5bfa-101b-bde5-00aa0044de52}" auto_rename // vb6ext.olb #import "libid:{0002e157-0000-0000-c000-000000000046}" auto_rename // excel.exe #import "libid:{00020813-0000-0000-c000-000000000046}" auto_rename