数据自动同步

数据的自动流转之二

一般用户可以忽略本节内容。
本节的内容可以参考CaseStudy目录下的文件:自动流转二.Table
打开此文件的时候,请以张三、李四、王五或赵六登录。

上一节介绍的自动流转,是批量处理、批量保存、批量流转的。
本节介绍的自动流转是逐行处理、逐行保存,逐行流转的。

本节对于流程定义与上节基本相同,只是增加了一个流程:

1、流程一由张三负责,输入第一列、第二列的数据。
2、流程二由李四负责,输入第三列、第四列的数据。
3、流程三由王五负责,输入第五列、第六列的数据。
4、流程四由赵六负责,输入第七列、第八列的数据。
4、上级流程处理完毕,才能处理本级流程,例如流程一处理完毕,才能处理流程二。
5、禁止李四、王五、赵六增加行,因为他们不是流程的初始发起者,只是后续流程的处理者。
6、只加载登录用户负责处理的行,例如李四负责流程二,所以李四登录后,只加载流程一处理完毕,但流程二未处理的行。

但要求:

1、禁止直接在表中编辑数据,改用录入窗口形式编辑。
2、双击表中的任何一行,即可打开录入窗口编辑此行(即处理本流程)。
3、每次只能处理一条记录(行)
4、在保存或取消此记录的修改前,不能移到其他记录,也不能关闭窗口。
5、单击窗口中的"保存"按钮,保存对当前行的修改,如果此行已经处理完毕,则移除此行(注意不是删除)。
6、单击窗口中的"撤销"按钮,撤销对当前行的修改,所有列返回修改前的值。
7、单击窗口中的"追载"按钮,从后台追载新的待处理记录。
8、单击窗口中的"打回"按钮,将当前行返回上一流程重新处理。
9、如果所有行均处理完毕,那么定期从后台追载新的待处理行。

设计步骤:

1、给表增加一个名为“进度”的字符型列,用于记录流程的处理进度,此列的值和流程进度的对应关系为:

值 进度
1A 流程一开始
1B 流程一完毕
2A 流程二开始
2B 流程二完毕
3A 流程三开始
3B 流程三完毕
4A 流程四开始
4B 流程四完毕

每个流程都有开始和结束标记,是行自动流转的需要。
例如新增行的初始流程状态为"1A",张三处理完毕后,状态变为"1B"(表示流程一处理完毕),李四检测到后台有状态为"1B"的行,将其追载到表中处理,并将其状态设置为"2A"(表示流程二开始)并保存。
假定没有开始标记,只有结束标记,当李四再次追载后台数据时,由于此行的状态还是"1B",将无法判断此行是否已经追载,影响行的自动流转效率。

2、为了只加载登录用户负责处理的行,首先必须确保默认不加载任何数据:

如果是内部表,设置项目事件BeforeLoadInnerTable的代码为:

If e.DataTableName = "表A" Then
e.Filter = "[_Identify] Is Null"
End If

如果是外部表,可以在定义外部表的时候,直接定义加载条件:

3、然后在项目事件LoadUserSetting中设置代码,加载由登录用户负责处理的行,并设置本流程开始标记:

'加载登录用户负责处理的行
Dim Filter As String
Dim bj As String
Select Case User.Name
Case "张三"
Filter = "进度 Is Null Or 进度 = '1A'" '进度为空或进度一开始
bj = "1A"
Case "李四"
Filter = "进度 = '1B' Or 进度 = '2A'" '进度一结束或进度二开始
bj = "2A"
Case "王五"
Filter = "进 = '2B' Or 进度 = '3A'" '进度二结束或进度三开始
bj = "3A"
Case "赵六"
Filter = "进度 = '3B' Or 进度 = '4A'" '进度三结束或进度四开始
bj = "4A"
Case Else
Filter = "" '其他用户加载全部记录
End Select
DataTables("表A").LoadFilter = Filter
DataTables("表A").Load()
'设置本流程开始标记
If bj >"" Then
For Each dr As DataRow In DataTables("表A").DataRows
dr("进度") = bj
Next
End If
DataTables("表A").Save() '一定要保存,以更新后台的流程开始标记

4、禁止直接在表中输入数据,为此将表的PrepareEdit事件代码设置为:

