Dynamic display layer
MyDynamicLayerClass.cs
// Copyright 2012 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.Data;
using System.Drawing;
using System.Timers;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using ESRI.ArcGIS.ADF.CATIDs;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.SystemUI;
using ESRI.ArcGIS.ADF;

namespace MyDynamicLayer
{
  /// <summary>
  /// This layer implements a DynamicLayer based on the DynamicLayerBase base-class
  /// </summary>
  [Guid("27FB44EB-0426-415f-BFA3-D1581056C0C4")]
  [ComVisible(true)]
  [ClassInterface(ClassInterfaceType.None)]
  [ProgId("MyDynamicLayer.MyDynamicLayerClass")]
  public sealed class MyDynamicLayerClass : ESRI.ArcGIS.ADF.BaseClasses.BaseDynamicLayer
  {
    #region class members
    private IPoint                    m_point                   = null;
    private bool                      m_bDynamicGlyphsCreated   = false;
    private bool                      m_bOnce                   = true;
    private IDynamicGlyph[]           m_markerGlyphs            = null;
    private IDynamicGlyph             m_textGlyph               = null;
    private Timer                     m_updateTimer             = null;
    private double                    m_extentMaxX              = 1000.0;
    private double                    m_extentMaxY              = 1000.0;
    private double                    m_extentMinX              = 0.0;
    private double                    m_extentMinY              = 0.0;
    private IDynamicGlyphFactory2      m_dynamicGlyphFactory     = null;
    private IDynamicSymbolProperties  m_dynamicSymbolProps      = null;
    private IDynamicCompoundMarker    m_dynamicCompoundMarker   = null;
    private DataTable                  m_table                    = null;
    private const int                  m_nNumOfItems              = 100;
    #endregion

    #region class constructor
    public MyDynamicLayerClass() : base()
    {
      m_table = new DataTable ("rows");
      m_table.Columns.Add("OID",      typeof (int));  //0
      m_table.Columns.Add("X",        typeof(double)); //1
      m_table.Columns.Add("Y",        typeof(double)); //2
      m_table.Columns.Add("STEPX",    typeof(double)); //3
      m_table.Columns.Add("STEPY",    typeof(double)); //4
      m_table.Columns.Add("HEADING",  typeof(double)); //5
      m_table.Columns.Add("TYPE",     typeof(int));    //6

      //set the ID column to be AutoIncremented
      m_table.Columns[0].AutoIncrement = true;

      m_point = new PointClass();

      //set an array to store the glyphs used to symbolize the tracked object
      m_markerGlyphs = new IDynamicGlyph[3];

      //set the update timer for the layer
      m_updateTimer = new Timer(50);
      m_updateTimer.Enabled = false;
      m_updateTimer.Elapsed += new ElapsedEventHandler(OnLayerUpdateEvent);

    }
    #endregion

