Executing geoprocessing tools in the background
RunGPForm.vb
' 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.
' 

Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Data
Imports System.Drawing
Imports System.Text
Imports System.Windows.Forms
Imports ESRI.ArcGIS.esriSystem
Imports ESRI.ArcGIS.Carto
Imports ESRI.ArcGIS.Controls
Imports ESRI.ArcGIS.SystemUI
Imports ESRI.ArcGIS.Display
Imports ESRI.ArcGIS.Geometry
Imports ESRI.ArcGIS.Geodatabase
Imports ESRI.ArcGIS.Geoprocessor
Imports ESRI.ArcGIS.Geoprocessing
Imports ESRI.ArcGIS.DataManagementTools

Public Partial Class RunGPForm
  Inherits Form
  Private _gp As ESRI.ArcGIS.Geoprocessor.Geoprocessor = Nothing

  ''' <summary>
  ''' A Dictionary contains the layers loaded into the application. The Keys are named appropriately.
  ''' </summary>
  Private _layersDict As New Dictionary(Of 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 _resultsList As New List(Of IFeatureLayer)()

  ''' <summary>
  ''' A Queue of GPProcess objects each of which represents a geoprocessing tool to be executed asynchronously.
  ''' </summary>
  Private _myGPToolsToExecute As New Queue(Of 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 Sub New()
    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 ESRI.ArcGIS.Geoprocessor.Geoprocessor()
    'All results will be written to the users TEMP folder
    Dim outputDir As String = System.Environment.GetEnvironmentVariable("TEMP")
    _gp.SetEnvironmentValue("workspace", outputDir)
    _gp.OverwriteOutput = True
    AddHandler _gp.ToolExecuted, New EventHandler(Of ToolExecutedEventArgs)(AddressOf _gp_ToolExecuted)
    AddHandler _gp.ProgressChanged, New EventHandler(Of ESRI.ArcGIS.Geoprocessor.ProgressChangedEventArgs)(AddressOf _gp_ProgressChanged)
    AddHandler _gp.MessagesCreated, New EventHandler(Of MessagesCreatedEventArgs)(AddressOf _gp_MessagesCreated)
    AddHandler _gp.ToolExecuting, New EventHandler(Of ToolExecutingEventArgs)(AddressOf _gp_ToolExecuting)

    'Add layers to the map, select a city, zoom in on it
    SetupMap()
  End Sub

  ''' <summary>
  ''' Handles the click event of the Button and runs the geoprocessing tasks asynchronously.
  ''' </summary>
  Private Sub btnRunGP_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnRunGP.Click
    Try
      '#Region "tidy up any previous gp runs"

      'Clear the ListView control
      listView1.Items.Clear()

      'Remove any result layers present in the map
      Dim mapLayers As IMapLayers = TryCast(axMapControl1.Map, IMapLayers)
      For Each resultLayer As IFeatureLayer In _resultsList
        mapLayers.DeleteLayer(resultLayer)
      Next

      axTOCControl1.Update()

      'Empty the results layer list
      _resultsList.Clear()

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

      '#End Region

      'Buffer the selected cities by the specified distance
      Dim bufferTool As 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
      Dim clipTool As 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 ex As Exception
      listView1.Items.Add(New ListViewItem(New String(1) {"N/A", ex.Message}, "error"))
    End Try
  End Sub

  ''' <summary>
  ''' Handles the ProgressChanged event.
  ''' </summary>
  Private Sub _gp_ProgressChanged(sender As Object, e As ESRI.ArcGIS.Geoprocessor.ProgressChangedEventArgs)
    Dim gpResult As IGeoProcessorResult2 = DirectCast(e.GPResult, IGeoProcessorResult2)

    If e.ProgressChangedType = ProgressChangedType.Message Then
      listView1.Items.Add(New ListViewItem(New String(1) {"ProgressChanged", e.Message}, "information"))
    End If
  End Sub

  ''' <summary>
  ''' Handles the ToolExecuting event. All tools start by firing this event.
  ''' </summary>
  Private Sub _gp_ToolExecuting(sender As Object, e As ToolExecutingEventArgs)
    Dim gpResult As IGeoProcessorResult2 = DirectCast(e.GPResult, IGeoProcessorResult2)
    listView1.Items.Add(New ListViewItem(New String(1) {"ToolExecuting", gpResult.Process.Tool.Name & " " & gpResult.Status.ToString()}, "information"))
  End Sub

  ''' <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>
  Private Sub _gp_ToolExecuted(sender As Object, e As ToolExecutedEventArgs)
    Dim gpResult As IGeoProcessorResult2 = DirectCast(e.GPResult, IGeoProcessorResult2)

    Try
      'The first GP tool has completed, if it was successful process the others
      If gpResult.Status = esriJobStatus.esriJobSucceeded Then
        listView1.Items.Add(New ListViewItem(New String(1) {"ToolExecuted", gpResult.Process.Tool.Name}, "success"))

        'Execute next tool in the queue
        If _myGPToolsToExecute.Count > 0 Then
          _gp.ExecuteAsync(_myGPToolsToExecute.Dequeue())
        Else
          'If last tool has executed add the output layer to the map
          Dim resultFClass As IFeatureClass = TryCast(_gp.Open(gpResult.ReturnValue), IFeatureClass)
          Dim resultLayer As IFeatureLayer = New FeatureLayerClass()
          resultLayer.FeatureClass = resultFClass
          resultLayer.Name = resultFClass.AliasName

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

          'add the result layer to the List of result layers
          _resultsList.Add(resultLayer)
        End If
      'If the GP process failed, do not try to process any more tools in the queue
      ElseIf gpResult.Status = esriJobStatus.esriJobFailed Then
        'The actual GP error message will be output by the MessagesCreated event handler
        listView1.Items.Add(New ListViewItem(New String(1) {"ToolExecuted", gpResult.Process.Tool.Name & " failed, any remaining processes will not be executed."}, "error"))
        'Empty the queue
        _myGPToolsToExecute.Clear()
      End If
    Catch ex As Exception
      listView1.Items.Add(New ListViewItem(New String(1) {"ToolExecuted", ex.Message}, "error"))
    End Try
  End Sub

  ''' <summary>
  ''' Handles the MessagesCreated event.
  ''' </summary>
  Private Sub _gp_MessagesCreated(sender As Object, e As MessagesCreatedEventArgs)

    Dim gpMsgs As IGPMessages = e.GPMessages

    If gpMsgs.Count > 0 Then
      For count As Integer = 0 To gpMsgs.Count - 1
        Dim msg As IGPMessage = gpMsgs.GetMessage(count)
        Dim imageToShow As String = "information"

        Select Case msg.Type
          Case esriGPMessageType.esriGPMessageTypeAbort
            imageToShow = "warning"
            Exit Select
          Case esriGPMessageType.esriGPMessageTypeEmpty
            imageToShow = "information"
            Exit Select
          Case esriGPMessageType.esriGPMessageTypeError
            imageToShow = "error"
            Exit Select
          Case esriGPMessageType.esriGPMessageTypeGDBError
            imageToShow = "error"
            Exit Select
          Case esriGPMessageType.esriGPMessageTypeInformative
            imageToShow = "information"
            Exit Select
          Case esriGPMessageType.esriGPMessageTypeProcessDefinition
            imageToShow = "information"
            Exit Select
          Case esriGPMessageType.esriGPMessageTypeProcessStart
            imageToShow = "information"
            Exit Select
          Case esriGPMessageType.esriGPMessageTypeProcessStop
            imageToShow = "information"
            Exit Select
          Case esriGPMessageType.esriGPMessageTypeWarning
            imageToShow = "warning"
            Exit Select
          Case Else
            Exit Select
        End Select

        listView1.Items.Add(New ListViewItem(New String(1) {"MessagesCreated", msg.Description}, imageToShow))
      Next
    End If
  End Sub

  #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 Sub SetupMap()
    Try
      'Relative path to the sample data from EXE location
      Dim dirPath As String = "..\..\..\..\..\data\USZipCodeData"

      'Create the cities layer
      Dim cities As IFeatureClass = TryCast(_gp.Open(dirPath & "\ZipCode_Boundaries_US_Major_Cities.shp"), IFeatureClass)
      Dim citiesLayer As IFeatureLayer = New FeatureLayerClass()
      citiesLayer.FeatureClass = cities
      citiesLayer.Name = "Major Cities"
      
      'Create the zip code boundaries layer
      Dim zipBndrys As IFeatureClass = TryCast(_gp.Open(dirPath & "\US_ZipCodes.shp"), IFeatureClass)
      Dim zipBndrysLayer As IFeatureLayer = New FeatureLayerClass()
      zipBndrysLayer.FeatureClass = zipBndrys
      zipBndrysLayer.Name = "Zip Code boundaries"
      
      'Create the highways layer
      dirPath = "..\..\..\..\..\data\USAMajorHighways"
      Dim highways As IFeatureClass = TryCast(_gp.Open(dirPath & "\usa_major_highways.shp"), IFeatureClass)
      Dim highwaysLayer As IFeatureLayer = 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)

      'Symbolize and set additional properties for each layer
      'Setup and symbolize the cities layer
      citiesLayer.Selectable = True
      citiesLayer.ShowTips = True
      Dim markerSym As ISimpleMarkerSymbol = CreateSimpleMarkerSymbol(CreateRGBColor(0, 92, 230), esriSimpleMarkerStyle.esriSMSCircle)
      markerSym.Size = 9
      Dim simpleRend As ISimpleRenderer = New SimpleRendererClass()
      simpleRend.Symbol = DirectCast(markerSym, ISymbol)
      DirectCast(citiesLayer, IGeoFeatureLayer).Renderer = DirectCast(simpleRend, IFeatureRenderer)

      'Setup and symbolize the zip boundaries layer
      zipBndrysLayer.Selectable = False
      Dim fillSym As ISimpleFillSymbol = CreateSimpleFillSymbol(CreateRGBColor(0, 0, 0), esriSimpleFillStyle.esriSFSHollow, CreateRGBColor(204, 204, 204), esriSimpleLineStyle.esriSLSSolid, 0.5)
      Dim simpleRend2 As ISimpleRenderer = New SimpleRendererClass()
      simpleRend2.Symbol = DirectCast(fillSym, ISymbol)
      DirectCast(zipBndrysLayer, IGeoFeatureLayer).Renderer = DirectCast(simpleRend2, IFeatureRenderer)

      'Setup and symbolize the highways layer
      highwaysLayer.Selectable = False
      Dim lineSym As ISimpleLineSymbol = CreateSimpleLineSymbol(CreateRGBColor(250, 52, 17), 3.4, esriSimpleLineStyle.esriSLSSolid)
      Dim simpleRend3 As ISimpleRenderer = New SimpleRendererClass()
      simpleRend3.Symbol = DirectCast(lineSym, ISymbol)
      DirectCast(highwaysLayer, IGeoFeatureLayer).Renderer = DirectCast(simpleRend3, IFeatureRenderer)

      'Add the layers to the Map
      For Each layer As IFeatureLayer In _layersDict.Values
        axMapControl1.AddLayer(DirectCast(layer, ILayer))
      Next

      'Select and zoom in on Los Angeles city
      Dim qf As IQueryFilter = New QueryFilterClass()
      qf.WhereClause = "NAME='Los Angeles'"
      Dim citiesLayerSelection As IFeatureSelection = DirectCast(citiesLayer, IFeatureSelection)
      citiesLayerSelection.SelectFeatures(qf, esriSelectionResultEnum.esriSelectionResultNew, True)
      Dim laFeature As IFeature = cities.GetFeature(citiesLayerSelection.SelectionSet.IDs.[Next]())
      Dim env As IEnvelope = laFeature.Shape.Envelope
      env.Expand(0.5, 0.5, False)
      axMapControl1.Extent = env
      axMapControl1.Refresh()
      axTOCControl1.Update()

      'Enable GP analysis button
      btnRunGP.Enabled = True
    Catch ex As Exception
      MessageBox.Show("There was an error loading the data used by this sample: " & ex.Message)
    End Try
  End Sub


  #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 (Standard, Advanced, Basic)
  ' 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 Function CreateSimpleFillSymbol(fillColor As ESRI.ArcGIS.Display.IRgbColor, fillStyle As ESRI.ArcGIS.Display.esriSimpleFillStyle, borderColor As ESRI.ArcGIS.Display.IRgbColor, borderStyle As ESRI.ArcGIS.Display.esriSimpleLineStyle, borderWidth As System.Double) As ESRI.ArcGIS.Display.ISimpleFillSymbol

    Dim simpleLineSymbol As ESRI.ArcGIS.Display.ISimpleLineSymbol = New ESRI.ArcGIS.Display.SimpleLineSymbolClass()
    simpleLineSymbol.Width = borderWidth
    simpleLineSymbol.Color = borderColor
    simpleLineSymbol.Style = borderStyle

    Dim simpleFillSymbol As ESRI.ArcGIS.Display.ISimpleFillSymbol = New ESRI.ArcGIS.Display.SimpleFillSymbolClass()
    simpleFillSymbol.Outline = simpleLineSymbol
    simpleFillSymbol.Style = fillStyle
    simpleFillSymbol.Color = fillColor

    Return simpleFillSymbol
  End Function
  #End Region


  #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 (Standard, Advanced, Basic)
  ' 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 Function CreateSimpleLineSymbol(rgbColor As ESRI.ArcGIS.Display.IRgbColor, inWidth As System.Double, inStyle As ESRI.ArcGIS.Display.esriSimpleLineStyle) As ESRI.ArcGIS.Display.ISimpleLineSymbol
    If rgbColor Is Nothing Then
      Return Nothing
    End If

    Dim simpleLineSymbol As ESRI.ArcGIS.Display.ISimpleLineSymbol = New ESRI.ArcGIS.Display.SimpleLineSymbolClass()
    simpleLineSymbol.Style = inStyle
    simpleLineSymbol.Color = rgbColor
    simpleLineSymbol.Width = inWidth

    Return simpleLineSymbol
  End Function
  #End Region


  #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 (Standard, Advanced, Basic)
  ' 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 Function CreateSimpleMarkerSymbol(rgbColor As ESRI.ArcGIS.Display.IRgbColor, inputStyle As ESRI.ArcGIS.Display.esriSimpleMarkerStyle) As ESRI.ArcGIS.Display.ISimpleMarkerSymbol

    Dim simpleMarkerSymbol As ESRI.ArcGIS.Display.ISimpleMarkerSymbol = New ESRI.ArcGIS.Display.SimpleMarkerSymbolClass()
    simpleMarkerSymbol.Color = rgbColor
    simpleMarkerSymbol.Style = inputStyle

    Return simpleMarkerSymbol
  End Function
  #End Region


  #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 (Standard, Advanced, Basic)
  ' 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 Function CreateRGBColor(myRed As System.Byte, myGreen As System.Byte, myBlue As System.Byte) As ESRI.ArcGIS.Display.IRgbColor
    Dim rgbColor As ESRI.ArcGIS.Display.IRgbColor = New ESRI.ArcGIS.Display.RgbColorClass()
    rgbColor.Red = myRed
    rgbColor.Green = myGreen
    rgbColor.Blue = myBlue
    rgbColor.UseWindowsDithering = True
    Return rgbColor
  End Function
  #End Region

  Private Sub txtBufferDistance_TextChanged(ByVal sender As Object, ByVal e As EventArgs) Handles txtBufferDistance.TextChanged

    Dim txtToCheck As String = txtBufferDistance.Text

    If ((IsDecimal(txtToCheck)) Or (IsInteger(txtToCheck))) AndAlso (txtToCheck <> "0") Then
      btnRunGP.Enabled = True
    Else
      btnRunGP.Enabled = False
    End If
  End Sub

  Private Function IsDecimal(theValue As String) As Boolean
    Try
      Convert.ToDouble(theValue)
      Return True
    Catch
      Return False
    End Try
  End Function
  'IsDecimal
  Private Function IsInteger(theValue As String) As Boolean
    Try
      Convert.ToInt32(theValue)
      Return True
    Catch
      Return False
    End Try
  End Function
  'IsInteger

  #End Region

End Class