Executing geoprocessing tools in the background
RunGPForm.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.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.SystemUI;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geoprocessor;
using ESRI.ArcGIS.Geoprocessing;
using ESRI.ArcGIS.DataManagementTools;

namespace RunGPAsync
{
  public partial class RunGPForm : Form
  {
    private Geoprocessor _gp = null;

    /// <summary>
    /// A Dictionary contains the layers loaded into the application. The Keys are named appropriately.
    /// </summary>
    private Dictionary<string, IFeatureLayer> _layersDict = new Dictionary<string, IFeatureLayer>();

    /// <summary>
    /// A List of FeatureLayer objects each of which represents result layers that are added to the Map.
    /// In this example only one Layer is added to the List.
    /// </summary>
    private List<IFeatureLayer> _resultsList = new List<IFeatureLayer>();

    /// <summary>
    /// A Queue of GPProcess objects each of which represents a geoprocessing tool to be executed asynchronously.
    /// </summary>
    private Queue<IGPProcess> _myGPToolsToExecute = new Queue<IGPProcess>();


    /// <summary>
    /// Initializes a new instance of the RunGPForm class which represents the ArcGIS Engine application.
    /// The constructor is used to perform setup: the ListViewControl is configured, a new Geoprocessor
    /// object is created, the output result directory is specified and event handlers created in order
    /// to listen to geoprocessing events. A helper method called SetupMap is called to create, add and
    /// symbolize the layers used by the application.
    /// </summary>
    public RunGPForm()
    {
      InitializeComponent();

      //Set up ListView control
      listView1.Columns.Add("Event", 200, HorizontalAlignment.Left);  
      listView1.Columns.Add("Message", 1000, HorizontalAlignment.Left);
      //The image list component contains the icons used in the ListView control
      listView1.SmallImageList = imageList1;

      //Create a Geoprocessor object and associated event handlers which will be fired during tool execution
      _gp = new Geoprocessor();
      //All results will be written to the users TEMP folder
      string outputDir = System.Environment.GetEnvironmentVariable("TEMP");
      _gp.SetEnvironmentValue("workspace", outputDir);
      _gp.OverwriteOutput = true;
      _gp.ToolExecuted += new EventHandler<ToolExecutedEventArgs>(_gp_ToolExecuted);
      _gp.ProgressChanged += new EventHandler<ESRI.ArcGIS.Geoprocessor.ProgressChangedEventArgs>(_gp_ProgressChanged);
      _gp.MessagesCreated += new EventHandler<MessagesCreatedEventArgs>(_gp_MessagesCreated);
      _gp.ToolExecuting += new EventHandler<ToolExecutingEventArgs>(_gp_ToolExecuting);

      //Add layers to the map, select a city, zoom in on it
      SetupMap();
    }

    /// <summary>
    /// Handles the click event of the Button and starts asynchronous geoprocessing.
    /// </summary>
    private void btnRunGP_Click(object sender, EventArgs e)
    {
      try
      {
        #region tidy up any previous gp runs

        //Clear the ListView control
        listView1.Items.Clear();

        //Remove any result layers present in the map
        IMapLayers mapLayers = axMapControl1.Map as IMapLayers;
        foreach (IFeatureLayer resultLayer in _resultsList)
        {
          mapLayers.DeleteLayer(resultLayer);
        }

        axTOCControl1.Update();
        
        //Empty the results layer list
        _resultsList.Clear();

        //make sure that my GP tool queue is empty
        _myGPToolsToExecute.Clear();

        #endregion

        //Buffer the selected cities by the specified distance
        ESRI.ArcGIS.AnalysisTools.Buffer bufferTool = new ESRI.ArcGIS.AnalysisTools.Buffer();
        bufferTool.in_features = _layersDict["Cities"];
        bufferTool.buffer_distance_or_field = txtBufferDistance.Text + " Miles";
        bufferTool.out_feature_class = "city_buffer.shp";
       
        //Clip the zip codes layer with the result of the buffer tool
        ESRI.ArcGIS.AnalysisTools.Clip clipTool = new ESRI.ArcGIS.AnalysisTools.Clip();
        clipTool.in_features = _layersDict["ZipCodes"];
        clipTool.clip_features = bufferTool.out_feature_class;
        clipTool.out_feature_class = "city_buffer_clip.shp";

        //To run multiple GP tools asynchronously, all tool inputs must exist before ExecuteAsync is called.
        //The output from the first buffer operation is used as an input to the second clip
        //operation. To deal with this restriction a Queue is created and all tools added to it. The first tool
        //is executed from this method, whereas the second is executed from the ToolExecuted event. This approach
        //is scalable - any additional geoprocessing tools added to this queue will also be executed in turn.
        _myGPToolsToExecute.Enqueue(bufferTool);
        _myGPToolsToExecute.Enqueue(clipTool);
        _gp.ExecuteAsync(_myGPToolsToExecute.Dequeue());
      }
      catch (Exception ex)
      {
        listView1.Items.Add(new ListViewItem(new string[2] { "N/A", ex.Message }, "error")); 
      }
    }

