Using MOLE symbol-based graphics with interactive maps
MainForm.cs
// 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.
// 

using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;

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


namespace MoleSymbols
{
  public sealed partial class MainForm : Form
  {

    #region class private members


    private IPoint           m_currentMouseLocation = null;
    private int              m_dragStartTime        = 0;
    private bool             m_firstTime            = true;
    private IPoint           m_lastMouseClick       = new PointClass();
    private IMapControl3     m_mapControl           = null;
    private IDisplayFeedback m_moveFeedBack         = null;
    private Random           m_random               = new Random();
    private IEnvelope        m_selectedBounds       = new EnvelopeClass();
    private DemoSIC          m_sic                  = new DemoSIC();
    private int              m_unitCount            = 0;


    #endregion
    
    #region Constructor
    
    
    public MainForm()
    {
      InitializeComponent();
      
      m_lastMouseClick.PutCoords (0, 0);
      UpdateStatusBar();
      UpdateTitle();
    }
    
    
    #endregion
    
    #region Tool strip button event handlers
    
    
    private void tsbAdd200_Click (object sender, EventArgs e)
    {
      // 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
      double centerLon = m_lastMouseClick.X;
      double centerLat = m_lastMouseClick.Y;
      const double circleRadiusInRad = 1.0;
      const int numberPerCircle = 10;
      for ( int i = 0; i < 200; ++i )
      {
        // draw a random symbol at the next position in the pattern
        double currentRadius = (i / numberPerCircle) * circleRadiusInRad + circleRadiusInRad;
        double currentAngle = (i % numberPerCircle) *  2.0 * 3.1415926536 / (double)numberPerCircle;
        DrawSymbol (
          CreatePoint(
            centerLon + (currentRadius * Math.Sin(currentAngle)),
            centerLat + (currentRadius * Math.Cos(currentAngle))
          ),
          m_sic[m_random.Next()],
          true
        );
      }
      //refresh the display and restore the cursor
      axMapControl1.ActiveView.PartialRefresh (esriViewDrawPhase.esriViewGraphics, null, null);
      Cursor = Cursors.Default;
    }


    private void tsbAddArea_Click (object sender, EventArgs e)
    {
      // create the symbol using a default symbol ID code (obstacle restricted area)
      IMoleSymbol moleSymbol = new MoleFillSymbolClass();
      moleSymbol.SymbolID = "GUMPOGR-------X";
      moleSymbol.TextLabels = GetLabels();

      // override the default fill color and outline symbol - these settings are optional
      //ILineSymbol lineSymbol = new SimpleLineSymbolClass();
      //lineSymbol.Color = GetRandomColor();
      //lineSymbol.Width = dRandom (1, 5);
      //IFillSymbol fillSymbol = moleSymbol as IFillSymbol;
      //fillSymbol.Outline = lineSymbol;
      //fillSymbol.Color = GetRandomColor();

      // create a new polygon geometry for this symbol (four points in this example)
      IPointCollection pointCollection = new PolygonClass();

      // center the polygon somewhere inside the current map extent
      IEnvelope extent = m_mapControl.ActiveView.Extent;
      double lat = dRandom (extent.YMin, extent.YMax);
      double lon = dRandom (extent.XMin, extent.XMax);
      
      // place the four corners somewhere within a specified threshold of the center
      const double threshold = 20;
      object missing = Type.Missing;
      pointCollection.AddPoint (CreatePoint(lon, dRandom(lat, lat + threshold)), ref missing, ref missing);
      pointCollection.AddPoint (CreatePoint(dRandom(lon, lon + threshold), lat), ref missing, ref missing);
      pointCollection.AddPoint (CreatePoint(lon, dRandom(lat - threshold, lat)), ref missing, ref missing);
      pointCollection.AddPoint (CreatePoint(dRandom(lon - threshold, lon), lat), ref missing, ref missing);

      // set up the graphic element with the random geometry
      IFillShapeElement fillShapeElement = new PolygonElementClass();
      fillShapeElement.Symbol = moleSymbol as IFillSymbol;
      IElement element = fillShapeElement as IElement;
      element.Geometry = pointCollection as 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, null, null);
      ++m_unitCount;
      UpdateTitle();
    }


