56. 表单和文档的数据绑定——详谈NotesUIDocument.Reload()

在Notes客户端应用程序开发中,我们创建表单,用它们新建文档,然后在视图里打开、修改和删除文档。有时候我们会忽略了表单和文档是截然不同的两种实体。表单是我们在Designer里设计的,在客户端里看到的界面。文档本身则只是一个数据实体,没有界面,不包括各种格式信息和控件、操作、代码。我们从视图打开一个文档时,Notes客户端自动查找它所用的表单,然后用这个表单显示文档里的数据。一个表单可以用来显示包含不同数据的文档,一个文档也可以用不同的表单来展现。文档可以比作一页书,表单则是上面开了各式各样口子并且在没开口的地方也写有字画了图的一张纸。将这张纸蒙在一页书上,可以透过那些口子看见一些语句和字词,用开的口子不同的纸,就能以不同的样式看见书页上不同的内容。

打开一个文档时,表单的各个域会显示文档的同名字段的内容。修改某个域,对应字段的内容会被更新;反过来通过代码修改某个字段,界面上的显示也会更新。这个全自动的双向更新的过程,对用户和程序员都是透明的,大大简化了图形用户界面的应用程序数据显示和更新的开发。在其他很多编程语言中,这样的技术被称为数据绑定,无论在开发桌面还是Web程序里,都是被提倡的良好架构。Lotus Notes也可以看作基本遵循它。

我们来看看LotusScript中分别代表界面和数据实现绑定的NotesUIDocument和NotesDocument。NotesDocument是LotusScript编程中使用最多的Notes后端对象;NotesUIDocument则是使用最多的前端对象。在Notes帮助里,对于两者分别只有Represents a document in a database.和Represents the document that's currently open in the Notes®workspace.的简短介绍,没有详细讨论它们的区别。NotesDocument代表的是Notes数据库的通用记录类型,它可以保存在数据库中,也可以存留在内存里。NotesUIDocument对应在客户端的一个窗口页里打开的文档,伴随一个NotesDocument的打开而产生,当窗口关闭时即消失,无法独立存在。更进一步看,NotesUIDocument的性质与其说是Document暗示的数据实体,不如说更类似于一般图形用户界面(GUI)的API里的表单(Form),如VB。FieldGetText()、FieldSetText()、GotoField()等方法看上去就更适合归在一个名叫NotesForm的对象下,只不过Lotus Notes将NotesForm的功能仅仅限制在暴露表单作为设计元素的属性,而将用户界面提供的交互功能交给NotesUIDocument。

我们来详细看看NotesUIDocument和NotesDocument之间的数据更新。在一个打开的使用这个表单的文档上,用户修改任何字段的内容,或者通过代码籍NotesUIDocument.FieldSetText(),都可以用NotesDocument.GetItemValue()得知(这中间会经过字段的校验和转换过程)。反过来,用NotesDocument.ReplacItemValue()做的修改,在界面上或籍NotesUIDocument.FieldGetText()也会反映出来。在这样的“绑定”中,NotesDocument因为不是专为此设计的,而是一个有着很多属性和方法要承担很多功能的对象,而且它的数据还包括NotesRichTextItem这样复杂的类型,所以在“绑定”上也多了一些行为和特性。这也就是本文要探讨的问题。

NotesUIDocument的Refresh()和Reload()方法及AutoReload属性分别与从界面到数据和从数据到界面,也就是从NotesUIDocument往NotesDocument和从NotesDocument往NotesUIDocument的两个方向的更新有关。

从NotesDocument往NotesUIDocument

NotesUIDocument.AutoReload

Modifications made to non-rich-text items on the back-end document accessed through the Document property appear on the current document immediately; AutoReload is unnecessary. Modifications made to non-rich-text items on the corresponding back-end document accessed from the front-end document but not through the Document property (for example, if you use GetDocumentByUNID) do not appear immediately unless AutoReload is True. To make the modifications appear when AutoReload is False, call the Reload method or close the document and reopen it.

 

Modifications made to rich-text items on the back-end document do not appear on the current document until it is closed and reopened.

 