    /// <summary>
    /// Handles the ProgressChanged event.
    /// </summary>
    void _gp_ProgressChanged(object sender, ESRI.ArcGIS.Geoprocessor.ProgressChangedEventArgs e)
    {
      IGeoProcessorResult2 gpResult = (IGeoProcessorResult2)e.GPResult;

      if (e.ProgressChangedType == ProgressChangedType.Message)
      {
        listView1.Items.Add(new ListViewItem(new string[2] {"ProgressChanged", e.Message}, "information"));
      }
    }

    /// <summary>
    /// Handles the ToolExecuting event. All tools start by firing this event.
    /// </summary>
    void _gp_ToolExecuting(object sender, ToolExecutingEventArgs e)
    {   
      IGeoProcessorResult2 gpResult = (IGeoProcessorResult2)e.GPResult;
      listView1.Items.Add(new ListViewItem(new string[2] { "ToolExecuting", gpResult.Process.Tool.Name + " " + gpResult.Status.ToString() }, "information"));
    }

    /// <summary>
    /// Handles the ToolExecuted event. All tools end by firing this event. If the tool executed successfully  
    /// the next tool in the queue is removed and submitted for execution.  If unsuccessful, geoprocessing is terminated,
    /// an error message is written to the ListViewControl and the is queue cleared. After the final tool has executed
    /// the result layer is added to the Map.
    /// </summary>
    void _gp_ToolExecuted(object sender, ToolExecutedEventArgs e)
    {   
      IGeoProcessorResult2 gpResult = (IGeoProcessorResult2)e.GPResult;

      try
      {
        //The first GP tool has completed, if it was successful process the others
        if (gpResult.Status == esriJobStatus.esriJobSucceeded)
        {
          listView1.Items.Add(new ListViewItem(new string[2] { "ToolExecuted", gpResult.Process.Tool.Name }, "success"));

          //Execute next tool in the queue
          if (_myGPToolsToExecute.Count > 0)
          {
            _gp.ExecuteAsync(_myGPToolsToExecute.Dequeue());
          }
          //If last tool has executed add the output layer to the map
          else
          {
            IFeatureClass resultFClass = _gp.Open(gpResult.ReturnValue) as IFeatureClass;
            IFeatureLayer resultLayer = new FeatureLayerClass();
            resultLayer.FeatureClass = resultFClass;
            resultLayer.Name = resultFClass.AliasName;

            //Add the result to the map
            axMapControl1.AddLayer((ILayer)resultLayer, 2);
            axTOCControl1.Update();

            //add the result layer to the List of result layers
            _resultsList.Add(resultLayer);
          }
        }
        //If the GP process failed, do not try to process any more tools in the queue
        else if (gpResult.Status == esriJobStatus.esriJobFailed)
        {
          //The actual GP error message will be output by the MessagesCreated event handler
          listView1.Items.Add(new ListViewItem(new string[2] { "ToolExecuted", gpResult.Process.Tool.Name + " failed, any remaining processes will not be executed." }, "error"));
          //Empty the queue
          _myGPToolsToExecute.Clear();
        }
      }
      catch (Exception ex)
      {
        listView1.Items.Add(new ListViewItem(new string[2] { "ToolExecuted", ex.Message }, "error")); 
      } 
    }

