GIS二次开发中经常需要写很多简单操作的重复代码,小到一般的一般的放大缩小,大到类似MapX的自定义工具(UserTools),或者SuperMap Object中的一些分析功能,这些功能的实现散布在程序的不同过程和事件,不仅每次书写麻烦,而且不易维护。在《应用Visual Basic的事件机制设计可复用的大粒度GIS组件》一文中,笔者提出了使用委托模式(非.net的委托)和事件机制,将这些功能设计为一个独立的自定义控件(User Control)或者类模块的思路,本文将以MapX的自定义工具为例来说明这个问题。
MapX中可以通过自定义工具来完成一些控件本身没有提供的功能,例如距离的量测。要自定义一个工具,必须首先调用MapX的CreateCustomTool方法,然后在MapX的不同事件中来写一些处理代码,来完成本工作,例如距离量测,可以通过自定义一个线工具:
Map1.CreateCustomTool RulerToolID, miToolTypeLine, miSizeAllCursor
然后在MouseMove事件中书写正在变化的距离代码:
If Button = 1 And Map1.CurrentTool = RulerToolID Then
Dim X2 As Double
Dim Y2 As Double
Map1.ConvertCoord X, Y, X2, Y2, miScreenToMap
sbStatusBar.SimpleText = Map1.Distance(MouseDownX1, MouseDownY1, X2, Y2)
End If
最后在ToolUsed事件中书写最终的距离代码:
If ToolNum = RulerToolID Then
sbStatusBar.SimpleText = ""
MsgBox "Distance: " & Map1.Distance(X1, Y1, X2, Y2)
End If
这样做本身没有什么问题,问题是如果有很多业务有关的地图交互代码也必须写在这里,例如在地图上单击定点后弹出对话框,输入相应的信息这样的代码,那么代码就成了很长的If…ElseIf语句的序列,修改维护非常不方便。我们以距离量测为例,来说明如何使用委托的方法,来将这些代码独立成一个类模块。其思路和《应用Visual Basic的事件机制设计可复用的大粒度GIS组件》一文相同。
首先需要定义需要的变量和对象、事件;需要注意使用WithEvents定义MapX对象,以响应其事件,然后定义一个Connect方法,可以将对象的实例传给这个对象,并做必要的初始化。代码如下:
Private Const m_RulerToolID = 104
Private WithEvents m_MapX As MapXLib.Map
Public Event PolyRulerToolDistanceChanged(Distance As Double)
Public Event PolyRulerToolUsed(Distance As Double)
Public Property Get RulerToolID() As Long
RulerToolID = m_RulerToolID
End Property
Public Function Connect(MapX As MapXLib.Map) As Boolean
If MapX Is Nothing Then
Connect = False
Else
Set m_MapX = MapX
m_MapX.CreateCustomTool m_RulerToolID, miToolTypeLine, miSizeAllCursor
End If
End Function
接着在处理其具体操作,例如画线时鼠标移动来改变其当前距离,以及最后的距离:
Private Sub m_MapX_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
If Button = 1 And m_MapX.CurrentTool = m_RulerToolID Then
Dim X2 As Double
Dim Y2 As Double
Dim Dis As Double
m_MapX.ConvertCoord X, Y, X2, Y2, miScreenToMap
Dis = m_MapX.Distance(m_X1, m_Y1, X2, Y2)
RaiseEvent RulerToolDistanceChanged(Dis)
End If
End Sub
Private Sub m_MapX_ToolUsed(ByVal ToolNum As Integer, ByVal X1 As Double, ByVal Y1 As Double, ByVal X2 As Double, ByVal Y2 As Double, ByVal Distance As Double, ByVal Shift As Boolean, ByVal Ctrl As Boolean, EnableDefault As Boolean)
If ToolNum = m_RulerToolID Then
RaiseEvent RulerToolUsed(m_MapX.Distance(X1, Y1, X2, Y2))
End If
End Sub
这样就完成了自定义工具的一个类模块的设计,模块使用如下:
定义:Private WithEvents RulerTool As MapXRulerTool
在Form_Load中实例化:
Set RulerTool = New MapXRulerTool
RulerTool.Connect Me.MapX
处理其事件:
Private Sub RulerTool_RulerToolDistanceChanged(Distance As Double)
frmMain.CommandBars.StatusBar.IdleText = Distance
End Sub
Private Sub RulerTool_RulerToolUsed(Distance As Double)
MsgBox Distance
End Sub
这种方式和思路和Bridge模式类似,但Bridge是将实现分离出去,又有些差别,事件机制本身又是Observer,因此简单的说使用委托机制可能更类似,欢迎批评指正,以提高我们的设计水平。
代码本身使用的是VB 6,但对其他版本和语言应该是类似的,这些天做项目在使用这个,所以文中代码也就用VB了。
类的UML图:
代码:
Public Class MapXRulerTool
Private Const _rulerToolID As Integer = 104
Private Const _polyRulerToolID As Integer = 105
Private WithEvents _mapX As AxMapXLib.AxMap
Private _x1 As Double
Private _y1 As Double
Public Event RulerToolDistanceChanged(ByVal Distance As Double)
Public Event RulerToolUsed(ByVal Distance As Double)
Public Event PolyRulerToolDistanceChanged(ByVal Distance As Double)
Public Event PolyRulerToolUsed(ByVal Distance As Double)
Public ReadOnly Property RulerToolID() As MapXLib.ToolConstants
Get
RulerToolID = CType(_rulerToolID, MapXLib.ToolConstants)
End Get
End Property
Public ReadOnly Property PolyRulerToolID() As MapXLib.ToolConstants
Get
PolyRulerToolID = CType(_polyRulerToolID, MapXLib.ToolConstants)
End Get
End Property
Public Function Connect(ByVal MapX As AxMapXLib.AxMap) As Boolean
If MapX Is Nothing Then
Connect = False
Else
_mapX = MapX
_mapX.CreateCustomTool(_rulerToolID, MapXLib.ToolTypeConstants.miToolTypeLine, _
MapXLib.CursorConstants.miSizeAllCursor)
_mapX.CreateCustomTool(_polyRulerToolID, MapXLib.ToolTypeConstants.miToolTypePoly, _
MapXLib.CursorConstants.miSizeAllCursor)
End If
End Function
Private Sub _mapX_MouseDownEvent(ByVal sender As Object, ByVal e As AxMapXLib.CMapXEvents_MouseDownEvent) Handles _mapX.MouseDownEvent
If e.button = 1 And _mapX.CurrentTool = _rulerToolID Then
' Place the current screen coordinates in MouseDownX1 and MouseDownY1
' Since these points will be used in the Map.Distance call, they
' must be in map coordinates, not screen coordinates
_mapX.ConvertCoord(e.x, e.y, _x1, _y1, MapXLib.ConversionConstants.miScreenToMap)
End If
End Sub
Private Sub _mapX_MouseMoveEvent(ByVal sender As Object, ByVal e As AxMapXLib.CMapXEvents_MouseMoveEvent) Handles _mapX.MouseMoveEvent
If e.button = 1 And _mapX.CurrentTool = _rulerToolID Then
Dim X2 As Double
Dim Y2 As Double
Dim Dis As Double
_mapX.ConvertCoord(e.x, e.y, X2, Y2, MapXLib.ConversionConstants.miScreenToMap)
Dis = _mapX.Distance(_x1, _y1, X2, Y2)
RaiseEvent RulerToolDistanceChanged(Dis)
End If
End Sub
Private Sub _mapX_PolyToolUsed(ByVal sender As Object, ByVal e As AxMapXLib.CMapXEvents_PolyToolUsedEvent) Handles _mapX.PolyToolUsed
If e.toolNum = _polyRulerToolID Then
Dim i As Integer
Dim DistanceSoFar As Double
DistanceSoFar = 0.0#
Dim mPoints As New MapXLib.Points
mPoints = CType(e.points, MapXLib.Points)
' Find the total distance by adding up each of the line segment distances
If mPoints.Count > 1 Then
For i = 2 To mPoints.Count
DistanceSoFar = DistanceSoFar + _
_mapX.Distance(mPoints.Item(i).X, mPoints.Item(i).Y, _
mPoints.Item(i - 1).X, mPoints.Item(i - 1).Y)
Next
End If
' Now, we have the total distance along the polyline
' If the user is done with the poly-ruler tool, show this distance
' in a message box. Otherwise, just show it in the status bar.
If e.flags = MapXLib.PolyToolFlagConstants.miPolyToolEnd Then
RaiseEvent PolyRulerToolUsed(DistanceSoFar)
Else
RaiseEvent PolyRulerToolDistanceChanged(DistanceSoFar)
End If
End If
End Sub
Private Sub _mapX_ToolUsed(ByVal sender As Object, ByVal e As AxMapXLib.CMapXEvents_ToolUsedEvent) Handles _mapX.ToolUsed
If e.toolNum = _rulerToolID Then
RaiseEvent RulerToolUsed(_mapX.Distance(e.x1, e.y1, e.x2, e.y2))
End If
End Sub
End Class