在 MFC 应用程序中使用 MS Office






4.90/5 (21投票s)
2000 年 10 月 15 日

684406

11271
使用 ActiveX Document 模式将 MS Office 集成到您的 MFC 应用程序中。
引言
我曾经参与的一个项目,其主要特点是需要处理大量通常与 Office 类型应用程序相关的典型输入和输出表单。这些文档需要从数据存储中填充,然后由程序通过视图显示。理想情况下,文档模板能够独立处理这些不同的数据。因此,我们决定将 Microsoft Office 集成到我们的应用程序中,以利用我们正在寻求的内置功能。
为此,我向您介绍本文,其中详细介绍了将 Microsoft Office 集成到您的 Visual C++/MFC 应用程序所需的具体步骤。
在此我还需要指出,在使用 Office 的 ActiveX Document 模式时,我遇到的一个持续存在的问题是 Office 本身的不稳定性。因此,在您阅读本文(以及演示)时,可能会看到一些效率不高的代码。一个我仍然遇到的问题是,即使我的应用程序已结束,Microsoft Office 应用程序仍会留在内存中,只能通过任务管理器才能清除。如果您解决了这个问题,请告诉我,以便我更新本文和我自己的代码。
集成 MS Office
我们开始吧。
- 通过 Visual C++ AppWizard,生成一个名为 XOffice 的新 MDI 应用程序。在第三步,需要选择“容器”单选按钮以及“Active Document 容器”复选框。
- 此外,此应用程序应为一个自动化服务器。我借鉴了 Nick Hodapp 在其文章《使用 ATL 自动化 MFC 应用程序》中的方法,该文章可在 CodeGuru 上找到。请熟悉此示例并执行程序转换为自动化服务器的所有步骤。
- 现在,我们将着手集成 MS Office。将包含以下内容的 Office.h 文件包含到项目中。
// Office.h #define Uses_MSO2000 #ifdef Uses_MSO2000 // for MS Office 2000 #import "C:\Program Files\Microsoft Office\Office\MSO9.DLL" #import "C:\Program Files\Common Files\Microsoft Shared\VBA\VBA6\VBE6EXT.OLB" #import "C:\Program Files\Microsoft Office\Office\MSWORD9.OLB" \ rename("ExitWindows","_ExitWindows") #import "C:\Program Files\Microsoft Office\Office\EXCEL9.OLB" \ rename("DialogBox","_DialogBox") \ rename("RGB","_RGB") \ exclude("IFont","IPicture") #import "C:\Program Files\Common Files\Microsoft Shared\DAO\DAO360.DLL" \ rename("EOF","EndOfFile") rename("BOF","BegOfFile") #import "C:\Program Files\Microsoft Office\Office\MSACC9.OLB" #else // for MS Office 97 #import "C:\Program Files\Microsoft Office\Office\MSO97.DLL" #import "C:\Program Files\Common Files\Microsoft Shared\VBA\VBEEXT1.OLB" #import "C:\Program Files\Microsoft Office\Office\MSWORD8.OLB" \ rename("ExitWindows","_ExitWindows") #import "C:\Program Files\Microsoft Office\Office\EXCEL8.OLB" \ rename("DialogBox","_DialogBox") \ rename("RGB","_RGB") \ exclude("IFont","IPicture") #import "C:\Program Files\Common Files\Microsoft Shared\DAO\DAO350.DLL" \ rename("EOF","EndOfFile") rename("BOF","BegOfFile") #import "C:\Program Files\Microsoft Office\Office\MSACC8.OLB" #endif
- 我们将创建一个新的表单,后续将需要用到它。从“插入”菜单中选择“新建表单”选项。
在“名称”字段中输入 CFormDemo。在“文档”字段旁边的“新建”按钮处按下,然后在新出现的表单中点击“确定”按钮。再次点击“确定”按钮。
我们将在新表单上放置三个编辑框和两个按钮。在 ClassWizard 中,我们将编辑框绑定到变量(分别为 CString m_str、double m_double、long m_long)。
- 我们将为按钮创建以下类型的处理程序。
// FormDemo.cpp void NewXOfficeDoc(LPCTSTR,LPCTSTR,double,long); void CFormDemo::OnButton1() { UpdateData(); NewXOfficeDoc("XOffice.doc",m_str,m_double,m_long); } void CFormDemo::OnButton2() { UpdateData(); NewXOfficeDoc("XOffice.xls",m_str,m_double,m_long); }
我们将以下代码添加到 XOfficeDoc.cpp 文件的开头。// XOfficeDoc.cpp static CString g_template; static CString g_str; static double g_double; static long g_long; void NewXOfficeDoc(LPCTSTR aTemplate,LPCTSTR aStr, double aDouble,long aLong) { CString str; POSITION pos = AfxGetApp()->GetFirstDocTemplatePosition(); while (pos != NULL) { CDocTemplate *temp = AfxGetApp()->GetNextDocTemplate(pos); if (temp->GetDocString(str,CDocTemplate::docName) { str == _T("XOffice")) { g_template = aTemplate; g_str = aStr; g_double = aDouble; g_long = aLong; temp->OpenDocumentFile(NULL); return; } } }
现在,我们就可以通过按下表单上的按钮来创建 MDI 文档了。
- MFC 类 ColeDocObjectItem 提供了对 ActiveX 文档的支持。该类已经可以做很多事情,但我们还需要教它加载我们设置的文档。
对
CXOfficeCntrItem
类进行以下更改。// CntrItem.h class CXOfficeCntrItem : public ColeDocObjectItem { ... public: CXOfficeCntrItem(CXOfficeDoc* pContainer,LPCTSTR); bool m_isCreate; bool CreateItem(LPCTSTR); ... };
// CntrItem.cpp CXOfficeCntrItem::CXOfficeCntrItem(CXOfficeDoc* pContainer,LPCTSTR templ) : COleDocObjectItem(pContainer), m_isCreate(false) { CreateItem(templ); } bool CXOfficeCntrItem::CreateItem(LPCTSTR templ) { USES_CONVERSION; // get storage for the object via virtual function call m_dwItemNumber = GetNewItemNumber(); GetItemStorage(); // add AfxOleInit(); in CXOfficeApp::InitInstance AfxOleGetMessageFilter()->EnableNotRespondingDialog(FALSE); // attempt to create the object LPOLECLIENTSITE lpClientSite = GetClientSite(); SCODE sc = ::OleCreateFromFile(CLSID_NULL, T2COLE(templ), IID_IUnknown, OLERENDER_DRAW, NULL, lpClientSite, m_lpStorage, (LPVOID*)&m_lpObject); return m_isCreate = FinishCreate(sc) == TRUE; }
- 最后要做的是对
CXOfficeDoc
和CXOfficeView
类进行更改,以显示 ActiveX 文档。// XOfficeDoc.h ... class CXOfficeCntrItem; class CXOfficeDoc : public COleDocument, ... { ... public: CXOfficeCntrItem *m_ctrl; CString m_template; CString m_str; double m_double; long m_long; bool LoadTemplate(); ... };
// XOfficeDoc.cpp CXOfficeDoc::CXOfficeDoc() : m_ctrl(0) { EnableCompoundFile(); } BOOL CXOfficeDoc::OnNewDocument() { if (!COleDocument::OnNewDocument()) return FALSE; m_template = g_template; m_str = g_str; m_double = g_double; m_long = g_long; return LoadTemplate(); } bool CXOfficeDoc::LoadTemplate() { char path [_MAX_PATH]; char drive[_MAX_DRIVE]; char dir [_MAX_DIR]; char fname[_MAX_FNAME]; char ext [_MAX_EXT]; ::GetModuleFileName(NULL,path,sizeof(path)); _splitpath(path, drive,dir,0, 0); _splitpath(g_template,0, 0, fname,ext); _makepath (path, drive,dir,fname,ext); { CWaitCursor cw; m_ctrl = new CXOfficeCntrItem(this,path); } if (m_ctrl == 0 || m_ctrl->m_isCreate == false) { CString str = "Can not open the doc:\n"; str += path; AfxMessageBox(str,MB_ICONSTOP); return false; } return true; }
// XOfficeView.cpp void CXOfficeView::OnInitialUpdate() { CView::OnInitialUpdate(); CWaitCursor wc; m_pSelection = GetDocument()->m_ctrl; ... }
现在,我们就可以自动加载 ActiveX 文档了。这已经相当不错了。
请注意,**我们的**文档的保存和加载过程也正常工作,从而保存了初始模板文档的内容。事实上,我们完全不需要它。唯一不起作用的是打印预览。我对此未能理解,因此,如果有人能做到,我将非常感激能先了解它。
- 现在,我们将教会
CXOfficeDoc
类**仅**存储和加载**我们的**数据,而不询问有关 ActiveX 文档本身任何数据更改的问题。为此,我们将添加OnOpenDocument
和SaveModified
方法,并通过 ClassWizard 进行以下更改。// XOfficeDoc.cpp void CXOfficeDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { ar << m_template << m_str << m_double << m_long; } else { ar >> m_template >> m_str >> m_double >> m_long; } // COleDocument::Serialize(ar); } BOOL CXOfficeDoc::OnOpenDocument(LPCTSTR lpszPathName) { if (!COleDocument::OnOpenDocument(lpszPathName)) return FALSE; return LoadTemplate(); } BOOL CXOfficeDoc::SaveModified() { return CDocument::SaveModified(); }
// CntrItem.cpp void CXOfficeCntrItem::OnChange(OLE_NOTIFICATION nCode, DWORD dwParam) { BOOL modified = m_pDocument->IsModified(); COleDocObjectItem::OnChange(nCode, dwParam); m_pDocument->SetModifiedFlag(modified); GetDocument()->UpdateAllViews(NULL); }
- 下一步是获取 ActiveX 文档的
IDispatch
接口。我们将对CXOfficeCntrItem
类进行以下更改。// CntrItem.h ... #include <comdef.h> <comdef.h> ... class CXOfficeCntrItem : public COleDocObjectItem { public: ... int m_who; // 0 - ?, 1 - Word, 2 - Excel IDispatchPtr m_disp; LPDISPATCH GetIDispatch(); void AttachDisp (); void ActivateDisp(); void CloseDisp (); ... };
我不想展示相应方法的文本,因为这会占用太多空间。它们可以在程序的源代码中找到。还需要对
CXOfficeDoc
和CXOfficeView
类进行以下更改。// CXOfficeView.cpp void CXOfficeView::OnInitialUpdate() { ... m_pSelection = GetDocument()->m_ctrl; m_pSelection->AttachDisp(); //Active documents should always be activated ... m_pSelection->ActivateDisp(); }
// CXOfficeDoc.cpp void CXOfficeDoc::OnCloseDocument() { if (m_ctrl) m_ctrl->CloseDisp(); COleDocument::OnCloseDocument(); }
- 现在是时候让我们的自动化服务器变得智能了。为此,我们将为
IApplication
接口定义ActiveDocument
和IsActiveDocument
属性,并为IDocument
接口定义PStr
、PDouble
和PLong
属性。使用 ATL Wizard 可以轻松完成。
- 工作空间 -> 类视图 -> IApplication -> 右键 -> 添加属性
- 工作空间 -> 类视图 -> IDocument -> 右键 -> 添加属性
方法的实现可以在源代码中找到。
- XOffice.doc 和 XOffice.xls 文件分别是 Word 和 Excel 文档的示例。在 Word 中,文档字段的初始化发生在
Document_New
事件中,该事件显然是从程序中调用的。字段的值被赋给命名书签。在 Excel 中,单元格的文档初始化在Workbook_Activate
事件中完成。这不太方便,但我尝试了多种变体,并认为(Workbook_Activate
)是最佳且最稳定的。如前所述,直接调用 VBA 中的宏即使在程序结束时也会使 Excel 保持在内存中。