e.Cancel = True

5、为了禁止除张三之外的人增加行,将表的BeforeAddDataRow事件代码设置为:

If User.Name <> "张三" Then
e.Cancel= True
End If

6、将表的DataRowAdding事件代码设置为:

e.DataRow("进度") = "1A"

这样新增行的时候,进度列的值默认为"1A"。

7、在计划管理中新增一个计划,计划的执行间隔为10秒(即10000毫秒),代码为:

Dim Filter As String
Dim bj As String
Dim drs As List(Of DataRow)
If DataTables("表A").DataRows.Count > 0 Then
Return
End If
Select Case User.Name
Case "张三"
Filter = "进度 Is Null" '进度为空
bj = "1A"
Case "李四"
Filter = "进度 = '1B'" '进度一结束
bj = "2A"
Case "王五"
Filter = "进度 = '2B'" '进度二结束
bj = "3A"
Case "赵六"
Filter = "进度 = '3B'" '进度三结束
bj = "4A"
Case Else
Return '其他用户不追载
End Select
drs = DataTables("表A").AppendLoad(Filter,False) '追载待处理行
If drs.Count > 0 Then '设置新流程的开始标记
For Each dr As DataRow In drs
dr("进度") = bj
dr.Save() '一定要保存,以更新后台的流程开始标记
Next
End If

上面的代码就会每隔10秒执行一次,如果当前表已经没有数据,就从后台追载新的待处理行,并为新追载行设置本流程开始标记。
具体间隔时间可以根据需要调整,但不宜过短,以免服务器的负载过重。

利用消息推送实现即时刷新

前述代码利用计划,每隔10秒追载一下待处理的数据,用户量大的时候,这种定时轮询的方式会导致服务器负载过重。
Foxtable 2016内置了消息推送工具OpenQQ,可以通过代码进行信息的自动收发。
这样负责上一流程的用户处理完成之后,可以给负责下一流程的用户给发一个约定格式的信号,该用户收到信号后,立即自动追载新的数据。
这种主动追载数据的方式,不仅即时,而且高效,具体实现可以参考帮助文件《消息推送》这一章。
 

8、新建一个下图所示的窗口:

为了编码方便,八个文本框的名称和其绑定的列名保持一致,例如第一个文本框绑定到第一列,其名称也应该设置为第一列:

将窗口的AfterLoad事件代码设置为:

e.Form.Controls("第一列").Enabled = (User.Name = "张三")
e.Form.Controls("第二列").Enabled = (User.Name = "张三")
e.Form.Controls("第三列").Enabled = (User.Name = "李四")
e.Form.Controls("第四列").Enabled = (User.Name = "李四")
e.Form.Controls("第五列").Enabled = (User.Name = "王五")
e.Form.Controls("第六列").Enabled = (User.Name = "王五")
e.Form.Controls("第七列").Enabled = (User.Name = "赵六")
e.Form.Controls("第八列").Enabled = (User.Name = "赵六")

上述代码确保每个用户只能编辑自己负责的列。

窗口的BeforeClose事件设置为:

If Tables("表A").Current.DataRow.RowState <> DataRowState.Unchanged Then '如果当前行已经修改过
e.Cancel = True
End If

上面的代码在关闭窗口前执行,如果当前记录(行)是已经修改但没有保存,将禁止关闭窗口,直到保存或撤销修改。

窗口中各按钮的代码:

