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 );