Using MOLE symbol-based graphics with interactive maps
MainForm.vb
' Copyright 2010 ESRI
' 
' All rights reserved under the copyright laws of the United States
' and applicable international laws, treaties, and conventions.
' 
' You may freely redistribute and use this sample code, with or
' without modification, provided you include the original copyright
' notice and use restrictions.
' 
' See the use restrictions.
' 


Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Drawing
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

Imports ESRI.ArcGIS.ADF
Imports ESRI.ArcGIS.Carto
Imports ESRI.ArcGIS.Controls
Imports ESRI.ArcGIS.DataSourcesFile
Imports ESRI.ArcGIS.DefenseSolutions
Imports ESRI.ArcGIS.Display
Imports ESRI.ArcGIS.esriSystem
Imports ESRI.ArcGIS.Geodatabase
Imports ESRI.ArcGIS.Geometry
Imports ESRI.ArcGIS.SystemUI
Imports ESRI.ArcGIS


Namespace MoleSymbols
  Public NotInheritable Partial Class MainForm
    Inherits Form

    #Region "class private members"


    Private m_currentMouseLocation As IPoint = Nothing
    Private m_firstTime As Boolean = True
    Private m_lastMouseClick As IPoint = New PointClass()
    Private m_mapControl As IMapControl3 = Nothing
    Private m_moveFeedBack As IDisplayFeedback = Nothing
    Private m_random As New Random()
    Private m_selectedBounds As IEnvelope = New EnvelopeClass()
    Private m_sic As New DemoSIC()
    Private m_dragStartTime As Integer = 0
    Private m_unitCount As Integer = 0


    #End Region

    #Region "Constructor"


        Public Sub New()
            InitializeComponent()

            m_lastMouseClick.PutCoords(0, 0)
            UpdateStatusBar()
            UpdateTitle()
        End Sub


    #End Region

    #Region "Tool strip button event handlers"


        Private Sub tsbAdd200_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbAdd200.Click
            ' this one takes a little while, especially when it's the first one chosen
            Cursor = Cursors.WaitCursor

            ' create concentric rings of units centered around where the user last clicked
            Dim centerLon As Double = m_lastMouseClick.X
            Dim centerLat As Double = m_lastMouseClick.Y
            Const circleRadiusInRad As Double = 1
            Const numberPerCircle As Integer = 10
            For i As Integer = 0 To 199
                ' draw a random symbol at the next position in the pattern
                Dim currentRadius As Double = (i / numberPerCircle) * circleRadiusInRad + circleRadiusInRad
                Dim currentAngle As Double = (i Mod numberPerCircle) * 2 * 3.1415926536 / CDbl(numberPerCircle)
                DrawSymbol(CreatePoint(centerLon + (currentRadius * Math.Sin(currentAngle)), centerLat + (currentRadius * Math.Cos(currentAngle))), m_sic(m_random.Next()), True)
            Next
            'refresh the display and restore the cursor
            axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGraphics, Nothing, Nothing)
            Cursor = Cursors.Default
        End Sub


        Private Sub tsbAddArea_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbAddArea.Click
            ' create the symbol using a default symbol ID code (obstacle restricted area)
            Dim moleSymbol As IMoleSymbol = New MoleFillSymbolClass()
            moleSymbol.SymbolID = "GUMPOGR-------X"
            moleSymbol.TextLabels = GetLabels()

            ' override the default fill color and outline symbol - these settings are optional
            'Dim lineSymbol As ILineSymbol = New SimpleLineSymbolClass()
            'lineSymbol.Color = GetRandomColor()
            'lineSymbol.Width = dRandom(1, 5)
            'Dim fillSymbol As IFillSymbol = TryCast(moleSymbol, IFillSymbol)
            'fillSymbol.Outline = lineSymbol
            'fillSymbol.Color = GetRandomColor()

            ' create a new polygon geometry for this symbol (four points in this example)
            Dim pointCollection As IPointCollection = New PolygonClass()

            ' center the polygon somewhere inside the current map extent
            Dim extent As IEnvelope = m_mapControl.ActiveView.Extent
            Dim lat As Double = dRandom(extent.YMin, extent.YMax)
            Dim lon As Double = dRandom(extent.XMin, extent.XMax)

            ' place the four corners somewhere within a specified threshold of the center
            Const threshold As Double = 20
            Dim missing As Object = Type.Missing
            pointCollection.AddPoint(CreatePoint(lon, dRandom(lat, lat + threshold)), missing, missing)
            pointCollection.AddPoint(CreatePoint(dRandom(lon, lon + threshold), lat), missing, missing)
            pointCollection.AddPoint(CreatePoint(lon, dRandom(lat - threshold, lat)), missing, missing)
            pointCollection.AddPoint(CreatePoint(dRandom(lon - threshold, lon), lat), missing, missing)

            ' set up the graphic element with the random geometry
            Dim fillShapeElement As IFillShapeElement = New PolygonElementClass()
            fillShapeElement.Symbol = TryCast(moleSymbol, IFillSymbol)
            Dim element As IElement = TryCast(fillShapeElement, IElement)
            element.Geometry = TryCast(pointCollection, IGeometry)

            ' add the new element to the map and update the user interface
            m_mapControl.ActiveView.GraphicsContainer.AddElement(element, 0)
            m_mapControl.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGraphics, Nothing, Nothing)
            m_unitCount += 1
            UpdateTitle()
        End Sub


        Private Sub tsbAddLine_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbAddLine.Click
            ' create the symbol using a default symbol ID code (fix task line)
            Dim moleSymbol As IMoleSymbol = New MoleLineSymbolClass()
            moleSymbol.SymbolID = "GUTPF---------X"
            moleSymbol.TextLabels = GetLabels()

            ' override the default line color and width - these settings are optional
            'Dim lineSymbol As ILineSymbol = TryCast(moleSymbol, ILineSymbol)
            'lineSymbol.Color = GetRandomColor()
            'lineSymbol.Width = dRandom(1, 5)

            ' create a new line geometry for the symbol - this symbol requires two points
            Dim pointCollection As IPointCollection = New PolylineClass()

            ' place the first endpoint of the line somewhere inside the current map extent
            Dim ext As IEnvelope = m_mapControl.ActiveView.Extent
            Dim lat As Double = dRandom(ext.YMin, ext.YMax)
            Dim lon As Double = dRandom(ext.XMin, ext.XMax)
            Dim missing As Object = Type.Missing
            pointCollection.AddPoint(CreatePoint(lon, lat), missing, missing)

            ' place the second endpoint somewhere within a specified threshold of the first
            Const threshold As Double = 20
            pointCollection.AddPoint(CreatePoint(dRandom(lon - threshold, lon + threshold), dRandom(lat - threshold, lat + threshold)), missing, missing)
            ' set up the graphic element with the random geometry
            Dim lineElement As ILineElement = New LineElementClass()
            lineElement.Symbol = TryCast(moleSymbol, ILineSymbol)
            Dim element As IElement = TryCast(lineElement, IElement)
            element.Geometry = TryCast(pointCollection, IGeometry)

            ' add the new element to the map and update the user interface
            m_mapControl.ActiveView.GraphicsContainer.AddElement(element, 0)
            m_mapControl.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGraphics, Nothing, Nothing)
            m_unitCount += 1
            UpdateTitle()
        End Sub


        Private Sub tsbAddMoleSymbol_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbAddMoleSymbol.Click
            ' make this TSB exclusive both in the tool strip and in the AxToolbarControl
            ' the primary logic is in axMapControl1_OnMouseDown
            If tsbAddMoleSymbol.Checked Then
                axToolbarControl1.SetBuddyControl(Nothing)
                axToolbarControl1.Enabled = False
                tsbSelect.Checked = False
            Else
                axToolbarControl1.SetBuddyControl(axMapControl1)
                axToolbarControl1.Enabled = True
            End If
        End Sub


        Private Sub tsbMoveUnits_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbMoveUnits.Click
            ' MoveGraphics only applies to units in the selection - this will erase any previous selection
            Dim graphicsContainerSelect As IGraphicsContainerSelect = TryCast(m_mapControl.ActiveView.GraphicsContainer, IGraphicsContainerSelect)
            graphicsContainerSelect.SelectAllElements()
            MoveGraphics(0.75, 0.75)
            graphicsContainerSelect.UnselectAllElements()
            m_selectedBounds.SetEmpty()
        End Sub


        Private Sub tsbSelect_Click(ByVal sender As Object, ByVal e As EventArgs) Handles tsbSelect.Click
            ' make this TSB exclusive both in the tool strip and in the AxToolbarControl
            ' the primary logic is in axMapControl1_OnMouseDown and axMapControl1_OnMouseMove
            If tsbSelect.Checked Then
                axToolbarControl1.SetBuddyControl(Nothing)
                axToolbarControl1.Enabled = False
                tsbAddMoleSymbol.Checked = False
            Else
                axToolbarControl1.SetBuddyControl(axMapControl1)
                axToolbarControl1.Enabled = True
            End If
        End Sub


    #End Region

    #Region "Form event handlers"

        Private Function GetSdkDataPath() As String
            'get the ArcGIS path from the registry
            Dim key As Microsoft.Win32.RegistryKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SOFTWARE\ESRI\ArcGIS_SXS_SDK")
            Dim path As String = Convert.ToString(key.GetValue("InstallDir"))

            'set the of the logo
            Dim str As String = System.IO.Path.Combine(path, "Samples\data\")
            If (Not System.IO.Directory.Exists(str)) Then
                MessageBox.Show("Path :" & str & " does not exist!")
                Return String.Empty
            End If

            Return str
        End Function

        Private Sub MainForm_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
            ' get a reference to the MapControl for local use
            m_mapControl = DirectCast(axMapControl1.Object, IMapControl3)

            ' get a map from the SDK sample data
            Dim dataPath As String = GetSdkDataPath() + "MilitaryOverlayEditor\"
            Dim defaultMxDoc As String = dataPath & "molebasemap.mxd"
            If m_mapControl.CheckMxFile(defaultMxDoc) Then
                ' load the map into the map control
                Dim missing As Object = System.Reflection.Missing.Value
                m_mapControl.LoadMxFile(defaultMxDoc, missing, missing)
            Else
                Dim errorMsg As String = "Could not load default map document - Application may not work!"
                errorMsg &= Environment.NewLine & defaultMxDoc
                Trace.WriteLine(errorMsg)
                MessageBox.Show(errorMsg)
            End If
        End Sub


        Private Sub MainForm_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles MyBase.FormClosing
            ' tell MOLE to save its settings and release its resources
            Dim helper As IMoleCoreHelper = New MoleCoreHelperClass()
            helper.ReleaseForceElementRenderer()
        End Sub


    #End Region

    #Region "Map control event handlers"


        Private Sub axMapControl1_OnMapReplaced(ByVal sender As Object, ByVal e As IMapControlEvents2_OnMapReplacedEvent) Handles axMapControl1.OnMapReplaced
            ' initialize the last mouse click point to the center of the map's extent
            Dim extent As IEnvelope = m_mapControl.ActiveView.Extent
            m_lastMouseClick.PutCoords(extent.XMin + extent.Width * 0.5, extent.YMin + extent.Height * 0.5)
            ' update the status bar and the title bar
            m_unitCount = 0
            UpdateStatusBar()
            UpdateTitle()
        End Sub


        Private Sub axMapControl1_OnMouseDown(ByVal sender As Object, ByVal e As IMapControlEvents2_OnMouseDownEvent) Handles axMapControl1.OnMouseDown
            m_lastMouseClick.PutCoords(e.mapX, e.mapY)
            If tsbAddMoleSymbol.Checked Then
                ' "Add MOLE Symbol" command:  draw a symbol at the click point
                DrawSymbol(m_lastMouseClick, tstSIC.Text, False)
                tstSIC.Text = m_sic(m_random.Next())
            ElseIf tsbSelect.Checked AndAlso SelectElements(m_lastMouseClick, m_mapControl.ActiveView, m_selectedBounds) = True Then
                ' "Select & Drag Graphics" command:  initialize mouse tracking to move the selected elements
                Trace.WriteLine("Start tracking at (" & m_lastMouseClick.X & ", " & m_lastMouseClick.Y & ")")

                ' the envelope feedback draws a rectangle of the selected elements' extent following the mouse
                Dim moveEnvelopeFeedback As IMoveEnvelopeFeedback = New MoveEnvelopeFeedback()
                moveEnvelopeFeedback.Start(m_selectedBounds, m_lastMouseClick)
                m_moveFeedBack = TryCast(moveEnvelopeFeedback, IDisplayFeedback)
                m_moveFeedBack.Display = axMapControl1.ActiveView.ScreenDisplay

                ' the tick count is used to filter out short, unintentional mouse drags
                m_dragStartTime = Environment.TickCount
            End If
        End Sub


        Private Sub axMapControl1_OnMouseMove(ByVal sender As Object, ByVal e As IMapControlEvents2_OnMouseMoveEvent) Handles axMapControl1.OnMouseMove
            ' update the current map location of the mouse
            If m_currentMouseLocation Is Nothing Then
                m_currentMouseLocation = New PointClass()
            End If
            m_currentMouseLocation.PutCoords(e.mapX, e.mapY)
            UpdateStatusBar()

            ' "Select & Drag Graphics" command:  move the feedback graphic
            If tsbSelect.Checked AndAlso e.button = 1 AndAlso m_moveFeedBack IsNot Nothing Then
                m_moveFeedBack.MoveTo(m_currentMouseLocation)
            End If
        End Sub


        Private Sub axMapControl1_OnMouseUp(ByVal sender As Object, ByVal e As IMapControlEvents2_OnMouseUpEvent) Handles axMapControl1.OnMouseUp
            If m_moveFeedBack IsNot Nothing Then
                ' stop the feedback graphic and save its geometry for future use
                m_selectedBounds = DirectCast(m_moveFeedBack, IMoveEnvelopeFeedback).Stop()
                m_moveFeedBack = Nothing
                Dim endTicks As Integer = Environment.TickCount - m_dragStartTime
                If endTicks > 250 AndAlso m_currentMouseLocation IsNot Nothing Then
                    ' only update the graphics if the minimum move time has elapsed
                    Trace.WriteLine("drag start = (" & m_lastMouseClick.X & ", " & m_lastMouseClick.Y & ")")
                    Trace.WriteLine("drag end = (" & m_currentMouseLocation.X & ", " & m_currentMouseLocation.Y & ")")
                    MoveGraphics(m_currentMouseLocation.X - m_lastMouseClick.X, m_currentMouseLocation.Y - m_lastMouseClick.Y)
                End If
            End If
        End Sub


    #End Region

    #Region "Helper Methods"


        Private Function CreatePoint(ByVal x As Double, ByVal y As Double) As IPoint
            ' create a new point instance and initialize its coordinates
            Dim point As IPoint = New PointClass()
            point.PutCoords(x, y)
            Return point
        End Function


    Private Sub DrawSymbol(ByVal location As IPoint, ByVal sic As String, ByVal suppressRefresh As Boolean)
      ' the first time we create a symbol, display the wait cursor while MOLE loads up
      Dim previousCursor As System.Windows.Forms.Cursor = Cursor
      If m_firstTime Then
        Cursor = Cursors.WaitCursor
            End If

            ' set up a MOLE symbol for the new graphic; minimally validate the symbol ID code
      Dim moleSymbol As IMoleSymbol = New MoleMarkerSymbolClass()
      If sic.Length = 15 Then
        moleSymbol.SymbolID = sic
      End If
      moleSymbol.TextLabels = GetLabels()

            ' to remove the symbol's fill, uncomment this code
            'Dim moleMarkerSymbol As IMoleMarkerSymbol = TryCast(moleSymbol, IMoleMarkerSymbol)
            'moleMarkerSymbol.ShowFill = False

      ' initialize the marker symbol properties
      Dim markerSymbol As IMarkerSymbol = TryCast(moleSymbol, IMarkerSymbol)
      Dim size As Double
            If Double.TryParse(tstSize.Text, size) Then
                markerSymbol.Size = size
            Else
                markerSymbol.Size = 48
            End If

      ' create the graphic element for the marker symbol and add it to the map
      Dim markerElement As IMarkerElement = New MarkerElementClass()
      markerElement.Symbol = markerSymbol
      Dim element As IElement = TryCast(markerElement, IElement)
      element.Geometry = TryCast(location, IGeometry)
      m_mapControl.ActiveView.GraphicsContainer.AddElement(element, 0)
      If Not suppressRefresh Then
        m_mapControl.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGraphics, Nothing, Nothing)
      End If

      ' update the user interface
      If m_firstTime Then
        Cursor = previousCursor
        m_firstTime = False
      End If
      m_unitCount += 1
      UpdateTitle()
    End Sub


    Private Function dRandom(ByVal low As Double, ByVal high As Double) As Double
      ' generate a random floating-point number within the indicated range [low, high)
      Return low + m_random.NextDouble() * (high - low)
    End Function


    Private Function GetLabels() As IPropertySet
      Dim labelSet As IPropertySet = New PropertySetClass()

      ' all of the below are supported - comment and uncomment to experiment
      labelSet.SetProperty("Name", "Name")
      labelSet.SetProperty("Comment", "Comment")
      'labelSet.SetProperty ("Parent", "Parent");
      'labelSet.SetProperty ("Info", "Info");
      'labelSet.SetProperty ("Strength", "Strength");
      'labelSet.SetProperty ("EvalRating", "EvalRating");
      'labelSet.SetProperty ("Location", "Location");
      'labelSet.SetProperty ("Alt_Depth", "Alt_Depth");
      'labelSet.SetProperty ("Speed", "Speed");
      'labelSet.SetProperty ("DTG", "DTG");
      'labelSet.SetProperty ("HQ", "HQ");
      'labelSet.SetProperty ("Quantity", "Quantity");
      'labelSet.SetProperty ("EType", "EType");
      'labelSet.SetProperty ("Effective", "Effective");
      'labelSet.SetProperty ("Signature", "Signature");
      'labelSet.SetProperty ("IFFSIF", "IFFSIF");

      Return labelSet
    End Function


    Private Function GetRandomColor() As IColor
      ' create a random opaque RGB color
      Dim rgb As IRgbColor = New RgbColorClass()
            rgb.Red = m_random.Next(0, 255)
            rgb.Green = m_random.Next(0, 255)
            rgb.Blue = m_random.Next(0, 255)
      Return TryCast(rgb, IColor)
    End Function


        Private Sub MoveGraphics(ByVal deltaX As Double, ByVal deltaY As Double)
            Try
                ' move all selected graphics along a delta (change) vector
                Trace.WriteLine("moving delta = (" & deltaX & ", " & deltaY & ")")

                ' get reference to graphics container and its selected elements
                Dim graphicsContainer As IGraphicsContainer = axMapControl1.ActiveView.GraphicsContainer
                Dim graphicsContainerSelect As IGraphicsContainerSelect = TryCast(graphicsContainer, IGraphicsContainerSelect)
                Dim enumElement As IEnumElement = graphicsContainerSelect.SelectedElements

                ' iterate through the selected elements
                enumElement.Reset()
                Dim element As IElement = enumElement.Next()
                While element IsNot Nothing
                    ' apply the delta vector to each element's geometry and update it the container
                    Dim geometry As IGeometry = element.Geometry
                    TryCast(geometry, ITransform2D).Move(deltaX, deltaY)
                    element.Geometry = geometry
                    graphicsContainer.UpdateElement(element)
                    element = enumElement.Next()
                End While
                ' refresh the active view
                axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGraphics, Nothing, Nothing)
            Catch ex As Exception
                MessageBox.Show("Exception:  " & ex.GetBaseException().ToString(), "MoveGraphics")
            End Try
        End Sub


        Private Function SelectElements(ByVal point As IPoint, ByVal activeView As IActiveView, ByVal selectedBounds As IEnvelope) As Boolean
            ' this function is written in such a way that it should be pastable
            Trace.WriteLine("selecting graphics near (" & point.X & ", " & point.Y & ")")

            Dim graphicsContainer As IGraphicsContainer = activeView.GraphicsContainer
            Dim graphicsContainerSelect As IGraphicsContainerSelect = TryCast(graphicsContainer, IGraphicsContainerSelect)
            Dim screenDisplay As IScreenDisplay = activeView.ScreenDisplay
            Dim selected As Boolean = False
            Dim refreshRequired As Boolean = False

            ' start with a precise search, and then widen the tolerance if nothing is found
            ' (you may need to change these tolerances if using this code in your own application)
            Dim enumElement As IEnumElement = graphicsContainer.LocateElements(point, 0.0000001)
            If enumElement Is Nothing Then
                enumElement = graphicsContainer.LocateElements(point, 0.5)
            End If

            ' if no elements were selected
            If enumElement Is Nothing Then
                ' if the previous selection is nonempty
                If graphicsContainerSelect.ElementSelectionCount > 0 Then
                    ' clear the selection and refresh the display
                    Trace.WriteLine("clearing selection")
                    graphicsContainerSelect.UnselectAllElements()
                    selectedBounds.SetEmpty()
                    refreshRequired = True
                    ' else do nothing
                End If
            Else
                ' get the extent of the selected elements
                Dim envelope As IEnvelope = New EnvelopeClass()
                enumElement.Reset()
                Dim element As IElement = enumElement.Next()
                While element IsNot Nothing
                    ' establish selectedBounds as the extent of all selected elements
                    element.QueryBounds(screenDisplay, envelope)
                    selectedBounds.Union(envelope)
                    element = enumElement.Next()
                End While
                ' add all the newly selected elements to the graphics container's selection
                enumElement.Reset()
                graphicsContainerSelect.SelectElements(enumElement)
                refreshRequired = True
                selected = True
            End If
            ' refresh the display if anything has changed
            If refreshRequired Then
                activeView.PartialRefresh(esriViewDrawPhase.esriViewGraphics, Nothing, Nothing)
            End If

            ' return true if any elements on the display are currently selected; selectedBounds has their extent
            Return selected
        End Function


    Private Sub UpdateStatusBar()
      If m_mapControl IsNot Nothing Then
        ' put the MXD name and current mouse location in the status bar, if available
        Dim documentFileName As String = System.IO.Path.GetFileName(m_mapControl.DocumentFilename)
                If documentFileName Is Nothing OrElse documentFileName = String.Empty Then
                    documentFileName = "<no map>"
                End If
        If m_currentMouseLocation Is Nothing Then
          statusBarXY.Text = documentFileName
        Else
                    statusBarXY.Text = String.Format("{0}:  {1}, {2} {3}", documentFileName, m_currentMouseLocation.X.ToString("#######.##"), m_currentMouseLocation.Y.ToString("#######.##"), axMapControl1.MapUnits.ToString().Substring(4))
        End If
      End If
    End Sub


    Private Sub UpdateTitle()
      ' put the number of symbols in the title bar, when there are any
      Dim title As String = "MOLE Symbols"
      If m_unitCount = 1 Then
                title &= " (" & m_unitCount & " unit)"
            ElseIf m_unitCount > 1 Then
                title &= " (" & m_unitCount & " units)"
      End If
      Text = title
    End Sub


    #End Region
  End Class
End Namespace