按钮 代码
上一条 With Tables("表A")
If .Current IsNot Nothing Then
If .Current.DataRow.RowState = DataRowState.Unchanged Then '如果当前行未曾修改
.Position = .Position - 1
End If
End If
End With
下一条 With Tables("表A")
If .Current IsNot Nothing Then
If .Current.DataRow.RowState = DataRowState.Unchanged Then '如果当前行未曾修改
.Position = .Position + 1
End If
End If
End With
第一条 With Tables("表A")
If .Current IsNot Nothing Then
If .Current.DataRow.RowState = DataRowState.Unchanged Then '如果当前行未曾修改
.Position = 0
End If
End If
End With
最末条 With Tables("表A")
If .Current IsNot Nothing Then
If .Current.DataRow.RowState = DataRowState.Unchanged Then '如果当前行未曾修改
.Position = .Rows.Count - 1
End If
End If
End With
新增 With Tables("表A")
If .Current Is Nothing OrElse .Current.DataRow.RowState = DataRowState.Unchanged Then '如果当前行未曾修改
.AddNew()
End If
End With
删除 With Tables("表A")
If .Current IsNot Nothing Then
.Current.Delete()
End If
End With
追载 Dim Filter As String
Dim bj As String
Dim drs As List(Of DataRow)
Select Case User.Name
Case "张三"
Filter = "进度 Is Null" '进度为空
bj = "1A"
Case "李四"
Filter = "进度 = '1B'" '进度一结束
bj = "2A"
Case "王五"
Filter = "进度 = '2B'" '进度二结束
bj = "3A"
Case "赵六"
Filter = "进度 = '3B'" '进度三结束
bj = "4A"
Case Else
Return '其他用户不追载
End Select
drs = DataTables("表A").AppendLoad(Filter,False) '追载待处理行
If drs.Count > 0 Then '设置新流程的开始标记
For Each dr As DataRow In drs
dr("进度") = bj
dr.Save()
Next
End If
打回 Dim r As Row = Tables("表A").Current
If r IsNot Nothing
Select Case User.Name
Case "李四"
If r("进度") = "2A" Then '进度3打回的时候,将进度标记设置为Nothing,意思是进度1处理无效,需要重新处理.
r("进度") = Nothing
r("第三列") = Nothing
r("第四列") = Nothing
r.Save()
r.Remove()
End If
Case "王五"
If r("进度") = "3A" Then '进度3打回的时候,将进度标记设置为1B,意思是进度1已经完成,进度2重新处理.
r("进度") = "1B"
r("第五列") = Nothing
r("第六列") = Nothing
r.Save()
r.Remove()
End If
Case "赵六"
If r("进度") = "4A" Then '进度4打回的时候,将进度标记设置为2B,意思是进度2已经完成,进度3重新处理.
r("进度") = "2B"
r("第七列") = Nothing
r("第八列") = Nothing
r.Save()
r.Remove()
End If
End Select
End If
说明: 以进度3打回给进度2为例,需要将进度调整为“1B”,也许你会感到奇怪,为什么不直接将进度调整为“2A”? 这是为了方便负责进度2的用户及时追载打回的行,因为负责进度2的用户会定期追载进度为“1B”的行,也就是说那些进度1已经完成但是进度2还未开始的行;如果进度3打回给进度2的时候,直接将进度调整为“2A”,那么负责负责进度2的用户只有重新打开项目,才能看到这些被打回的行。

保存 Dim r As Row = Tables("表A").Current
If r IsNot Nothing Then
Dim jd As String = "1A"
If r.IsNull("第一列") = False AndAlso r.IsNull("第二列") = False Then
jd = "1B"
If r.IsNull("第三列") = False AndAlso r.IsNull("第四列") = False Then
jd = "2B"
If r.IsNull("第五列") = False AndAlso r.IsNull("第六列") = False Then
jd = "3B"
If r.IsNull("第七列") = False AndAlso r.IsNull("第八列") = False Then
jd = "4B"
End If
End If
End If
End If
If jd = "1B" AndAlso r("进度") = "2A" Then
ElseIf jd = "2B" AndAlso r("进度") = "3A" Then
ElseIf jd = "3B" AndAlso r("进度") = "4A" Then
Else
r("进度") = jd
End If
r.Save()
Select Case User.Name
Case "张三"
If r("进度") = "1B" Then
r.Remove
End If
Case "李四"
If r("进度") = "2B" Then
r.Remove
End If
Case "王五"
If r("进度") = "3B" Then
r.Remove
End If
Case "赵六"
If r("进度") = "4B" Then
r.Remove
End If
Case Else
Return '其他用户正常返回
End Select
End If
取消 With Tables("表A")
If.Current IsNot Nothing Then
.Current.Reject()
End If
End With

从上面的代码可以看出,如果当前记录(行)已经修改但没有保存,窗口中的上一条、下一条、第一条、最末条、新增按钮将拒绝执行,直到保存或撤销修改。

10、将表的DoubleClick事件代码设置为:

Forms("窗口1").Open()