Bridge? 一个GIS二次开发中常用的设计模式

问题由来

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了。

附:VB 2005代码及UML图

类的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


 

posted on 2005-12-13 16:15  马维峰  阅读(3260)  评论(4编辑  收藏  举报