    /// <summary>
    /// Handles the MessagesCreated event.
    /// </summary>
    void _gp_MessagesCreated(object sender, MessagesCreatedEventArgs e)
    {     
      IGPMessages gpMsgs = e.GPMessages;

      if (gpMsgs.Count > 0)
      {
        for (int count = 0; count < gpMsgs.Count; count++)
        {                 
          IGPMessage msg = gpMsgs.GetMessage(count);
          string imageToShow = "information";

          switch (msg.Type)
          {
            case esriGPMessageType.esriGPMessageTypeAbort:
              imageToShow = "warning";
              break;
            case esriGPMessageType.esriGPMessageTypeEmpty:
              imageToShow = "information";
              break;
            case esriGPMessageType.esriGPMessageTypeError:
              imageToShow = "error";
              break;
            case esriGPMessageType.esriGPMessageTypeGDBError:
              imageToShow = "error";
              break;
            case esriGPMessageType.esriGPMessageTypeInformative:
              imageToShow = "information";    
              break;
            case esriGPMessageType.esriGPMessageTypeProcessDefinition:
              imageToShow = "information";
              break;
            case esriGPMessageType.esriGPMessageTypeProcessStart:
              imageToShow = "information";
              break;
            case esriGPMessageType.esriGPMessageTypeProcessStop:
              imageToShow = "information";    
              break;
            case esriGPMessageType.esriGPMessageTypeWarning:
              imageToShow = "warning";    
              break;
            default:
              break;
          }

          listView1.Items.Add(new ListViewItem(new string[2]{"MessagesCreated", msg.Description}, imageToShow));   
        }
      }
    }

    #region Helper methods for setting up map and layers and validating buffer distance input

