Common Task results
Common_TaskResults_VBNet\TaskResultsWebSite\App_Code\TaskResultsPanel.vb
' Copyright 2011 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 ESRI.ADF.Samples.CustomTasks
  Public Class TaskResultsPanel
    Inherits ESRI.ArcGIS.ADF.Web.UI.WebControls.FloatingPanel
    #Region "Instance Variables"

    Private _resultsGraphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer
    Private _map As ESRI.ArcGIS.ADF.Web.UI.WebControls.Map

    #End Region

    #Region "Public Properties"

    ' Maximum panel width when the panel is first shown
    Public Property InitialMaxWidth() As System.Web.UI.WebControls.Unit
      Get
        If Me.StateManager.GetProperty("InitialMaxWidth") Is Nothing Then
          Return New System.Web.UI.WebControls.Unit(-1, System.Web.UI.WebControls.UnitType.Pixel)
        Else
          Return CType(Me.StateManager.GetProperty("InitialMaxWidth"), System.Web.UI.WebControls.Unit)
        End If
      End Get
      Set
        Me.StateManager.SetProperty("InitialMaxWidth", Value)
      End Set
    End Property

    ' Maximum panel height when the panel is first shown
    Public Property InitialMaxHeight() As System.Web.UI.WebControls.Unit
      Get
        If Me.StateManager.GetProperty("InitialMaxHeight") Is Nothing Then
          Return New System.Web.UI.WebControls.Unit(-1, System.Web.UI.WebControls.UnitType.Pixel)
        Else
          Return CType(Me.StateManager.GetProperty("InitialMaxHeight"), System.Web.UI.WebControls.Unit)
        End If
      End Get
      Set
        Me.StateManager.SetProperty("InitialMaxHeight", Value)
      End Set
    End Property

    ' Gets the Map control associated with the panel
    Public ReadOnly Property MapInstance() As ESRI.ArcGIS.ADF.Web.UI.WebControls.Map
      Get
        ' Return the map member variable if it already references a Map control
        If Not _map Is Nothing Then
          Return _map
        End If

        ' If the MapID has been initialized, get the control referenced by that ID
        If (Not String.IsNullOrEmpty(Me.MapID)) Then
                    _map = TryCast(ESRI.ArcGIS.ADF.Web.UI.WebControls.Utility.FindControl(Me.MapID, Me.Page), ESRI.ArcGIS.ADF.Web.UI.WebControls.Map)
        End If

        Return _map
      End Get
    End Property

    ' ID of the Map control associated with the panel
    Public Property MapID() As String
      Get
        Return TryCast(Me.StateManager.GetProperty("ResultsPanelMapID"), String)
      End Get
      Set
        ' Write the ID to state
        Me.StateManager.SetProperty("ResultsPanelMapID", Value)
        ' Reset the Map member variable so the map is retrieved next time the MapInstance
        ' property is referenced
        _map = Nothing
      End Set
    End Property

    ' Gets the GraphicsLayer associated with the panel
    Public ReadOnly Property GraphicsLayer() As ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer
      Get
        If Not _resultsGraphicsLayer Is Nothing Then
          Return _resultsGraphicsLayer
        End If

        _resultsGraphicsLayer = Me.GetGraphicsLayer()
        Return _resultsGraphicsLayer
      End Get
    End Property

    #End Region

    #Region "Private Properties"

    ' Name of the resource containing the associated GraphicsLayer
    Private Property ResourceName() As String
      Get
        Return TryCast(Me.StateManager.GetProperty("ResultsResourceName"), String)
      End Get
      Set
        Me.StateManager.SetProperty("ResultsResourceName", Value)
      End Set
    End Property

    ' Name of the GraphicsLayer
    Private Property LayerName() As String
      Get
        Return TryCast(Me.StateManager.GetProperty("ResultsLayerName"), String)
      End Get
      Set
        Me.StateManager.SetProperty("ResultsLayerName", Value)
      End Set
    End Property

    ' Client ID of the GraphicsLayer
    Private Property GraphicsLayerClientID() As String
      Get
        Dim id As String = TryCast(Me.StateManager.GetProperty("GraphicsLayerClientID"), String)
        If id Is Nothing Then
          Me.GraphicsLayerClientID = Me.MapInstance.GetGraphicsLayerClientID(Me.GraphicsLayer)
                    id = TryCast(Me.StateManager.GetProperty("GraphicsLayerClientID"), String)
        End If
        Return id
      End Get
      Set
        Me.StateManager.SetProperty("GraphicsLayerClientID", Value)
      End Set
    End Property

    ' Stores the width of the panel's contents
    Private Property ContentWidth() As Integer
      Get
        If StateManager.GetProperty("ContentWidth") Is Nothing Then
          Return 0
        Else
          Return CInt(Fix(StateManager.GetProperty("ContentWidth")))
        End If
      End Get
      Set
        StateManager.SetProperty("ContentWidth", Value)
      End Set
    End Property

    ' Stores the height of the panel's contents
    Private Property ContentHeight() As Integer
      Get
        If StateManager.GetProperty("ContentHeight") Is Nothing Then
          Return 0
        Else
          Return CInt(Fix(StateManager.GetProperty("ContentHeight")))
        End If
      End Get
      Set
        StateManager.SetProperty("ContentHeight", Value)
      End Set
    End Property

    #End Region

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

    ' Creates the panel interface
    Protected Overrides Overloads Sub CreateChildControls()
      Controls.Clear()
      MyBase.CreateChildControls()

      ' If the panel does not have an associated GraphicsLayer, exit the function
      If Me.GraphicsLayer Is Nothing Then
        Return
      End If

      ' Create a div to hold all the panel's content
      Dim contentDiv As System.Web.UI.HtmlControls.HtmlGenericControl = New System.Web.UI.HtmlControls.HtmlGenericControl("div")
      contentDiv.ID = "ContentDiv"
      contentDiv.Style(System.Web.UI.HtmlTextWriterStyle.Overflow) = "auto"

      ' If the content height and width have already been calculated, apply them to the
      ' content div
      If Me.ContentWidth > 0 Then
        contentDiv.Style(System.Web.UI.HtmlTextWriterStyle.Width) = String.Format("{0}px", Me.ContentWidth.ToString())
      End If
      If Me.ContentHeight > 0 Then
        contentDiv.Style(System.Web.UI.HtmlTextWriterStyle.Height) = String.Format("{0}px", Me.ContentHeight.ToString())
      End If

      ' Add the div to the task's controls collection
      Me.Controls.Add(contentDiv)

      ' Create a table to store the task results and initialize its styling
      Dim resultsTable As System.Web.UI.WebControls.Table = New System.Web.UI.WebControls.Table()
      resultsTable.ID = "ContentTable"
      resultsTable.Style("border-right") = "silver 2px solid"
      resultsTable.Style("border-bottom") = "silver 2px solid"
      resultsTable.CellPadding = 0

      ' If the content height and width have already been calculated, apply them to the
      ' results table
      If Me.ContentWidth > 0 Then
        resultsTable.Style(System.Web.UI.HtmlTextWriterStyle.Width) = String.Format("{0}px", Me.ContentWidth.ToString())
      End If
      If Me.ContentHeight > 0 Then
        resultsTable.Style(System.Web.UI.HtmlTextWriterStyle.Height) = String.Format("{0}px", Me.ContentHeight.ToString())
      End If

      ' Add the table to the content div
      contentDiv.Controls.Add(resultsTable)

      ' Get the indexes of the columns to be displayed
      Dim displayColumnIndexList As System.Collections.Generic.List(Of Integer) = Me.GetDisplayColumnIndexes()

      Dim columnWidthList As System.Collections.Generic.List(Of Single) = New System.Collections.Generic.List(Of Single)()
      Dim rowHeightList As System.Collections.Generic.List(Of Single) = New System.Collections.Generic.List(Of Single)()

      ' Create a header row and add it to the table
      resultsTable.Rows.Add(Me.CreateHeaderRow(displayColumnIndexList, columnWidthList, rowHeightList))

      ' Create data rows and add them to the table
      resultsTable.Rows.AddRange(Me.CreateDataRows(displayColumnIndexList, columnWidthList, rowHeightList))

      ' Calculate the table width by summing the stored widths of the text of each column, then adding the 
      ' width of each column's border.
      Dim tableWidth As Single = 0
      For Each columnWidth As Single In columnWidthList
        tableWidth += columnWidth
      Next columnWidth
      tableWidth += columnWidthList.Count * 2
      tableWidth = CSng(System.Math.Round(tableWidth +.5))

      ' Calculate the table height by summing the stored heights of the text of each row, then adding the
      ' height of each column's border.
      Dim tableHeight As Single = 0
      For Each rowHeight As Single In rowHeightList
        tableHeight += rowHeight * 2
      Next rowHeight
      tableHeight += rowHeightList.Count
      tableHeight = CSng(System.Math.Round(tableHeight +.5))

      ' Apply the calculated dimensions to the task, the content div, and the table containing the results
      If tableWidth > 0 AndAlso tableHeight > 0 Then
        Me.Width = New System.Web.UI.WebControls.Unit(tableWidth, System.Web.UI.WebControls.UnitType.Pixel)
        Me.Height = New System.Web.UI.WebControls.Unit(tableHeight, System.Web.UI.WebControls.UnitType.Pixel)
        contentDiv.Style(System.Web.UI.HtmlTextWriterStyle.Width) = Me.Width.ToString()
        contentDiv.Style(System.Web.UI.HtmlTextWriterStyle.Height) = Me.Height.ToString()
        resultsTable.Style(System.Web.UI.HtmlTextWriterStyle.Width) = Me.Width.ToString()
        resultsTable.Style(System.Web.UI.HtmlTextWriterStyle.Height) = Me.Height.ToString()
      End If

      If Me.Page.IsCallback Then
        ' Apply the size on the client by passing the calculated dimensions to the task's size property
        Dim resizePanelJavaScript As String = "$find('{0}').set_size('{1}', '{2}')"
        resizePanelJavaScript = String.Format(resizePanelJavaScript, Me.ClientID, Me.Width.ToString(), Me.Height.ToString())
        Dim resizePanelCallbackResult As ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(resizePanelJavaScript)
      End If

    End Sub

    #End Region

    #Region "Public Methods"

    ' Updates the panel contents with the data of the passed-in layer.
    Public Sub SetLayer(ByVal featureGraphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer, ByVal resourceName As String, ByVal mapID As String, ByVal graphicsLayerClientID As String)
      ' Validate method input
      If featureGraphicsLayer Is Nothing OrElse String.IsNullOrEmpty(resourceName) OrElse String.IsNullOrEmpty(mapID) Then
        Return
      End If

      ' Set the member variable holding a reference to the panel's GraphicsLayer
      _resultsGraphicsLayer = featureGraphicsLayer

      ' Set private properties
      Me.ResourceName = resourceName
      Me.LayerName = featureGraphicsLayer.TableName
      Me.MapID = mapID

      If (Not String.IsNullOrEmpty(graphicsLayerClientID)) Then
        Me.GraphicsLayerClientID = graphicsLayerClientID
      End If

      ' Call CreateChildControls to reconstruct the panel's contents
      Me.CreateChildControls()

      Me.Refresh()
    End Sub

    ' Adds the JavaScript necessary to initialize the panel on the client to the panel's callback results 
    ' collection.  Necessary when a panel is being dynamically added at run time.
    Public Sub InitializeOnClient(ByVal parentControl As System.Web.UI.WebControls.WebControl, ByVal callbackFunctionString As String)
      ' Get the URLs of the images for the panel's expand and collapse buttons
      Dim expandedImageUrl As String = ESRI.ArcGIS.ADF.Web.UI.WebControls.ResourceUtility.GetImage("collapse.png", Me, GetType(ESRI.ArcGIS.ADF.Web.UI.WebControls.FloatingPanel), "Runtime")
      Dim collapsedImageUrl As String = ESRI.ArcGIS.ADF.Web.UI.WebControls.ResourceUtility.GetImage("expand.png", Me, GetType(ESRI.ArcGIS.ADF.Web.UI.WebControls.FloatingPanel), "Runtime")

      ' Get the panel's HTML and remove characters that will prevent it from being interpreted correctly
      ' on the client.
      Dim taskResultsPanelHtml As String = Me.GetControlHtml(Me)
      taskResultsPanelHtml = taskResultsPanelHtml.Replace("""", "\""")
      taskResultsPanelHtml = taskResultsPanelHtml.Replace(Constants.vbCr, "")
      taskResultsPanelHtml = taskResultsPanelHtml.Replace(Constants.vbLf, "")

      ' Construct JavaScript to update the panel's HTML and create an AJAX component for the panel
      Dim initTaskResultsPanelJavaScript As String = "" & ControlChars.CrLf & "                var parentControl = $get('{0}');" & ControlChars.CrLf & "                var row = parentControl.insertRow(parentControl.rows.length);" & ControlChars.CrLf & "                var cell = row.insertCell(0);" & ControlChars.CrLf & "                cell.innerHTML = ""{1}"";" & ControlChars.CrLf & "                var taskResultsPanel = $create(ESRI.ADF.Samples.CustomTasks.TaskResultsPanel,{{ 'id':'{2}', " & ControlChars.CrLf & "                    'transparency':{3},'callbackFunctionString':""{4}"", 'forcePNG':true, 'isDocked':false, " & ControlChars.CrLf & "                    'initialMaxWidth':'{5}', 'initialMaxHeight':'{6}', 'expandedImage':'{7}', " & ControlChars.CrLf & "                    'collapsedImage':'{8}' }},null,null, $get('{2}'));"
      initTaskResultsPanelJavaScript = String.Format(initTaskResultsPanelJavaScript, parentControl.ClientID, taskResultsPanelHtml, Me.ClientID, Me.Transparency, callbackFunctionString, Me.InitialMaxWidth.ToString(), Me.InitialMaxHeight.ToString(), expandedImageUrl, collapsedImageUrl)

      ' Embed the JavaScript in a callback result and add it to the panel's collection
      Dim initTaskResultsPanelCallbackResult As ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(initTaskResultsPanelJavaScript)

      Me.CallbackResults.Add(initTaskResultsPanelCallbackResult)
    End Sub

    ' Registers JavaScript files required by the class.  This method allows external classes to register these
    ' scripts in situations where this class will not be able to (i.e. when TaskResultsPanels are only created
    ' dynamically at run time)
    Public Shared Sub RegisterScripts(ByVal registeringControl As ESRI.ArcGIS.ADF.Web.UI.WebControls.CompositeControl)
      If (Not registeringControl.Page.ClientScript.IsStartupScriptRegistered("TaskResultsPanelScript")) Then
        registeringControl.Page.ClientScript.RegisterStartupScript(registeringControl.GetType(), "TaskResultsPanelScript", "<script type='text/javascript' language='javascript' src='JavaScript/TaskResultsPanel.js'></script>")
      End If
    End Sub

    #End Region

    #Region "Private Methods"

    #Region "Table Creation Methods"

    Private Function GetDisplayColumnIndexes() As System.Collections.Generic.List(Of Integer)
      Dim DisplayColumnIndexList As System.Collections.Generic.List(Of Integer) = New System.Collections.Generic.List(Of Integer)()

      ' Get the contents template for the panel's associated GraphicsLayer.  This will contain
      ' information about the layer's display columns.
      Dim layerContentsTemplate As String = Me.GraphicsLayer.GetContentsTemplate(True, System.Drawing.Color.White, True, Nothing)

      ' Iterate through the GraphicsLayer's columns building a list of the indexes of those that
      ' are to be displayed
      Dim i As Integer = 0
      Do While i < Me.GraphicsLayer.Columns.Count
        ' Get the curernt column
        Dim currentColumn As System.Data.DataColumn = Me.GraphicsLayer.Columns(i)

        ' Get the column's visibility from its extended properties
        Dim visibility As String = TryCast(currentColumn.ExtendedProperties(ESRI.ArcGIS.ADF.Web.Constants.ADFVisibility), String)

        ' Get the display name of the field
        Dim fieldName As String
        If (Not String.IsNullOrEmpty(currentColumn.Caption)) Then
          fieldName = currentColumn.Caption
        Else
          fieldName = currentColumn.ColumnName
        End If

        ' If the column is not supposed to be displayed, skip to the next
        If (Not visibility Is Nothing AndAlso (Not System.Convert.ToBoolean(visibility))) OrElse currentColumn.DataType Is GetType(ESRI.ArcGIS.ADF.Web.Geometry.Geometry) OrElse ((Not layerContentsTemplate.Contains(fieldName))) Then
          i += 1
          Continue Do
        End If

        ' Add the current column index to the list
        DisplayColumnIndexList.Add(i)
        i += 1
      Loop

      Return DisplayColumnIndexList
    End Function

    ' Creates the header row for the panel's data table
    Private Function CreateHeaderRow(ByVal displayColumnIndexList As System.Collections.Generic.List(Of Integer), ByRef columnWidthList As System.Collections.Generic.List(Of Single), ByRef rowHeightList As System.Collections.Generic.List(Of Single)) As System.Web.UI.WebControls.TableRow
      Dim tableRow As System.Web.UI.WebControls.TableRow = New System.Web.UI.WebControls.TableRow()

      ' Create the column for checkoxes to allow for selection/deselection of results
      If Not Me.GraphicsLayer Is Nothing Then
        ' Initialize a cell for the column header
        Dim tableCell As System.Web.UI.WebControls.TableCell = New System.Web.UI.WebControls.TableCell()
        tableCell.Style("border-right") = "silver 2px solid"
        tableCell.Style("border-bottom") = "silver 2px solid"
        tableCell.Style(System.Web.UI.HtmlTextWriterStyle.WhiteSpace) = "nowrap"
        ' Copy the task's font to the cell
        Me.CopyFont(tableCell.Font, Me.Font)
        tableCell.Font.Bold = True
        tableCell.Text = "Selected"

        ' Add the cell to the header row
        tableRow.Cells.Add(tableCell)

        ' Get the width and height of the text in the cell and add these to the row height
        ' and column width lists
        Dim cellSize As System.Drawing.SizeF = Me.GetTextScreenSize(tableCell.Text, tableCell.Font)
        rowHeightList.Add(cellSize.Height)
        columnWidthList.Add(cellSize.Width)
      End If

      ' Loop through the display column indexes, making a header cell for each
      For Each index As Integer In displayColumnIndexList
        ' Initialize a cell for the column header
        Dim tableCell As System.Web.UI.WebControls.TableCell = New System.Web.UI.WebControls.TableCell()
        tableCell.Style("border-right") = "silver 2px solid"
        tableCell.Style("border-bottom") = "silver 2px solid"
        tableCell.Style(System.Web.UI.HtmlTextWriterStyle.WhiteSpace) = "nowrap"
        ' Copy the task's font to the cell
        Me.CopyFont(tableCell.Font, Me.Font)
        tableCell.Font.Bold = True

        ' Add the cell to the header row
        tableRow.Cells.Add(tableCell)

        ' If the current column has a caption, use that as the header text.  Otherwise, use the 
        ' column name.  We do this because the Web ADF stores display aliases column captions.
        Dim currentColumn As System.Data.DataColumn = Me.GraphicsLayer.Columns(index)
        If (String.IsNullOrEmpty(currentColumn.Caption)) Then
          tableCell.Text = currentColumn.ColumnName
        Else
          tableCell.Text = currentColumn.Caption
        End If

        ' Get the width and height of the text in the cell and add these to the row height
        ' and column width lists
        Dim cellSize As System.Drawing.SizeF = Me.GetTextScreenSize(tableCell.Text, tableCell.Font)
        If cellSize.Height > rowHeightList(0) Then
          rowHeightList(0) = cellSize.Height
        Else
          rowHeightList(0) = rowHeightList(0)
        End If
        columnWidthList.Add(cellSize.Width)
      Next index
      Return tableRow
    End Function

    ' Creates the data rows for the panel's data table
    Private Function CreateDataRows(ByVal displayColumnIndexList As System.Collections.Generic.List(Of Integer), ByRef columnWidthList As System.Collections.Generic.List(Of Single), ByRef rowHeightList As System.Collections.Generic.List(Of Single)) As System.Web.UI.WebControls.TableRow()
      ' JavaScript to highlight and un-highlight the table row and corresponding graphic feature.  
      ' Wired to mouseover and mouseout.
      Dim setHighlightJavaScript As String = "" & ControlChars.CrLf & "                try {{" & ControlChars.CrLf & "                    this.style.backgroundColor = '{0}';" & ControlChars.CrLf & "                    var graphicFeatureGroup = $find('{1}');" & ControlChars.CrLf & "                    var graphicFeature = graphicFeatureGroup.get({2});" & ControlChars.CrLf & "                    graphicFeature.set_highlight({3});" & ControlChars.CrLf & "                }}" & ControlChars.CrLf & "                catch (ex) {{" & ControlChars.CrLf & "                }}"

      ' JavaScript to zoom to the feature corresponding to the clicked row.
      Dim zoomToFeatureJavaScript As String = "" & ControlChars.CrLf & "                try {{" & ControlChars.CrLf & "                    // Get the envelope of the feature" & ControlChars.CrLf & "                    var map = $find('{0}');" & ControlChars.CrLf & "                    var graphicFeatureGroup = $find('{1}');" & ControlChars.CrLf & "                    var graphicFeature = graphicFeatureGroup.get({2});" & ControlChars.CrLf & "                    var geometry = graphicFeature.get_geometry();" & ControlChars.CrLf & "                    var envelope = geometry.getEnvelope();" & ControlChars.CrLf & ControlChars.CrLf & "                    // Expand the envelope so some area round the feature is included" & ControlChars.CrLf & "                    var envelopeDivisor = 3;" & ControlChars.CrLf & "                    var xExpansionFactor = envelope.get_width() / envelopeDivisor;" & ControlChars.CrLf & "                    var yExpansionFactor = envelope.get_height() / envelopeDivisor;" & ControlChars.CrLf & ControlChars.CrLf & "                    // Update the envelope with the expansion factor" & ControlChars.CrLf & "                    var xMax = envelope.get_xmax() + xExpansionFactor;" & ControlChars.CrLf & "                    envelope.set_xmax(xMax);" & ControlChars.CrLf & ControlChars.CrLf & "                    var xMin = envelope.get_xmin() - xExpansionFactor;" & ControlChars.CrLf & "                    envelope.set_xmin(xMin);" & ControlChars.CrLf & ControlChars.CrLf & "                    var yMax = envelope.get_ymax() + yExpansionFactor;" & ControlChars.CrLf & "                    envelope.set_ymax(yMax);" & ControlChars.CrLf & ControlChars.CrLf & "                    var yMin = envelope.get_ymin() - yExpansionFactor;" & ControlChars.CrLf & "                    envelope.set_ymin(yMin);" & ControlChars.CrLf & ControlChars.CrLf & "                    // Zoom the map to the envelope" & ControlChars.CrLf & "                    map.zoomToBox(envelope, false);" & ControlChars.CrLf & "                }}" & ControlChars.CrLf & "                catch (ex) {{" & ControlChars.CrLf & "                }}"

      ' JavaScript to toggle whether or not a feature is selected.  Wired to each checkbox's onclick event.
      Dim toggleFeatureJavaScript As String = "" & ControlChars.CrLf & "                try {{" & ControlChars.CrLf & "                    var graphicFeatureGroup = $find('{0}');" & ControlChars.CrLf & "                    var graphicFeature = graphicFeatureGroup.get({1});" & ControlChars.CrLf & "                    graphicFeature.set_isSelected($get('{2}').checked);" & ControlChars.CrLf & "                }}" & ControlChars.CrLf & "                catch (ex) {{" & ControlChars.CrLf & "                }}"

      Dim tableRowArray As System.Web.UI.WebControls.TableRow() = New System.Web.UI.WebControls.TableRow(Me.GraphicsLayer.Rows.Count - 1){}
      Dim tableRow As System.Web.UI.WebControls.TableRow

      ' Loop through the rows (i.e. features) in the GraphicsLayer, creating a row in the rows array for each
      Dim i As Integer = 0
      Do While i < Me.GraphicsLayer.Rows.Count
        ' Create a new row and add it to the rows array
        tableRow = New System.Web.UI.WebControls.TableRow()
        tableRowArray(i) = tableRow

        ' Wire the row's onmouseover and onmouseout events to the JavaScript that applies and removes row and
        ' feature highlighting
        tableRow.Attributes.Add("onmouseover", String.Format(setHighlightJavaScript, "gray", Me.GraphicsLayerClientID, i, "true"))
        tableRow.Attributes.Add("onmouseout", String.Format(setHighlightJavaScript, "white", Me.GraphicsLayerClientID, i, "false"))

        ' Wire the row's onclick event to the JavaScript that will zoom the map to the coresponding graphic feature
        tableRow.Attributes.Add("onclick", String.Format(zoomToFeatureJavaScript, Me.MapInstance.ClientID, Me.GraphicsLayerClientID, i))

        ' Change the mouse cursor to ta pointer when the row is hovered over so users know clicking it will do
        ' something
        tableRow.Style(System.Web.UI.HtmlTextWriterStyle.Cursor) = "pointer"

        ' Get the current row (feature) from the GraphicsLayer
        Dim currentRow As System.Data.DataRow = Me.GraphicsLayer.Rows(i)

        ' Create a cell to hold the checkbox that toggles whether the corresponding feature is selected
        If Not GraphicsLayer Is Nothing Then
          ' Instantiate and initialize the styling of the cell
          Dim tableCell As System.Web.UI.WebControls.TableCell = New System.Web.UI.WebControls.TableCell()
          tableCell.Style("border-right") = "silver 2px solid"
          tableCell.Style("border-bottom") = "silver 2px solid"
          tableCell.Style(System.Web.UI.HtmlTextWriterStyle.WhiteSpace) = "nowrap"

          ' Add the cell to the current row
          tableRow.Cells.Add(tableCell)

          ' Create a checkbox and set its ID to the current row index.  This row index corresponds to 
          ' the ID of the graphic feature required to retrieve that feature through the Web ADF JavaScript
          ' GraphicFeatureGroup::get function.
          Dim checkBox As System.Web.UI.HtmlControls.HtmlInputCheckBox = New System.Web.UI.HtmlControls.HtmlInputCheckBox()
          checkBox.ID = i.ToString()

          ' Set the checked state of the checkbox based on the selected state of the current feature
          checkBox.Checked = System.Convert.ToBoolean(currentRow(GraphicsLayer.IsSelectedColumn))

          ' Wire the JavaScript to toggle the selected state of the corresponding feature to the 
          ' checkbox's onclick event
          Dim clientAction As String = String.Format(toggleFeatureJavaScript, Me.GraphicsLayerClientID, i, checkBox.ClientID)
          checkBox.Attributes.Add("onclick", clientAction)

          ' Add the checkbox to the current cell
          tableCell.Controls.Add(checkBox)
        End If

        ' Initialize column index and row index variables that are used to traverse the lists containing
        ' the table's column widths and row heights.
        Dim columnIndex As Integer = 1
        Dim rowIndex As Integer = i + 1

        ' Loop through the indexes of the display columns creating a table cell and adding the 
        ' corresponding feature data for each
        For Each index As Integer In displayColumnIndexList
          ' Instantiate the cell and initialize its styling
          Dim tableCell As System.Web.UI.WebControls.TableCell = New System.Web.UI.WebControls.TableCell()
          tableCell.Style("border-right") = "silver 2px solid"
          tableCell.Style("border-bottom") = "silver 2px solid"
          tableCell.Style(System.Web.UI.HtmlTextWriterStyle.WhiteSpace) = "nowrap"

          ' Apply the task's font to the cell
          Me.CopyFont(tableCell.Font, Me.Font)
          tableCell.Font.Bold = False

          ' Add the cell to the current row
          tableRow.Cells.Add(tableCell)

          ' Set the cell's text to the value of the corresponding attribute for the current feature
          tableCell.Text = currentRow(index).ToString()

          ' Add content to empty cells so they are still formatted
          If String.IsNullOrEmpty(tableCell.Text) Then
            tableCell.Text = "&nbsp;"
          End If

          ' Get the size of the cell given its text
          Dim cellSize As System.Drawing.SizeF = Me.GetTextScreenSize(tableCell.Text, tableCell.Font)

          ' Only update the row height if the current row height has not been initialized or the height 
          ' of the current cell is greater than the stored row height
          If columnIndex = 1 Then
            rowHeightList.Add(cellSize.Height)
          Else
            If cellSize.Height > rowHeightList(rowIndex) Then
              rowHeightList(rowIndex) = cellSize.Height
            Else
              rowHeightList(rowIndex) = rowHeightList(rowIndex)
            End If
          End If

          ' Only update the column width if the width of the current cell is greater than the stored 
          ' column width
          If cellSize.Width > columnWidthList(columnIndex) Then
            columnWidthList(columnIndex) = cellSize.Width
          Else
            columnWidthList(columnIndex) = columnWidthList(columnIndex)
          End If

          columnIndex += 1
        Next index
        i += 1
      Loop

      Return tableRowArray
    End Function

    #End Region

    ' Retrieves the GraphicsLayer associated with the panel based on the MapInstance, ResourceName, and
    ' LayerName properties
    Private Function GetGraphicsLayer() As ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer
      Dim featureGraphicsLayer As ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer = Nothing

      ' Make sure a resource name and layer name have been defined.  These are initialized in SetLayer.
      If (Not String.IsNullOrEmpty(Me.ResourceName)) AndAlso (Not String.IsNullOrEmpty(Me.LayerName)) Then
        ' Get the resource item corresponding to the resource name
        Dim mapResourceItem As ESRI.ArcGIS.ADF.Web.UI.WebControls.MapResourceItem = Me.MapInstance.MapResourceManagerInstance.ResourceItems.Find(Me.ResourceName)

        ' Make sure the resource item was found
        If Not mapResourceItem Is Nothing AndAlso Not mapResourceItem.Resource Is Nothing Then
          ' Get a reference to the resource underlying the resource item as a graphics resource
          Dim graphicsResource As ESRI.ArcGIS.ADF.Web.DataSources.Graphics.MapResource = TryCast(mapResourceItem.Resource, ESRI.ArcGIS.ADF.Web.DataSources.Graphics.MapResource)

          ' Make sure the resource could be referenced as a graphics resource
          If Not graphicsResource Is Nothing Then
            ' Loop through the tables and find the one matching the panel's layer name.  This will be
            ' the associated GraphicsLayer.
            For Each table As System.Data.DataTable In graphicsResource.Graphics.Tables
              If table.TableName = Me.LayerName Then
                featureGraphicsLayer = TryCast(table, ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer)
                Exit For
              End If
            Next table
          End If
        End If
      End If

      Return featureGraphicsLayer
    End Function

    ' Gets the screen size of the passed-in text in the passed-in font
    Private Function GetTextScreenSize(ByVal text As String, ByVal fontInfo As System.Web.UI.WebControls.FontInfo) As System.Drawing.SizeF
      Dim size As System.Drawing.SizeF
      Dim emSize As Single = System.Convert.ToSingle(fontInfo.Size.Unit.Value + 1)
      If emSize = 0 Then
        emSize = 12
      Else
        emSize = emSize
      End If

      Dim font As System.Drawing.Font = New System.Drawing.Font(fontInfo.Name, emSize)
      Dim bitmap As System.Drawing.Bitmap = New System.Drawing.Bitmap(1000, 100)
      Dim graphics As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(bitmap)

      size = graphics.MeasureString(text, font)
      graphics.Dispose()
      Return size
    End Function

    ' Copies the properties of one FontInfo to another
    Private Sub CopyFont(ByVal targetFont As System.Web.UI.WebControls.FontInfo, ByVal sourceFont As System.Web.UI.WebControls.FontInfo)
      targetFont.Bold = sourceFont.Bold
      targetFont.Italic = sourceFont.Italic
      targetFont.Name = sourceFont.Name
      targetFont.Names = sourceFont.Names
      targetFont.Overline = sourceFont.Overline
      targetFont.Size = sourceFont.Size
      targetFont.Strikeout = sourceFont.Strikeout
      targetFont.Underline = sourceFont.Underline
    End Sub

    ' Retrieves the HTML of a WebControl
    Private Function GetControlHtml(ByVal webControl As System.Web.UI.Control) As String
      ' Instantaite an HtmlTextWriter object
      Dim stringWriter As System.IO.StringWriter = New System.IO.StringWriter()
      Dim htmlTextWriter As System.Web.UI.HtmlTextWriter = New System.Web.UI.HtmlTextWriter(stringWriter)

      ' Render the passed-in control's html to the HtmlTextWriter
      webControl.RenderControl(htmlTextWriter)

      ' Get the control's html as a string
      Return stringWriter.ToString()
    End Function

    #End Region
  End Class
End Namespace