用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(Key)
Then
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()