15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


Использование MS Office в MFC приложении


Автор: Igor Tkachev.

Введение

Однажды я был занят проектом, особенностью которого было наличие большого количесва форм ввода/вывода связанными с приложениями типа MS Office. Документы должны были заполняться данными различных типов, которые программа отображала через представления. Очень желательно было, чтобы шаблон документа понимал эти разнородные данные. Следовательно мы приняли решение интегрировать Microsoft Office в наше приложение и использовать в программе уже наработанные механизмы MS Office.

 

Далее я представляю Вам статью, описывающую по шагам интеграцию Microsoft Office в ваше Visual C++/MFC приложение.

Должен обратить Ваше внимание, на то что я постоянно сталкивался с проблеммой неустойчивой работы Office при использовании его в качестве ActiveX документа. Ещё одна проблемма в том, что при завершении программы приложение Microsoft Office остаётся в памяти и может быть удалено только с помощью Task manager.

Интеграция MS Office

Приступим.

  1. В Visual C++ AppWizard, создаём новое MDI приложение и называем его XOffice. На третьем шаге, необходимо выбрать радои кнопку Container , а также поставить галочку напротив Active Document Container.
  2. Наше приложение будет представлять из себя Automation server.
  3. Включите файл Office.h в Ваш проект:
    // office.h
    
    #define Uses_MSO2000
    
    // для MS Office 2000
    #ifdef Uses_MSO2000
    #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 // для 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. Создаём новую форму, которая нам потребуется в дальнейшем (выбираем опцию New Form в Insert menu).

    В имени формы вводим CFormDemo. Нажимаем New и затем OK, в только что появившейся форме. И в заключении ещё раз жмём кнопку OK.

    Размещаем на форме три Edit Box'а и два Button'а. В ClassWizard связываем Edit Box'ы с переменными (соответственно 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();
    
     // незабудьте добавить AfxOleInit(); в CXOfficeApp::InitInstance
     AfxOleGetMessageFilter()->EnableNotRespondingDialog(FALSE);
    
     // создаём сам объект
     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. И последнее что нужно сделать - это внести изменения в классы 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 документы автоматически. J Pay attention to the fact that the procedures of preservation and loading of our documents work normally too, saving thus the contents of the initial template document. The truth is that we don't need it at all. Единственное, что не работает - это Print Preview. Но в этом вопросе я пока не разобрался.

  8. Теперь давайте изучим класс 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);
    }
    
    
  9. Следующим шагом мы рассмотрим приёмы интерфейса IDispatch в ActiveX документах. Давайте внесём следующие изменения в класс CXOfficeCntrItem :
    // CntrItem.h
    
    ...
    #include <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();
    }
    
  10. Теперь пришло время сделать наш сервер автоматизации интеллектуальным. Для этой цели нужно объявить свойства ActiveDocument и IsActiveDocument для интерфейса IApplication а также свойства PStr, PDouble и PLong для интерфейса IDocument.

    Проще всего это сделать с помощью ATL Визарда.

    • Workspace->Class View->IApplication->Right button->Add Property
    • Workspace->Class View->IDocument->Right button->Add Property

    Реализацию методов Вы найдёте  в исходных текстах.

  11. Файлы XOffice.doc и XOffice.xls - это примеры документов Word и Excel. В Word инициализация документа происходит через событие Document_New, которое вызывается из программы. The value of fields is given to the named bookmarks. В Excel инициализация ячеек документа сделана в событии Workbook_Activate. Это не совсем удобно, но я попробовал много разных вариантов и определил, что такой способ (Workbook_Activate) является наиболее удобным и устойчивым. Ещё раз хочу сказать, что прямой вызов макроса из VBA оставляет Excel в памяти, даже когда программа завершится.

Downloads

Скачать demo project - 36 Kb
Скачать исходник - 71 Kb