    /// <summary>
    /// Loads and symbolizes data used by the application.  Selects a city and zooms to it
    /// </summary>
    private void SetupMap()
    {
      try
      {           
        //Relative path to the sample data from EXE location
        string dirPath = @"..\..\..\..\..\data\USZipCodeData";
        
        //Create the cities layer
        IFeatureClass cities = _gp.Open(dirPath + @"\ZipCode_Boundaries_US_Major_Cities.shp") as IFeatureClass;
        IFeatureLayer citiesLayer = new FeatureLayerClass();
        citiesLayer.FeatureClass = cities;
        citiesLayer.Name = "Major Cities";
        
        //Create he zip code boundaries layer
        IFeatureClass zipBndrys = _gp.Open(dirPath + @"\US_ZipCodes.shp") as IFeatureClass;
        IFeatureLayer zipBndrysLayer = new FeatureLayerClass();
        zipBndrysLayer.FeatureClass = zipBndrys;
        zipBndrysLayer.Name = "Zip Code boundaries";
        
        //Create the highways layer
        dirPath = @"..\..\..\..\..\data\USAMajorHighways";
        IFeatureClass highways = _gp.Open(dirPath + @"\usa_major_highways.shp") as IFeatureClass;
        IFeatureLayer highwaysLayer = new FeatureLayerClass();
        highwaysLayer.FeatureClass = highways;
        highwaysLayer.Name = "Highways";

        //***** Important code *********
        //Add the layers to a dictionary. Layers can then easily be returned by their 'key'
        _layersDict.Add("ZipCodes", zipBndrysLayer);
        _layersDict.Add("Highways", highwaysLayer);
        _layersDict.Add("Cities", citiesLayer);
        
        #region Symbolize and set additional properties for each layer
        //Setup and symbolize the cities layer
        citiesLayer.Selectable = true;
        citiesLayer.ShowTips = true;
        ISimpleMarkerSymbol markerSym = CreateSimpleMarkerSymbol(CreateRGBColor(0, 92, 230), esriSimpleMarkerStyle.esriSMSCircle);
        markerSym.Size = 9;
        ISimpleRenderer simpleRend = new SimpleRendererClass();
        simpleRend.Symbol = (ISymbol)markerSym;
        ((IGeoFeatureLayer)citiesLayer).Renderer = (IFeatureRenderer)simpleRend;

        //Setup and symbolize the zip boundaries layer
        zipBndrysLayer.Selectable = false;
        ISimpleFillSymbol fillSym = CreateSimpleFillSymbol(CreateRGBColor(0, 0, 0), esriSimpleFillStyle.esriSFSHollow, CreateRGBColor(204, 204, 204), esriSimpleLineStyle.esriSLSSolid, 0.5);
        ISimpleRenderer simpleRend2 = new SimpleRendererClass();
        simpleRend2.Symbol = (ISymbol)fillSym;
        ((IGeoFeatureLayer)zipBndrysLayer).Renderer = (IFeatureRenderer)simpleRend2;

        //Setup and symbolize the highways layer
        highwaysLayer.Selectable = false;
        ISimpleLineSymbol lineSym = CreateSimpleLineSymbol(CreateRGBColor(250, 52, 17), 3.4, esriSimpleLineStyle.esriSLSSolid);
        ISimpleRenderer simpleRend3 = new SimpleRendererClass();
        simpleRend3.Symbol = (ISymbol)lineSym;
        ((IGeoFeatureLayer)highwaysLayer).Renderer = (IFeatureRenderer)simpleRend3;
        #endregion
  
        //Add the layers to the Map
        foreach (IFeatureLayer layer in _layersDict.Values)
        {
          axMapControl1.AddLayer((ILayer)layer);
        }

        #region select city and set map extent
        //Select and zoom in on  Los Angeles city
        IQueryFilter qf = new QueryFilterClass();
        qf.WhereClause = "NAME='Los Angeles'";
        IFeatureSelection citiesLayerSelection = (IFeatureSelection)citiesLayer;
        citiesLayerSelection.SelectFeatures(qf, esriSelectionResultEnum.esriSelectionResultNew, true);
        IFeature laFeature = cities.GetFeature(citiesLayerSelection.SelectionSet.IDs.Next());
        IEnvelope env = laFeature.Shape.Envelope;
        env.Expand(0.5, 0.5, false);
        axMapControl1.Extent = env;
        axMapControl1.Refresh();
        axTOCControl1.Update();
        #endregion

        //Enable GP analysis button
        btnRunGP.Enabled = true;
      }
      catch (Exception ex)
      {
        MessageBox.Show("There was an error loading the data used by this sample: " + ex.Message);
      }
    }


    #region"Create Simple Fill Symbol"
    // ArcGIS Snippet Title:
    // Create Simple Fill Symbol
    // 
    // Long Description:
    // Create a simple fill symbol by specifying a color, outline color and fill style.
    // 
    // Add the following references to the project:
    // ESRI.ArcGIS.Display
    // ESRI.ArcGIS.System
    // 
    // Intended ArcGIS Products for this snippet:
    // ArcGIS Desktop (ArcEditor, ArcInfo, ArcView)
    // ArcGIS Engine
    // ArcGIS Server
    // 
    // Applicable ArcGIS Product Versions:
    // 9.2
    // 9.3
    // 9.3.1
    // 10.0
    // 
    // Required ArcGIS Extensions:
    // (NONE)
    // 
    // Notes:
    // This snippet is intended to be inserted at the base level of a Class.
    // It is not intended to be nested within an existing Method.
    // 

