65.9K
CodeProject 正在变化。 阅读更多。
Home

在 MFC 应用程序中使用 MS Office

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (21投票s)

2000 年 10 月 15 日

viewsIcon

684406

downloadIcon

11271

使用 ActiveX Document 模式将 MS Office 集成到您的 MFC 应用程序中。

  • 下载源文件 - 46 Kb
  • 下载演示项目 - 36 Kb
  • Sample Image - xoffice.jpg

    引言

    我曾经参与的一个项目,其主要特点是需要处理大量通常与 Office 类型应用程序相关的典型输入和输出表单。这些文档需要从数据存储中填充,然后由程序通过视图显示。理想情况下,文档模板能够独立处理这些不同的数据。因此,我们决定将 Microsoft Office 集成到我们的应用程序中,以利用我们正在寻求的内置功能。

    为此,我向您介绍本文,其中详细介绍了将 Microsoft Office 集成到您的 Visual C++/MFC 应用程序所需的具体步骤。

    在此我还需要指出,在使用 Office 的 ActiveX Document 模式时,我遇到的一个持续存在的问题是 Office 本身的不稳定性。因此,在您阅读本文(以及演示)时,可能会看到一些效率不高的代码。一个我仍然遇到的问题是,即使我的应用程序已结束,Microsoft Office 应用程序仍会留在内存中,只能通过任务管理器才能清除。如果您解决了这个问题,请告诉我,以便我更新本文和我自己的代码。

    集成 MS Office

    我们开始吧。

    1. 通过 Visual C++ AppWizard,生成一个名为 XOffice 的新 MDI 应用程序。在第三步,需要选择“容器”单选按钮以及“Active Document 容器”复选框。
    2. 此外,此应用程序应为一个自动化服务器。我借鉴了 Nick Hodapp 在其文章《使用 ATL 自动化 MFC 应用程序》中的方法,该文章可在 CodeGuru 上找到。请熟悉此示例并执行程序转换为自动化服务器的所有步骤。
    3. 现在,我们将着手集成 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
      
    4. 我们将创建一个新的表单,后续将需要用到它。从“插入”菜单中选择“新建表单”选项。

      在“名称”字段中输入 CFormDemo。在“文档”字段旁边的“新建”按钮处按下,然后在新出现的表单中点击“确定”按钮。再次点击“确定”按钮。

      我们将在新表单上放置三个编辑框和两个按钮。在 ClassWizard 中,我们将编辑框绑定到变量(分别为 CString m_str、double m_double、long m_long)。

    5. 我们将为按钮创建以下类型的处理程序。
      // 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 文档了。

    6. 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;
      }
      
    7. 最后要做的是对 CXOfficeDocCXOfficeView 类进行更改,以显示 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 文档了。这已经相当不错了。

      请注意,**我们的**文档的保存和加载过程也正常工作,从而保存了初始模板文档的内容。事实上,我们完全不需要它。唯一不起作用的是打印预览。我对此未能理解,因此,如果有人能做到,我将非常感激能先了解它。

    8. 现在,我们将教会 CXOfficeDoc 类**仅**存储和加载**我们的**数据,而不询问有关 ActiveX 文档本身任何数据更改的问题。为此,我们将添加 OnOpenDocumentSaveModified 方法,并通过 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);
      }
      
    9. 下一步是获取 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   ();
      ...
      };
      

      我不想展示相应方法的文本,因为这会占用太多空间。它们可以在程序的源代码中找到。还需要对 CXOfficeDocCXOfficeView 类进行以下更改。

      // 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();
      }
      
    10. 现在是时候让我们的自动化服务器变得智能了。为此,我们将为 IApplication 接口定义 ActiveDocumentIsActiveDocument 属性,并为 IDocument 接口定义 PStrPDoublePLong 属性。

      使用 ATL Wizard 可以轻松完成。

      • 工作空间 -> 类视图 -> IApplication -> 右键 -> 添加属性
      • 工作空间 -> 类视图 -> IDocument -> 右键 -> 添加属性

      方法的实现可以在源代码中找到。

    11. XOffice.doc 和 XOffice.xls 文件分别是 Word 和 Excel 文档的示例。在 Word 中,文档字段的初始化发生在 Document_New 事件中,该事件显然是从程序中调用的。字段的值被赋给命名书签。在 Excel 中,单元格的文档初始化在 Workbook_Activate 事件中完成。这不太方便,但我尝试了多种变体,并认为(Workbook_Activate)是最佳且最稳定的。如前所述,直接调用 VBA 中的宏即使在程序结束时也会使 Excel 保持在内存中。

    © . All rights reserved.