用Visual C++增强Notes打印功能

发表于:2007-07-14来源:作者:点击数: 标签:
Lotus公司推出的Lotus Domino/Notes作为办公自动化系统的平台近年来在国内得到了广泛的应用,许多的政府主管部门、 金融 单位、企事业单位都使用了Notes以及在Notes上 开发 的各种办公系统,工作效率得到了极大的提高。 在实际的应用中,为了存档以及供没安
Lotus公司推出的Lotus Domino/Notes作为办公自动化系统的平台近年来在国内得到了广泛的应用,许多的政府主管部门、金融单位、企事业单位都使用了Notes以及在Notes上开发的各种办公系统,工作效率得到了极大的提高。

  在实际的应用中,为了存档以及供没安装Notes系统的部门传阅,许多在Notes系统中流转的电子文档需要打印出来。不幸的是,Notes提供的打印功能很弱,一个文档只能按照给定表单的版式进行打印。但在实际的使用中,如政府部门,内容相同的一个文档,其上行公文和下行公文的版式是不一样的,这就需要将同一文档用多种样式打印。最直接的想法当然是在Designer中修改表单的版式,但由于应用系统一般是隐藏设计的,表单无法修改。还有就是最终用户的计算机水平有限,直接修改表单从技术上讲也行不通。

  这时一个可行的做法就是:用VC++给用户提供一个"所见即所得"的编辑界面,并列出Notes文档中各部分的内容,让用户以拖放的方式将相关内容放到适当的位置上,同时还可以加入文字、图片等修饰内容,然后按照最终的版式在Notes外部直接生成一个Notes表单,并用此表单进行打印。这种方法既绕过了隐藏设计的障碍,又降低了对最终用户的技术要求。当然这一切都得益于Notes提供的API函数。

  由于只需一个NSFItemScan函数就能收集到Notes文档中所有的域,而又有多种灵活的方式实现"所见即所得"的排版功能,因此在提出上述的思路后,本文将主要介绍如何构造Notes表单。
  一 Notes表单结构简介

  一个表单中有三个必需的域:$TITLE、$INFO和$BODY,辅助性的还有$FIELDS域及属性为placeholder的各域。

  1.$TITLE域

  $TITLE域的类型为TYPE_TEXT,其中保存表单的名称,Notes客户端窗口中"创建"菜单下列出的各表单名即为各表单note中$TITLE域的值。在Notes提供的C API头文件"stdnames.h"中有预定义的常量ITEM_NAME_TEMPLATE_NAME代表表单note的名称域,为保证程序的向后兼容,建议使用常量而避免直接使用$TITLE。

  2.$INFO域

  由于表单和文档的创建有关,$INFO域定义了通过此表单创建的文档的一些属性。实际上$INFO域中存储的是一个名为CDDOCUMENT的结构体,对生成文档属性的设定就是通过对该结构体中各分量的不同赋值实现的。结构体CDDOCUMENT 的定义及说明见Lotus C API 的参考文档。

  $INFO域的类型为TYPE_COMPOSITE,对应的预定义常量为ITEM_NAME_DOCUMENT。

  3. $BODY域

  $BODY域是表单note中的核心域,整个表单显示和打印时的格式,还有通过此表单生成的文档所包含的域及其类型,都是在本域中定义的。由于$BODY域的结构非常复杂,本文将在第二部分专门介绍。$BODY域也是TYPE_COMPOSITE类型的,名称预定义常量为ITEM_NAME_TEMPLATE。

  4. $FIELDS域

  $FIELDS域是一个TYPE_TEXT_LIST类型的域,其中包含了用此表单生成的文档包含的所有域。但专为打印生成的表单中可以没有此域。

  5. "placeholder"域

  对$BODY域中定义的将来文档中要含有的每一个域,在表单中都对应一个类型为TYPE_INVALID_OR_UNKNOWN而标志为ITEM_PLACEHOLDER的域,域名和$BODY域中定义的一样,而其值为NULL。
标志为ITEM_PLACEHOLDER的域将被加入到"域名表"中,这样当用户选择了客户端中的"设计"菜单中的"视图"子菜单后,在弹出的对话框中选择"添加域"时,该域名才会被显示出来。
同样,这些域在打印的表单中不是必需的。

