
一、概述
无论是西文还是中文Visual FoxPro 3.0(以下简称VFP),其报表输出都有一个严重缺陷,就是当数据库的某一
字段具有溢出延伸属性(就是当字段的内容长于指定的输出宽度时,自动进行换行的属性)时,在换行处容易出
现半个汉字,使输出的报表内容出现"乱码"。
另外,VFP与其他数据库管理系统一样,报表输出模块也存在以下严重不足:
①报表输出的宽度和长度不能由用户改变,无法实现页面的任意变化;
②由于数据库中数据的长度不一,必然会导致输出报表中各栏位的宽度不适,系统无法根据数据库的实际内容
对输出的各栏位的宽度进行随机调整,报表的整体效果差;
③报表一旦输出,用户不能对报表格式进行编辑;
④不宜实现对某些特定栏位的单独输出或组合输出。
为此,笔者利用VFP的OLE自动化技术,通过Microsoft Word 6.0 提供的Word.Basic对象,将数据库的记录输出
成Word文档。
二、在VFP中自动生成Word文档的基本原理和方法
1.用Word.Basic生成数据Word文档的基本原理我们知道,Windows的OLE技术是在两个具有OLE能力的应用程序
之间建立了一种信息共享机制。能够提供OLE服务的应用程序叫服务器,请求OLE服务的应用程序叫客户机。由
于VFP不能为其他具有OLE能力的应用程序提供可用的对象,不能作为OLE服务器。为了在VFP中使用OLE自动化
技术来解决报表输出存在的问题,必须在Windows环境下选择一个能作为OLE服务器的应用程序。Microsoft
Word 6.0不仅能够向VFP提供OLE对象类型Word.Basic,而且能够生成复杂的表格,具有较强的编辑功能,是生成
数据库报表的理想工具。
VFP和Microsoft Word 6.0之间的信息共享机制是通过在VFP中创建由MS Word 6.0提供可用OLE对象
Word.Basic实现的。因此,要使用OLE自动化技术,必须首先使用VFP的OLE对象创建函数CREATEOBJECT() 创建
MS Word 6.0的OLE对象,即: oleWord=CREATEOBJECT("Word.Basic")
2.用Word.Basic生成数据Word文档的基本方法在用CREATEOBJECT()函数创建OLE对象之后,我们就可以利用面
向对象的程序设计方法,调用Word.Basic的具体命令,将数据库的具体内容生成到Word文档。
用Word.Basic生成数据库的Word文档,常用的方法有两种:第一种方法是用Word.Basic的一系列命令生成数据
库的整个Word文档;另一种方法是利用Word.Basic打开预制的基本文档框架,再用Word.Basic的编辑命令来生
成数据库的Word文档。这两种方法各有优点,有时要将它们结合起来使用。第一种方法主要用于对数据库报表
没有任何要求的全自动生成,由于采用全自动生成技术,尽管也能实现输出页面的任意变化和报表中各栏位宽
度的自动调整等功能,但不能直接根据具体的数据库输出具有相应特色的标题。第二种方法在标题及输出的格
式上具有更大的灵活性。通常情况下,先用第一种方法输出某些数据库的一般文档,再利用W ord将其修改成特
殊报表输出的基本文档框架,供第二种方法使用。
下面结合应用介绍第二种方法的具体实现和主要算法。
(1) 定制基本的Word文档框架
在基本的文档框架中可以包含Word文档的任何内容,比如在标题的两边各插入一些图片等等。但是为了实现对
数据库字段的定位输出,该文档框架必须包含一个表,该表的表头可以设计成任何复杂的形式, 如下面的文档
框架:
表1 不良地质:瓦斯信息
为了能使Word.Basic将数据库中要输出的字段正确地输出到基本框架中,在基本框架中需要增加位置标记。我
们可以用在Word基本文档框架中添加书签的方法来指导Word.Basic在基本框架中的正确定位,这些书签分别添
加在基本文档框架中仅有的一行表体的各栏位处。由于Word.Basic在进行后续记录的输出时是按第一表体行
中输出字段的顺序进行输出的,这样就可以保证后续各行输出的正确性了。为了方便起见可将书签名定义成与
字段名同名。
在实际的数据输出过程中,由于我们允许输出页面任意变化,并允许对输出的各栏位宽度进行优化调整,这样就
很难保证各页的行数相同。为了使输出页面的标题能够正确地打印在每一输出页面的首部,将该表的表头设置
成"标题允许"(先选定要设置成标题允许的一行或多行,再用"表格"菜单中的"标题"命令进行设置)。为了使输
出页面的高度一致,可用"表格"菜单中"单元格高度和宽度"对话框选择"允许跨页断行"选项,当然也可用
Word.Basic的TableR owHeight命令对"允许跨页断行"进行设置。
另外,在定义基本文档框架时,不必考虑总的表格宽度应放大至何种尺寸以及各栏位之间的比例关系,仅仅需要
将各栏位的宽度定义成用户所想要的最小宽度即可,其具体目的在下面的数据库输出的主要算法中会作解释。
(2) 生成Word文档的主要算法
在用Word.Basic将数据库的记录输出成Word文档时,需要编程调整输出报表的页面和各栏位的宽度,下面介绍
编程中使用的调整策略和主要算法。
由于数据库中数据的随机性,特别是字符型和备注型数据长度的不定性,表中各栏位宽度的比例关系很难预先
确定,需要在整个报表生成之后进行调整。调整的基本策略是先将各记录中要输出的字段的具体内容输出到表
格中,并在输出过程中记下每一栏位在各行中的最大宽度(字符数),用它作为调整各栏位宽度比例关系的依
据。如果完全按照此依据进行调整,表体部分一定能编排得比较匀称,但表头部分的编排有可能发生变化(如上
面的"采取措施"表头,由于数据库中该字段对应的内容可能很少,在进行栏位宽度调整时,有可能使表头文字变
成 多行排列),报表的整体效果仍然较差。所以有必要预先知道各栏位的最小宽度,以防止在调整各栏位宽度
时表头多行排列。为此,约定原始文档框架中各栏位的宽度即为最小宽度,且栏位宽度调整仅对字符型和备注
型字段进行。
基于上述调整策略,我们介绍相应的算法。为了便于介绍,我们假设通过页面和页边距的设置得出需要将表格
的宽度调宽至nWidth英寸,并定义如下数据类型:
LOCAL aFieldName[nOutColumn,6]
其中:
nOutColumn为要输出字段个数,也是表格的最大列数;
aFiledName[nOutColumn,1]为书签名;
aFiledName[nOutColumn,2]为该书签所在栏位对应字段的字段类型;
aFiledName[nOutColumn,3]为该书签所在栏位对应字段的字段宽度;
aFiledName[nOutColumn,4]为该书签所在栏位对应字段的小数位数;
aFiledName[nOutColumn,5]为该书签所在栏位最小TWIP数;(1英寸 =1440 TWIP)
aFiledName[nOutColumn,6]为该书签所在栏位在各行中的最大字符个数。
这样在计算出各栏位的最大字符个数和各栏位的最小TWIP数后,就可以计算出需要调整栏位的总字符数和可供
使用的宽度总TWIP数。
假设可供使用的宽度总数为j(TWIP),需要调整栏位的总字符数为(字符),则某要调整栏位的宽度应为:
j/k*aFieldName[i,6] 式中i为该调整栏在表中的列号。 但是该宽度有可能小于该栏位的最小宽度,这样该栏
位就不应调 整,需要将该栏的类型a FieldName[i,2]修改为"U"(非C且非M即可), 而将该栏位列入非调整的行
列,同时也要相应修改j和k,再从第一列逐 一检查,查看是否仍有这样的列,如果有需要再重复上述步骤。直到
不 再有上述类型的列为止。最后再用TableColumnWidth命令对剩下需要 调整的栏位进行调整即可。
三、生成Word文档的程序实现 根据上面的调整策略和算法,我们给出用VFP编制的从VFP数据库 输出Word文档
的通用程序。此程序不依赖具体的数据库结构,也不依赖于具体的报表输出格式,可用于任何VFP数据库的Word
文档输出。
LOCAL nOldrecno,oleWord,i,j,k,nOutColumn,cString LOCAL aFieldList[1] USE b1 IF EOF() &&无输出结果则直接返回 RETURN ENDIF =AFIELDS(aFieldList) &&将当前数据库的字段信息存入数组中 nOldrecno=RECNO() *下面首先生成Word.Basic对象,再用Word.Basic的打开文档命令打开基本文档框架 oleWord=CREATEOBJECT("Word.Basic") oleWord.FileOpen("c:\mf\data\word.doc") *下面是从Word文档的首部向下移动插入点指针,直到移动到表格中为止。 *这样做的原因是,基本文档框架中在表格的上面可以有附加内容。 DO WHILE oleWord.SelInfo(12)#-1 oleWord.LineDown ENDDO *下面是检查书签数与表格的最大列数是否相同 nOutColumn=oleWord.CountBookMarks() IF nOutColumn#oleWord.SelInfo(18) oleWord.AppClose("Microsoft Word") =MESSAGEBOX("书签与表格的列数不符",16,"提示信息") RETURN ENDIF *下面是定义列信息数组,并且填写该数组的前4列内容 LOCAL aFieldName[nOutColumn,6] *-- aFieldName列的意义:1书签名,2字段类型,3字段宽度,4,小数位数, *-- 5原始表中各列宽度的最小TWIP数 *-- 6在具体的填写过程中该书签所在列的所有行中的字符的最大个数 FOR i=1 to nOutColumn aFieldName[i,1]=oleWord.BookMarkName(i) FOR j = 1 TO FCOUNT()+1 IF j=FCOUNT()+1 oleWord.AppClose("Microsoft Word") =MESSAGEBOX("标签名:"+aFieldName[i,1]; +"不是数据库的字段名",16," 提示信息") RETURN ENDIF IF UPPER(aFieldName[i,1])==UPPER(ALLT(aFieldLis t[j,1])) aFieldName[i,2]=aFieldList[j,2] &&类型 aFieldName[i,3]=aFieldList[j,3] &&宽度 aFieldName[i,4]=aFieldList[j,4] &&小数位 EXIT ENDIF ENDFOR ENDFOR *将插入点移到表格最底行的最右端 DO WHILE oleWord.SelInfo(12)=-1 oleWord.LineDown ENDDO &&按下箭头向下移出表格 DO WHILE oleWord.SelInfo(12)#-1 oleWord.CharLeft ENDDO &&按左箭头移到表格最后一列的末尾处 IF oleWord.SelInfo(16)#oleWord.SelInfo(18)+1 oleWord.AppClose("Microsoft Word") =MESSAGEBOX("未能移到表格最后列的尾部",16,"提示信息") RETURN ENDIF *--下面计算原始表格中各列的宽度,即列的最小宽度(以TWIP为单位) j=oleWord.SelInfo(5) FOR i=nOutColumn TO 1 STEP -1 oleWord.PrevCell k=oleWord.SelInfo(5) aFieldName[i,5]=j-k aFieldName[i,6]=0 j=k ENDFOR IF oleWord.SelInfo(16)#1 oleWord.AppClose("Microsoft Word") =MESSAGEBOX("最后不是移到表格列的首列",16,"提示信息") RETURN ENDIF *各列的最小宽度计算完毕 *下面表格填写具体内容, *并且在填写具体内容时将各列的最大字符数记录到aFieldName[ i,6]中 LOCATE oleWord.EditGoto((aFieldName[1,1])) oleWord.PrevCell() DO WHILE !EOF() FOR i=1 TO nOutColumn DO CASE CASE aFieldName[i,2]=="N" cString=STR(&aFieldName[i,1],aFieldName[i,3], aFieldName[i,4]) CASE aFieldName[i,2]=="I" cString=ALLT(STR(&aFieldName[i,1],10,0])) CASE aFieldName[i,2]=="C" OR aFieldName[i,2]=="M" cString=TRIM(&aFieldName[i,1]) IF LEN(cString)>aFieldName[i,6] aFieldName[i,6]=LEN(cString) ENDIF OTHERWISE cString="暂不处理类型" ENDCASE oleWord.NextCell() oleWord.Insert((cString)) &&字符型变量必须外加() ENDFOR SKIP ENDDO *--表格内容填写完毕 *下面根据填写的具体内容重新调整表格某些列的宽度 *(在下面的计算过程中单位为TWIP) j=nWidth*1440 &&nWidth为版心宽度是一全局变量,单位为英寸 &&1英寸=1440TWIP k=0 FOR i=1 TO nOutColumn IF aFieldName[i,2]#"C" AND aFieldName[i,2]#"M" j=j-aFieldName[i,5] &&剩余可用宽度 ELSE k=k+aFieldName[i,6] &&需要调整宽度的最大字符数之和 ENDIF ENDFOR &&减去禁止调列的宽度 FOR i=1 TO nOutColumn IF aFieldName[i,2]="C" OR aFieldName[i,2]="M" *如果要调整的宽度小于或等于最小宽度,则将其列入非调整之列 IF j/k*aFieldName[i,6] <= aFieldName[i,5] aFieldName[i,2]="U" j=j-aFieldName[i,5] k=k-aFieldName[i,6] i=1 &&从头循环 LOOP ENDIF ENDIF ENDFOR FOR i=1 TO nOutColumn IF aFieldName[i,2]="C" OR aFieldName[i,2]="M" oleWord.EditGoto((aFieldName[i,1])) oleWord.TableSelectColumn oleWord.TableColumnWidth(j/k*aFieldName[i,6]/20,2,0,0,0,0) *1磅=20TWIP,TableColumn()的缺省单位为磅 ENDIF ENDFOR *宽度调整完毕 oleWord.FileSaveAs("c:\mf\data\word2.doc") *oleWord.FilePrint(0,0,0,"","","",0,1,"",0,0,0,"") oleWord.AppClose("Microsoft Word") WAIT "卸载Word BAsic" WINDOW NOWAIT WAIT CLEAR RETURN
在将数据库内容生成Word文档的最终结果一种方法是以文档的形
式保存下来,另一方法是不仅要把文档保存下来,还要进行打印,此时
需要注意的一点是:不能采用后台打印,将上述程序的中*olwWord.Fil
ePrint(0,0,0,"","","",0,1,"",0,0,0,"")前面的星号"*"去掉即可
。
----------------------------------------------
猫猫的心里话
加菲猫的VFP|狐友会社群接收投稿啦
加菲猫的VFP,用VFP不局限VFP,用VFP混合一切。无论是VFP,还是JS,还是C,只要能混合起来,都可以发表。
商业模式,销售技巧、需求规划、产品设计的知识通通可以发表。
暂定千字50元红包,,优秀的文章红包更大,一经发表,红包到手。
如何帮助使用VFP的人?
用VFP的人,有专业的,有非专业了,很多人其实是小白,问出的问题是小白,如果问题不对,我们引导他们问正确的问题。无论如何请不要嘲笑他们说帮助都不看,这么简单的问题都不会,嘲笑别人不行,而无法提出建设性答案,是很low的。
我们无论工作需要,还是有自己的软件,都是是需要真正的知识,如何让更多人学习真正的VFP知识呢,只需要点赞,在看,能转发朋友圈就更好了。
加菲猫的vfp倡导用"VFP极简混合开发,少写代码、快速出活,用VFP,但不局限于VFP,各种语言混合开发"。
我已经带领一百多名会员成功掌到VFP的黑科技,进入了移动互联网时代,接下来我们要进入物联网领域。
2025年狐友会社群会员继续招募中
社群会员获取的权益有:
祺佑三层开发框架商业版(猫框),终身免费升级,终身技术支持。
开放的录播课程有:
微信小程序,微信公众号开发,H5 APP开发,Extjs BS开发,VFP面向对象进阶,VFP中间层开发。
源码类资源有:
支付组件源码,短信源码,权限组件源码,一些完整系统的源码。这个可以单独出售的,需要的可以联系我。
会员也可以实现群内资源对接,可以接分包,合作等各项商业或技术业务


