用OpenQQ实现独占式编辑

本节讲述如何避免多人同时编辑同一行,既如何实现独占式编辑。
利用OpenQQ,可以很轻松实现此功能。

要实现此功能,服务器端和客户端都要进行相应的设计,我们学习的时候,也要结合客户端和服务端的代码来理解。

服务器端的设计

1、在服务器端项目的全局代码中,加入如下代码:

Public tbrk As new Dictionary(of String,String)

tbrk是一个字典,用于登记每一行是谁在编辑。
我们约定接下来编码的时候:字典的键由表名和行的主键组合成,值则等于编辑者名。

2、在服务端项目的OpenQQ服务端的ReceivedMessage事件加上代码:

Dim msg As String = e.Message
If
msg.StartsWith("?#") AndAlso msg.EndsWith("#?") Then '收到请求编辑信号
   
Dim Key As String = msg.SubString(2,msg.Length - 4)
    If tbrk.Containskey(Key) = False Then
'
如果无人编辑此行
        tbrk.Add(Key,e.UserName)
'
登记申请者为此行的编辑者
        e.ReturnValue = "OK"
'
通知申请者可以编辑
    ElseIf tbrk(Key) = e.UserName Then
'
如果申请者就是之前登记的编辑者
        e.ReturnValue = "OK"
'
通知申请者可以编辑
    Else
'
如果之前登记的编辑者为其他人
        e.ReturnValue = tbrk(Key) &
"
正在编辑此行!" '告知申请者是谁在编辑此行
   