二 $BODY域详解

  $BODY域中可以包含各种Notes对象,如文本、域、图像、热点、链接等,还有一些辅助性对象,如段定义、段引用等。为方便管理,所有这些对象的定义都是通过不同的结构体实现的。Notes中定义对象的结构体都以"CD"开头,如CDTEXT定义静态文本、CDFIELD定义域等,其他对象的具体定义请查阅Lotus C API 的参考文档。

  通常,一个$BODY域的整体结构是这样的:

  CDPABDEFINITION
  CDPABDEFINITION
  ...
  CDPARAGRAPH
  CDPABREFERENCE
  CDTEXT
  text
  ...
  CDPARAGRAPH
  CDPABREFERENCE
  CDBEGINRECORD
  CDFIELD
  CDBEGINRECORD
  ...

  下面对其中的各部分分别予以说明。

  1.段落预定义部分

  CDPABDEFINITION定义页面上一个段落的属性,在这个结构体中我们可以定义段落的对齐方式、页边距、段间距、行间距等。在后面的某个具体段落中,如果定义了到此段定义的引用,则该段落就具有了此处定义的各属性。

  段落的定义可以放在$BODY域的开头,也可以放在中间,只要保证序号PABID不重复就可以了。

  2.静态文本的定义

  上述总体结构的中间部分定义了一段文本:CDPARAGRAPH定义一段的开始,类似文本串中的一个回车换行符;CDPABREFERENCE定义一个到段定义的引用,从而本段就具有了前面定义的各种属性;CDTEXT是文本的头部,包含有文本的长度、字体、颜色等信息;text是实际的文本。

  3.域的定义

  对域的定义也是以CDPARAGRAPH和CDPABREFERENCE开始,但与文本不同的是,像域、图像等对象的定义,除了有作为头部的结构体外,还要有一对界定结构体CDBEGINRECORD和CDENDRECORD放在对象定义的前后两端。

  有时在域的前面还要有一些提示性文字,如一个用于接收姓名的域name,通常在其前面要有"姓名"两个字,以便具体操作者知道此处要输入姓名。具体创建域时,这部分内容以文本形式放在CDBEGINRECORD之前,格式如上一步中所述。

  在货币型或数值型的域中,为了对数据的格式进行更进一步的控制,在CDBEGINRECORD和CDFIELD中间还要插入一个CDEXT2FIELD结构,该结构提供了附加的格式定义。

  域中的其他元素,如默认值计算公式、输入变换公式、域名、描述字串等放在CDFIELD后面,排列顺序和其长度值在CDFIELD结构体中的位置顺序一致。当然除域名外,其他元素如不是必要可以省略。

  在本部分中,以文本和域为例,介绍了$BODY域中各对象的具体定义方式,其他对象与此类似。

  三 创建Notes表单

  在了解了Notes表单结构的基础上,通过API函数建立表单就很容易了。

  首先打开一个数据库,然后在其中新建一个空白note,接下来就可以向其中添加各域了。像$TITLE这样的单一类型的域,可以直接调用NSFItemSetText函数创建。而像$INFO和$BODY这样的复合类型的域,就比较麻烦一些。通常的做法是,先申请一块足够大的内存,然后顺序写入各部分内容,最后调用NSFItemAppend函数创建域。

  在向复合域中写入数据时,文本、域名等一般字符串可以直接写入,而各种结构体需调用ODSWriteMemory函数以Domino规范的形式写入,另外就是域定义中用到的各种公式,在写入前要经过NSFFormulaCompile变换。

四 例程

  下面的程序段定义了一个带有默认值公式的名为"TextField"的域:

  char TextFieldName[] = "TextField";
  char TextDescription[] = "This is a Simple Text Field";
  char TextDefValFormula[] = "\"Default\"";
  char far *pBufferStart, far *pBuffer;
  HANDLE hMem;
  CDPABREFERENCE CDPabRef;
  CDPARAGRAPH CDPara;
  CDBEGINRECORD CDBegin;
  CDENDRECORD CDEnd;
  CDEXT2FIELD CDExt2Field;
  CDFIELD CDField;
  FONTIDFIELDS *pFontFields;

// 申请内存并锁定内存,获得指向该块内存的指针
  OSMemAlloc (0, wCDBufferLength, &hMem);
  pBufferStart = (char far *)OSLockObject(hMem);
  memset( pBufferStart, 0, (size_t) wCDBufferLength );
  pBuffer = pBufferStart;

// 填写 PARAGRAPH 结构
  // 结构体的长度
  CDPara.Header.Length = (BYTE) ODSLength(_CDPARAGRAPH);
  // 结构体的类型
  CDPara.Header.Signature = (BYTE)SIG_CD_PARAGRAPH;
  // 转换为Domino规范的形式写入申请的内存
  ODSWriteMemory( (void far * far *)&pBuffer, _CDPARAGRAPH, &CDPara, 1 );

