前几日对软件“文件整理助手”进行了完善。该软件有文本文件合并,文本文件内容的替换、插入、删除、为特定行首/尾添加字符、清理空行等,以及文件批量替换、改名等功能。 一同事见后,希望能对Word文件进行合并。尽管Word的“插入文件”可以实现这个功能,但不能在插入文件时调整插入的顺序,也不能控制插入的新文件是否另起一页。Word虽然功能强大,但还是有一定的局限性。当然,通过VBA录入脚本、编写宏代码也许可以实现这些复杂的功能。但囿于其缺乏通用性和移植性,对于不善于编程的人来说,还是存在诸多不便。 因此,打算做一个“Word文档合并器”。刚做出这个决定时,以为很简单,因为Delphi的Servers组件页有WordApplication、WordDocument等控件,通过它们来控制全不是那么回事!以前做过涉及到Excel的小程序,没觉得有多难。首次跟Word打交道,竟给我来了个大大的下马威。 以前用过函数,用过过程,也用过带参数的函数、带参数的过程。见过参数多的,但没见过打开、保存Word时尽然要用多达15个、16个参数的函数、过程。而且这些参数青一色地被定义为OleVariant类型,哪些应该是字符串,哪些应该是布尔型,或者专门为Word程序和文档定义的变量类型,没有详细的、系统的资料,只好摸着石头过河,慢慢研究了。 经过几翻碰壁、几翻查证、几翻试验,把要实现的功能一步步拆解,逐一进行调试,通过后再重新组合起来试验。经过拆解、调试、组装三步曲之后,总算是完成了“Word文档合并器”这样一个小小的软件。 为避免下次还重复这种繁琐的基础工作,现将有关技术要点总结于下: (本程序在Word2003中调试通过,其他版本未进行测试。网上找的一些资料在过程调用、函数语句及参数个数上有出入,可能与Word版本不一样有关。)说明: 主窗体中放置以下三个与Word有关的控件: Word: TWordApplication; //Word应用程序接口 Document1: TWordDocument; //Word文档 ole_ShowDoc: TOleContainer; //用以显示Word文档 【一】相关Word组件 这里仅整理Delphi通过自身所提供的Server组件连接Office(Word)的有关资料,其他方法暂不研究。Delphi中提供的与操作Word有关的组件有共有5个,常用的有4个: 1、TWordApplication对象。对应于Microsoft Word应用程序,主要用来在Delphi程序中直接启动或关闭Word应用程序,建立或断开与Word程序的连接。 2、TWordDocument对象。对应于Word文档,主要用来实现创建、销毁一个Word文档,文档的连接和断开,文档中的字符匹配查询,拼写和语法检查以及文档打印等功能。 3、TWordFont对象。对应于Word的字体对象,用来设置Word文档中的字体属性。 4、TWordParagraphFormat对象。对应于Word的段落对象,用来设置文档中的段落格式。【二】启动Word程序//建立与Word应用程序的连接 try Word:=TWordApplication.Create(nil); //必加此句,否则WORD.Quit后无法再启用 //提示:“RPC服务器不可用”。 word.Connect; except Application.MessageBox("无法连接Word。"+#13#10#13#10 +"请确认已正确安装了Word,并关闭了Word中的对话框!","提醒:",Mb_Ok+ MB_ICONSTOP); Exit;
end;
说明:不要“Word:=TWordApplication.Create(nil); ”也可建立与Word应用程序的连接,启动Word应用程序,但
如果用Word.Quit或Word.Destroy退出或注销Word后,便无法再次与Word建立连接,提示“RPC 服务器不可用”。添加
此句后,便可随心所欲地启动、退出Word应用程序。【三】创建Word文件新建空白文档的函数原型:{//===========================================================================Word.Documents.Add(var Template: OleVariant; var NewTemplate: OleVariant; var DocumentType: OleVariant; var Visible: OleVariant): WordDocument;
============================================================================//}
由于在程序中需要多次调用Add函数,而函数中又不能直接使用变量的值,必须通过OleVariant型的变量名进行传递,
为避免繁琐的变量定义和赋值,我将其简化为AddDoc过程://WordApplication建立空白文档过程procedure AddDoc(word:TWordApplication;Dot:String;NewDot,DocVisible:Boolean); //打开文件var Template,NewTemplate,DocumentType,Visible: OleVariant;begin Template:=Dot; //使用模板的名称, NewTemplate:=NewDot; //新建文档的类型,True表示为模板,False表示为文档 DocumentType:=EmptyParam; //文档类型,默认为空白文档 Visible:=DocVisible; //打捞的窗口是否可见 Word.Documents.Add(Template,NewTemplate,DocumentType,Visible);end;【四】打开Word文件打开文件的函数原型:{//===========================================================================Word.Documents.Open(var FileName: OleVariant; var ConfirmConversions: OleVariant; var ReadOnly: OleVariant; var AddToRecentFiles: OleVariant; var PasswordDocument: OleVariant; var PasswordTemplate: OleVariant; var Revert: OleVariant; var WritePasswordDocument: OleVariant; var WritePasswordTemplate: OleVariant; var Format: OleVariant; var Encoding: OleVariant; var Visible: OleVariant; var OpenAndRepair: OleVariant; var DocumentDirection: OleVariant; var NoEncodingDialog: OleVariant): WordDocument;============================================================================//}由于在程序中需要多次调用Open函数,我将其简化只有2个参数的OpenDoc过程://WordApplication打开文件过程procedure OpenDoc(word:TWordApplication;sFileName:string);var //打开文件的参数 FileName,CfCversions,ReadOnly,AddToRctFiles,PswDocument,PswTemplate,Revert, WPswDocument,WPswTemplate,Format,Encoding,Visible,OpenAndRepair, DocumentDirection,NoEncodingDialog:OleVariant;begin// ===== 创建对象 ===== try Word:=TWordApplication.Create(nil); word.Connect; except Application.MessageBox("本机可能没有正确安装WORD!","提醒:",Mb_Ok+MB_ICONSTOP); Exit; end;// ===== 打开文件 ===== Word.Visible := false; FileName:=sFileName; CfCversions := false; ReadOnly:=False; AddToRctFiles:= false; PswDocument:= ""; PswTemplate:= ""; Revert:=true; WPswDocument:= "";//文档密码 WPswTemplate:= "";//模板密码 Format:= EmptyParam; Encoding:= ""; Visible:=False; OpenAndRepair:= EmptyParam; DocumentDirection:= EmptyParam; NoEncodingDialog:= EmptyParam; Word.Documents.open(FileName,CfCversions,ReadOnly,AddToRctFiles,PswDocument, PswTemplate,Revert,WPswDocument,WPswTemplate,Format,Encoding,Visible, OpenAndRepair,DocumentDirection,NoEncodingDialog); end;【五】连接Word文件将新建的或打开的word文档通过TWordDocument对象的ConnectTo方法与TWordapplication实例建立关联。 var DocInx: OleVariant;begin DocInx:=1;// Document1.ConnectTo(Word.ActiveDocument); Document1.ConnectTo(Word.Documents.Item(DocInx));end;【六】保存Word文件Word保存文件过程的原型:{//===========================================================================Word.ActiveDocument.SaveAs(var FileName: OleVariant; var FileFormat: OleVariant; var LockComments: OleVariant; var Password: OleVariant; var AddToRecentFiles: OleVariant; var WritePassword: OleVariant; var ReadOnlyRecommended: OleVariant; var EmbedTrueTypeFonts: OleVariant; var SaveNativePictureFormat: OleVariant; var SaveFormsData: OleVariant; var SaveAsAOCELetter: OleVariant; var Encoding: OleVariant; var InsertLineBreaks: OleVariant; var AllowSubstitutions: OleVariant; var LineEnding: OleVariant; var AddBiDiMarks: OleVariant); safecall;============================================================================//}为避免繁琐地使用保存文件过程,精简为://WordApplication保存文件过程procedure SaveDoc(word:TWordApplication;sFileName:string);var//保存文件的参数 FileName,FileFormat,LockComments,Password,AddToRecentFiles, WritePassword, ReadOnlyRecommended,EmbedTrueTypeFonts,SaveNativePictureFormat, SaveFormsData,SaveAsAOCELetter,Encoding,InsertLineBreaks,AllowSubstitutions, LineEnding,AddBiDiMarks: OleVariant;begin FileName:=sFileName; FileFormat:= EmptyParam; LockComments:= EmptyParam; Password:= EmptyParam; AddToRecentFiles:= EmptyParam; WritePassword:= EmptyParam; ReadOnlyRecommended:= EmptyParam; EmbedTrueTypeFonts:= EmptyParam; SaveNativePictureFormat:= EmptyParam; SaveFormsData:= EmptyParam; SaveAsAOCELetter:= EmptyParam; Encoding:= EmptyParam; InsertLineBreaks:= EmptyParam; AllowSubstitutions:= EmptyParam; LineEnding:= EmptyParam; AddBiDiMarks:= EmptyParam; Word.ActiveDocument.SaveAs(FileName,FileFormat,LockComments,Password, AddToRecentFiles,WritePassword,ReadOnlyRecommended,EmbedTrueTypeFonts, SaveNativePictureFormat, SaveFormsData,SaveAsAOCELetter,Encoding, InsertLineBreaks,AllowSubstitutions,LineEnding,AddBiDiMarks);end;如果通过TWordDocument对象保存文件,则比较简单: Document1.SaveAs(newFileName);【七】插入Word文件Word插入文件过程的原型:{//===========================================================================InsertFile(const FileName: WideString; var Range: OleVariant; var ConfirmConversions: OleVariant; var Link: OleVariant; var Attachment: OleVariant); safecall;============================================================================//}//向打开的Word文件中插入外部文件var i:Integer; myRange,CfCversions,Link,Attachment: OleVariant; s:WideString;begin for i :=0 to List.Items.Count-1 do begin myRange:=EmptyParam; CfCversions:=EmptyParam; Link:=EmptyParam; Attachment:=EmptyParam; myType:=wdPageBreak; if (chk_AddNewPage.Checked) and (i>0) then Word.Selection.InsertBreak(myType); s:=List.Items[i]; Word.Selection.InsertFile(s,myRange,CfCversions,Link,Attachment); end;end;如果在插入文件时,要另起一页,则可在插入文件前执行://var myType:OleVariant;myType:=wdPageBreak;Word.Selection.InsertBreak(myType);插入“分隔符”的类型定义如下:const wdSectionBreakNextPage = $00000002; wdSectionBreakContinuous = $00000003; wdSectionBreakEvenPage = $00000004; wdSectionBreakOddPage = $00000005; wdLineBreak = $00000006; wdPageBreak = $00000007; wdColumnBreak = $00000008; wdLineBreakClearLeft = $00000009; wdLineBreakClearRight = $0000000A; wdTextWrappingBreak = $0000000B;如果要插入字符串,可以使用如下方法: Document1.Characters.Last.Select;//选择最后字符 Document1.Range.InseflAfter("要输入的文字"+#13); 【八】关闭Word文件//关闭打开的Word文件{//===========================================================================procedure Close(var SaveChanges: OleVariant; var OriginalFormat: OleVariant; var RouteDocument: OleVariant); safecall;============================================================================//}var SaveChanges,OriginalFormat,RouteDocument: OleVariant;begin Document1.Disconnect; Document1.Close; SaveChanges:=False; OriginalFormat:=EmptyParam; RouteDocument:=EmptyParam; Word.Documents.Close(SaveChanges,OriginalFormat,RouteDocument);end;【九】退出Word程序//退出Wordbegin Word.Disconnect;// Word.Destroy;
WORD.Quit;
end;
【十】其他相关操作var WORDAPP:Twordapplication; WORDdocument:TWordDocument; itemindex:OleVariant; template,newtemplate,documenttype,visible:olevariant; curr_range:Range; row,col:Integer; direction:OleVariant; defaulttablebehvior,autofitbehvior:OleVariant; curr_table:Table; myrange:OleVariant; savefile:OleVariant;begin try try WORDAPP:=TWordApplication.Create(nil); WORDdocument:=TWordDocument.Create(nil); except ShowMessage("本机可能没有装WORD!"); Exit; end; itemindex:=1; WORDAPP.Connect; WORDAPP.Visible:=true; WORDAPP.Documents.AddOld(EmptyParam,EmptyParam) ; WORDdocument.ConnectTo(WORDAPP.Documents.Item(itemindex) as _document); //关闭拼写检查,因为这会浪费较多时间 WORDAPP.Options.CheckSpellingAsYouType:= False; WORDAPP.Options.CheckGrammarAsYouType:= False; //页面设置 with WORDdocument.PageSetup do begin PaperSize:=wdPaperA4; //wdPaperA4 = $00000007;; LeftMargin:= WORDAPP.CentimetersToPoints(2.0); RightMargin:= WORDAPP.CentimetersToPoints(2.0); TopMargin:= WORDAPP.CentimetersToPoints(2.0); BottomMargin:=WORDAPP.CentimetersToPoints(2.0); Orientation:= wdOrientLandscape; //横向打印 //centerHorizontally:= True; //水平对齐方式 end; //写标题 curr_range:= WORDdocument.Range; curr_range.InsertAfter("插入标题文字"+#13#10); curr_range.Font.Size:=14; curr_range.Bold:=1; curr_range.ParagraphFormat.Alignment:=wdAlignPageNumberCenter; //居中对齐 //加入表格 direction:=wdCollapseEnd; //定位到标题的下一行加入表格 curr_range.Collapse(direction); defaulttablebehvior:=wdWord10ListBehavior; //画边框线 autofitbehvior:=wdAutoFitWindow; col:=DBGridEh1.DataSource.DataSet.FieldCount-1; //列数 row:=DBGridEh1.DataSource.DataSet.RecordCount+1; //行数 //下面两种方式也能通过 //myrange:=WORDdocument.Content.End_-1; //定位到标题的下一行加入表格 //curr_table:=WORDdocument.Tables.AddOld(WORDdocument.Range(myrange),row,col); curr_table:=WORDdocument.Tables.Add(curr_range,row,col,defaulttablebehvior,autofitbehvior); //WORDdocument.Tables.AddOld(curr_range,row,col); curr_table.Range.Paragraphs.Alignment:= wdAlignParagraphLeft; //对齐方式左对齐 //写字段名及值 with DBGridEh1.DataSource.DataSet do begin for col:=1 to FieldCount-1 do //写字段名 begin curr_table.Cell(1,col).Range.Font.Name:="宋体"; curr_table.Cell(1,col).Range.Font.Size:=12; curr_table.Cell(1,col).Range.Font.Bold:=Integer(False); if col=1 then curr_table.Cell(1,col).Range.Text:="序号" else curr_table.Cell(1,col).Range.Text:=Fields[col-1].FieldName; //curr_table.Columns.AutoFit; end; row:=2; First; while not Eof do //写值 begin for col:=1 to FieldCount-1 do begin if col=1 then curr_table.Cell(row,col).Range.Text:=IntToStr(row-1) else begin
if ( Fields[COL-1].FieldName="开始时间" ) or ( Fields[COL-1].FieldName="终止时间" ) then
curr_table.Cell(row,col).Range.Text:=FormatDateTime("yyyy-MM-dd",Fields[COL-
1].AsDateTime) else curr_table.Cell(row,col).Range.Text:=Fields[COL-1].AsString; end; curr_table.Cell(row,col).Range.Font.Size:=11; curr_table.Cell(row,col).Range.Font.Name:="宋体"; curr_table.Cell(row,col).Range.Font.Bold:=Integer(False); end; inc(row); Next; end; curr_table.Columns.AutoFit; end; finally if Trim(savefilename)<>"" then begin savefile:=savefilename+".doc"; WORDdocument.SaveAs(savefile); end; WORDdocument.Disconnect; WORDAPP.Disconnect; FreeAndNil(WORDdocument); FreeAndNil(WORDAPP);
end;
end;