Использование MS Office в MFC
приложении
Автор: Igor Tkachev.
Введение
Однажды я был занят проектом, особенностью
которого было наличие большого количесва форм
ввода/вывода связанными с приложениями типа MS
Office. Документы должны были заполняться данными
различных типов, которые программа отображала
через представления. Очень желательно было,
чтобы шаблон документа понимал эти разнородные
данные. Следовательно мы приняли решение
интегрировать Microsoft Office в наше приложение и
использовать в программе уже наработанные
механизмы MS Office.
Далее я представляю Вам статью, описывающую по
шагам интеграцию Microsoft Office в ваше Visual C++/MFC
приложение.
Должен обратить Ваше внимание, на то что я
постоянно сталкивался с проблеммой неустойчивой
работы Office при использовании его в качестве ActiveX
документа. Ещё одна проблемма в том, что при
завершении программы приложение Microsoft Office
остаётся в памяти и может быть удалено только с
помощью Task manager.
Интеграция MS Office
Приступим.
- В Visual C++ AppWizard, создаём новое MDI приложение и
называем его XOffice. На третьем шаге, необходимо
выбрать радои кнопку Container , а также поставить
галочку напротив Active Document Container.
- Наше приложение будет представлять из себя
Automation server.
- Включите файл 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
- Создаём новую форму, которая нам потребуется в
дальнейшем (выбираем опцию New Form в Insert menu).
В
имени формы вводим CFormDemo. Нажимаем New и затем OK, в
только что появившейся форме. И в заключении ещё
раз жмём кнопку OK.
Размещаем на форме три Edit Box'а и два Button'а. В
ClassWizard связываем Edit Box'ы с переменными
(соответственно 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();
// незабудьте добавить 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;
}
- И последнее что нужно сделать - это внести
изменения в классы 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. Но в
этом вопросе я пока не разобрался.
- Теперь давайте изучим класс 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);
}
- Следующим шагом мы рассмотрим приёмы
интерфейса 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();
}
- Теперь пришло время сделать наш сервер
автоматизации интеллектуальным. Для этой цели
нужно объявить свойства ActiveDocument и IsActiveDocument для
интерфейса IApplication а также свойства PStr, PDouble и PLong
для интерфейса IDocument.
Проще всего это сделать с
помощью ATL Визарда.
- Workspace->Class View->IApplication->Right button->Add Property
- Workspace->Class View->IDocument->Right button->Add Property
Реализацию методов Вы найдёте в исходных
текстах.
- Файлы 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
|