主题 :Visual Foxpro 中的多用户及数据缓冲问题
绍
很多狐友( Foxers )都是从 Dbase―FoxBase―Foxpro―VFP 这样一条路走过来的,如果说从 FoxBase 到 Foxpro 是一次飞跃,那么从 Foxpro 到 VFP 就是一次升华。漫漫编程路上的两次大变化都伴随着升级的兴奋与适应的痛苦,惯性思维往往使我们容易忽略新版本的新内容。我们先来看看下面这个在表单中编辑记录的例子:
在 Foxpro 2.X 时代,我是这样设计的:
1 、 在屏幕上放置与表中字段对应的文本框( TEXT )控件,用来存放相应的内存变量(如 M.CUST_IDT 和 M.NAME 等);
2 、 当用户将表的指针定位到某一特定记录时(比如按了"下一个记录"按钮),就用 scatter memvar 语句把该记录的所有字段传给相应的内存变量,再用 show get 来刷新屏幕上的文本框中显示的值。在这一时刻,用户是不能编辑这些变量的(此时文本框或者被设置为不可用( Disabled ),或者其 when 子句返回的是 .F. 值),因为用户处在"浏览"状态。
3 、 当用户选择了"编辑"按钮时,程序就锁定该记录(如果无法锁定,则显示提示信息),然后检测每个字段值和相应的内存变量值(如果有不同的,说明一定是有其它用户在我们进入编辑状态后修改并保存了记录,在这种情况下,要显示提示信息),之后再用 scatter memvar 和 show get 语句刷新,这样用户就看到记录中的当前值了;
4 、 如果所有字段和相应的内存变量完全匹配,就把变量所在的文本框控件都设置为可用( Enabled )或都让 when 子句返回 .T. ,以便让用户可以来编辑这些变量;
5 、 当用户选择了"保存"按钮时,根据一定的规则对输入的数据进行检验,然后用 gatter memvar 语句将内存变量写回到记录中,并对记录解锁( unlock ),再把所有的内存变量所在文本框设置为不可用( Disabled ),或者让其 when 子句返回 .F. 值,这时,用户又回到了"浏览"状态。
请注意,在这一过程中我们没有直接读写记录,而是先将各字段值赋给同名内存变量,再让用户编辑这些内存变量,如果一切正常再把这些内存变量写回到记录当中。之所以用这种方法主要是为了保护表,如果不符合验证规则,就不允许数据回存到表中。另一点要注意的就是,当用户编辑记录时要对记录加锁( lock ),这样就防止了其它用户在同一时间编辑同一条记录。但是,这种方法有一个很大的缺点:假设一个用户开始编辑某一记录,在他按下"保存"按钮前,记录一直处于锁定状态,如果此时该用户有事暂时外出,如午餐,那么其它用户就不能对该记录进行编辑了 L 。
当然,你可以不在进入"编辑"状态时加锁,而只在"保存"记录之前加锁,保存完后马上解锁,这样可以使记录被锁定时间最短,以便其它用户有充分的时间编辑该记录。但是这也有缺点,试想:如果用户编辑了内存变量,这后点击"保存",可如果其它用户在你点"保存"之前也编辑了这条记录,并且还没保存,这时会发生什么现象呢?你的保存要覆盖他人的修改吗?要放弃你的修改吗?这些都是我们要在设计时认真考虑的问题。
我们如此费尽心机地设计,其目的都是为了保护数据。如果你写的程序只是你一人使用,那设计起来可能会简单得多:你可以直接读取记录中的字段,比如你可以直接在屏幕中 browse 一个表,这样你输入的内容就直接写进记录了。但是,我们不能担保那些最终用户也象您一样都清楚能输入什么不能输入什么,我们不得不在用户与数据表之间建立一个"防火墙"来保护数据。在 Foxpro 2.X 中创建这样的"防火墙"要写一大堆的代码!
值得我们高兴的是, VFP 提供了内建的"防火墙"机制,它有两方面的作用:一是可直接读取记录,二是只允许通过所有检验规则的数据被写回。这一机制就是缓冲。
缓冲
在刚才我们讲到的例子中,是通过内存变量存贮记录内容,这种方法可以被认为是手工建立了一个数据缓冲器,通过使用 scatter memvar 把数据从记录中传送到"缓冲"中,再用 gather memvar 从"缓冲"中传回到记录。
VFP 不仅能自动进行单条记录的缓冲(称为行缓冲或记录缓冲),而且还支持另一种类型的缓冲,即表缓冲,表缓冲可通过缓冲器存取多条记录。行缓冲一般用于一次存取一个记录时,这种机制普遍应用于数据录入,就象前文提到的那样:用户可以在表单中显示或编辑单条记录。表缓冲则适用于一次更新多条记录,比如一张订货单的明细录入屏幕,通过对物品明细表使用表缓冲,可以允许用户编辑多条明细记录,最后一次性地将所有明细记录保存或放弃。
除了两种缓冲机制, VFP 还有两种锁定机制。前文讲述的那种在 Foxpro 2.X 中的加锁方式被称为保守式锁定法(或悲观锁定法) ―― 当用户选择"编辑"时加锁,直到用户选择了"保存"后再解锁。这种加锁机制确保了当本用户修改记录时其它用户都不能修改该记录,但这样做有利有弊,视具体情况而定。前文所讲的另一种加锁方式叫做开放式加锁法(或乐观锁定法) ―― 只有在写回记录时才锁定该记录,然后马上解锁。这种加锁机制虽然使记录最大时间内可给别的用户使用,但我们必须处理当两个用户同时编辑记录时所造成的冲突。正如我们下面将要看到的,这对于 VFP 来说真是太简单了,在 VFP 中大多数的情况下都选用开放式缓冲机制。
因为在 VFP 中记录可以被自动缓冲,所以就不必再用"人工缓冲"机制了,换句话说,现在我们可以直接读记录中的字段而不必关心为每个字段设内存变量。要保存修改,我们只要简单地告诉 VFP 将缓冲器中的内容写到表中即可,若是取消修改,告诉 VFP 不写入即可。过会儿我们将看到这是如何操作的。
当打开一个表时, VFP 创建一个"临时表"( cursor )作为缓冲器,这个"临时表"用来定义表的属性。对于本地表来说,"临时表"的唯一属性是用来定义缓冲方式的的,视图和远程表还有一些本文讨论范围之外的其它属性,这些属性的值用 CursorSetProp() 函数设置,用 CursorGetProp() 取得。我们过会儿将看到如何使用这些函数。
当追加记录时,表缓冲有一个有趣的特性:随着记录添加到缓部器中,它们被赋予一个负记录号,第一个加入的记录, recno() 返回值为 -1 ,第二个返回值为 -2 ,依此类推。你可以用一个带负数的 go 命令在缓冲中定位到追加的记录上。这在处理记录号时很有用,比如为了确认变量 InRecno 是否为一个可用的记录号,在缓冲状态下,要检测 between(InRecno,1,reccount()) OR InRecno<0 而不能只用 between(InRecno,1,reccount()) 。
使用缓冲
缺省情况下缓冲器是关闭的,这种情况下在更新表时 VFP 和 Foxpro 2.X 是一样的,要使用缓冲你必需将它打开。缓冲适用于自由表及数据库中的表。要用缓冲还要 set multilocks on ,因为 multilocks 缺省值是 OFF ,如果你忘了设置其值为 ON ,会出现错误信息。你可以把 multilocks=ON 加入到 CONFIG.FPW 文件中,或是用"工具"栏下的"选项"功能保存其值为 ON 。
我们通过 cursorsetprop(‘Buffering’,
1<n>,<alias>) 来定义缓冲方式。如果是对当前表设置缓冲,不必定义 <alias> ,根据你想要的缓冲及锁定方式 <n> 为下列值:
2
3缓冲及锁定法
4<n> 值
5
6无缓冲
71
8
9保守式行缓冲
102
11
12开放式行缓冲
133
14
15保守式表缓冲
164
17
18开放式表缓冲
195
20
21
22例如,要将当前表设置为开放式行缓冲,用 cursorsetprop(‘Buffering’,3) ,要取得当前正打开的表的缓冲方式,用 cursorgetprop(‘Buffering’) 。
23
24要设置一个表单的缓冲方式,你可以在表单的 Load 事件中用 cursorsetprop() 定义所有用到的表,但最好的方法是直接设置表单的 BufferMode 属性来决定是开放式还是保守式(缺省值为"无"),这样设置后,该表单就会自动对绑定到网格 (Grid) 中的表使用表缓冲,对其它表则使用行缓冲。如果你的表单使用了数据环境,你可以对某一个表的 BufferModeOverride 属性按你的意图设置缓冲,以取代表单的 BufferMode 属性。
25
26如果有用户正在修改缓冲记录中的数据(此时用户处在"编辑"状态),你不仅可以取得他们输入到每个字段中的值,还能取得每个字段的初始值和当前值(此值为磁盘中的实际值),为此 VFP 提供了 oldval() 和 curval() 函数。
27
28要得到:
29使用:
30
31用户输入值(缓冲的数据)
32<fieldname> OR <alias.fieldname>
33
34用户未做任何改动之前的值
35Oldval(‘<fieldname>’)
36
37当前记录中的值
38Curval(‘<fieldname>’)
39
40
41注 : curval() 和 oldval() 仅用于开放式缓冲。
42
43你可能搞不懂 curval() 返回值和 oldval() 返回值有什么不同,如果是在单用户程序中,二者之间是没什么区别,但是如果是在网络中,在开放式锁定下,很可能在本用户编辑一条记录时,另一个用户也编辑同一条记录,并在本用户"保存"之前进行了"保存",下面是一个例子:
44
45郑某将 CONTACTS.DBF 指针定位到第 2 条记录,并点击了"编辑"按钮:
46
47字段
48缓冲值
49初始值 Oldval()
50当前值 Curval()
51
52姓名
53李达
54李达
55李达
56
57公司名称
58狐友技术开发公司
59狐友技术开发公司
60狐友技术开发公司
61
62
63然后,郑某将公司名称改为"狐友俱乐部",但还没有保存记录:
64
65字段
66缓冲值
67初始值 Oldval()
68当前值 Curval()
69
70姓名
71李达
72李达
73李达
74
75公司名称
76狐友俱乐部
77狐友技术开发公司
78狐友技术开发公司
79
80
81就在此时,于某也将 CONTACTS.DBF 指针定位到第 2 条记录,并点击了"编辑"按钮,他改变了公司名称为"猎狐者俱乐部",并保存。此时在郑某的机器上会看到如下结果:
82
83字段
84缓冲值
85初始值 Oldval()
86当前值 Curval()
87
88姓名
89李达
90李达
91李达
92
93公司名称
94狐友俱乐部
95狐友技术开发公司
96猎狐者俱乐部
97
98
99注意:在上表中 CONTACTS. 公司名称、 oldval(‘ 公司名称 ’) 以及 curval(‘ 公司名称 ’) 将返回不同的值。访问记录中各字段的初始值、缓冲值和当前值,你可以:
100
101l 通过比较缓冲值和初始值来确定哪些字段被用户修改了;
102
103l 通过比较初始值和当前值来检测在开始编辑后,网络中是否有其它用户修改了同一条记录。 如果你不关心初始值和当前值,而只是希望检测到某个字段中的内容是否被修改过,可以用 getfldstate() 函数。这个函数返回一个数值,指出当前记录是否被做了修改。 Getfldstate() 按以下格式调用:
104
105getfldstate( <fieldname> | <fieldnumber> [ , <alias> | <workarea> ] )
106
107返回值及其意义如下表所示:
108
109返回值
110意义
111
1121
113没改变
114
1152
116字段被编辑或者记录的删除标记被改变
117
1183
119添加了一条新记录但没编辑字段,以及记录的删除记录未改变
120
1214
122添加了一条新记录并编辑了字段,或者记录的删除标记被改变
123
124
125记录的删除标记被改变,是指删除记录或恢复( recall )记录。值得注意的是:对记录删除后又马上进行了恢复,尽管对记录来说没影响,但是其删除标记被改变过,因此, getfldstate() 函数会返回 2 或 4 。
126
127如果你没有定义别名或工作区, getfldstate() 将对当前打开的表进行操作。将 <fieldnumber> 定义为 0 ,该函数返回当前记录的添加及删除状态,如果定义为 -1 ,将返回一个字符串,在这个字符串中,第一个数字反映整个表的状态,以后每个数字返映的是各字段的状态。
128
129以我们前面讲到的情况为例,在郑某编辑第 2 条记录时, getfldstate(-1) 将返回" 112 ",第一个数字" 1 "说明记录没有添加或删除,第二个数字" 1 "说明第一个字段(姓名)没有改变,第三个数字" 2 "说明第二个字段(公司名称)内容改变了。
130
131缓冲记录的写回
132
133我们还继续刚才的例子。现在假设郑某点击了"保存"按钮,我们应该怎样将缓冲中的数据写到记录中(更新表)呢?对于行缓冲来说,当你移动记录指针或调用 tableupdate() 函数时,表就会被更新。对于表缓冲来说,移动记录指针并不会引起表的更新(因为它是多记录被同时缓冲),所以通常情况下只能调用 tableupdate() 函数来更新表。对于行缓冲最好也用 tableupdate() 函数,因为这样更好地控制程序的去向。
134
135如果缓冲器中的内容被正确地写入到记录中, tableupdate() 返回 .T. 值,如果记录缓冲没有改变(用户没有编辑任何字段、添加记录或改变记录的删除状态),此时, tableupdate() 也返回 .T. ,尽管实际上什么也没有做。
136
137Tableupdate() 可以带几个参数:
138
139Tableupdate( <allrows>,<forced>,<alias>|<workarea> )
140
141第一个参数指明哪些记录被更新:设为 .F. ,则只更新当前记录,若为 .T. ,则更新所有记录(仅影响表缓冲)。如果第二个参数是 .T. ,那么其它用户的任何修改将被当前用户的修改所覆盖。如果没定义第三个参数, tableupdate() 将更新当前表。
142
143怎样取消用户所做的修改呢?对于用内存变量的方法,可以再次用 scatter memevar 语句从磁盘上的数据恢复到内存变量中,而对于缓冲来说,用 tablerevert() 函数即可达到同样功能。
144
145错误处理
146
147我们继续"郑某和于某"的例子,当郑某点击"保存"按钮后,代码将执行 tableupdate() 函数以把缓冲中的数据写入记录。请记住,在郑某编辑记录时于某已经修改了同一条记录并做了保存。当郑某点击"保存"时, tableupdate() 将返回 .F. ,说明它不能将缓冲写入记录中,为什么会是这样呢?
148
149VFP 在以下几种情况下无法将缓冲写入记录:
150
151l 当一个用户编辑记录时,其它用户修改并保存了该记录(正如我们例子中的那种情况)。 VFP 自动对每个字段的 oldval() 值和 curval() 值进行比较,如果检测到任何不同,就会产生冲突。
152
153l 用户输入了重复的主索引或候选索引值。
154
155l 违背了某个字段或表的验证规则,或者不支持 null 的字段出现了 null 值。
156
157l 某个触发( trigger )失败。
158
159l 其它用户锁定了该记录。
160
161l 其它用户删除了该记录。
162
163当 tableupdate() 失败时,我们必须决定下一步做什么,而且,如果你的程序在编辑记录时允许用户点击"下一个"或"上一个"按钮 , 而这两个按钮中又没有调用 tableupdate() 的话,你必须得处理在自动保存时将会发生的错误。在这两种情况下,将程序指定到适当的位置就是错误陷阱处理程序。
164
165Visual Foxpro 中的多用户及数据缓冲问题 ( 下)
166
167\--------------------------------------------------------------------------------
168
1692000-10-6 17:07:00
170
171
172在 VFP 中错误处理已经得到改进。以前处理错误陷阱的方法(你仍然可以在 VFP 中继续使用这些方法)是当错误发生时用 on error 命令来决定要执行的程序,典型的错误处理程序是查看 error() 和 message() 来确定发生了什么错误,然后采取相应的动作。
173
174现在 VFP 提供了一种自动的错误处理机制:就是 Error 方法。如果定义了一个控件或表单中的 Error 方法,当错误发生时它就被自动执行。 aerror() 是 VFP 的一个新增函数,通过传递一个参数,该函数可以创建或更新一个含有以下元素的数组
175
176元素
177类型
178描述
179
1801
181数字
182错误号(与 error() 相同)
183
1842
185字符
186错误信息(与 message() 相同)
187
1883
189字符
190如果有一个错误信息参数(与 sys(2018) 相同),则返回之(例如:一个字段名),无,则返回 .NULL.
191
1924
193数字或字符
194发生错误的工作区。如果没有,则返回 .NULL.
195
1965
197数字或字符
198如果一个触发器失败,返回触发器号(插入为 1 ,更新为 2 ,删除为 3 ),如果没有则返回 .NULL.
199
2006
201数字或字符
202.NULL. (应用于 OLE 和 ODBC 错误)
203
2047
205数字
206.NULL. (应用于 OLE 错误)
207
208
209例如: aerror(IaERROR) 会创建或更新一个称为 IaERROR 的数组。
210
211以下是 VFP 在将缓冲写入表时可能发生的一些错误:
212
213错误号#
214错误信息
215说明
216
217109
218记录正由其它用户使用
219
220
2211539
222触发器失败
223检测数组的第 5 个元素可以确定是哪个触发器失败了
224
2251581
226字段不接受空值( null )
227检测数组的第 3 个元素可以确定是哪个字段引起的错误
228
2291582
230违反了字段的验证规则
231检测数组的第 3 个元素可以确定是哪个字段引起的错误
232
2331583
234违反了记录的验证规则
235
236
2371585
238记录已被其它用户修改
239
240
2411884
242违反了索引的唯一性
243检测数组的第 3 个元素可以确定是哪个索引标记引起的错误
244
245
246对于以上这些错误,大部分可以直接处理:提示用户问题所在,然后让用户在"编辑"状态下改正错误或是取消操作。对于# 1585 错误(记录已被其它用户修改),有以下几种处理错误的方法:
247
248l 可以提示当前用户有别人修改了该记录,然后用 tablerevert() 取消当前用户的编辑内容。我想多数人对这种方法会不高兴的。
249
250l 可以用 tableupdate(.F. , .T.) 来强行更新记录,使当前用户的修改覆盖其它用户修改。这样做,当前用户自然是高兴的,但其它用户可能就要不满了。:(
251
252l 复制一个相同的表单(在 VFP 中对同一表单创建多个实例是非常容易的),在上面显示出其它用户对该记录的修改,这样,当前用户就可以决定是保存还是不保存其它用户的修改,也就是说,可以用 tableupdate(.F. , .T.) 强行更新或是用 tablerevert() 来取消编辑。
253
254有更好的方案来检测我们是否遇到了"真正的"冲突,所谓"真正的"冲突,是指两个用户在同一时间修改了同一个字段。如果他们修改的是同一记录的两个不同字段,我们可以只更新当前用户修改的字段,而保留另一个用户修改的字段,使之不受影响。例如,在一个订单处理系统中,一个用户修改了产品介绍,而同时另一个用户在对该产品下订单,正在输入数量,这两个修改相互独立,并没有冲突,这时我们不要一次更新整条记录,而是只更新自己修改的那个字段,如此一来,两个用户都会感到满意。
255
256以下是实现这一想法的思路:
257
258l 查找 oldval() 与 curval() 不同的字段,如果有,说明该字段已被其它用户编辑过,如果该字段的缓冲值与 oldval() 相同,说明当前用户并没有修改该字段。这种情况下,我们可以将 curval() 中的值先传给缓冲值,再更新,这样就可以避免缓冲值覆盖新值了。
259
260l 查找缓冲值与 oldval() 不同的字段,这些字段是当前用户修改过的字段,如果 oldval() 又等于 curval() ,说明其它用户没有改动过该字段,这种情况下,我们可以放心地覆盖掉它。
261
262l 如果我们找到的字段,其缓冲值与 oldval() 不同,但与 curval() 相同,这说明两个用户对同一字段做了相同的修改。这种情况看起来好象不大可能,其实是会发生的。比如有人发来了一个公司的地址变动信息,而恰恰有两个用户同时决定据此来更新记录中的公司地址。因为他们所做的修改内容是相同的,我们可以覆盖另一个即可。但是,如果是以同一个数量更新一个量值的话(比如两人同时下订单,且输入了相同的数量),你就不能简单地覆盖字段,应该将此看作为"真正的"冲突。
263
264l 如果发现一个字段的缓冲值既不同于 oldval() 也不同于 curval() ,而且 oldval() 与 curval() 也各不相同,这说明两个用户都修改了同一个字段,且值不相同。这种情况才是我们遇到的真正的冲突,我们不得不决定怎样处理这一冲突。
265
266在手工输入存货数量或帐目余额时,有一种可能就是其它用户输入的值对缓冲值会产生影响。比如,如果检测到 oldval() 是 10 , curval() 是 20 ,说明其它用户将数额已增加了 10 ;如果现在缓冲值为 5 ,说明当前用户要将数额在原基础( 10 )上减少 5 ,因此新的缓冲值就应该是 value+ oldval()- curval() ,即等于 15 ,应该用此数来更新字段。
267
268对于日期型字段的冲突,就要考虑商业规则或按实际情况处理。例如,在"病人预约时间"程序中,有一个字段保存着病人下次预约医生的时间,如果该字段两个日期发生冲突的话,那么,靠近当前日期的那个日期很可能是正确的。当然,若是其中一个预约日期比当前日期还要早的话(已经过期),那就应该取靠后的那个日期了。
269
270其它类型的字段,特别是字符型和备注型字段,通常情况下如果不询问用户以决定是覆盖其它用户的修改还是取消自己的修改的话,则冲突不好解决。只有当用户在屏幕上看到其它用户到底做了什么修改后,他才能做出正确地判断。
271
272以下是解决冲突的一些代码(这些代码假设已检测到错误码为# 1585 ,即记录已被其它用户修改过。)
273
274* 检测每一个字段,看哪个发生了冲突。
275
276llConflict = .F.
277
278for lnI = 1 to fcount()
279
280lcField = field(lnI)
281
282llOtherUser = oldval(lcField) <> curval(lcField)
283
284llThisUser = evaluate(lcField) <> oldval(lcField)
285
286llSameChange = evaluate(lcField) == curval(lcField)
287
288do case
289
290* 其它用户编辑了该字段,而当前用户没编辑,所以直接用新值即可。
291
292case llOtherUser and not llThisUser
293
294replace (lcField) with curval(lcField)
295
296* 其它用户没有编辑该字段,或者二者做了相同的修改,因此我们无需做任何处理。
297
298case not llOtherUser or llSameChange
299
300* 两个用户以不同的值修改了该字段。
301
302otherwise
303
304llConflict = .T.
305
306endcase
307
308next lnI
309
310* 如果发生了冲突,处理之!
311
312if llConflict
313
314lnChoice = messagebox('Another user also changed this ' + ;
315
316'record. Do you want to overwrite their changes (Yes), ' + ;
317
318'not overwrite but see their changes (No), or cancel ' + ;
319
320'your changes (Cancel)?', 3 + 16, 'Problem Saving Record!')
321
322do case
323
324* 覆盖其它用户的修改。
325
326case lnChoice = 6
327
328= tableupdate(.F., .T.)
329
330* 通过产生一个表单实例来查看其它用户的修改内容。
331
332case lnChoice = 7
333
334do form MYFORM name oName
335
336* 取消当前用户的修改。
337
338otherwise
339
340= tablerevert()
341
342endcase
343
344* 如果没有发生冲突,则强行更新。
345
346else
347
348= tableupdate(.F., .T.)
349
350endif llConflict
351
352表缓冲的写入
353
354我们前面已经讲到,可以用 tableupdate(.T.) 将表缓冲中的所有记录一次写入磁盘。与行缓冲一样,如果其它用户修改了表(或是其它什么出错原因)而不能正确更新表, tableupdate(.T.) 将返回 .F. 值。
355
356前文所讲的错误处理程序在行缓冲模式下运行良好,因为我们在某一时刻只关心单条记录。但对于表缓冲来说,我们不得不考虑每一条记录,因为在缓冲区中可能既有修改过的记录,也有未修改过的记录,我们怎样知道到底更新哪条记录呢?如果用 tableupdate(.T.) 失败(返回 .F. ),情况就变得更加复杂化:我们不知道错在哪条记录上!而且有些记录可能被做过"保存",所以还不止一条记录会发生冲突呢。请不要着急:), VFP 新增函数 getnextmodified() 可以精确地告诉我们想知道的信息:该函数返回下一个被修改记录的记录号。如果返回值为 0 ,说明在缓冲区中没有被修改过的记录。这个函数接收两个参数:第一个参数是一个记录号,正是从这个记录号开始向下查找下一个被修改的记录;第二个参数是查找的工作区别名。最被,你应该将 0 传给第一个参数,这样 getnextmodified() 就会找到第一个被修改的记录,若要继续找下一个被修改的记录,只要将当前记录的记录号传给第一个参数即可。
357
358下面是在刚才处理冲突的程序基础上改进后的代码,它用来处理表缓冲更新失败时的操作。
359
360* 先找到第一个被修改过的记录。
361
362lnChanged = getnextmodified(0)
363
364do while lnChanged <> 0
365
366* 移动记录指针并尝试锁定它。
367
368go lnChanged
369
370if rlock()
371
372* 检测每一个字段,看哪个发生了冲突。
373
374llConflict = .F.
375
376for lnI = 1 to fcount()
377
378lcField = field(lnI)
379
380llOtherUser = oldval(lcField) <> curval(lcField)
381
382llThisUser = evaluate(lcField) <> oldval(lcField)
383
384llSameChange = evaluate(lcField) == curval(lcField)
385
386do case
387
388* 其它用户编辑了该字段,而当前用户没编辑,所以直接用新值即可。
389
390case llOtherUser and not llThisUser
391
392replace (lcField) with curval(lcField)
393
394* 其它用户没有编辑该字段,或者二者做了相同的修改,因此我们无需做任何处理。
395
396case not llOtherUser or llSameChange
397
398* 两个用户以不同的值修改了该字段。
399
400otherwise
401
402llConflict = .T.
403
404endcase
405
406next lnI
407
408* 如果发生了字段冲突,我们可以在此处理它,与行缓冲不同的是,我们也可以现在不处理,因为以后所有记录将被写入,到时会处理的。
409
410if llConflict
411
412lnChoice = messagebox('Another user also changed ' + ;
413
414'record ' + ltrim(str(lnChanged)) + '. Do you want to ' + ;
415
416'overwrite their changes (Yes), not overwrite but see ' + ;
417
418'their changes (No), or cancel your changes (Cancel)?', 3 + 16, ;
419
420'Problem Saving Record!')
421
422do case
423
424* 如果选择了覆盖其它用户的修改,在此可以不做处理,因为以后将一次性更新。
425
426case lnChoice = 6
427
428* 通过产生一个表单实例来查看其它用户的修改内容。
429
430&nb
431
432转载!</workarea></alias></forced></allrows></fieldnumber></workarea></alias></fieldnumber></fieldname></fieldname></fieldname></alias.fieldname></fieldname></n></n></alias></alias></n>