// 填写 PABREF 结构
  CDPabRef.Header.Signature = (BYTE)SIG_CD_PABREFERENCE;
  CDPabRef.Header.Length = (BYTE) ODSLength(_CDPABREFERENCE);
  // 要引用的段定义的序号
  CDPabRef.PABID = wPabDefNumber;
  ODSWriteMemory( (void far * far *)&pBuffer, _CDPABREFERENCE, &CDPabRef, 1 );

// 填写CDBEGINRECORD 结构
  CDBegin.Header.Length = (BYTE)ODSLength(_CDBEGINRECORD);
  CDBegin.Header.Signature = SIG_CD_BEGIN;
  CDBegin.Version = 0;
  CDBegin.Signature = SIG_CD_FIELD;
  ODSWriteMemory( (void far * far *)&pBuffer, _CDBEGINRECORD,(void far *) &CDBegin, 1 );

// 填写CDEXT2FIELD 结构
  memset(&CDExt2Field, 0, sizeof(CDEXT2FIELD));
  CDExt2Field.Header.Length = (WORD)ODSLength(_CDEXT2FIELD);
  CDExt2Field.Header.Signature = SIG_CD_EXT2_FIELD;
  ODSWriteMemory( (void far * far *)&pBuffer, _CDEXT2FIELD, (void far *) &CDExt2Field, 1 );

// 填写CDFIELD 结构,定义文本域
  CDField.Header.Signature = SIG_CD_FIELD;
  CDField.Flags = FEDITABLE;
  CDField.DataType = TYPE_TEXT;
  CDField.ListDelim = LDDELIM_SEMICOLON;

  // 本域中不用数值格式参数,全部清零
  CDField.NumberFormat.Digits = 0;
  CDField.NumberFormat.Format = 0;
  CDField.NumberFormat.Attributes = 0;
  CDField.NumberFormat.Unused = 0;

  file://本域中不用时间格式参数,全部清零
  CDField.TimeFormat.Date = 0;
  CDField.TimeFormat.Time = 0;
  CDField.TimeFormat.Zone = 0;
  CDField.TimeFormat.Structure = 0;

  // 设定FontID
  pFontFields = (FONTIDFIELDS *)&CDField.FontID;
  pFontFields->Face = FONT_FACE_ROMAN;
  pFontFields->Attrib = 0;
  pFontFields->Color = NOTES_COLOR_BLACK;
  pFontFields->PointSize = 14;

  // 编译默认值公式
  NSFFormulaCompile(NULL, 0, TextDefValFormula, (WORD) strlen(TextDefValFormula), &hTextDefValFormula, &wTextDefValFormulaLen, &wdc, &wdc, &wdc, &wdc, &wdc))

  // 填写CDFIELD 结构的其余部分,因为DVLength值只有公式编译后才知道
  CDField.DVLength = wTextDefValFormulaLen;
  CDField.ITLength = 0;
  CDField.TabOrder = 0;
  CDField.IVLength = 0;
  CDField.NameLength = strlen(TextFieldName);
  CDField.DescLength = strlen(TextDescription);
  CDField.TextValueLength = 0;
  CDField.Header.Length = ODSLength(_CDFIELD) +CDField.DVLength +CDField.ITLength +CDField.IVLength +CDField.NameLength +CDField.DescLength +CDField.TextValueLength;

  // 保证CDFIELD域长度为偶数
  if (CDField.Header.Length % 2)
    CDField.Header.Length++;
  ODSWriteMemory( (void far * far *)&pBuffer, _CDFIELD, (void far *)&CDField, 1 );

  // 获取指向编译后公式的指针
  pTextDefValFormula = OSLock( char, hTextDefValFormula );
  // 写入公式内容到内存
  memcpy( pBuffer, pTextDefValFormula, wTextDefValFormulaLen );
  pBuffer += CDField.DVLength;
  // 解锁并释放公式占用的空间
  OSUnlockObject(hTextDefValFormula);
  OSMemFree(hTextDefValFormula);

  // 域名部分,直接写入
  memcpy( pBuffer, TextFieldName, CDField.NameLength );
  pBuffer += CDField.NameLength;

  // 域描述部分,直接写入
  memcpy( pBuffer, TextDescription, CDField.DescLength );
  pBuffer += CDField.DescLength;

  // 保证整个域定义的长度为偶数
  if ((pBuffer-pBufferStart) %2)
    pBuffer++;

// 填写CDENDRECORD结构
  CDEnd.Header.Length = (BYTE)ODSLength(_CDENDRECORD);
  CDEnd.Header.Signature = SIG_CD_END;
  CDEnd.Version = 0;
  CDEnd.Signature = SIG_CD_FIELD;
  ODSWriteMemory( (void far * far *)&pBuffer, _CDENDRECORD, (void far *) &CDEnd, 1 );

原文转自:http://www.ltesting.net