    #region overriden methods
    /// <summary>
    /// The dynamic layer draw method
    /// </summary>
    /// <param name="DynamicDrawPhase">the current drawphase of the dynamic drawing</param>
    /// <param name="Display">the ActiveView's display</param>
    /// <param name="DynamicDisplay">the ActiveView's dynamic display</param>
    public override void DrawDynamicLayer(ESRI.ArcGIS.Display.esriDynamicDrawPhase DynamicDrawPhase, ESRI.ArcGIS.Display.IDisplay Display, ESRI.ArcGIS.Display.IDynamicDisplay DynamicDisplay)
    {
      try
      {
        //make sure that the display is valid as well as that the layer is visible
        if (null == DynamicDisplay || null == Display || !this.m_visible)
          return;

        //make sure that the current drawphase is immediate. In this sample there is no use of the
        //compiled drawPhase. Use the esriDDPCompiled drawPhase in order to draw semi-static items (items
        //which have update rate lower than the display update rate).
        if (DynamicDrawPhase != esriDynamicDrawPhase.esriDDPImmediate)
          return;

        if (m_bOnce)
        {
          //cast the DynamicDisplay into DynamicGlyphFactory
          m_dynamicGlyphFactory = DynamicDisplay.DynamicGlyphFactory as IDynamicGlyphFactory2;
          //cast the DynamicDisplay into DynamicSymbolProperties
          m_dynamicSymbolProps = DynamicDisplay as IDynamicSymbolProperties;
          //cast the compound marker symbol
          m_dynamicCompoundMarker = DynamicDisplay as IDynamicCompoundMarker;

          IntializeLayerData (Display.DisplayTransformation);

          GetLayerExtent();

          m_bOnce = false;
        }

        //get the display fitted bounds
        m_extentMaxX = Display.DisplayTransformation.FittedBounds.XMax;
        m_extentMaxY = Display.DisplayTransformation.FittedBounds.YMax;
        m_extentMinX = Display.DisplayTransformation.FittedBounds.XMin;
        m_extentMinY = Display.DisplayTransformation.FittedBounds.YMin;

        //create the dynamic symbols for the layer
        if (!m_bDynamicGlyphsCreated)
        {
          this.CreateDynamicSymbols(m_dynamicGlyphFactory);
          m_bDynamicGlyphsCreated = true;
        }

        double X, Y, heading;
        int type;
        //iterate through the layers' items
        foreach (DataRow r in m_table.Rows)
        {
          if (r[1] is DBNull || r[2] is DBNull)
            continue;

          //get the item's coordinate, heading and type
          X = Convert.ToDouble(r[1]);
          Y = Convert.ToDouble(r[2]);
          heading = Convert.ToDouble(r[5]);
          type = Convert.ToInt32(r[6]);

          //assign the items' coordinate to the cached point
          m_point.PutCoords(X, Y);

          //set the symbol's properties
          switch (type)
          {
            case 0:
              //set the heading of the current symbols' text
              m_dynamicSymbolProps.set_Heading(esriDynamicSymbolType.esriDSymbolText, 0.0f);
              m_dynamicSymbolProps.set_Heading(esriDynamicSymbolType.esriDSymbolMarker, 0.0f);

              //set the symbol alignment so that it will align with the screen
              m_dynamicSymbolProps.set_RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker, esriDynamicSymbolRotationAlignment.esriDSRAScreen);

              //set the text alignment so that it will also align with the screen
              m_dynamicSymbolProps.set_RotationAlignment(esriDynamicSymbolType.esriDSymbolText, esriDynamicSymbolRotationAlignment.esriDSRAScreen);

              //scale the item
              m_dynamicSymbolProps.SetScale(esriDynamicSymbolType.esriDSymbolMarker, 0.8f, 0.8f);
              //set the items' color (blue)
              m_dynamicSymbolProps.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 0.0f, 0.0f, 1.0f, 1.0f); // Blue
              //assign the item's glyph to the dynamic-symbol
              m_dynamicSymbolProps.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker, m_markerGlyphs[0]);
              //set the labels text glyph
              m_dynamicSymbolProps.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolText, m_textGlyph);
              //set the color of the text
              m_dynamicSymbolProps.SetColor(esriDynamicSymbolType.esriDSymbolText, 1.0f, 1.0f, 0.0f, 1.0f); // Yellow

              //draw the item as a compound marker. This means that you do not have to draw the items and their
              //accompanying labels separately, and thus allow you to write less code as well as get better
              //performance.  
              m_dynamicCompoundMarker.DrawCompoundMarker4
                (m_point,
                //"TOP",
                //"BOTTOM",
                "Item " + Convert.ToString(r[0]),
                heading.ToString("###.##"),
                m_point.X.ToString("###.#####"),
                m_point.Y.ToString("###.#####"));
              break;
            case 1:
              //set the heading of the current symbol
              m_dynamicSymbolProps.set_Heading(esriDynamicSymbolType.esriDSymbolMarker, (float)heading);

              //set the symbol alignment so that it will align with towards the symbol heading
              m_dynamicSymbolProps.set_RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker, esriDynamicSymbolRotationAlignment.esriDSRANorth);