Modifications made to rich-text items in the front-end document do not appear in the back-end document unless you call NotesUIDocument.Refresh(True).

 

Modifications made to items on the corresponding back-end document accessed outside the front-end document (for example, by an agent or another user) do not appear unless the document is closed and reopened.

 

NotesUIDocument.Reload

This method is valid only when the document is in Edit mode.

 

This method reloads the back-end document associated with the editing session, which can be acquired through the Document property. It does not reload the base back-end document saved on disk.You must close and reopen the document to reload the base back-end document.

 

This method is useful only when the AutoReload property is False. By default this property is True and modifications to the back-end document appear immediately in the front-end.Where reloading on every modification is slow, you might set the AutoReload property to False, make the modifications, and call Reload.

 

Rich text items are an exception.Modifications made to rich-text items in the back-end document do not appear in the front-end until the document is closed and reopened.

 

Modifications made to the back-end document outside the current editing session (for example, by an agent or another user)do not appear until the document is closed and reopened.

 

You can close and reopen a front-end document with NotesUIDocument.Close(True) and NotesUIWorkspace.EditDocument.

上面两大段英文都引自Notes帮助,之所以这样做,一是因为这两段文字非常详细地说明了这两个属性和方法的作用以及它们所影响的数据更新在各种情况下的行为,可以作为Notes帮助文档高质量之典范;二是因为籍这些帮助还可以对从NotesDocument向NotesUIDocument的更新得到更深入和本质的理解。

两点帮助对某些情况下非富文本域的修改不会显示的描述有差异。NotesUIDocument.AutoReload的帮助里说如果后端文档是在前端文档以外获取(access)的,修改不会显示在前端文档。NotesUIDocument.Reload里说的是如果后端文档是在当前会话(session)以外获取和修改的,前端文档不会被更新。前者涵盖的范围很广,只要不是籍前端文档的Document属性,都可以被理解成是在前端文档以外获取的后端文档。这就包含各种各样获得NotesDocument对象的途径,如Notes帮助里NotesDocument一文里所列,像NotesView.GetDocumentByKey()、NotesDatabase.GetDocumentByUNID()等。后者所说的情况比较狭窄,需要认识会话这个概念才能更好地理解。这里的Notes会话是指一个用户所做的一系列操作和这些操作在其中进行的时间段。每一段运行的LotusScript代码都是在一个会话中,开始时创建一个新会话,结束时会话也终止。代码开始时如果创建一个新的NotesSession对象,就表示这个会话;在前端事件的响应代码里,即使代码的出发点是NotesUIWorkspace,没有创建NotesSession,也是在一个新会话里进行的。这样我们再来看NotesUIDocument.Reload在帮助里说的后端文档在当前会话以外获取和修改,举的例子是一个代理或者其他用户修改当前前端文档对应的后端文档,这里说的代理意思就是定时或由某个事件触发的,而不是当前用户运行的,所以和当前运行的代码不在同一个会话里。

有了上述说明后,我们就会发现NotesUIDocument.AutoReload和NotesUIDocument.Reload的帮助里在哪些情况下非富文本域的修改不会显示的描述有差异。前者说的在前端文档以外获取和修改后端文档,如果不是在后者说的另一会话里,而是很普通地就在当前脚本的同一会话里,前端文档会不会显示这些修改?实际情况是有时会,有时不会。通过代码实验,我们可以悟出原因,理解何时前端文档的非富文本域会自动更新的背后机制。

建一个表单,其中包含一个文本域Message和一个按钮。用该表单创建的文档包含在视图All里。在表单的按钮里,加入以下代码:

Dim ws As New NotesUIWorkspace
Dim uidoc As NotesUIDocument
Set uidoc=ws.CurrentDocument
print uidoc.AutoReload

Dim s As New NotesSession
Dim db As NotesDatabase
Set db=s.CurrentDatabase
Dim view As NotesView
Set view=db.GetView("All")
Dim doc As NotesDocument
Set doc=view.GetLastDocument
doc.Message="hello"
代码一

用该表单新建一个文档(确定它是视图All里的最后一个文档),打开编辑该文档,单击按钮。结果是显示前端文档的AutoReload属性是其默认值True,但是对Message字段的修改并没有显示在前端文档上,即使在这段代码后面加上Call uidoc.Reload(),也没有作用。如果我们在上面的代码的第一节后面加上:

Dim curdoc As NotesDocument
Set curdoc=uidoc.Document
代码二

后端文档的修改就能立即显示在前端文档上。这时,AutoReload属性和Reload()方法就会发挥相应的作用。设置AutoReload为False,就必须调用Reload()才能显示后端文档的更新。

于是我们看出这个更新之奥秘,Notes帮助里的两段说明都没有指出它的本质。这个背后的机制就是如果运行脚本的当前会话获得了当前前端文档(籍NotesUIWorkspace.CurrentDocument)和对应的后端文档对象(籍NotesUIDocument.Document),之后无论以哪种途径获取该后端文档并修改,前端文档都能显示更新;反之,如果没有提前获取这两个对象,则前端文档无法显示更新。为什么会这样呢?NotesDocument.IsUIDocOpen属性给了我们一个提示。Notes客户端在获得一个后端文档时,都会检查该文档是否已经在某个标签页中打开了。这也就是我们一般如果从视图打开某个已经打开的文档时,客户端会直接跳到那个已经打开的文档,而不是在一个新的标签页里再次打开的原因(如果是通过程序打开文档,则有些情况会发生客户端没有检测到某个文档已经被打开,从而再次打开)。在脚本里,如果当前前端文档对应的后端文档对象已经被获取了,通过其他途径获取同一后端文档时,系统就会直接采用已经被获取的文档对象,这时该文档的IsUIDocOpen属性为True。这样一来,对该后端文档的修改,就符合Notes帮助里上面所引的一段话了:

Modifications made to non-rich-text items on the back-end document accessed through the Document property appear on the current document immediately;

也就是像直接修改由前端文档的Document属性获得的后端文档一样(实际上Notes帮助在这里也不完全准确,以这样的方式获得的后端文档的修改是否显示在前段文档上,受AutoReload属性和Reload()方法控制),上面的代码效果就和下面的代码一样:

Dim ws As New NotesUIWorkspace
Dim uidoc As NotesUIDocument
Set uidoc=ws.CurrentDocument
Dim curdoc As NotesDocument
Set curdoc=uidoc.Document
curdoc.Message="hello"
代码三

相对地,如果前端文档对应的后端文档对象没有由Document属性获取,脚本由其他途径获取到该文档对象时,就不能判断该文档已经在前台打开了(此时该文档的IsUIDocOpen属性为False),对它做的修改也就不能显示到前端文档上。这两种情况的区别还可以通过在代码一后面附加下述代码来验证:

Print "identical: " & Cstr(uidoc.Document Is doc)
Print "identical: " & Cstr(curdoc Is doc)
代码四

前者是在没有获取curdoc的情况下,结果为False,显示前端文档对应的后端文档与通过视图获得的文档是不同的对象。后者的结果为True,表明两个文档是同一个对象。

从NotesUIDocument往NotesDocument

这个方向的更新情况要简单很多。在一个打开的文档里,用NotesUIDocument.FieldSetText()设置域值,等价于用户直接输入。此时,在文档的属性信息框的域标签页看到的域值没有变化。不过用前端文档对应的后端文档的GetItemValue()或读取字段的扩展属性都能获得最新的值。从用户输入的值到读取的字段值,中间有一个校验和转换的过程。比如数字、日期域里输入的原义(literal)字符串,就被试图转化成数字和日期值,多值域里的内容根据设定的分隔符被转换成多值列表(list),用户设定的域的校验公式和转换公式也发生作用。如果校验失败,就会产生错误。例如,在文档保存时,客户端会提示某个数字域里的输入不能被转化成数字;在脚本里读取后端文档的字段值时(如doc.ANumberField(0)),会引发Variant does not contain a container的错误,意为ANumberField字段因为没有通过校验没有形成一个有效的字段值,不能以值数组的形式被读取;文档的属性信息框的域标签页看到的该域值为Cannot convert text to a number。

对于富文本字段,从前端文档作了修改后,后端文档不能自动获得更新,需保存文档或调用前端文档的Refresh(True)方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值