Download source files - 46 Kb
Download demo project - 36 Kb
Introduction
Once I was engaged in a project whose main features were the presence of
a great amount of the typical forms of input and output generally associated
with Office-type applications. The documents were to be filled from a data
store, which the program would display via the use of views.
It would be extremely desirable that the document template could understand
these different data independently. Therefore, we made the decision
to integrate Microsoft Office into our application in order to leverage
the already built-in functionalty that we were seeking.
To that extent, I present to you this article detailing exactly what steps
are necessary in order to integrate Microsoft Office into your
Visual C++/MFC applications.
I should also note at this point that one constant problem that I have run
into with regards to Office is the unstable nature of the product itself
when using it in relation to an ActiveX Document. Therefore, as you look
through this article (and the demo) you might see some less than efficient
code. An example of a problem that I still have is that even after
my application ends, Microsoft Office application remains in memory
and can only be removed via the Task manager. If you do solve this problem,
please let me know so that I can update this article as well as my own code.
Integrating MS Office
Let's begin.
-
Via the Visual C++ AppWizard, generate a new MDI application called XOffice.
On the third step, it will be necessary to select the Container radio button
as well as the Active Document Container checkbox.
-
In addition, this application should be an Automation server. I have taken
advantage of a way offered by
Nick Hodapp in his article, Using ATL to Automate a MFC Application,
which can be found here at CodeGuru. Please familiarize yourself with this
example and execute all steps of program transformation to the automation server.
-
Now we shall engage in integrating MS Office. Include a file Office.h
with the contents into the project:
#define Uses_MSO2000
#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
#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
-
We shall create a new form, which we shall require further. Select the
New Form option from the Insert menu.
Enter CFormDemo into a field Name. Press the button New
located near a Document field and then click the OK button,
in the newly appeared form. And once again click the OK button.
We shall place three Edit Boxes and two Buttons on the new form.
In ClassWizard we shall bind Edit Boxes to variables (accordingly
CString m_str, double m_double, long m_long).
-
We shall create handler of the following kind for Buttons:
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);
}
We shall add the following code to the beginning of XOfficeDoc.cpp file:
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;
}
}
}
Now we are able to create MDI documents by pressing the buttons on the form.
-
The MFC class ColeDocObjectItem provides the support for ActiveX documents.
The class can do a lot already, but we also need to teach it to load the
documents, which we set up.
Make the following change into a class CXOfficeCntrItem
:
class CXOfficeCntrItem : public ColeDocObjectItem
{
...
public:
CXOfficeCntrItem(CXOfficeDoc* pContainer,LPCTSTR);
bool m_isCreate;
bool CreateItem(LPCTSTR);
...
};
CXOfficeCntrItem::CXOfficeCntrItem(CXOfficeDoc* pContainer,LPCTSTR templ)
: COleDocObjectItem(pContainer), m_isCreate(false)
{
CreateItem(templ);
}
bool CXOfficeCntrItem::CreateItem(LPCTSTR templ)
{
USES_CONVERSION;
m_dwItemNumber = GetNewItemNumber();
GetItemStorage();
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;
}
-
And the last thing to do is to make changes in
CXOfficeDoc
and
CXOfficeView
classes for ActiveX document display.
...
class CXOfficeCntrItem;
class CXOfficeDoc : public COleDocument,
...
{
...
public:
CXOfficeCntrItem *m_ctrl;
CString m_template;
CString m_str;
double m_double;
long m_long;
bool LoadTemplate();
...
};
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;
}
void CXOfficeView::OnInitialUpdate()
{
CView::OnInitialUpdate();
CWaitCursor wc;
m_pSelection = GetDocument()->m_ctrl;
...
}
So, now we are able to load ActiveX documents automatically.
It is quite not bad already.
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.
The only thing that does not work is Print Preview. I was not able to
understand it therefore, if someone manages to do it I shall be very
obliged to find out about it first.
-
Now we shall teach
CXOfficeDoc
class to store and to load only
our data and not to ask questions about any data change of ActiveX document
itself. For this purpose we shall add methods of OnOpenDocument
and
SaveModified
and bring in the following changes with the help
of ClassWizard:
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;
}
}
BOOL CXOfficeDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
if (!COleDocument::OnOpenDocument(lpszPathName))
return FALSE;
return LoadTemplate();
}
BOOL CXOfficeDoc::SaveModified()
{
return CDocument::SaveModified();
}
void CXOfficeCntrItem::OnChange(OLE_NOTIFICATION nCode, DWORD dwParam)
{
BOOL modified = m_pDocument->IsModified();
COleDocObjectItem::OnChange(nCode, dwParam);
m_pDocument->SetModifiedFlag(modified);
GetDocument()->UpdateAllViews(NULL);
}
-
The following step shall be the reception of the
IDispatch
interface of
ActiveX document. Let's bring in the following changes to CXOfficeCntrItem
class:
...
#include <comdef.h> <comdef.h>
...
class CXOfficeCntrItem : public COleDocObjectItem
{
public:
...
int m_who;
IDispatchPtr m_disp;
LPDISPATCH GetIDispatch();
void AttachDisp ();
void ActivateDisp();
void CloseDisp ();
...
};
I did not want to present the text of the appropriate methods, as it would have
taken too much space. They can be looked up in the source texts of the program.
It shall be necessary also to bring in the alterations to CXOfficeDoc
and
CXOfficeView
classes listed below.
void CXOfficeView::OnInitialUpdate()
{
...
m_pSelection = GetDocument()->m_ctrl;
m_pSelection->AttachDisp();
...
m_pSelection->ActivateDisp();
}
void CXOfficeDoc::OnCloseDocument()
{
if (m_ctrl)
m_ctrl->CloseDisp();
COleDocument::OnCloseDocument();
}
-
Now it's high time to make our automation server intelligent. For this purpose
we shall define
ActiveDocument
and IsActiveDocument
properties
for codeb>IApplication interface and also PStr
, PDouble
and
PLong
properties for IDocument
interface.
Is easy to do with the help of ATL Wizard.
- Workspace->Class View->IApplication->Right button->Add Property
- Workspace->Class View->IDocument->Right button->Add Property
The realization of methods can be looked up in the source texts.
-
The files XOffice.doc and XOffice.xls are the examples of Word and Excel
documents. In Word the document initialization of field occurs in event
Document_New
, which is obviously called from the program. The value of
fields is given to the named bookmarks. In Excel the document initialization
of cells is made in event Workbook_Activate
. It is not quite convenient, but
I have tried a good deal of variants and have defined this one (Workbook_Activate
)
as the best and most stable. As I have already said, the direct call of macros
from VBA leaves Excel in memory even when the program is finished.