End If
ElseIf
msg.StartsWith("!#") AndAlso msg.EndsWith("#!") Then '收到结束编辑信号
    Dim Key As String = msg.SubString(2,msg.Length - 4)
    If tbrk.Containskey(KeyThen
       
tbrk.Remove(Key) '从集合中移除此行的编辑登记
    End
If

End
If

上述代码的注释详尽明了,就不做解释了。

3、在服务端项目的OpenQQ服务端的UserLogout事件加上代码:

Dim Keys As New List(of String)
For
Each Key As String In tbrk.Keys
   If tbrk(Key) = e.UserName Then
      Keys.Add(Key)
    End
If

Next
For
Each Key As String In Keys
   
tbrk.Remove(Key)
Next

这样当有用户退出登录时,不管他是正常退出还是异常退出,都可以将字典中该用户的编辑登记移除,避免死锁。
客户端用户异常退出时,服务端的编辑登记的移除会有一个延时,时长取决于QQServer的
HeartbeatTimeout(心跳超时)属性。
上述编码有一个地方需要注意,我们不能在遍历字典或集合的过程中移除其成员(运行时会报错),所以我们用一个临时的集合Keys,先将要移除的键值保存在这个集合中,最后遍历这个集合中的键值,从字典tbrk中移除这些键值。

客户端的设计

1、在客户端项目的全局代码中,加入如下代码:

Public tbrk As new List(of String)

tbrk是一个集合,用于记录当前用户正在编辑的行,接下来编码的时候,我们用表名加上行的主键值来表示某行。

2、在客户端的对应表的StartEdit事件中编写代码:

Dim r As Row = e.Table.Current
If
r.DataRow.RowState = DataRowState.Added Then '新增行正常编辑
   
Return

End
If
If
QQClient.Ready = False Then '如果QQClient没有启动,则禁止编辑
    PopMessage(
"
必须启动QQClient,才能编辑此表数据!","提示",PopIconEnum.Infomation,5)
    e.Cancel = True
   
Return

End
If
Dim
key As String = e.Table.DataTable.Name  & ":" & r("_Identify")
If
tbrk.Contains(key) Then '如果 本人之前已经编辑此行,则正常编辑
   
Return

Else
'如果 本人之前没有编辑此行
    Dim msg = QQClient.SendWait("?#" & Key & "#?",5)
'
向服务器发送请求编辑信息
    If msg = "OK" Then
'
如果服务器返回OK
        tbrk.Add(key)
'
在本地登记正在编辑此行
    ElseIf msg > "" Then
'
否则显示服务器返回的信息,并取消编辑
        PopMessage(
"
无法编辑此行,因为:" & vbcrlf & msg ,"提示",PopIconEnum.Infomation,5)
        e.Cancel = True
    Else
'
如果服务器没有返回信息,则取消编辑
        PopMessage(
"
因服务器无响应,无法编辑此行!" ,"提示",PopIconEnum.Infomation,5)
       
e.Cancel = True
    End
If

End
If

3、在客户端的对应表的BeforeSaveDataRow事件中编写代码:

If e.DataRow.RowState=DataRowState.Modified  Then
    Dim Key As String = e.DataTable.Name & ":" & e.DataRow("_Identify")
    If tbrk.Contains(Key) Then
        tbrk.Remove(Key) '
移除本地编辑登记
        QQClient.Send("!#" & Key & "#!") '
通知服务器此行已经结束 编辑
   
End If
End
If

这样客户端在保存某行前,会从集合tbrk移除此行的在本地的编辑登记,并给服务端发送一个信息,通知服务端我已经结束编辑此行了,其他人可以开始编辑了。

代码似乎编写完毕,但是有一个漏洞
假定你双击某个单元格进入编辑状态,服务器已经登记你为此行的编辑者,如果你不做任何修改退出编辑状态,那么之后保存的时候,此行并不会触发BeforeSaveDataRow事件,以至于服务器始终认为你还在编辑此行。
所以接下来还需要完善。

4、在客户端对应表的AfterEdit事件中编写代码:

If e.Row.DataRow.RowState =DataRowState.Unchanged Then
    Dim Key As String = e.Table.DataTable.Name & ":" & e.Row("_Identify")
    If tbrk.Contains(Key) Then
        tbrk.Remove(Key)
       
QQClient.Send("!#" & Key & "#!")
    End
If

End
If

在用户结束编辑某单元格的时候,上述代码判断当前行是否已经修改过,如果没有修改,则移除此行在本地的编辑标记,并通知服务器此行的编辑已经结束,其他人可以编辑此行了。

至此,我们的独占式编辑功能已经完成。

各种可能的异常

我们看看,如果网络不稳定或客户端异常中断,会发生什么:

异常 后果 解决方法
服务器没有收到客户端发出的编辑请求 本次申请失败,对后续操作没有影响,任何用户都可以继续尝试编辑此行。  
客户端没有收到服务器发出的允许编辑信号 本次申请失败,但对本人的后续操作没有影响,不过其他用户对此行的编辑申请将被拒绝。 1、本人可以继续尝试编辑此行,编辑后保存,其他用户即可正常申请编辑此行了。
2、如果本人关闭项目或退出QQClient,那么其他用户也可以正常申请编辑此行了。
3、如果本人没有进行上述操作,但是其他人需要编辑此行,由于其他人在尝试编辑的时候,会知道是谁正在编辑此行,他可以通知你采取措施解除锁定。
服务端没有收到客户端发出的结束编辑信号 对本人的后续操作没有影响,不过其他用户对此行的编辑申请将被拒绝。 同上
客户端在编辑过程中异常退出 其他用户对此行的编辑申请将被拒绝。 1、本人可以重新打开项目,继续编辑此行,编辑后保存,其他用户即可正常申请编辑此行了。
2、本人可以重新打开项目,然后直接关闭,其他用户也可以正常申请编辑此行了。
3、即使本人异常退出后,不再打开项目,一段时间后,服务器也会自动清除此行的编辑标记,其他用户可以继续申请编辑此行,等待的时间由
QQServer的HeartbeatTimeout(心跳超时)属性决定。

可以看到,即使偶尔出现网络不稳定,即使异常退出,本方案也不会带来死锁这样难以接受的后果。

如果是窗口编辑

上述方案针对的是直接在表中编辑数据,如果不是在表中编辑,而是在窗口编辑,编码方式也是基本相同的。
通常只需将打开编辑窗口的代码改为:

Dim r As Row = Tables("B").Current
If
QQClient.Ready = False Then '如果QQClient没有启动,则禁止编辑
    PopMessage("
必须启动QQClient,才能编辑此表数据!","提示",PopIconEnum.Infomation,5)
    Return

End
If
If
r.DataRow.RowState <>  DataRowState.Added Then '如果不是新增行
    Dim key As String = "
B"  & ":" & r("_Identify")
    If tbrk.Contains(key) = False Then
        Dim msg = QQClient.SendWait("?#" & Key & "#?",5) '
向服务器发送请求编辑信息
        If msg = "OK" Then '
如果服务器返回OK
            tbrk.Add(key) '
在本地登记正在编辑此行
        ElseIf msg > "" Then '
否则显示服务器返回的信息,并取消编辑
            PopMessage("
无法编辑此行,因为:" & vbcrlf & msg ,"提示",PopIconEnum.Infomation,5)
            Return
        Else '
如果服务器没有返回信息,则取消编辑
           
PopMessage("因服务器无响应,无法编辑此行!" ,"提示",PopIconEnum.Infomation,5)
            Return
        End If
    End If

End
If
Forms
("编辑窗口").Open()


本页地址:http://www.foxtable.com/webhelp/topics/3007.htm