试试下面这个简单的例子:
Dim ws As New notesuiworkspace
Dim uidoc As notesuidocument
Dim doc As notesdocument
Set uidoc=ws.currentdocument
Set doc=uidoc.document
For i=1 To 10
Call doc.appenditemvalue("myitem",i)
Next
Call doc.save(True,True )
这个程序用以对当前文档增加10个ITEM,名字都叫myitem,但值从1到10不等。结果如何?用调试方式进行观察,发现:确实增加了10个ITEM,名字都叫myitem,但值却都是1!这与NOTES中的帮助不符。帮助里宣称:
If the document already has an item called itemName$, AppendItemValue does not replace it.
Instead, it creates another item of the same name, and gives it the value you specify.
^^^^^^^^^^^^^^^^^^^^
从4.6到5.0结果都一样。
二、区分NOTES的前台类与后台类
由于两者的使用范围不一,在写程序时应注意这一点,尤其写代理时。如果在后台服务器运行的代理里加一句:
Dim ws As New notesuiworkspace
代理运行日志报错:Unkown Error.
三、关于NOTES与OLE的共享域
NOTES提供了一个很好的功能:共享域。NOTES用共享域来与OLE应用程序交换彼此信息。但除非确有必要与OLE应用程序共享信息,建议在设计表单时,选上:禁止共享域。 笔者曾在一个表单中创建了一个作者域AUTHOR,又在它的RTF域中嵌入MS-WORD文档:
CALL UIDOC.CREATOBJECT("MYDOC","WORD.DOCUMENT.8","")
似乎一切都正常。但当我变更了NOTES的作者域AUTHOR(因笔者试图通过作者域的改变来控制NOTES文档的修改进而达到流程控制的目的),因为流程的需要,我把它变成了两个值,在NOTES中显示为:
user1/co1/server1,user2/co1/server2
然后对RTF域中所嵌入的WORD文档进行了修改,然后退出,保存。结果问题出现了,NOTES报错:你不是文档的作者,不能保存! 什么原因?当时我明明是用user1/co1/server1进行修改的!后来,仔细调试,把AUTHOR的隐藏属性去掉,仔细观察,发现只要激活了RTF域中嵌入的WORD文档,在RTF域中退出来时AUTHOR的值竟变成了:
user1/co1/server1/co1/server2!
原来,WORD也有一个作者域,名字也叫AUTHOR(似乎是不能改变的)WORD的AUTHOR与NOTES的AUTHOR相互影响(怎么影响?我也不知道),于是变出了上面的这个怪东西!解决办法,当然,就是把NOTES表单“禁止域交换”了!(OLE应用程序有太多的域,想弄清这些域的名字,似乎不太可能。所以“禁止域交换”应是解决这类问题的最好办法了,窃以为)
四、使用NOTES整合OA应用、OFFICE弥补NOTES不足
Sub Entering(Source As Field)
Dim curws As New notesuiworkspace
Dim uidoc As notesuidocument
Set uidoc=curws.currentdocument
lnflag=uidoc.fieldgettext("docadd")
If lnflag=0 Then
Call uidoc.fieldsettext("docadd","1")
Call uidoc.createobject("worddoc","word.document.8","")
End If
Exit Sub
End Sub
当提交后,须记录并显示编辑者对WORD文档的所有修改,故而要将WORD文档改成修订状态。在提交按钮中,写如下程序:
Sub Click(Source As Button)
Dim ws As New notesuiworkspace
Dim uidoc As notesuidocument
Set uidoc=ws.currentdocument
Dim curdoc As notesdocument
Set curdoc=uidoc.document
Dim worddoc As notesembeddedobject
Dim wordapp As Variant
Call ws.editdocument(True)
lnflag=uidoc.fieldgettext("docadd")
If lnflag="1" Then
Set worddoc=curdoc.embeddedobjects(0)
Set wordapp=worddoc.activate(False)
Call worddoc.doverb("编辑(&E)")
wordapp.application.visible=False
wordapp.application.activedocument.trackrevisions=True
wordapp.application.activedocument.showrevisions=True
Call wordapp.application.activedocument.save
Call wordapp.application.exit
End If
Call uidoc.save
Call ws.editdocument(False)
End Sub
上述修改WORD文档为修订状态的那段程序,其实可以改为:
Set wordapp=uidoc.GetObject("worddoc")
wordapp.application.visible=False
wordapp.application.activedocument.trackrevisions=True
wordapp.application.activedocument.showrevisions=True
Call wordapp.application.activedocument.save
即通过UIDOC的GetObject方法进行访问。但通过OLE对象访问时,应注意拼写的正确性,尤其是能过指明OLE对象名称(而不是文件名称)来创建时,更应注意OLE对象名称的拼写正确性。否则,错误难以预料。例如,在RTF域的Entering事件中将创建对象语句按如下修改:
把: Call uidoc.createobject("worddoc","word.document.8","")
改为: Call uidoc.createobject("worddoc","word.document","")
OLE对象还是可以创建,一切似乎都很正常。但是当执行提交时,若通过EmbeddedObject的访问文档,则在Set wordapp=worddoc.activate(False)时出错:不能储存对象。
若通过UIDOC的GetObject方法进行访问,则在
wordapp.application.activedocument.trackrevisions=True时出错:没有激活文档。
五、使用APPENDTOTEXTLIST
APPENDTOTEXTLIST增加文本列表项。使用方法如下:
Dim rtitem as notesitem
dim uidoc as notesuidocument
dim ws as notesuiworkspace
dim curdoc as notesdocument
set uidoc=ws.currentdocument
set curdoc=uidoc.document
set rtitem=curdoc.getfirstitem("authors")
lcreader=curdoc.getitemvalue("readers")
for i=0 to ubound(lcreader)
call rtitem.appendtotextlist(lcreader(i))
next
上例把多值域READERS的值逐个追加到另一个多值域AUTHORS中。要实现这种目的,还有一种办法,即:通过NOTESDOCUMENT的GETITEMVALUE方法分别把两个域的值放到两个数组中,再合并两个数组到一个新的数组中,通过调用NOTESDOCUMENT的REPLACEITEMVALUE方法把新数组的值赋给AUTHORS。但显然这种方法,编程量要大得多。 也许有人会说,为什么不用字符串的相加,再通NOTESUIDOCUMENT的FIELD SETTEXT把相加后的字符串赋给相应的域?按NOTES的说明及其相应的帮助来说,这应该是可以的(当然,两个字符串之间的分隔符应用设计域时指定的分隔符分开)。但实际上,这种方法极不可靠!尤其是两个域的值均为姓名时,实在无法保证这样运算产生的结果是怎样的。NOTES对姓名域有自己的处理办法,但它怎样将前台显示的姓名转换成后台的姓名?只怕没人能搞得清楚。本人有试过这种方法。当只有一两个值时,这种运算一般不会错;但当值多时(我试的时候有7个左右),有时一切正常,有时出现的错误不可思议:NOTES居然把整个字符串当做一个值!它竟然根本没有理会域的多值分隔符!还要注意一点,不须通过NOTESDOCUMENT的SAVE方法,上例所做的变更依然有效。NOTESITEM的变量应是指向相应文档的指针(我猜的)。
六、NOTES的ODBC支持缺陷
A NOTES无法支持后台数据库(如ORACLE)的预储程序Stored Procedure,即使新版的6.2也是如此。尽管帮助自称有提供运行后台数据库SP的函数(resultset.execprocedure),但其实并不起作用。
B 如果在一个odbc connection中有多句SQL的话,会出错:too many cursor。尽管你的sql语句根本不含cursor。Lotus自称在4.6及以后版本中,这个bug已解决,不过,答案也确是如此
七、NOTES的数组
A 定义数组
有两种方式:DIM和REDIM。
DIM定义的是固定个数、数据类型的数组;而REDIM则不同,它可以定义不同类型的数据,也可以定义个数并非固定的数据。比较下面几个例子。 都合法的例子:
Dim myarray(5,2) as string Redim myarray(5,2) as string
前者错误而后者合法的例子:
n=10 n=10
Dim myarray(n) as string Redim myarray(n,2) as string
另外REDIM还可以定义未定类型的数组,如:Redim myarray(10)
B 数组个数
在以DIM或REDIM定义数组时指定的下标,表示的是访问该数组时所容许的最大下标,却不是该数组的个数。实际上,一维数组个数总是等于(最
大下标+1),访问时是通过下标从0开始逐个访问的。
比如:Dim myarray(5) As String定义的数组元素有6个,分别是:myarray (0)、
myarray(1)、myarray(2)、myarray(3)、myarray(4)、myarray(5)。
再如:Redim thisarray(2,5) As String 实际上定义了一个(2+1)*(5+1)=1 8的二维数组。
既然如此,那么,可不可义定义一个只有一个元素的数组呢?答案是:不可以。
如前所说,Redim thisarray(1)定义的数组实际上有(1+1)个数组元素,但类似于: Redim thisarray(0)的语法,NOTES又认为是错误的。所以,不能定义一个只有一个数组元素的数组。其实,以上说的只是其默认状况。其实,定义数组可以通过定义下标的起止从而达到定义数组的个数甚至下标的起止编号的。比如:Redim thisarray(1980 to1990)就 定义了一个含有11个元素的数组,下标从1980到1990。
C 关于UBOUND函数
UBOUND返回的是一维数组的最大下标,而不是元素个数。比如:Dim Myarray(5) As Integer,那么UBOUND(Myarray)返回的值是5,而不是6。
UBOUND也可以应用于二维数组。应用于二维数组时,它返回的是第一个下标的最大值。
比如:Dim Myarray(6,3) As Integer,
那么UBOUND(Myarray)返回的值是6,而不是7,更不是18(6*3=18)。
若要返回第二个下标的最大值,则使用:UBOUND(Myarray,2)。
与UBOUND相对应的是另外一个函数:LBOUND,它返回数组的最小下标。与UBOUND类似,LBOUND(Myarray,2)则返回数组MYARRAY的第二个下标的最
小值。所以,准确地说,一维数组Myarray的元素个数为:UBOUND(Myarray)-LBOUND(Myarray)+1,而二维数组的元素个数则为:
(UBOUND(Myarray)-LBOUND(Myarray)+1)*(UBOUND(Myarray,2)-LBOUND(Myarray,2)+1)
多维数组依此类推。
D 返回数组的函数
可以定义一个函数,使其返回数组。宣告函数时只要宣告它返回Variant型即可
。如下例:
Function db_string(Byval fdname As String) As Variant
fdnum=Len(fdname)
Redim lcarray(fdnum,2) As String
lcarray(0,0)="thisstr"
lcarray(0,1)=","
lcarray(0,2)=","
......
db_string=lcarray '使函数返回数组lcarray的值
End Function
在调用函数时,以如下方式调用:
thisstring="AAAAAAAA"
Dim Thisarray As Variant
thisarray=Db_string(thisstring)
print thisarray(0,0)
八、
NOTES的ODBC:(LS:DO)
A 使用LotusScript编写ODBC程序时需要掌握的几个类:ODBCConnection、ODBCQ
uery、与
ODBCResultSet。通常做法为:
Set con = New ODBCConnection
Dim dbqry As New ODBCQuery
Dim dbresult As New ODBCResultSet
ret=con.ConnectTo(DBSOURCENAME,DBUSERNAME,DBPASS)
If con.isconnected=False Then
ret1=Msgbox("数据库无法连接,请洽系统管理员",48,"提示信息")
odbc_insert=-2
Exit Function
End If
dbqry.sql="select * from THISTABLE where THISTABLE.ID=THISID"
Set dbqry.Connection = con
Set dbresult.query=dbqry
dbresult.execute
%REM
通过dbresult访问关系数据库:取值、更新数据库
%END REM
status=dbresult.close(db_commit)
ret=con.disconnect
B 通过ODBCResultSet更新数据(插入、删除、修改),可以有两种方式。一种方式如上例,对ODBCQry.SQL赋为查询语句,然后能过
ODBCResultSet类的ADDROW、UPDATEROW、DELETEROW 及SETVALUE等方法更新关系型数据库。如下例,对THISTABLE增加一笔记录,并赋字符串型
字段field1的值为test:
Set con = New ODBCConnection
Dim dbqry As New ODBCQuery
Dim dbresult As New ODBCResultSet
ret=con.ConnectTo(DBSOURCENAME,DBUSERNAME,DBPASS)
If con.isconnected=False Then
ret1=Msgbox("数据库无法连接,请洽系统管理员",48,"提示信息")
odbc_insert=-2
Exit Function
End If
dbqry.sql="select * from THISTABLE where 1=0"
Set dbqry.Connection = con
Set dbresult.query=dbqry
dbresult.execute
Call dbresult.addrow
Call dbresult.setvalue("field1","test")
Call dbresult.updaterow
status=dbresult.close(db_commit)
ret=con.disconnect
由于updaterow方法只有对只含一笔记录的ODBCResultSet才有效(多笔时会报错),故而在SQL中的条件中只有一个永远不成立的条件:1=0,以
保证在执行addrow之后在updaterow 之前ODBCResultSet中只有一笔。 另外一种方式是直接用SQL语句更新数据库。它的好处在于一次可以更新
多笔,而且比较灵活,坏处则在于必须考虑DOMINO与后台关系数据库之间的数据类型的转换。如下例,可以达到与上例一样的效果。
Set con = New ODBCConnection
Dim dbqry As New ODBCQuery
Dim dbresult As New ODBCResultSet
ret=con.ConnectTo(DBSOURCENAME,DBUSERNAME,DBPASS)
If con.isconnected=False Then
ret1=Msgbox("数据库无法连接,请洽系统管理员",48,"提示信息")
odbc_insert=-2
Exit Function
End If
dbqry.sql="insert into THISTABLE (THISID) values ('test')"
Set dbqry.Connection = con
Set dbresult.query=dbqry
dbresult.execute
status=dbresult.close(db_commit)
ret=con.disconnect
C 关于类型转换
通过ODBCResultSet更新数据时通常要进行类型转换。通过上述的方式一中的方法更新数据 库可以不用类型转换即可更新数据库,条件是:
NOTES表单中的相应域值不能为空(因为在 NOTES中任何域值若为空,则其数据类型总是string,值为空字符串)。如何实现?将方式 一的例子
中的
Call dbresult.setvalue("field1","test")改造如下:
ltmp=Doc.GetItemValue("doc_field1")
Call dbresult.setvalue("field1",ltmp(0))
其中,doc_field1为关系数据库字段field1对应的NOTES域名。如此改造,即可通过域名更 改相应的后台关系数据库了。 通过上述方式二更新
数据库,则要进行类型转换。可以通过前台NOTES的域的 数据类型来 判断生成可以正确执行的SQL语句,例如:字符型,则在生成时在其值的前
后加单引号;数值型,则不必。但如何取得数据类型呢?通过NotesItem的Type属性访问可返回NotesDocument的条目的数据类型,例如:字符型
时,返回1280;数值型时,返回768;日期时间型,返回 1024,等等。但当相应的域值为空时NotesItem.Type永远返回1280(即字符型),而不管
实 际该域值类型为数值还是日期。 还有一种方法进行类型转换,通过后台关系数据库的数据类型生成可以正确执行的SQL语句。
ODBCResultSet.FieldNativeDataType方法返回后台数据类型的相应字段类型,如: SQL_CHAR,SQL_INTEGER,SQL_TIMESTAMP等等。