Common Task results
Common_TaskResults_VBNet\OnDemandTaskResults\OnDemandTaskResults.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 Microsoft.VisualBasic
Imports System
Namespace OnDemandTaskResults_VBNet
  ' Specify the OnDemandTaskResults JavaScript file as a client script resource.  This will register the script
  ' on the client.  LoadOrder is specified to ensure that this custom script is loaded after the Web ADF's 
  ' TaskResults control's script is registered, as the custom script requires the availability of the client
  ' tier TaskResults control.
    <System.Web.UI.ToolboxData("<{0}:OnDemandTaskResults runat=""server"" Width=""200px"" Height=""200px"" " & ControlChars.CrLf & "        BackColor=""#ffffff"" Font-Names=""Verdana"" Font-Size=""8pt"" ForeColor=""#000000""" & ControlChars.CrLf & "        ShowAttributesOnDemand=""True"" ActivityIndicatorText=""Retrieving Data""> </{0}:OnDemandTaskResults>"), AjaxControlToolkit.ClientScriptResource("OnDemandTaskResults_VBNet.OnDemandTaskResults", "OnDemandTaskResults.js", LoadOrder:=3)> _
 Public Class OnDemandTaskResults
        Inherits ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults
#Region "Public Properties - ShowAttributesOnDemand, ActivityIndicatorText"

        ''' <summary>
        ''' Determines whether to retrieve feature attributes individually when a feature node is expanded (true) 
        ''' or all at once when the task results are first generated (false)
        ''' </summary>
        Public Property ShowAttributesOnDemand() As Boolean
            Get
                If (Me.StateManager.GetProperty("ShowAttributesOnDemand") Is Nothing) Then
                    Return True
                Else
                    Return CBool(Me.StateManager.GetProperty("ShowAttributesOnDemand"))
                End If
            End Get
            Set(ByVal value As Boolean)
                Me.StateManager.SetProperty("ShowAttributesOnDemand", Value)
            End Set
        End Property

        ''' <summary>
        ''' Determines the text shown while data is being retrieved for task results
        ''' </summary>
        Public Property ActivityIndicatorText() As String
            Get
                If (Me.StateManager.GetProperty("ActivityIndicatorText") Is Nothing) Then
                    Return "Retrieving Data"
                Else
                    Return CStr(Me.StateManager.GetProperty("ActivityIndicatorText"))
                End If
            End Get
            Set(ByVal value As String)
                Me.StateManager.SetProperty("ActivityIndicatorText", Value)
            End Set
        End Property
#End Region

#Region "Private Properties - NodeDataCache, GraphicsAttributeCache, ProcessedNodes"

        ''' <summary>
        ''' Stores the text (markup) for nodes containing feature data.  The text for a node is retrieved
        ''' from the cache when that node is first expanded.  Node ID is used as each entry's key.
        ''' </summary>
        Private ReadOnly Property NodeDataCache() As System.Collections.Generic.Dictionary(Of String, String)
            Get
                ' Attempt to retrieve the node data from state
                'INSTANT VB NOTE: The local variable nodeDataCache was renamed since Visual Basic will not allow local variables with the same name as their method:
                Dim nodeDataCache_Renamed As System.Collections.Generic.Dictionary(Of String, String) = TryCast(Me.StateManager.GetProperty("NodeCache"), System.Collections.Generic.Dictionary(Of String, String))

                ' If the node data is not yet stored, create a new dictionary for the data and store it
                If nodeDataCache_Renamed Is Nothing Then
                    nodeDataCache_Renamed = New System.Collections.Generic.Dictionary(Of String, String)()
                    Me.StateManager.SetProperty("NodeCache", nodeDataCache_Renamed)
                End If

                Return nodeDataCache_Renamed
            End Get
        End Property

        ''' <summary>
        ''' Stores the attribute data for the Graphics associated with GraphicsLayerNodes.  The attributes for 
        ''' a graphic feature is retrieved from the cache when that feature's MapTips is first shown or when
        ''' its task results node is first expanded.
        ''' </summary>
        Private ReadOnly Property GraphicsAttributeCache() As System.Collections.Generic.Dictionary(Of String, String)
            Get
                ' Attempt to retrieve the node data from state
                'INSTANT VB NOTE: The local variable graphicsAttributeCache was renamed since Visual Basic will not allow local variables with the same name as their method:
                Dim graphicsAttributeCache_Renamed As System.Collections.Generic.Dictionary(Of String, String) = TryCast(Me.StateManager.GetProperty("GraphicsAttributeCache"), System.Collections.Generic.Dictionary(Of String, String))

                ' If the node data is not yet stored, create a new dictionary for the data and store it
                If graphicsAttributeCache_Renamed Is Nothing Then
                    graphicsAttributeCache_Renamed = New System.Collections.Generic.Dictionary(Of String, String)()
                    Me.StateManager.SetProperty("GraphicsAttributeCache", graphicsAttributeCache_Renamed)
                End If

                Return graphicsAttributeCache_Renamed
            End Get
        End Property

        ''' <summary>
        ''' Stores the IDs of nodes for which attribute data has been retrieved
        ''' </summary>
        Private ReadOnly Property ProcessedNodes() As System.Collections.Generic.List(Of String)
            Get
                ' Attempt to retrieve the node data from state
                'INSTANT VB NOTE: The local variable processedNodes was renamed since Visual Basic will not allow local variables with the same name as their method:
                Dim processedNodes_Renamed As System.Collections.Generic.List(Of String) = TryCast(Me.StateManager.GetProperty("ProcessedNodes"), System.Collections.Generic.List(Of String))

                ' If the node data is not yet stored, create a new dictionary for the data and store it
                If processedNodes_Renamed Is Nothing Then
                    processedNodes_Renamed = New System.Collections.Generic.List(Of String)()
                    Me.StateManager.SetProperty("ProcessedNodes", processedNodes_Renamed)
                End If

                Return processedNodes_Renamed
            End Get
        End Property

#End Region

#Region "ASP.NET WebControl Life Cycle Event Handlers - CreateChildControls"

        Protected Overrides Sub CreateChildControls()
            MyBase.CreateChildControls()

            For Each menuItem As ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItem In Me.FeatureContextMenu.Items
                If menuItem.Text.ToLower() = "remove" Then
                    Me.FeatureContextMenu.Items.Remove(menuItem)
                    Exit For
                End If
            Next menuItem

            For Each menuItem As ESRI.ArcGIS.ADF.Web.UI.WebControls.ContextMenuItem In Me.GraphicsLayerContextMenu.Items
                If menuItem.Text.ToLower() = "remove" Then
                    Me.GraphicsLayerContextMenu.Items.Remove(menuItem)
                    Exit For
                End If
            Next menuItem

            ' Wire event handlers to fire when a node is added, removed, or expanded
            AddHandler NodeAdded, AddressOf OnDemandTaskResults_NodeAdded
            AddHandler NodeExpanded, AddressOf OnDemandTaskResults_NodeExpanded
            AddHandler NodeRemoved, AddressOf OnDemandTaskResults_NodeRemoved
        End Sub

#End Region

#Region "Web ADF Control Event Handlers - NodeAdded, NodeExpanded, NodeRemoved"

        ' Fires when a node is added.  Replaces any feature data with an activity indicator and 
        ' stores that data for on-demand retrieval.
        Private Sub OnDemandTaskResults_NodeAdded(ByVal sender As Object, ByVal args As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeEventArgs)
            ' If attributes are not to be retrieved on-demand, exit the method
            If (Not Me.ShowAttributesOnDemand) OrElse Not (TypeOf args.Node Is ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode) Then
                Return
            End If

            ' Attempt to get GraphicsLayerNodes that are descendants of the added node
            Dim graphicsLayerNodes As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode) = Me.FindChildGraphicsLayerNodes(args.Node)

            ' Make sure a GraphicsLayerNode was found
            If graphicsLayerNodes.Count = 0 Then
                Return
            End If

            ' Get the control that is adding the result node
            Dim callingControl As System.Web.UI.Control = Me.GetCallingControl(Me.Page)

            ' Cast the control to IAttributesOnDemandProvider
            Dim attributesOnDemandProvider As OnDemandTaskResults_VBNet.IAttributesOnDemandProvider = TryCast(callingControl, OnDemandTaskResults_VBNet.IAttributesOnDemandProvider)

            ' Set a boolean indicating whether the task results control needs to cache attributes.  This will
            ' be the case if the control that added the results does not implement IAttributesOnDemandProvider.
            Dim cacheAttributesInWebTier As Boolean = (attributesOnDemandProvider Is Nothing)

            ' If the control that has added results implements IAttributesOnDemandProvider, add the ID of 
            ' the control as an attribute on the task result node.
            If Not attributesOnDemandProvider Is Nothing Then
                args.Node.Attributes.Add("AttributesProviderID", callingControl.ID)
            End If

            ' Stores the properties that will be used to initialize client tier graphics layer nodes
            Dim graphicsLayerNodeList As System.Collections.Generic.List(Of System.Collections.Generic.Dictionary(Of String, Object)) = New System.Collections.Generic.List(Of System.Collections.Generic.Dictionary(Of String, Object))()

            For Each graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode In graphicsLayerNodes
                ' Get the feature nodes contained by the current graphics layer node
                Dim featureNodes As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) = Me.FindChildFeatureNodes(graphicsLayerNode)

                ' Get the markup for the activity indicator that will be shown when retrieving attributes for
                ' a MapTip or feature node
                Dim activityIndicatorMarkup As String = Me.CreateActivityIndicatorMarkup()

                ' Replace the markup for any feature nodes contained in the passed-in node with the
                ' activity indicator markup.  This method will store the replaced feature data markup 
                ' for retrieval the first time a node is expanded.
                Me.CreateOnDemandNodes(args.Node, activityIndicatorMarkup, cacheAttributesInWebTier)

                ' Update the task results MapTips so that attributes are retrieved on-demand (i.e. 
                ' when a MapTip is first expanded)
                Me.CreateOnDemandMapTips(graphicsLayerNode, activityIndicatorMarkup, cacheAttributesInWebTier)

                ' Create a dictionary to hold the graphics layer node's properties and add the node ID and the
                ' IDs of child feature nodes
                Dim graphicsLayerNodeProperties As System.Collections.Generic.Dictionary(Of String, Object) = New System.Collections.Generic.Dictionary(Of String, Object)()
                graphicsLayerNodeProperties.Add("nodeID", graphicsLayerNode.NodeID)
                graphicsLayerNodeProperties.Add("featureNodes", Me.GetCurrentPageNodeIDs(graphicsLayerNode))

                ' If the node has more than one page of child nodes, add properties storing page information
                If graphicsLayerNode.NumberOfPages > 1 Then
                    graphicsLayerNodeProperties.Add("pagingTextFormatString", graphicsLayerNode.PagingTextFormatString)
                    graphicsLayerNodeProperties.Add("pageSize", graphicsLayerNode.PageSize)
                    graphicsLayerNodeProperties.Add("pageCount", graphicsLayerNode.NumberOfPages)
                    graphicsLayerNodeProperties.Add("nodeCount", graphicsLayerNode.Nodes.Count)
                End If

                ' Add the properties to the list of graphics layer node properties
                graphicsLayerNodeList.Add(graphicsLayerNodeProperties)
            Next graphicsLayerNode

            ' Create a dictionary to hold the node ID of the task results node and the client tier properties of child
            ' graphics layer nodes.  This information is used to initialize the task results node and its child nodes
            ' on the client.
            Dim taskResultNodeProperties As System.Collections.Generic.Dictionary(Of String, Object) = New System.Collections.Generic.Dictionary(Of String, Object)()
            taskResultNodeProperties.Add("nodeID", args.Node.NodeID)
            taskResultNodeProperties.Add("graphicsLayerNodes", graphicsLayerNodeList)

            Dim jsSerializer As System.Web.Script.Serialization.JavaScriptSerializer = New System.Web.Script.Serialization.JavaScriptSerializer()
            ' Construct script to call the client-side task result node initialization routine, passing
            ' it the task results JSON constructed above.  Wrap the script in a callback result for
            ' processing on the client
            Dim nodeInitScript As String = "" & ControlChars.CrLf & "                var taskResults = $find('{0}');" & ControlChars.CrLf & "                taskResults.initClientNodes({1});"
            nodeInitScript = String.Format(nodeInitScript, Me.ClientID, jsSerializer.Serialize(taskResultNodeProperties))
            Me.CallbackResults.Add(ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(nodeInitScript))
        End Sub

        ' Fires when a node is expanded.  Updates the node text and associated graphic feature with
        ' stored attribute data.
        Private Sub OnDemandTaskResults_NodeExpanded(ByVal sender As Object, ByVal args As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeEventArgs)
            ' Make sure the expanded node is a feature node
            If Not (TypeOf args.Node Is ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) Then
                Return
            End If

            ' If the node data cache contains data for the graphic feature, then update the feature
            ' and its node from the web tier cache.  Otherwise, the control that created the result
            ' must implement IAttributesOnDemandProvider, so we update the result with information
            ' retrieved from that control.
            If Me.NodeDataCache.ContainsKey(args.Node.Attributes("GraphicID")) Then
                Me.UpdateResultFromCache(TryCast(args.Node, ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode), Nothing)
            ElseIf (Not Me.ProcessedNodes.Contains(args.Node.NodeID)) Then
                Me.UpdateResultFromProvider(TryCast(args.Node, ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode), Nothing)
            End If

            ' === USED ONLY FOR DEMONSTRATION - REMOVE FOR PRODUCTION PURPOSES ==
            '  Suspend the current thread to allow the retrieving data activity indicator to display
            System.Threading.Thread.Sleep(1000)
        End Sub

        ' Fires when a node is removed.  Removes any feature data associated with the passed-in node
        ' or child nodes from the graphic feature and node data caches.
        Private Sub OnDemandTaskResults_NodeRemoved(ByVal sender As Object, ByVal args As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNodeRemovedEventArgs)
            ' Remove any feature data associated with the node or any of its descendants from the web tier cache
            Me.RemoveDataFromCache(args.Node)

            ' Add a callback result to dispose the client tier node object
            Dim disposeClientNodeScript As String = String.Format("$find('{0}').dispose()", args.Node.NodeID)
            Me.CallbackResults.Add(ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(disposeClientNodeScript))
        End Sub

#End Region

#Region "ICallbackEventHandler Overrides - RaiseCallbackEvent"

        ' Retrieves node and graphic feature attributes when a task result MapTip is expanded
        Public Overrides Sub RaiseCallbackEvent(ByVal eventArgument As String)
            ' Get the callback arguments
            Dim callbackArgs As System.Collections.Specialized.NameValueCollection = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackUtility.ParseStringIntoNameValueCollection(eventArgument)

            ' Check whether the argument specifies retrieval of a graphic feature's attributes or a new node page
            Select Case callbackArgs("EventArg")
                Case "getAttributes"
                    ' Get the graphics layer and feature nodes associated with the graphic feature
                    Dim graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode = Me.Nodes.FindByNodeID(callbackArgs("LayerNodeID"))
                    Dim featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode = TryCast(Me.FindNodeByAttribute(graphicsLayerNode, "GraphicID", callbackArgs("GraphicID")), ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode)

                    If featureNode Is Nothing Then
                        Return
                    End If

                    ' If the node data cache contains data for the graphic feature, then update the feature
                    ' and its node from the web tier cache.  Otherwise, the control that created the result
                    ' must implement IAttributesOnDemandProvider, so we update the result with information
                    ' retrieved from that control.
                    If Me.NodeDataCache.ContainsKey(featureNode.Attributes("GraphicID")) Then
                        Me.UpdateResultFromCache(featureNode, callbackArgs("MapTipsID"))
                    ElseIf (Not Me.ProcessedNodes.Contains(featureNode.NodeID)) Then
                        Me.UpdateResultFromProvider(featureNode, callbackArgs("MapTipsID"))
                    End If

                    ' === USED ONLY FOR DEMONSTRATION - REMOVE FOR PRODUCTION PURPOSES ==
                    '  Suspend the current thread to allow the retrieving attributes indicator to display
                    System.Threading.Thread.Sleep(1000)
                Case "getPage"
                    ' Get the parent node for which a new page of child nodes needs to be retrieved
                    Dim node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode = Me.Nodes.FindByNodeID(callbackArgs("NodeID"))
                    ' Update the node's page number property
                    node.PageNumber = Integer.Parse(callbackArgs("PageNumber"))

                    ' Get JSON used to initialize the new page of nodes on the client
                    Dim nodesJson As String = Me.GetNodesJson(node)

                    ' Construct JavaScript to initialize the new page on the client using client-side initialization
                    ' methods and the nodes JSON.  Wrap the script in a callback result for processing on the client.
                    Dim changeNodePageScript As String = "" & ControlChars.CrLf & "                        var node = $find('{0}');" & ControlChars.CrLf & "                        node._newNodes[{1}] = {2};" & ControlChars.CrLf & "                        node.set_page({1});"
                    changeNodePageScript = String.Format(changeNodePageScript, callbackArgs("NodeID"), node.PageNumber, nodesJson)
                    Me.CallbackResults.Add(ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(changeNodePageScript))
                Case "nodeChecked"
                    ' Get the node that was checked
                    Dim checkedNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode = TryCast(Me.Nodes.FindByNodeID(callbackArgs("NodeID")), ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode)
                    If checkedNode Is Nothing Then
                        Exit Select
                    End If

                    ' Update the web tier Checked property of the node
                    checkedNode.Checked = Boolean.Parse(callbackArgs("Checked"))

                    ' Get the parent node of the checked node and make sure it's a GraphicsLayerNode
                    Dim parentNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode = TryCast(checkedNode.Parent, ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode)
                    If parentNode Is Nothing Then
                        Exit Select
                    End If

                    ' Find the row in the graphics layer corresponding to the checked node and update its selected
                    ' value.  This is necessary so Web ADF operations relying on this value, such as zooming to 
                    ' selected features, still function properly.
                    For Each row As System.Data.DataRow In parentNode.Layer.Rows
                        Dim graphicFeatureID As String = Me.GetGraphicClientID(row(parentNode.Layer.GraphicsIDColumn).ToString(), parentNode)
                        If graphicFeatureID = checkedNode.Attributes("GraphicID") Then
                            row(parentNode.Layer.IsSelectedColumn) = Boolean.Parse(callbackArgs("Checked"))
                            Exit For
                        End If
                    Next row
            End Select

            MyBase.RaiseCallbackEvent(eventArgument)
        End Sub

#End Region

#Region "Private Methods"

#Region "On-Demand Initialization - CreateOnDemandNodes, CreateOnDemandMapTips, CreateMapTipsInitScript"

        ' Replaces any feature data displayed by the passed-in node or child nodes with a "retrieving
        ' data" activity indicator and stores that feature data for on-demand retrieval
        Private Sub CreateOnDemandNodes(ByVal parentNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode, ByVal nodeMarkup As String, ByVal cacheInWebTier As Boolean)
            ' Attempt to get a reference to the passed-in node as a FeatureNode
            Dim featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode = TryCast(parentNode, ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode)

            ' Check whether the node is a FeatureNode and has a child node.  The child node will contain
            ' the feature's attribute data
            If Not featureNode Is Nothing AndAlso featureNode.Nodes.Count > 0 AndAlso featureNode.Nodes(0).Text.ToLower().Contains("<table>") Then
                ' Add the node's feature data to the node data cache
                If cacheInWebTier Then
                    Me.NodeDataCache.Add(featureNode.Attributes("GraphicID"), featureNode.Nodes(0).Text)
                End If


                ' Update the node's text with the passed-in markup
                featureNode.Nodes(0).Text = nodeMarkup
            End If

            ' Recursively call this method for any nodes that are children of the passed-in node
            If parentNode.Nodes.Count > 0 Then
                For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In parentNode.Nodes
                    Me.CreateOnDemandNodes(childNode, nodeMarkup, cacheInWebTier)
                Next childNode
            End If

            Return
        End Sub

        ' Removes the attribute data of features contained in the graphics layer referenced by the passed-in node
        ' and stores this data in the web tier.  Generates script to initialize the graphics layer's MapTips to
        ' display an activity indicator and retrieve stored data when a MapTip is expanded.
        Private Sub CreateOnDemandMapTips(ByVal graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode, ByVal activityIndicatorMarkup As String, ByVal cacheAttributes As Boolean)
            ' Get the layer referenced by the node
            Dim graphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer = graphicsLayerNode.Layer

            If cacheAttributes Then
                ' Store a JSON string containing the MapTips attributes for each feature (row) in the
                ' graphics layer.  
                For Each row As System.Data.DataRow In graphicsLayer.Rows
                    Dim jsonAttributes As String = Me.GetAttributesJson(row, graphicsLayer)

                    ' Add the attributes for the current row to the graphics attribute cache, specifying the 
                    ' corresponding client-side GraphicFeature ID as the key
                    Dim graphicID As String = Me.GetGraphicClientID(row(graphicsLayer.GraphicsIDColumn).ToString(), graphicsLayerNode)
                    Me.GraphicsAttributeCache.Add(graphicID, jsonAttributes)
                Next row

                ' Get the graphics layer's title template.  This is the template used to format the title
                ' of the layer's MapTips
                Dim titleTemplate As String = graphicsLayer.GetTitleTemplate(True)

                ' Remove data from the graphics layer for all fields except IsSelected, graphics ID, geometry, and any column 
                ' that is included in the MapTips title.
                Dim removeIndex As Integer = 0
                Dim columnCount As Integer = graphicsLayer.Columns.Count
                Dim currentColumn As System.Data.DataColumn
                Dim i As Integer = 0
                Do While i < columnCount
                    currentColumn = graphicsLayer.Columns(removeIndex)
                    If (Not titleTemplate.Contains("{" & currentColumn.ColumnName & "}")) AndAlso (currentColumn.ColumnName <> graphicsLayer.GraphicsIDColumn.ColumnName) AndAlso (currentColumn.ColumnName <> graphicsLayer.IsSelectedColumn.ColumnName) AndAlso (Not currentColumn.DataType.IsAssignableFrom(GetType(ESRI.ArcGIS.ADF.Web.Geometry.Geometry))) Then
                        graphicsLayer.Columns.RemoveAt(removeIndex)
                    Else
                        removeIndex += 1
                    End If
                    i += 1
                Loop
            End If

            ' Generate JavaScript to initialize on-demand functionality for the graphics layer's MapTips
            ' on the client, and add this script to the TaskResults control's callback results
            Dim initializeMapTipsJavaScript As String = Me.CreateMapTipsInitScript(graphicsLayerNode, activityIndicatorMarkup)
            Me.CallbackResults.Add(ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(initializeMapTipsJavaScript))
        End Sub

        ' Generates the JavaScript necessary to initialize attributes-on-demand functionality for the MapTips
        ' of the graphics layer referenced by the passed-in node.
        Private Function CreateMapTipsInitScript(ByVal graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode, ByVal activityIndicatorMarkup As String) As String
            ' Get the AJAX client ID of the GraphicFeatureGroup corresponding to the passed-in graphics layer
            Dim graphicsLayerClientID As String = Me.GetGraphicsLayerClientID(graphicsLayerNode.Layer)

            ' Add an attribute on the node to allow convenient retrieval of the GraphicFeatureGroup on the client
            graphicsLayerNode.Attributes.Add("GraphicFeatureGroupID", graphicsLayerClientID)

            ' Create a dictionary storing the on-demand mapTips initialization properties
            Dim mapTipsInitProperties As System.Collections.Generic.Dictionary(Of String, Object) = New System.Collections.Generic.Dictionary(Of String, Object)()
            mapTipsInitProperties.Add("graphicFeatureGroupID", graphicsLayerClientID)
            mapTipsInitProperties.Add("graphicsLayerNodeID", graphicsLayerNode.NodeID)
            mapTipsInitProperties.Add("activityIndicatorTemplate", activityIndicatorMarkup)
            mapTipsInitProperties.Add("callbackFunctionString", Me.CallbackFunctionString)
            Dim jsSerializer As System.Web.Script.Serialization.JavaScriptSerializer = New System.Web.Script.Serialization.JavaScriptSerializer()

            ' Construct script to call the client tier on-demand MapTips initialization method.  This logic is
            ' embedded in a timeout so it executes after the results graphics layer has been created.  The 
            ' initialization properties, stored in a dictionary above, are serialized to JSON and temporarily 
            ' stored on the client tier task results object so they can be accessed from within the timeout.
            Dim mapTipsInitJavaScript As String = "var taskResults = $find('{0}');" & ControlChars.CrLf & "                if (!taskResults._initializationProps)" & ControlChars.CrLf & "                    taskResults._initializationProps = new Array();" & ControlChars.CrLf & "                taskResults._initializationProps.push({1});" & ControlChars.CrLf & "                window.setTimeout(""var mapTips = $find('{2}').get_mapTips();"" +" & ControlChars.CrLf & "                    ""mapTips.setupOnDemandMapTips($find('{0}')._initializationProps.splice(0,1)[0]);"", 0);"
            Return String.Format(mapTipsInitJavaScript, Me.ClientID, jsSerializer.Serialize(mapTipsInitProperties), graphicsLayerClientID)
        End Function

#End Region

#Region "On-Demand Data Retrieval - UpdateResultFromProvider, UpdateResultFromCache, UpdateNodeData, UpdateGraphicFeatureData"

        ' Creates and adds the callback results necessary to update the graphic feature and feature node
        ' referred to by the passed-in node.  Retrieves the feature data from the control that created
        ' the result.
        Private Sub UpdateResultFromProvider(ByVal featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode, ByVal mapTipsClientID As String)
            ' Get the graphics layer and task result nodes that contain the passed-in feature node
            Dim graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode = TryCast(featureNode.Parent, ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode)
            If graphicsLayerNode Is Nothing Then
                Return
            End If

            Dim taskResultNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode = TryCast(graphicsLayerNode.Parent, ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResultNode)
            If taskResultNode Is Nothing Then
                Return
            End If

            ' Get the control that added the result and cast it to the IAttributesOnDemandProvider interface
            Dim attributesProvider As OnDemandTaskResults_VBNet.IAttributesOnDemandProvider = TryCast(ESRI.ArcGIS.ADF.Web.UI.WebControls.Utility.FindControl(taskResultNode.Attributes("AttributesProviderID"), Me.Page), OnDemandTaskResults_VBNet.IAttributesOnDemandProvider)

            ' Extract the feature ID from the graphic feature ID
            Dim graphicFeatureID As String = featureNode.Attributes("GraphicID")
            Dim featureID As String = graphicFeatureID.Substring(graphicFeatureID.LastIndexOf("_"c) + 1)

            ' Get the feature's attributes
            Dim attributesRow As System.Data.DataRow = attributesProvider.GetAttributeData(featureID)

            ' Update the feature node and graphic feature with the attributes
            Dim nodeMarkup As String = Me.GetDataRowHtmlTable(attributesRow)
            Me.UpdateNodeData(featureNode, nodeMarkup)
            Dim attributesJson As String = Me.GetAttributesJson(attributesRow, graphicsLayerNode.Layer)
            Me.UpdateGraphicFeatureData(graphicFeatureID, attributesJson, mapTipsClientID)

            ' Flag the passed-in node as processed so the control won't attempt to retrieve attributes for the same
            ' node again.
            Me.ProcessedNodes.Add(featureNode.NodeID)
        End Sub

        ' Creates and adds the callback results necessary to update the graphic feature and feature node
        ' referred to by the passed-in node.  Retrieves the feature data from the web tier cache created
        ' by this instance.
        Private Sub UpdateResultFromCache(ByVal featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode, ByVal mapTipsClientID As String)
            ' Get the graphic feature ID
            Dim graphicID As String = featureNode.Attributes("GraphicID")

            ' Update the server-side representation of the node with the stored data
            Dim nodeMarkup As String = Me.NodeDataCache(graphicID)
            Me.UpdateNodeData(featureNode, nodeMarkup)

            ' Remove the node's data from web tier storage
            Me.NodeDataCache.Remove(graphicID)

            ' Get the JSON string containing the feature data from storage
            Dim attributesJson As String = Me.GraphicsAttributeCache(graphicID)

            ' Update the graphic feature's data
            Me.UpdateGraphicFeatureData(graphicID, attributesJson, mapTipsClientID)

            ' Remove the attribute data for the specified feature from storage
            Me.GraphicsAttributeCache.Remove(graphicID)

            ' Flag the passed-in node as processed so the control won't attempt to retrieve attributes for the same
            ' node again.
            Me.ProcessedNodes.Add(featureNode.NodeID)
        End Sub

        ' Updates the text of the passed-in node with the feature data corresponding to the node
        Private Sub UpdateNodeData(ByVal node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode, ByVal nodeMarkup As String)
            ' Update the node's text
            node.Nodes(0).Text = nodeMarkup

            ' Get the node's markup and serialize it to JSON
            Dim jsSerializer As System.Web.Script.Serialization.JavaScriptSerializer = New System.Web.Script.Serialization.JavaScriptSerializer()
            nodeMarkup = jsSerializer.Serialize(Me.GetNodeHtml(node))

            ' Construct JavaScript to update the node's content on the client.  Package the script in a callback
            ' result for client-side processing.
            Dim nodeUpdateScript As String = "" & ControlChars.CrLf & "                var node = $find('{0}');" & ControlChars.CrLf & "                if (node)" & ControlChars.CrLf & "                    node.set_content({1});"
            nodeUpdateScript = String.Format(nodeUpdateScript, node.NodeID, nodeMarkup)
            Me.CallbackResults.Add(ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(nodeUpdateScript))
        End Sub

        ' Updates the attributes of the client tier GraphicFeature specified by the passed-in ID with
        ' the feature data stored in the web tier
        Private Sub UpdateGraphicFeatureData(ByVal graphicFeatureID As String, ByVal attributesJson As String, ByVal mapTipsClientID As String)
            Dim updateAttributesScript As String

            ' Check whether a client-side MapTips control was specified
            If Not mapTipsClientID Is Nothing Then
                ' Create script to update the attributes from the specified client tier MapTips control. This 
                ' will not only update the GraphicFeature, but also immediately update the MapTip to show 
                ' those attributes, if that MapTip is still open when the data is returned to the client.
                updateAttributesScript = "" & ControlChars.CrLf & "                    var mapTips = $find('{0}');" & ControlChars.CrLf & "                    mapTips.updateAttributes('{1}', {2});"
                updateAttributesScript = String.Format(updateAttributesScript, mapTipsClientID, graphicFeatureID, attributesJson)
            Else
                ' Create script to update the attributes of the specified GraphicFeature directly
                updateAttributesScript = "" & ControlChars.CrLf & "                    var graphicFeature = $find('{0}');" & ControlChars.CrLf & "                    graphicFeature.set_attributes({1});" & ControlChars.CrLf & "                    graphicFeature.set_hasAttributes(true);"
                updateAttributesScript = String.Format(updateAttributesScript, graphicFeatureID, attributesJson)
            End If

            ' Add the script to the TaskResults control's callback results
            Me.CallbackResults.Add(ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(updateAttributesScript))
        End Sub

#End Region

#Region "Activity Indicator Creation - CreateActivityIndicatorTable, GetControlHtml"

        ' Generates a Table WebControl containing an activity indicator with the text specified via the
        ' ActivityIndicatorText property
        Private Function CreateActivityIndicatorMarkup() As String
            ' Get the web resource URL for the activity indicator gif
            Dim activityIndicatorUrl As String = Me.Page.ClientScript.GetWebResourceUrl(GetType(OnDemandTaskResults_VBNet.OnDemandTaskResults), "callbackActivityIndicator.gif")

            ' Create an Image control and initialize it to reference the activity indicator
            Dim activityIndicator As System.Web.UI.WebControls.Image = New System.Web.UI.WebControls.Image()
            activityIndicator.ImageUrl = activityIndicatorUrl

            ' Create a cell for the indicator
            Dim tableCell As System.Web.UI.WebControls.TableCell = New System.Web.UI.WebControls.TableCell()
            tableCell.Controls.Add(activityIndicator)

            ' Create a row and add the indicator cell to it
            Dim tableRow As System.Web.UI.WebControls.TableRow = New System.Web.UI.WebControls.TableRow()
            tableRow.VerticalAlign = System.Web.UI.WebControls.VerticalAlign.Middle
            tableRow.Cells.Add(tableCell)

            ' Create the indicator label
            Dim activityLabel As System.Web.UI.WebControls.Label = New System.Web.UI.WebControls.Label()
            activityLabel.Text = Me.ActivityIndicatorText
            activityLabel.Font.Italic = True
            activityLabel.ForeColor = System.Drawing.Color.Gray

            ' Add the indicator label to a new cell
            tableCell = New System.Web.UI.WebControls.TableCell()
            tableCell.Controls.Add(activityLabel)
            tableRow.Cells.Add(tableCell)

            ' Add the indicator label cell to the indicator row
            Dim activityIndicatorTable As System.Web.UI.WebControls.Table = New System.Web.UI.WebControls.Table()
            activityIndicatorTable.Rows.Add(tableRow)

            ' Return the table's markup
            Return Me.GetControlHtml(activityIndicatorTable)
        End Function

        ' Used to retrieve the markup for the activity indicator that will be shown when task results feature 
        ' nodes or MapTips are first expanded
        Private Function GetControlHtml(ByVal control As System.Web.UI.WebControls.WebControl) As String
            ' Use RenderControl to retrieve the markup for the passed-in control
            Dim stringWriter As System.IO.StringWriter = New System.IO.StringWriter()
            Dim htmlWriter As System.Web.UI.HtmlTextWriter = New System.Web.UI.HtmlTextWriter(stringWriter)
            control.RenderControl(htmlWriter)

            ' Return the markup
            Return stringWriter.ToString()
        End Function

#End Region

#Region "Node Retrieval - FindNodeByAttribute, FindChildGraphicsLayerNode, FindFeatureNodes"

        ' Searches the passed-in node and its descendants for a node with the specified attribute
        Private Function FindNodeByAttribute(ByVal parentNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode, ByVal attribute As String, ByVal value As String) As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode
            ' Retrieve the specified attribute from the passed-in node
            Dim currentValue As String = parentNode.Attributes(attribute)

            ' Return the passed-in node if it has an attribute matching the one sought
            If currentValue = value Then
                Return parentNode
            End If

            ' Recursively call this function to search for the specified attribute on child nodes
            Dim matchingNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode = Nothing
            For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In parentNode.Nodes
                matchingNode = Me.FindNodeByAttribute(childNode, attribute, value)
                If Not matchingNode Is Nothing Then
                    Exit For
                End If
            Next childNode

            Return matchingNode
        End Function

        ' Retrieves all GraphicsLayerNodes that are descendants of the passed-in node, if available
        Private Function FindChildGraphicsLayerNodes(ByVal node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode)
            Dim nodes As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode) = New System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode)()

            Dim graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode = TryCast(node, ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode)
            If Not graphicsLayerNode Is Nothing Then
                nodes.Add(graphicsLayerNode)
            End If

            For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In node.Nodes
                nodes.AddRange(Me.FindChildGraphicsLayerNodes(childNode))
            Next childNode

            Return nodes
        End Function

        ' Retrieves all FeatureNodes that are descendants of the passed-in node, if available
        Private Function FindChildFeatureNodes(ByVal node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode)
            Dim nodes As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) = New System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode)()

            Dim featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode = TryCast(node, ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode)
            If Not featureNode Is Nothing Then
                nodes.Add(featureNode)
            End If

            For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In node.Nodes
                nodes.AddRange(Me.FindChildFeatureNodes(childNode))
            Next childNode

            Return nodes
        End Function

#End Region

#Region "Node Information - GetNodesJson, GetNodeHtml, GetCurrentPageNodeIDs"

        ' Retrieves the IDs and html for child nodes of the passed-in node on the node's current page, 
        ' formatted as a JSON string
        Private Function GetNodesJson(ByVal parentNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As String
            ' Get the index of the first and last nodes on the curernt page
            Dim startIndex As Integer = (parentNode.PageNumber - 1) * parentNode.PageSize
            Dim stopIndex As Integer = parentNode.PageNumber * parentNode.PageSize
            If stopIndex > parentNode.Nodes.Count Then
                stopIndex = parentNode.Nodes.Count
            End If

            ' Create a list to store all the node properties
            Dim nodeList As System.Collections.Generic.List(Of System.Collections.Generic.Dictionary(Of String, Object)) = New System.Collections.Generic.List(Of System.Collections.Generic.Dictionary(Of String, Object))()
            ' Create a dictionary to store each node's contents
            Dim nodeContents As System.Collections.Generic.Dictionary(Of String, Object)

            ' Loop through the nodes on the current page and add the ID and markup of each to the property list
            Dim i As Integer = startIndex
            Do While i < stopIndex
                nodeContents = New System.Collections.Generic.Dictionary(Of String, Object)()
                nodeContents.Add("nodeID", parentNode.Nodes(i).NodeID)
                nodeContents.Add("_content", Me.GetNodeHtml(parentNode.Nodes(i)))
                nodeList.Add(nodeContents)
                i += 1
            Loop

            ' Serialize the node properties to JSON and return
            Dim jsSerializer As System.Web.Script.Serialization.JavaScriptSerializer = New System.Web.Script.Serialization.JavaScriptSerializer()
            Return jsSerializer.Serialize(nodeList)
        End Function

        ' Retrieves the markup for the passed-in node
        Private Function GetNodeHtml(ByVal node As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As String
            Dim stringWriter As System.IO.StringWriter = New System.IO.StringWriter()
            Dim htmlWriter As System.Web.UI.HtmlTextWriter = New System.Web.UI.HtmlTextWriter(stringWriter)

            ' Output the node markup to the html text writer and return its string representation
            node.Render(htmlWriter)
            Return stringWriter.ToString()
        End Function

        ' Retrieves the Node IDs of any feature nodes on the passed-in graphics layer node's current page and returns 
        ' them as a comma-delimited string.
        Private Function GetCurrentPageNodeIDs(ByVal graphicsLayerNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode) As System.Collections.Generic.List(Of String)
            ' Retrieve feature nodes that are descendants of the passed-in node
            Dim featureNodes As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode) = Me.FindChildFeatureNodes(graphicsLayerNode)

            ' Get the indexes of the first and last nodes on the current page
            Dim startIndex As Integer = (graphicsLayerNode.PageNumber - 1) * graphicsLayerNode.PageSize
            Dim stopIndex As Integer = graphicsLayerNode.PageNumber * graphicsLayerNode.PageSize
            If stopIndex > graphicsLayerNode.Nodes.Count Then
                stopIndex = graphicsLayerNode.Nodes.Count
            End If

            ' Loop through the nodes on the page and add the ID of each to a string
            Dim featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode = Nothing

            Dim nodeIDs As System.Collections.Generic.List(Of String) = New System.Collections.Generic.List(Of String)()
            Dim i As Integer = startIndex
            Do While i < stopIndex
                featureNode = TryCast(graphicsLayerNode.Nodes(i), ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode)
                If Not featureNode Is Nothing Then
                    nodeIDs.Add(featureNode.NodeID)
                End If
                i += 1
            Loop

            Return nodeIDs
        End Function

#End Region

#Region "Client Graphic ID Retrieval - GetGraphicsLayerClientID, GetGraphicClientID"

        ' Retrieves the client ID of the GraphicFeatureGroup corresponding to the passed-in GraphicsLayer.
        ' Assumes this GraphicsLayer is associated with a GraphicsLayerNode - the ID will be different otherwise.
        Private Function GetGraphicsLayerClientID(ByVal graphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer) As String
            Dim featureGraphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer = TryCast(graphicsLayer, ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer)
            If featureGraphicsLayer Is Nothing Then
                Return Nothing
            End If

            Dim graphicsLayerID As String = String.Format("{0}_{1} {2} Results_{3}", Me.MapInstance.ClientID, Me.ClientID, featureGraphicsLayer.FeatureType.ToString(), graphicsLayer.TableName)
            Return graphicsLayerID
        End Function

        ' Retrieves the AJAX component ID of a GraphicFeature referenced by the passed-in node or its descendants, 
        ' given the unique ID specified in the feature's attribute data.  The unique ID is part of the component
        ' ID, but the two are not the same.
        Private Function GetGraphicClientID(ByVal attributeUniqueID As String, ByVal parentNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode) As String
            ' Attempt to get a reference to the passed-in node as a FeatureNode
            Dim featureNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode = TryCast(parentNode, ESRI.ArcGIS.ADF.Web.UI.WebControls.FeatureNode)

            Dim clientGraphicID As String = Nothing
            If Not featureNode Is Nothing AndAlso (Not String.IsNullOrEmpty(featureNode.Attributes("GraphicID"))) Then
                ' Get the AJAX componenent ID of the GraphicFeature corresponding to the current FeatureNode
                Dim graphicID As String = featureNode.Attributes("graphicID")

                ' Extract the GraphicFeature's unique ID from the component ID
                Dim nodeUniqueID As String = graphicID.Substring(graphicID.LastIndexOf("_") + 1)

                ' If the extracted unique ID matches that passed-in, use the passed-in node's GraphicFeature ID
                ' to initialize the return value
                If attributeUniqueID = nodeUniqueID Then
                    clientGraphicID = graphicID
                End If
            ElseIf parentNode.Nodes.Count > 0 Then
                ' Try retrieving the GraphicFeature ID from the passed-in node's child nodes
                For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In parentNode.Nodes
                    clientGraphicID = Me.GetGraphicClientID(attributeUniqueID, childNode)
                    If (Not String.IsNullOrEmpty(clientGraphicID)) Then
                        Exit For
                    End If
                Next childNode
            End If

            Return clientGraphicID
        End Function

#End Region

        ' Removes feature data contained by the passed-in node or any of its descendants from the node and
        ' graphic feature attributes data caches
        Private Sub RemoveDataFromCache(ByVal parentNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode)
            ' If the passed-in node's graphic ID has entries in the node and graphic attribute caches,
            ' remove those entries
            Dim graphicID As String = parentNode.Attributes("GraphicID")
            If (Not String.IsNullOrEmpty(graphicID)) AndAlso Me.NodeDataCache.ContainsKey(graphicID) Then
                Me.NodeDataCache.Remove(graphicID)
                Me.GraphicsAttributeCache.Remove(graphicID)
            End If

            ' Remove node and graphic attribute data corresponding to nodes that are children of the
            ' passed-in node
            If parentNode.Nodes.Count > 0 Then
                For Each childNode As ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode In parentNode.Nodes
                    Me.RemoveDataFromCache(childNode)
                Next childNode
            End If

            Return
        End Sub

        ' Retrieves the control that initiated the asynchronous request
        Private Function GetCallingControl(ByVal page As System.Web.UI.Page) As System.Web.UI.Control
            If page Is Nothing Then
                Return Nothing
            End If
            If page.IsCallback Then
                Dim controlID As String = page.Request.Params("__CALLBACKID")
                Dim control As System.Web.UI.Control = page.FindControl(controlID)
                Return control
                ' For 9.3 we could be using a partial postback instead
            ElseIf page.IsPostBack AndAlso Not System.Web.UI.ScriptManager.GetCurrent(page) Is Nothing AndAlso System.Web.UI.ScriptManager.GetCurrent(page).IsInAsyncPostBack Then
                Dim controlID As String = System.Web.UI.ScriptManager.GetCurrent(page).AsyncPostBackSourceElementID
                Dim control As System.Web.UI.Control = page.FindControl(controlID)
                Return control
            Else 'Not an asyncronous request
                Return Nothing
            End If
        End Function

        ' Converts a DataRow to a JSON string.  Uses the passed-in graphics layer to determine which fields
        ' in the row should be included.
        Private Function GetAttributesJson(ByVal attributesRow As System.Data.DataRow, ByVal graphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer) As String
            Dim visibilityString As String
            Dim visibility As Boolean
            Dim attributesDictionary As System.Collections.Generic.Dictionary(Of String, Object) = New System.Collections.Generic.Dictionary(Of String, Object)()

            ' Add the value of each column that is visible, not the is selected column, and not a geometry column
            ' to the string dictionary.
            For Each column As System.Data.DataColumn In attributesRow.Table.Columns
                visibilityString = TryCast(column.ExtendedProperties(ESRI.ArcGIS.ADF.Web.Constants.ADFVisibility), String)
                If (visibilityString Is Nothing) Then
                    visibility = False
                Else
                    visibility = Boolean.Parse(visibilityString)
                End If
                If (column.ColumnName <> graphicsLayer.IsSelectedColumn.ColumnName) AndAlso visibility AndAlso (Not column.DataType.IsAssignableFrom(GetType(ESRI.ArcGIS.ADF.Web.Geometry.Geometry))) Then
                    attributesDictionary.Add(column.ColumnName, attributesRow(column.ColumnName))
                End If
            Next column

            ' Convert the string dictionary to JSON
            Dim jsSerialzer As System.Web.Script.Serialization.JavaScriptSerializer = New System.Web.Script.Serialization.JavaScriptSerializer()
            Return jsSerialzer.Serialize(attributesDictionary)
        End Function

#End Region
    End Class
End Namespace