    ///<summary>Create a simple fill symbol by specifying a color, outline color and fill style.</summary>
    ///  
    ///<param name="fillColor">An IRGBColor interface. The color for the inside of the fill symbol.</param>
    ///<param name="fillStyle">An esriSimpleLineStyle enumeration for the inside fill symbol. Example: esriSFSSolid.</param>
    ///<param name="borderColor">An IRGBColor interface. The color for the outside line border of the fill symbol.</param>
    ///<param name="borderStyle">An esriSimpleLineStyle enumeration for the outside line border. Example: esriSLSSolid.</param>
    ///<param name="borderWidth">A System.Double that is the width of the outside line border in points. Example: 2</param>
    ///   
    ///<returns>An ISimpleFillSymbol interface.</returns>
    ///  
    ///<remarks></remarks>
    public ESRI.ArcGIS.Display.ISimpleFillSymbol CreateSimpleFillSymbol(ESRI.ArcGIS.Display.IRgbColor fillColor, ESRI.ArcGIS.Display.esriSimpleFillStyle fillStyle, ESRI.ArcGIS.Display.IRgbColor borderColor, ESRI.ArcGIS.Display.esriSimpleLineStyle borderStyle, System.Double borderWidth)
    {

      ESRI.ArcGIS.Display.ISimpleLineSymbol simpleLineSymbol = new ESRI.ArcGIS.Display.SimpleLineSymbolClass();
      simpleLineSymbol.Width = borderWidth;
      simpleLineSymbol.Color = borderColor;
      simpleLineSymbol.Style = borderStyle;

      ESRI.ArcGIS.Display.ISimpleFillSymbol simpleFillSymbol = new ESRI.ArcGIS.Display.SimpleFillSymbolClass();
      simpleFillSymbol.Outline = simpleLineSymbol;
      simpleFillSymbol.Style = fillStyle;
      simpleFillSymbol.Color = fillColor;

      return simpleFillSymbol;
    }
    #endregion


    #region"Create Simple Line Symbol"
    // ArcGIS Snippet Title:
    // Create Simple Line Symbol
    // 
    // Long Description:
    // Create a simple line symbol by specifying a color, width and line style.
    // 
    // Add the following references to the project:
    // ESRI.ArcGIS.Display
    // ESRI.ArcGIS.System
    // 
    // Intended ArcGIS Products for this snippet:
    // ArcGIS Desktop (ArcEditor, ArcInfo, ArcView)
    // ArcGIS Engine
    // ArcGIS Server
    // 
    // Applicable ArcGIS Product Versions:
    // 9.2
    // 9.3
    // 9.3.1
    // 10.0
    // 
    // Required ArcGIS Extensions:
    // (NONE)
    // 
    // Notes:
    // This snippet is intended to be inserted at the base level of a Class.
    // It is not intended to be nested within an existing Method.
    // 

    ///<summary>Create a simple line symbol by specifying a color, width and line style.</summary>
    ///  
    ///<param name="rgbColor">An IRGBColor interface.</param>
    ///<param name="inWidth">A System.Double that is the width of the line symbol in points. Example: 2</param>
    ///<param name="inStyle">An esriSimpleLineStyle enumeration. Example: esriSLSSolid.</param>
    ///   
    ///<returns>An ISimpleLineSymbol interface.</returns>
    ///  
    ///<remarks></remarks>
    public ESRI.ArcGIS.Display.ISimpleLineSymbol CreateSimpleLineSymbol(ESRI.ArcGIS.Display.IRgbColor rgbColor, System.Double inWidth, ESRI.ArcGIS.Display.esriSimpleLineStyle inStyle)
    {
      if (rgbColor == null)
      {
        return null;
      }

      ESRI.ArcGIS.Display.ISimpleLineSymbol simpleLineSymbol = new ESRI.ArcGIS.Display.SimpleLineSymbolClass();
      simpleLineSymbol.Style = inStyle;
      simpleLineSymbol.Color = rgbColor;
      simpleLineSymbol.Width = inWidth;

      return simpleLineSymbol;
    }
    #endregion


    #region"Create Simple Marker Symbol"
    // ArcGIS Snippet Title:
    // Create Simple Marker Symbol
    // 
    // Long Description:
    // Create a simple marker symbol by specifying and input color and marker style.
    // 
    // Add the following references to the project:
    // ESRI.ArcGIS.Display
    // ESRI.ArcGIS.System
    // 
    // Intended ArcGIS Products for this snippet:
    // ArcGIS Desktop (ArcEditor, ArcInfo, ArcView)
    // ArcGIS Engine
    // ArcGIS Server
    // 
    // Applicable ArcGIS Product Versions:
    // 9.2
    // 9.3
    // 9.3.1
    // 10.0
    // 
    // Required ArcGIS Extensions:
    // (NONE)
    // 
    // Notes:
    // This snippet is intended to be inserted at the base level of a Class.
    // It is not intended to be nested within an existing Method.
    // 