              m_dynamicSymbolProps.SetScale(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f);
              m_dynamicSymbolProps.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 0.0f, 1.0f, 0.6f, 1.0f); // GREEN
              m_dynamicSymbolProps.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker, m_markerGlyphs[1]);

              //draw the current location
              DynamicDisplay.DrawMarker(m_point);
              break;
            case 2:
              //set the heading of the current symbol
              m_dynamicSymbolProps.set_Heading(esriDynamicSymbolType.esriDSymbolMarker, (float)heading);

              //set the symbol alignment so that it will align with towards the symbol heading
              m_dynamicSymbolProps.set_RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker, esriDynamicSymbolRotationAlignment.esriDSRANorth);

              m_dynamicSymbolProps.SetScale(esriDynamicSymbolType.esriDSymbolMarker, 1.1f, 1.1f);
              m_dynamicSymbolProps.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f, 1.0f, 1.0f); // WHITE
              m_dynamicSymbolProps.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker, m_markerGlyphs[2]);

              //draw the current location
              DynamicDisplay.DrawMarker(m_point);
              break;
          }
        }

       // by setting immediate flag to false, we signal the dynamic display that the layer is current.
        base.m_bIsImmediateDirty = false;
        
      }     
      catch (Exception ex)
      {
        System.Diagnostics.Trace.WriteLine(ex.Message);
      }
    }
    
    /// <summary>
    /// Returns the UID (ProgID or CLSID)
    /// </summary>
    public override UID ID
    {
      get
      {
        m_uid.Value = "MyDynamicLayer.MyDynamicLayerClass";
        return m_uid;
      }
    }
    #endregion

    #region public methods
    public void Connect()
    {
      m_updateTimer.Enabled = true;
    }

    public void Disconnect()
    {
      m_updateTimer.Enabled = false;
    }

    public DataRow NewItem ()
    {
      if (m_table == null)
        return null;
      else
        return m_table.NewRow ();
    }

    public void AddItem (DataRow row)
    {
      if (row == null)
        return;
      else
        m_table.Rows.Add (row);
    }
    #endregion

    #region private utility methods
    /// <summary>
    /// create the layer's glyphs used to set the symbol of the dynamic-layer items
    /// </summary>
    /// <param name="pDynamicGlyphFactory"></param>
    private void CreateDynamicSymbols(IDynamicGlyphFactory2 pDynamicGlyphFactory)
    {
      try
      {
        //set the background color
        IColor color = ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor (Color.FromArgb (255, 255, 255)) as IColor;

        // Create Character Marker Symbols glyph
        // --------------------------------------
        ICharacterMarkerSymbol characterMarkerSymbol = new CharacterMarkerSymbolClass ();
        characterMarkerSymbol.Color = color as IColor;
                characterMarkerSymbol.Font = ESRI.ArcGIS.ADF.Connection.Local.Converter.ToStdFont(new Font("ESRI Environmental & Icons", 32));
        characterMarkerSymbol.Size = 40;
        characterMarkerSymbol.Angle = 0;
        characterMarkerSymbol.CharacterIndex = 36;

        //create the glyph from the marker symbol
        m_markerGlyphs[0] = pDynamicGlyphFactory.CreateDynamicGlyph(characterMarkerSymbol as ISymbol);

        characterMarkerSymbol.Size = 32;
        characterMarkerSymbol.CharacterIndex = 224;

        //create the glyph from the marker symbol
        m_markerGlyphs[1] = pDynamicGlyphFactory.CreateDynamicGlyph(characterMarkerSymbol as ISymbol);

        // Create the glyph from embedded bitmap
        // -----------------------------------
        // Sets the transparency color
        IColor transparentColor = ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(255, 255, 255)) as IColor;

        Bitmap bitmap = new Bitmap (GetType (), "B2.bmp");
        m_markerGlyphs[2] = pDynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker, bitmap.GetHbitmap ().ToInt32 (), false, transparentColor);
     
        // Create a glyph for the labels text, use the first 'internal' text glyph 
        // ------------------------------------------------------------------------
        m_textGlyph = pDynamicGlyphFactory.get_DynamicGlyph(1, esriDynamicGlyphType.esriDGlyphText, 1);

      }
      catch (Exception ex)
      {
        System.Diagnostics.Trace.WriteLine(ex.Message);
      }
    }
    
    /// <summary>
    /// timer elapsed event handler, used to update the layer
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// <remarks>This layer has synthetic data, and therefore need the timer in order
    /// to update the layers' items.</remarks>
    private void OnLayerUpdateEvent(object sender, ElapsedEventArgs e)
    {
      try
      {
        double X, Y, stepX, stepY, heading;

        //iterate through the layers' records
        foreach (DataRow r in m_table.Rows)
        {
          if (r[1] is DBNull || r[2] is DBNull)
            continue;

          //get the current item location and the item's steps
          X = Convert.ToDouble(r[1]);
          Y = Convert.ToDouble(r[2]);
          stepX = Convert.ToDouble(r[3]);
          stepY = Convert.ToDouble(r[4]);

          //increment the item's location
          X += stepX;
          Y += stepY;

          //test that the item's location is within the fitted bounds
          if (X > m_extentMaxX) stepX = -Math.Abs(stepX);
          if (X < m_extentMinX) stepX = Math.Abs(stepX);
          if (Y > m_extentMaxY) stepY = -Math.Abs(stepY);
          if (Y < m_extentMinY) stepY = Math.Abs(stepY);
          //calculate the item's heading
          heading = (360.0 + 90.0 - Math.Atan2(stepY, stepX) * 180 / Math.PI) % 360.0;

          //update the item's record
          r[1] = X;
          r[2] = Y;
          r[3] = stepX;
          r[4] = stepY;
          r[5] = heading;
          lock (m_table)
          {
            r.AcceptChanges();
          }

          //set the dirty flag to true in order to let the DynamicDisplay that the layer needs redraw.
          base.m_bIsImmediateDirty = true;
        }
      }
      catch (Exception ex)
      {
        System.Diagnostics.Trace.WriteLine(ex.Message);
      }
    }

    /// <summary>
    /// Calculates the layer extent
    /// </summary>
    private void GetLayerExtent()
    {
      if (null == m_table)
        return;

      IEnvelope env = new EnvelopeClass();
      env.SpatialReference = base.m_spatialRef;
      IPoint point = new PointClass();
      foreach (DataRow r in m_table.Rows)
      {
        if (r[1] is DBNull || r[2] is DBNull)
          continue;

        point.Y = Convert.ToDouble(r[3]);
        point.X = Convert.ToDouble(r[4]);

        env.Union(point.Envelope);
      }

      base.m_extent = env;
    }

    /// <summary>
    /// Initialize the synthetic data of the layer
    /// </summary>
    private void IntializeLayerData (IDisplayTransformation displayTransformation)
    {
      try
      {

        //get the map's fitted bounds
        IEnvelope extent = displayTransformation.FittedBounds;

        //calculate the step for which will be used to increment the items
        double XStep = extent.Width / 5000.0;
        double YStep = extent.Height / 5000.0;

        Random rnd = new Random ();
        double stepX, stepY;

        //generate the items
        for (int i = 0; i < m_nNumOfItems; i++)
        {
          //calculate the step for each item
          stepX = XStep * rnd.NextDouble ();
          stepY = YStep * rnd.NextDouble ();

          //create new record
          DataRow r = NewItem ();
          //set the item's coordinate
          r[1] = extent.XMin + rnd.NextDouble () * (extent.XMax - extent.XMin);
          r[2] = extent.YMin + rnd.NextDouble () * (extent.YMax - extent.YMin);
          //set the item's steps
          r[3] = stepX;
          r[4] = stepY;
          //calculate the heading
          r[5] = (360.0 + 90.0 - Math.Atan2 (stepY, stepX) * 180 / Math.PI) % 360.0;

          //add a type ID in order to define the symbol for the item
          switch (i % 3)
          {
            case 0:
              r[6] = 0;
              break;
            case 1:
              r[6] = 1;
              break;
            case 2:
              r[6] = 2;
              break;
          }

          //add the new item record to the table
          AddItem (r);
        }
      }
      catch (Exception ex)
      {
        System.Diagnostics.Trace.WriteLine (ex.Message);
      }
    }
    #endregion

  }
}