最近完成了一個使用VC++ 操作word生成掃描報告的功能,在這里將過程記錄下來,開發環境為visual studio 2008
導入接口
首先在創建的MFC項目中引入word相關組件
右鍵點擊 項目 --> 添加 --> 新類,在彈出的對話框中選擇Typelib中的MFC類。
然后在彈出的對話框中選擇文件,從文件中導入MSWORD.OLB組件。
這個文件的路徑一般在C:\Program Files (x86)\Microsoft Office\Office14 中,注意:最后一層可能不一定是Office14,這個看機器中安裝的office 版本。
選擇之后會要求我們決定導入那些接口,為了方便我們導入所有接口。
導入之后可以看到項目中省成本了很多代碼文件,這些就是系統生成的操作Word的相關類。
這里編譯可能會報錯,error C2786: “BOOL (HDC,int,int,int,int)”: __uuidof 的操作數無效
解決方法:
修改對應頭文件
#import "C:\\Program Files\\Microsoft Office\\Office14\\MSWORD.OLB" no_namespace
為:
#import "C:\\Program Files\\Microsoft Office\\Office14\\MSWORD.OLB" no_namespace raw_interfaces_only \
rename("FindText","_FindText") \
rename("Rectangle","_Rectangle") \
rename("ExitWindows","_ExitWindows")
再次編譯,錯誤消失
常見接口介紹
要了解一些常見的類,我們首先需要明白這些接口的層次結構:
Application(WORD 為例,只列出一部分)
Documents(所有的文檔)
Document(一個文檔)
......
Templates(所有模板)
Template(一個模板)
......
Windows(所有窗口)
Window
Selection
View
Selection(編輯對象)
Font
Style
Range
這些組件其實是采用遠程調用的方式調用word進程來完成相關操作。
- Application:相當於一個word進程,每次操作之前都需要一個application對象,這個對象用於創建一個word進程。
- Documents:相當於word中打開的所有文檔,如果用過word編輯多個文件,那么這個概念應該很好理解
- Templates:是一個模板對象,至於word模板,不了解的請自行百度
- Windows:word進程中的窗口
- Selection:編輯對象。也就是我們要寫入word文檔中的內容。一般包括文本、樣式、圖形等等對象。
回憶一下我們手動編寫word的情景,其實使用這些接口是很簡單的。我們在使用word編輯的時候首先會打開word程序,這里對應在代碼里面就是創建一個Application對象。然后我們會用word程序打開一個文檔或者新建一個文檔。這里對應着創建Documents對象並從中引用一個Document對象表示一個具體的文檔。當然這個Document對象可以是新建的也可以是打開一個現有的。接着就是進行相關操作了,比如插入圖片、插入表格、編寫段落文本等等了。這些都對應着創建類似於Font、Style、TypeText對象,然后將這些對象進行添加的操作了。
說了這么多。這些接口這么多,我怎么知道哪個接口對應哪個對象呢,而且這些參數怎么傳遞呢?其實這個問題很好解決。我們可以手工進行相關操作,然后用宏記錄下來,最后我們再將宏中的VB代碼轉化為VC代碼即可。
相關操作
為了方便在項目中使用,這里創建一個類用於封裝Word的相關操作
class CCreateWordReport
{
private:
CApplication m_wdApp;
CDocuments m_wdDocs;
CDocument0 m_wdDoc;
CSelection m_wdSel;
CRange m_wdRange;
CnlineShapes m_wdInlineShapes;
CnlineShape m_wdInlineShape;
public:
CCreateWordReport();
virtual ~CCreateWordReport();
public:
//操作
//**********************創建新文檔*******************************************
BOOL CreateApp(); //創建一個新的WORD應用程序
BOOL CreateDocuments(); //創建一個新的Word文檔集合
BOOL CreateDocument(); //創建一個新的Word文檔
BOOL Create(); //創建新的WORD應用程序並創建一個新的文檔
void ShowApp(); //顯示WORD文檔
void HideApp(); //隱藏word文檔
//**********************打開文檔*********************************************
BOOL OpenDocument(CString fileName);//打開已經存在的文檔。
BOOL Open(CString fileName); //創建新的WORD應用程序並打開一個已經存在的文檔。
BOOL SetActiveDocument(short i); //設置當前激活的文檔。
//**********************保存文檔*********************************************
BOOL SaveDocument(); //文檔是以打開形式,保存。
BOOL SaveDocumentAs(CString fileName);//文檔以創建形式,保存。
BOOL CloseDocument();
void CloseApp();
//**********************文本書寫操作*****************************************
void WriteText(CString szText); //當前光標處寫文本
void WriteNewLineText(CString szText, int nLineCount = 1); //換N行寫字
void WriteEndLine(CString szText); //文檔結尾處寫文本
void WholeStory(); //全選文檔內容
void Copy(); //復制文本內容到剪貼板
void InsertFile(CString fileName); //將本地的文件全部內容寫入到當前文檔的光標處。
void InsertTable(int nRow, int nColumn, CTable0& wdTable);
//**********************圖片插入操作*****************************************
void InsertShapes(CString fileName);//在當前光標的位置插入圖片
//**********************超鏈接插入操作*****************************************
void InsertHyperlink(CString fileLink);//超級鏈接地址,可以是相對路徑。
//***********************表格操作表格操作**********************************
BOOL InsertTableToMarkBook(const CString csMarkName, int nRow, int nColumn, CTable0& wdTable); //表格行數與列數
BOOL WriteDataToTable(CTable0& wdTable, int nRow, int nColumn, const CString &csData); //往表格中寫入輸入
};
BOOL CCreateWordReport::CreateApp()
{
if (FALSE == m_wdApp.CreateDispatch("word.application"))
{
AfxMessageBox("Application創建失敗,請確保安裝了word 2000或以上版本!", MB_OK|MB_ICONWARNING);
return FALSE;
}
return TRUE;
}
BOOL CCreateWordReport::CreateDocuments()
{
if (FALSE == CreateApp())
{
return FALSE;
}
m_wdDocs = m_wdApp.get_Documents();
if (!m_wdDocs.m_lpDispatch)
{
AfxMessageBox("Documents創建失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
return TRUE;
}
BOOL CCreateWordReport::CreateDocument()
{
if (!m_wdDocs.m_lpDispatch)
{
AfxMessageBox("Documents為空!", MB_OK|MB_ICONWARNING);
return FALSE;
}
COleVariant varTrue(short(1),VT_BOOL),vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
CComVariant Template(_T("")); //沒有使用WORD的文檔模板
CComVariant NewTemplate(false),DocumentType(0),Visible;
m_wdDocs.Add(&Template,&NewTemplate,&DocumentType,&Visible);
//得到document變量
m_wdDoc = m_wdApp.get_ActiveDocument();
if (!m_wdDoc.m_lpDispatch)
{
AfxMessageBox("Document獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
//得到selection變量
m_wdSel = m_wdApp.get_Selection();
if (!m_wdSel.m_lpDispatch)
{
AfxMessageBox("Select獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
//得到Range變量
m_wdRange = m_wdDoc.Range(vOptional,vOptional);
if(!m_wdRange.m_lpDispatch)
{
AfxMessageBox("Range獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
return TRUE;
}
BOOL CCreateWordReport::Create()
{
if (FALSE == CreateDocuments())
{
return FALSE;
}
return CreateDocument();
}
BOOL CCreateWordReport::OpenDocument(CString fileName)
{
if (!m_wdDocs.m_lpDispatch)
{
AfxMessageBox("Documents為空!", MB_OK|MB_ICONWARNING);
return FALSE;
}
COleVariant vTrue((short)TRUE),
vFalse((short)FALSE),
vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR),
vZ((short)0);
COleVariant vFileName(_T(fileName));
//得到document變量
m_wdDoc = m_wdDocs.Open(
vFileName, // FileName
vTrue, // Confirm Conversion.
vFalse, // ReadOnly.
vFalse, // AddToRecentFiles.
vOptional, // PasswordDocument.
vOptional, // PasswordTemplate.
vOptional, // Revert.
vOptional, // WritePasswordDocument.
vOptional, // WritePasswordTemplate.
vOptional, // Format. // Last argument for Word 97
vOptional, // Encoding // New for Word 2000/2002
vOptional, // Visible
/*如下4個是word2003需要的參數。本版本是word2000。*/
vOptional, // OpenAndRepair
vZ, // DocumentDirection wdDocumentDirection LeftToRight
vOptional, // NoEncodingDialog
vOptional
);
if (!m_wdDoc.m_lpDispatch)
{
AfxMessageBox("Document獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
//得到selection變量
m_wdSel = m_wdApp.get_Selection();
if (!m_wdSel.m_lpDispatch)
{
AfxMessageBox("Select獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
//得到全部DOC的Range變量
m_wdRange = m_wdDoc.Range(vOptional,vOptional);
if(!m_wdRange.m_lpDispatch)
{
AfxMessageBox("Range獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
return TRUE;
}
BOOL CCreateWordReport::Open(CString fileName)
{
if (FALSE == CreateDocuments())
{
return FALSE;
}
return OpenDocument(fileName);
}
BOOL CCreateWordReport::SetActiveDocument(short i)
{
COleVariant vIndex(_T(i)),vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
m_wdDoc.AttachDispatch(m_wdDocs.Item(vIndex));
m_wdDoc.Activate();
if (!m_wdDoc.m_lpDispatch)
{
AfxMessageBox("Document獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
//得到selection變量
m_wdSel = m_wdApp.get_Selection();
if (!m_wdSel.m_lpDispatch)
{
AfxMessageBox("Select獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
//得到全部DOC的Range變量
m_wdRange = m_wdDoc.Range(vOptional,vOptional);
if(!m_wdRange.m_lpDispatch)
{
AfxMessageBox("Range獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
// HideApp();
return TRUE;
}
BOOL CCreateWordReport::SaveDocument()
{
if (!m_wdDoc.m_lpDispatch)
{
AfxMessageBox("Document獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
m_wdDoc.Save();
return TRUE;
}
BOOL CCreateWordReport::SaveDocumentAs(CString fileName)
{
if (!m_wdDoc.m_lpDispatch)
{
AfxMessageBox("Document獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
COleVariant covOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR);
COleVariant varZero((short)0);
COleVariant varTrue(short(1),VT_BOOL);
COleVariant varFalse(short(0),VT_BOOL);
COleVariant vFileName(_T(fileName));
m_wdDoc.SaveAs(
vFileName,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional,
covOptional
);
return TRUE;
}
BOOL CCreateWordReport::CloseDocument()
{
COleVariant vTrue((short)TRUE),
vFalse((short)FALSE),
vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
m_wdDoc.Close(vFalse, // SaveChanges.
vTrue, // OriginalFormat.
vFalse // RouteDocument.
);
m_wdDoc.AttachDispatch(m_wdApp.get_ActiveDocument());
if (!m_wdDoc.m_lpDispatch)
{
AfxMessageBox("Document獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
//得到selection變量
m_wdSel = m_wdApp.get_Selection();
if (!m_wdSel.m_lpDispatch)
{
AfxMessageBox("Select獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
//得到全部DOC的Range變量
m_wdRange = m_wdDoc.Range(vOptional,vOptional);
if(!m_wdRange.m_lpDispatch)
{
AfxMessageBox("Range獲取失敗!", MB_OK|MB_ICONWARNING);
return FALSE;
}
return TRUE;
}
void CCreateWordReport::CloseApp()
{
COleVariant vTrue((short)TRUE),
vFalse((short)FALSE),
vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
m_wdDoc.Save();
m_wdApp.Quit(vFalse, // SaveChanges.
vTrue, // OriginalFormat.
vFalse // RouteDocument.
);
//釋放內存申請資源
m_wdInlineShape.ReleaseDispatch();
m_wdInlineShapes.ReleaseDispatch();
//m_wdTb.ReleaseDispatch();
m_wdRange.ReleaseDispatch();
m_wdSel.ReleaseDispatch();
//m_wdFt.ReleaseDispatch();
m_wdDoc.ReleaseDispatch();
m_wdDocs.ReleaseDispatch();
m_wdApp.ReleaseDispatch();
}
void CCreateWordReport::WriteText(CString szText)
{
m_wdSel.TypeText(szText);
}
void CCreateWordReport::WriteNewLineText(CString szText, int nLineCount /* = 1 */)
{
int i;
if (nLineCount <= 0)
{
nLineCount = 0;
}
for (i = 0; i < nLineCount; i++)
{
m_wdSel.TypeParagraph();
}
WriteText(szText);
}
void CCreateWordReport::WriteEndLine(CString szText)
{
m_wdRange.InsertAfter(szText);
}
void CCreateWordReport::WholeStory()
{
m_wdRange.WholeStory();
}
void CCreateWordReport::Copy()
{
m_wdRange.CopyAsPicture();
}
void CCreateWordReport::InsertFile(CString fileName)
{
COleVariant vFileName(fileName),
vTrue((short)TRUE),
vFalse((short)FALSE),
vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR),
vNull(_T(""));
/*
void InsertFile(LPCTSTR FileName, VARIANT* Range, VARIANT* ConfirmConversions, VARIANT* Link, VARIANT* Attachment);
*/
m_wdSel.InsertFile(
fileName,
vNull,
vFalse,
vFalse,
vFalse
);
}
void CCreateWordReport::InsertShapes(CString fileName)
{
COleVariant vTrue((short)TRUE),
vFalse((short)FALSE),
vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
m_wdInlineShapes=m_wdSel.get_InlineShapes();
m_wdInlineShape=m_wdInlineShapes.AddPicture(fileName,vFalse,vTrue,vOptional);
}
void CCreateWordReport::InsertHyperlink(CString fileLink)
{
COleVariant vAddress(_T(fileLink)),vSubAddress(_T(""));
CRange aRange = m_wdSel.get_Range();
CHyperlinks vHyperlinks(aRange.get_Hyperlinks());
vHyperlinks.Add(
aRange, //Object,必需。轉換為超鏈接的文本或圖形。
vAddress, //Variant 類型,可選。指定的鏈接的地址。此地址可以是電子郵件地址、Internet 地址或文件名。請注意,Microsoft Word 不檢查該地址的正確性。
vSubAddress, //Variant 類型,可選。目標文件內的位置名,如書簽、已命名的區域或幻燈片編號。
vAddress, //Variant 類型,可選。當鼠標指針放在指定的超鏈接上時顯示的可用作“屏幕提示”的文本。默認值為 Address。
vAddress, //Variant 類型,可選。指定的超鏈接的顯示文本。此參數的值將取代由 Anchor 指定的文本或圖形。
vSubAddress //Variant 類型,可選。要在其中打開指定的超鏈接的框架或窗口的名字。
);
aRange.ReleaseDispatch();
vHyperlinks.ReleaseDispatch();
}
這樣我們就封裝好了一些基本的操作,其實這些操作都是我自己根據網上的資料以及VB宏轉化而來得到的代碼。
特殊操作
在這里主要介紹一些比較騷的操作,這也是這篇文章主要有用的內容,前面基本操作網上都有源代碼直接拿來用就OK了,這里的騷操作是我在項目中使用的主要操作,應該有應用價值。先請各位仔細想想,如果我們要根據前面的代碼,從0開始完全用代碼生成一個完整的報表是不是很累,而且一般報表都會包含一些通用的廢話,這些話基本不會變化。如果將這些寫到代碼里面,如果后面這些話變了,我們就要修改並重新編譯,是不是很麻煩。所以這里介紹的第一個操作就是利用模板和書簽在合適的位置插入內容。
書簽的使用
首先我們在Word中的適當位置創建一個標簽,至於如何創建標簽,請自行百度。然后在代碼中的思路就是在文檔中查找我們的標簽,再獲取光標的位置,最后就是在該位置處添加相應的內容了,這里我們舉一個在光標位置插入文本的例子:
void CCreateWordReport::WriteTextToBookMark(const CString& csMarkName, const CString& szText)
{
CBookmarks bks = m_wdDoc.get_Bookmarks(); //獲取文檔中的所有書簽
CBookmark0 bk;
COleVariant bk_name(csMarkName);
bk = bks.Item(&bk_name); //查詢對應名稱的書簽
CRange hRange = bk.get_Range(); //獲取書簽位置
if (hRange != NULL)
{
hRange.put_Text(szText); //在該位置處插入文本
}
//最后不要忘記清理相關資源
hRange.ReleaseDispatch();
bk.ReleaseDispatch();
bks.ReleaseDispatch();
}
表格的使用
在word報表中表格應該是一個重頭戲,表格中常用的接口如下:
- CTables0: 表格集合
- CTable0: 某個具體的表格,一般通過CTables來創建CTable
- CColumn: 表格列對象
- CRow:表格行對象
- CCel:表格單元格對象
創建表格一般的操作如下:
void CCreateWordReport::InsertTable(int nRow, int nColumn, CTable0& wdTable)
{
VARIANT vtDefault;
COleVariant vtAuto;
vtDefault.vt = VT_INT;
vtDefault.intVal = 1;
vtAuto.vt = VT_INT;
vtAuto.intVal = 0;
CTables0 wordtables = m_wdDoc.get_Tables();
wdTable = wordtables.Add(m_wdSel.get_Range(), nRow, nColumn, &vtDefault, &vtAuto);
wordtables.ReleaseDispatch();
}
往表格中寫入內容的操作如下:
BOOL CCreateWordReport::WriteDataToTable(CTable0& wdTable, int nRow, int nColumn, const CString &csData)
{
CCell cell = wdTable.Cell(nRow, nColumn);
cell.Select(); //將光標移動到單元格
m_wdSel.TypeText(csData);
cell.ReleaseDispatch();
return TRUE;
}
合並單元格的操作如下:
CTable0 wdTable;
InsertTable(5, 3, wdTable); //創建一個5行3列的表格
CCell cell = wdTable.Cell(1, 1); //獲得第一行第一列的單元格
//設置第二列列寬
CColumns0 columns = wdTable.get_Columns();
CColumn col;
col.AttachDispatch(columns.Item(2));
col.SetWidth(40, 1);
cell.Merge(wdTable.Cell(5, 1)); //合並單元格,一直合並到第5行的第1列。
cell.SetWidth(30, 1);
cell.ReleaseDispatch();
合並單元格用的是Merge函數,該函數的參數是一個單元格對象,表示合並結束的單元格。這里合並類似於我們畫矩形時提供的左上角坐標和右下角坐標
移動光標跳出表格
當時由於需要連續的生成多個表格,當時我將前一個表格的數據填完,光標位於最后一個單元格里面,這個時候如果再插入的時候會在這個單元格里面插入表格,這個時候需要我手動向下移動光標,讓光標移除到表格外。移動光標的代碼如下:
m_wdSel.MoveDown(&COleVariant((short)wdLine), &COleVariant((short)1), &COleVariant((short)wdNULL));
這里wdLine 是word相關接口定義的,表示希望以何種單位來移動,這里我是以行為單位。后面的1表示移動1行。
但是我發現在面臨換頁的時候一次移動根本移動不出來,這個時候我又添加了一行這樣的代碼移動兩行。但是問題又出現了,這一系列表格后面跟着另一個大標題,多移動幾次之后可能會造成它移動到大標題的位置,而破壞我原來定義的模板,這個時候該怎么辦呢?我采取的辦法是,判斷當前光標是否在表格中,如果是則移動一行,知道出了表格。這里的代碼如下:
//移動光標,直到跳出表格外
while (TRUE)
{
m_wdSel.MoveDown(&COleVariant((short)wdLine), &COleVariant((short)1), &COleVariant((short)wdNULL));
m_wdSel.Collapse(&COleVariant((short)wdCollapseStart));
if (!m_wdSel.get_Information((long)wdWithInTable).boolVal)
{
break;
}
}
樣式的使用
在使用樣式的時候當然也可以用代碼來定義,但是我們可以采取另一種方式,我們可以事先在模板文件中創建一系列樣式,然后在需要的時候直接定義段落或者文本的樣式即可
m_wdSel.put_Style(COleVariant("二級標題")); //在當前光標處的樣式定義為二級標題樣式,這里的二級標題樣式是我們在word中事先定義好的
m_wdSel.TypeText(csTitle); //在當前位置輸出文本
m_wdSel.TypeParagraph(); //插入段落,這里主要為了換行,這個時候光標也會跟着動
m_wdSel.put_Style(COleVariant("正文")); //定義此處樣式為正文樣式
m_wdSel.TypeText(csText;
插入圖表
我自己嘗試用word生成的圖表樣式還可以,但是用代碼插入的時候,樣式就特別丑,這里沒有辦法,我采用GDI+繪制了一個餅圖,然后將圖片插入word中。
BOOL CCreateWordReport::DrawVulInforPic(const CString& csMarkName, int nVulCnt, int nVulCris, int nHigh, int nMid, int nLow, int nPossible)
{
CBookmarks bks = m_wdDoc.get_Bookmarks();
COleVariant bk_name(csMarkName);
CBookmark0 bk = bks.Item(&bk_name);
bk.Select();
CnlineShapes isps = m_wdSel.get_InlineShapes();
COleVariant vFalse((short)FALSE);
COleVariant vNull("");
COleVariant vOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR);
//創建一個與桌面環境兼容的內存DC
HWND hWnd = GetDesktopWindow();
HDC hDc = GetDC(hWnd);
HDC hMemDc = CreateCompatibleDC(hDc);
HBITMAP hMemBmp = CreateCompatibleBitmap(hDc, PICTURE_WIDTH + GLOBAL_MARGIN, PICTURE_LENGTH + 2 * GLOBAL_MARGIN + LENGED_BORDER_LENGTH);
SelectObject(hMemDc, hMemBmp);
//繪制並保存圖表
DrawPie(hMemDc, nVulCnt, nVulCris, nHigh, nMid, nLow, nPossible);
COleVariant vTrue((short)TRUE);
CnlineShape isp=isps.AddPicture("D:\\Program\\WordReport\\WordReport\\test.png",vFalse,vTrue,vOptional); //以圖片的方式插入圖表
//設置圖片的大小
isp.put_Height(141);
isp.put_Width(423);
bks.ReleaseDispatch();
bk.ReleaseDispatch();
isps.ReleaseDispatch();
isp.ReleaseDispatch();
DeleteObject(hMemDc);
DeleteDC(hMemDc);
ReleaseDC(hWnd, hDc);
return TRUE;
}
最后,各個接口的參數可以參考下面的鏈接:
.net Word office組件接口文檔