    ///<summary>Create a simple marker symbol by specifying and input color and marker style.</summary>
    ///  
    ///<param name="rgbColor">An IRGBColor interface.</param>
    ///<param name="inputStyle">An esriSimpleMarkerStyle enumeration. Example: esriSMSCircle.</param>
    ///   
    ///<returns>An ISimpleMarkerSymbol interface.</returns>
    ///  
    ///<remarks></remarks>
    public ESRI.ArcGIS.Display.ISimpleMarkerSymbol CreateSimpleMarkerSymbol(ESRI.ArcGIS.Display.IRgbColor rgbColor, ESRI.ArcGIS.Display.esriSimpleMarkerStyle inputStyle)
    {
      
      ESRI.ArcGIS.Display.ISimpleMarkerSymbol simpleMarkerSymbol = new ESRI.ArcGIS.Display.SimpleMarkerSymbolClass();
      simpleMarkerSymbol.Color = rgbColor;
      simpleMarkerSymbol.Style = inputStyle;

      return simpleMarkerSymbol;
    }
    #endregion


    #region"Create RGBColor"
    // ArcGIS Snippet Title:
    // Create RGBColor
    // 
    // Long Description:
    // Generate an RgbColor by specifying the amount of Red, Green and Blue.
    // 
    // Add the following references to the project:
    // ESRI.ArcGIS.Display
    // ESRI.ArcGIS.System
    // 
    // Intended ArcGIS Products for this snippet:
    // ArcGIS Desktop (ArcEditor, ArcInfo, ArcView)
    // ArcGIS Engine
    // ArcGIS Server
    // 
    // Applicable ArcGIS Product Versions:
    // 9.2
    // 9.3
    // 9.3.1
    // 10.0
    // 
    // Required ArcGIS Extensions:
    // (NONE)
    // 
    // Notes:
    // This snippet is intended to be inserted at the base level of a Class.
    // It is not intended to be nested within an existing Method.
    // 

    ///<summary>Generate an RgbColor by specifying the amount of Red, Green and Blue.</summary>
    /// 
    ///<param name="myRed">A byte (0 to 255) used to represent the Red color. Example: 0</param>
    ///<param name="myGreen">A byte (0 to 255) used to represent the Green color. Example: 255</param>
    ///<param name="myBlue">A byte (0 to 255) used to represent the Blue color. Example: 123</param>
    ///  
    ///<returns>An IRgbColor interface</returns>
    ///  
    ///<remarks></remarks>
    public ESRI.ArcGIS.Display.IRgbColor CreateRGBColor(System.Byte myRed, System.Byte myGreen, System.Byte myBlue)
    {
      ESRI.ArcGIS.Display.IRgbColor rgbColor = new ESRI.ArcGIS.Display.RgbColorClass();
      rgbColor.Red = myRed;
      rgbColor.Green = myGreen;
      rgbColor.Blue = myBlue;
      rgbColor.UseWindowsDithering = true;
      return rgbColor;
    }
    #endregion

    private void txtBufferDistance_TextChanged(object sender, EventArgs e)
    {

      string txtToCheck = txtBufferDistance.Text;

      if (((IsDecimal(txtToCheck)) | (IsInteger(txtToCheck))) && (txtToCheck != "0"))
      {
        btnRunGP.Enabled = true;
      }
      else
      {
        btnRunGP.Enabled = false;
      }
    }

    private bool IsDecimal(string theValue)
    {
      try
      {
        Convert.ToDouble(theValue);
        return true;
      }
      catch
      {
        return false;
      }
    } //IsDecimal

    private bool IsInteger(string theValue)
    {
      try
      {
        Convert.ToInt32(theValue);
        return true;
      }
      catch
      {
        return false;
      }
    } //IsInteger


    #endregion

  }
}