    private void tsbAddLine_Click (object sender, EventArgs e)
    {
      // create the symbol using a default symbol ID code (fix task line)
      IMoleSymbol moleSymbol = new MoleLineSymbolClass();
      moleSymbol.SymbolID = "GUTPF---------X";
      moleSymbol.TextLabels = GetLabels();

      // override the default line color and width - these settings are optional
      //ILineSymbol lineSymbol = moleSymbol as ILineSymbol;
      //lineSymbol.Color = GetRandomColor();
      //lineSymbol.Width = dRandom (1, 5);
      
      // create a new line geometry for the symbol - this symbol requires two points
      IPointCollection pointCollection = new PolylineClass();

      // place the first endpoint of the line somewhere inside the current map extent
      IEnvelope ext = m_mapControl.ActiveView.Extent;
      double lat = dRandom (ext.YMin, ext.YMax);
      double lon = dRandom (ext.XMin, ext.XMax);
      object missing = Type.Missing;
      pointCollection.AddPoint (CreatePoint(lon, lat), ref missing, ref missing);

      // place the second endpoint somewhere within a specified threshold of the first
      const double threshold = 20;
      pointCollection.AddPoint (
        CreatePoint(
          dRandom(lon - threshold, lon + threshold),
          dRandom(lat - threshold, lat + threshold)
        ),
        ref missing,
        ref missing
      );
      // set up the graphic element with the random geometry
      ILineElement lineElement = new LineElementClass();
      lineElement.Symbol = moleSymbol as ILineSymbol;
      IElement element = lineElement as IElement;
      element.Geometry = pointCollection as 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, null, null);
      ++m_unitCount;
      UpdateTitle();
    }


    private void tsbAddMoleSymbol_Click (object sender, EventArgs e)
    {
      // make this TSB exclusive both in the tool strip and in the AxToolbarControl
      // the primary logic is in axMapControl1_OnMouseDown
      if ( tsbAddMoleSymbol.Checked )
      {
        axToolbarControl1.SetBuddyControl (null);
        axToolbarControl1.Enabled = false;
        tsbSelect.Checked = false;
      }
      else
      {
        axToolbarControl1.SetBuddyControl (axMapControl1);
        axToolbarControl1.Enabled = true;
      }
    }


    private void tsbMoveUnits_Click (object sender, EventArgs e)
    {
      // MoveGraphics only applies to units in the selection - this will erase any previous selection
      IGraphicsContainerSelect graphicsContainerSelect = m_mapControl.ActiveView.GraphicsContainer as IGraphicsContainerSelect;
      graphicsContainerSelect.SelectAllElements();
      MoveGraphics (0.75, 0.75);
      graphicsContainerSelect.UnselectAllElements();
      m_selectedBounds.SetEmpty();
    }


    private void tsbSelect_Click (object sender, EventArgs e)
    {
      // 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 )
      {
        axToolbarControl1.SetBuddyControl (null);
        axToolbarControl1.Enabled = false;
        tsbAddMoleSymbol.Checked = false;
      }
      else
      {
        axToolbarControl1.SetBuddyControl (axMapControl1);
        axToolbarControl1.Enabled = true;
      }
    }
    
    
    #endregion
    
    #region Form event handlers

        private string GetSdkDataPath()
        {
            //get the ArcGIS path from the registry
            Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\ESRI\ArcGIS_SXS_SDK");
            string path = Convert.ToString(key.GetValue("InstallDir"));

            //set the of the logo
            string str = System.IO.Path.Combine(path, @"Samples\data\");

            if (!System.IO.Directory.Exists(str))
            {
                MessageBox.Show("Path :" + str + " does not exist!");
                return string.Empty;
            }

            return str;
        }
    
    private void MainForm_Load (object sender, EventArgs e)
    {
      // get a reference to the MapControl for local use
      m_mapControl = axMapControl1.Object as IMapControl3;

      // get a map from the SDK sample data
            string dataPath = GetSdkDataPath() + @"MilitaryOverlayEditor\";
 
      string defaultMxDoc = dataPath + "molebasemap.mxd";
      if ( m_mapControl.CheckMxFile(defaultMxDoc) )
      {
        // load the map into the map control
        object missing = Type.Missing;
        m_mapControl.LoadMxFile (defaultMxDoc, missing, missing);
      }
      else
      {
        string errorMsg = "Could not load default map document - Application may not work!";
        errorMsg += Environment.NewLine + defaultMxDoc;
        Trace.WriteLine (errorMsg);
        MessageBox.Show (errorMsg);
      }
    }


    private void MainForm_FormClosing (object sender, FormClosingEventArgs e)
    {
      // tell MOLE to save its settings and release its resources
      (new MoleCoreHelperClass()).ReleaseForceElementRenderer();
    }
    
    
    #endregion

    #region Map control event handlers


    private void axMapControl1_OnMapReplaced (object sender, IMapControlEvents2_OnMapReplacedEvent e)
    {
      // initialize the last mouse click point to the center of the map's extent
      IEnvelope extent = 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();
    }
    
    
    private void axMapControl1_OnMouseDown (object sender, IMapControlEvents2_OnMouseDownEvent e)
    {
      m_lastMouseClick.PutCoords (e.mapX, e.mapY);
      if ( tsbAddMoleSymbol.Checked )
      {
        // "Add MOLE Symbol" command:  draw a symbol at the click point
        DrawSymbol (m_lastMouseClick, tstSIC.Text, false);
        tstSIC.Text = m_sic[m_random.Next()];
      }
      else if (
        tsbSelect.Checked &&
        SelectElements(m_lastMouseClick, m_mapControl.ActiveView, m_selectedBounds)
      )
      {
        // "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
        IMoveEnvelopeFeedback moveEnvelopeFeedback = new MoveEnvelopeFeedbackClass();
        moveEnvelopeFeedback.Start (m_selectedBounds, m_lastMouseClick);
        m_moveFeedBack = moveEnvelopeFeedback as IDisplayFeedback;
        m_moveFeedBack.Display = axMapControl1.ActiveView.ScreenDisplay;
        
        // the tick count is used to filter out short, unintentional mouse drags
        m_dragStartTime = Environment.TickCount;
      }
    }


    private void axMapControl1_OnMouseMove (object sender, IMapControlEvents2_OnMouseMoveEvent e)
    {
      // update the current map location of the mouse
      if ( m_currentMouseLocation == null )
        m_currentMouseLocation = new PointClass();
      m_currentMouseLocation.PutCoords (e.mapX, e.mapY);
      UpdateStatusBar();
      
      // "Select & Drag Graphics" command:  move the feedback graphic
      if ( tsbSelect.Checked && e.button == 1 && m_moveFeedBack != null )
        m_moveFeedBack.MoveTo (m_currentMouseLocation);
    }


    private void axMapControl1_OnMouseUp (object sender, IMapControlEvents2_OnMouseUpEvent e)
    {
      if ( m_moveFeedBack != null )
      {
        // stop the feedback graphic and save its geometry for future use
        m_selectedBounds = ((IMoveEnvelopeFeedback)m_moveFeedBack).Stop();
        m_moveFeedBack = null;
        int endTicks = Environment.TickCount - m_dragStartTime;
        if ( endTicks > 250 && m_currentMouseLocation != null )
        {
          // 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
          );
        }
      }
    }


    #endregion

    #region Helper Methods


    private IPoint CreatePoint (double x, double y)
    {
      // create a new point instance and initialize its coordinates
      IPoint point = new PointClass();
      point.PutCoords (x, y);
      return point;
    }


    private void DrawSymbol (IPoint location, string sic, bool suppressRefresh)
    {
      // the first time we create a symbol, display the wait cursor while MOLE loads up
      System.Windows.Forms.Cursor previousCursor = Cursor;
      if ( m_firstTime )
        Cursor = Cursors.WaitCursor;

      // set up a MOLE symbol for the new graphic; minimally validate the symbol ID code
      IMoleSymbol moleSymbol = new MoleMarkerSymbolClass();
      if ( sic.Length == 15 )
        moleSymbol.SymbolID = sic;
      moleSymbol.TextLabels = GetLabels();

      // to remove the symbol's fill, uncomment this code
      //IMoleMarkerSymbol moleMarkerSymbol = moleSymbol as IMoleMarkerSymbol;
      //moleMarkerSymbol.ShowFill = false;

      // initialize the marker symbol properties
      IMarkerSymbol markerSymbol = moleSymbol as IMarkerSymbol;
      double size;
      if ( Double.TryParse(tstSize.Text, out size) )
        markerSymbol.Size = size;
      else
        markerSymbol.Size = 48;

      // create the graphic element for the marker symbol and add it to the map
      IMarkerElement markerElement = new MarkerElementClass();
      markerElement.Symbol = markerSymbol;
      IElement element = markerElement as IElement;
      element.Geometry = location as IGeometry;
      m_mapControl.ActiveView.GraphicsContainer.AddElement (element, 0);
      if ( ! suppressRefresh )
        m_mapControl.ActiveView.PartialRefresh (esriViewDrawPhase.esriViewGraphics, null, null);
      
      // update the user interface
      if ( m_firstTime )
      {
        Cursor = previousCursor;
        m_firstTime = false;
      }
      ++m_unitCount;
      UpdateTitle();
    }


    private double dRandom (double low, double high)
    {
      // generate a random floating-point number within the indicated range [low, high)
      return low + m_random.NextDouble() * (high - low);
    }


    private IPropertySet GetLabels()
    {
      IPropertySet labelSet = 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;
    }


    private IColor GetRandomColor()
    {
      // create a random opaque RGB color
      IRgbColor rgb = new RgbColorClass();
      rgb.Red = m_random.Next (0, 255);
      rgb.Green = m_random.Next (0, 255);
      rgb.Blue = m_random.Next (0, 255);
      return rgb as IColor;
    }
    
    
    private void MoveGraphics (double deltaX, double deltaY)
    {
      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
        IGraphicsContainer       graphicsContainer       = axMapControl1.ActiveView.GraphicsContainer;
        IGraphicsContainerSelect graphicsContainerSelect = graphicsContainer as IGraphicsContainerSelect;
        IEnumElement             enumElement             = graphicsContainerSelect.SelectedElements;

        // iterate through the selected elements
        enumElement.Reset();
        IElement element = enumElement.Next();
        while (element != null)
        {
          // apply the delta vector to each element's geometry and update it the container
          IGeometry geometry = element.Geometry;
          (geometry as ITransform2D).Move (deltaX, deltaY);
          element.Geometry = geometry;
          graphicsContainer.UpdateElement (element);
          element = enumElement.Next();
        }
        // refresh the active view
        axMapControl1.ActiveView.PartialRefresh (esriViewDrawPhase.esriViewGraphics, null, null);
      }
      catch ( Exception ex )
      {
        MessageBox.Show ("Exception:  " + ex.GetBaseException().ToString(), "MoveGraphics");
      }
    }


    private bool SelectElements (IPoint point, IActiveView activeView, IEnvelope selectedBounds)
    {
      // this function is written in such a way that it should be pastable
      Trace.WriteLine ("selecting graphics near (" + point.X + ", " + point.Y + ")");

      IGraphicsContainer       graphicsContainer       = activeView.GraphicsContainer;
      IGraphicsContainerSelect graphicsContainerSelect = graphicsContainer as IGraphicsContainerSelect;
      IScreenDisplay           screenDisplay           = activeView.ScreenDisplay;
      bool                     selected                = false;
      bool                     refreshRequired         = 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)
      IEnumElement enumElement = graphicsContainer.LocateElements (point, 0.0000001);
      if ( enumElement == null )
        enumElement = graphicsContainer.LocateElements (point, 0.5);
      
      // if no elements were selected
      if ( enumElement == null )
      {
        // if the previous selection is nonempty
        if ( graphicsContainerSelect.ElementSelectionCount > 0 )
        {
          // clear the selection and refresh the display
          Trace.WriteLine ("clearing selection");
          graphicsContainerSelect.UnselectAllElements();
          selectedBounds.SetEmpty();
          refreshRequired = true;
        }
        // else do nothing
      }
      else
      {
        // get the extent of the selected elements
        IEnvelope envelope = new EnvelopeClass();
        enumElement.Reset();
        IElement element = enumElement.Next();
        while ( element != null )
        {
          // establish selectedBounds as the extent of all selected elements
          element.QueryBounds (screenDisplay, envelope);
          selectedBounds.Union (envelope);
          element = enumElement.Next();
        }
        // add all the newly selected elements to the graphics container's selection
        enumElement.Reset();
        graphicsContainerSelect.SelectElements (enumElement);
        refreshRequired = selected = true;
      }
      // refresh the display if anything has changed
      if ( refreshRequired )
        activeView.PartialRefresh (esriViewDrawPhase.esriViewGraphics, null, null);
      
      // return true if any elements on the display are currently selected; selectedBounds has their extent
      return selected;
    }
    
    
    private void UpdateStatusBar()
    {
      if ( m_mapControl != null )
      {
        // put the MXD name and current mouse location in the status bar, if available
        string documentFileName = System.IO.Path.GetFileName (m_mapControl.DocumentFilename);
        if ( documentFileName == null || documentFileName == String.Empty )
          documentFileName = "<no map>";
        if ( m_currentMouseLocation == null )
          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)
          );
      }
    }


    private void UpdateTitle()
    {
      // put the number of symbols in the title bar, when there are any
      string title = "MOLE Symbols";
      if ( m_unitCount == 1 )
        title += " (" + m_unitCount + " unit)";
      else if ( m_unitCount > 1 )
        title += " (" + m_unitCount + " units)";
      Text = title;
    }


    #endregion
  }
}