Common Custom tasks
Common_CustomTasks_CSharp\FindNearTask_CSharp\TaskResultsPanel.cs
// Copyright 2010 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions.
// 

[assembly: System.Web.UI.WebResource("FindNearTask_CSharp.Resources.javascript.TaskResultsPanel.js", "text/javascript")]

namespace FindNearTask_CSharp
{
    public class TaskResultsPanel : ESRI.ArcGIS.ADF.Web.UI.WebControls.FloatingPanel
    {
        #region Instance Variables

        private ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer _resultsGraphicsLayer;
        private ESRI.ArcGIS.ADF.Web.UI.WebControls.Map _map;

        #endregion

        #region Public Properties

        // Maximum panel width when the panel is first shown
        public System.Web.UI.WebControls.Unit InitialMaxWidth
        {
            get 
            { 
                if (this.StateManager.GetProperty("InitialMaxWidth") == null)
                    return new System.Web.UI.WebControls.Unit(-1, System.Web.UI.WebControls.UnitType.Pixel);
                else
                    return (System.Web.UI.WebControls.Unit)this.StateManager.GetProperty("InitialMaxWidth"); 
            }
            set { this.StateManager.SetProperty("InitialMaxWidth", value);  }
        }

        // Maximum panel height when the panel is first shown
        public System.Web.UI.WebControls.Unit InitialMaxHeight
        {
            get
            {
                if (this.StateManager.GetProperty("InitialMaxHeight") == null)
                    return new System.Web.UI.WebControls.Unit(-1, System.Web.UI.WebControls.UnitType.Pixel);
                else
                    return (System.Web.UI.WebControls.Unit)this.StateManager.GetProperty("InitialMaxHeight");
            }
            set { this.StateManager.SetProperty("InitialMaxHeight", value); }
        }

        // Gets the Map control associated with the panel
        public ESRI.ArcGIS.ADF.Web.UI.WebControls.Map MapInstance
        {
            get
            {
                // Return the map member variable if it already references a Map control
                if (_map != null)
                    return _map;

                // If the MapID has been initialized, get the control referenced by that ID
                if (!string.IsNullOrEmpty(this.MapID))
                {
                    _map = ESRI.ArcGIS.ADF.Web.UI.WebControls.Utility.FindControl(this.MapID, this.Page) 
                        as ESRI.ArcGIS.ADF.Web.UI.WebControls.Map;
                }

                return _map;
            }
        }

        // ID of the Map control associated with the panel
        public string MapID
        {
            get { return this.StateManager.GetProperty("ResultsPanelMapID") as string; }
            set {
                // Write the ID to state
                this.StateManager.SetProperty("ResultsPanelMapID", value);
                // Reset the Map member variable so the map is retrieved next time the MapInstance
                // property is referenced
                _map = null;
            }
        }

        // Gets the GraphicsLayer associated with the panel
        public ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer GraphicsLayer
        {
            get
            {
                if (_resultsGraphicsLayer != null)
                    return _resultsGraphicsLayer;

                _resultsGraphicsLayer = this.GetGraphicsLayer();
                return _resultsGraphicsLayer;
            }
        }

        #endregion

        #region Private Properties

        // Name of the resource containing the associated GraphicsLayer
        private string ResourceName
        {
            get { return this.StateManager.GetProperty("ResultsResourceName") as string; }
            set { this.StateManager.SetProperty("ResultsResourceName", value); }
        }

        // Name of the GraphicsLayer
        private string LayerName
        {
            get { return this.StateManager.GetProperty("ResultsLayerName") as string; }
            set { this.StateManager.SetProperty("ResultsLayerName", value); }
        }

        // Stores the width of the panel's contents
        private int ContentWidth
        {
            get { 
                return StateManager.GetProperty("ContentWidth") == null ? 0 : 
                (int)StateManager.GetProperty("ContentWidth"); 
            }
            set { StateManager.SetProperty("ContentWidth", value); }
        }

        // Stores the height of the panel's contents
        private int ContentHeight
        {
            get { 
                return StateManager.GetProperty("ContentHeight") == null ? 0 : 
                (int)StateManager.GetProperty("ContentHeight"); 
            }
            set { StateManager.SetProperty("ContentHeight", value); }
        }

        #endregion

        #region ASP.NET WebControl Life Cycle Event Handlers - CreateChildControls

        // Creates the panel interface
        protected override void CreateChildControls()
        {
            Controls.Clear();
            base.CreateChildControls();

            // If the panel does not have an associated GraphicsLayer, exit the function
            if (this.GraphicsLayer == null)
                return;

            // Create a div to hold all the panel's content
            System.Web.UI.HtmlControls.HtmlGenericControl contentDiv = 
                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 (this.ContentWidth > 0)
                contentDiv.Style[System.Web.UI.HtmlTextWriterStyle.Width] =
                    string.Format("{0}px", this.ContentWidth.ToString());
            if (this.ContentHeight > 0)
                contentDiv.Style[System.Web.UI.HtmlTextWriterStyle.Height] =
                    string.Format("{0}px", this.ContentHeight.ToString());

            // Add the div to the task's controls collection
            this.Controls.Add(contentDiv);

            // Create a table to store the task results and initialize its styling
            System.Web.UI.WebControls.Table resultsTable = 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 (this.ContentWidth > 0)
                resultsTable.Style[System.Web.UI.HtmlTextWriterStyle.Width] =
                    string.Format("{0}px", this.ContentWidth.ToString());
            if (this.ContentHeight > 0)
                resultsTable.Style[System.Web.UI.HtmlTextWriterStyle.Height] =
                    string.Format("{0}px", this.ContentHeight.ToString());

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

            // Get the indexes of the columns to be displayed
            System.Collections.Generic.List<int> displayColumnIndexList = this.GetDisplayColumnIndexes();

            System.Collections.Generic.List<float> columnWidthList = new System.Collections.Generic.List<float>();
            System.Collections.Generic.List<float> rowHeightList = new System.Collections.Generic.List<float>();

            // Create a header row and add it to the table
            resultsTable.Rows.Add(this.CreateHeaderRow(displayColumnIndexList, ref columnWidthList, 
                ref rowHeightList));

            // Create data rows and add them to the table
            resultsTable.Rows.AddRange(this.CreateDataRows(displayColumnIndexList, ref columnWidthList, 
                ref 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.
            float tableWidth = 0;
            foreach (float columnWidth in columnWidthList)
                tableWidth += columnWidth;
            tableWidth += columnWidthList.Count * 2;
            tableWidth = (float)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.
            float tableHeight = 0;
            foreach (float rowHeight in rowHeightList)
                tableHeight += rowHeight * 2;
            tableHeight += rowHeightList.Count;
            tableHeight = (float)System.Math.Round(tableHeight + .5);

            // Apply the calculated dimensions to the task, the content div, and the table containing the results
            if (tableWidth > 0 && tableHeight > 0)
            {
                this.Width = new System.Web.UI.WebControls.Unit(tableWidth, System.Web.UI.WebControls.UnitType.Pixel);
                this.Height = new System.Web.UI.WebControls.Unit(tableHeight, System.Web.UI.WebControls.UnitType.Pixel);
                contentDiv.Style[System.Web.UI.HtmlTextWriterStyle.Width] = this.Width.ToString();
                contentDiv.Style[System.Web.UI.HtmlTextWriterStyle.Height] = this.Height.ToString();
                resultsTable.Style[System.Web.UI.HtmlTextWriterStyle.Width] = this.Width.ToString();
                resultsTable.Style[System.Web.UI.HtmlTextWriterStyle.Height] = this.Height.ToString();
            }

            if (this.Page.IsCallback)
            {
                // Apply the size on the client by passing the calculated dimensions to the task's size property
                string resizePanelJavaScript = "$find('{0}').set_size('{1}', '{2}')";
                resizePanelJavaScript = string.Format(resizePanelJavaScript, this.ClientID,
                    this.Width.ToString(), this.Height.ToString());
                ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult resizePanelCallbackResult =
                    ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(resizePanelJavaScript);
            }

        }

        #endregion

        #region Public Methods

        // Updates the panel contents with the data of the passed-in layer.
        public void SetLayer(ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer featureGraphicsLayer, 
            string resourceName, string mapID)
        {
            // Validate method input
            if (featureGraphicsLayer == null || string.IsNullOrEmpty(resourceName) || string.IsNullOrEmpty(mapID))
                return;

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

            // Set private properties
            this.ResourceName = resourceName;
            this.LayerName = featureGraphicsLayer.TableName;
            this.MapID = mapID;

            // Call CreateChildControls to reconstruct the panel's contents
            this.CreateChildControls();

            this.Refresh();
        }

        // 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 void InitializeOnClient(System.Web.UI.WebControls.WebControl parentControl, 
            string callbackFunctionString)
        {
            // Get the URLs of the images for the panel's expand and collapse buttons
            string expandedImageUrl = ESRI.ArcGIS.ADF.Web.UI.WebControls.ResourceUtility.GetImage("collapse.png",
                this, typeof(ESRI.ArcGIS.ADF.Web.UI.WebControls.FloatingPanel), "Runtime");
            string collapsedImageUrl = ESRI.ArcGIS.ADF.Web.UI.WebControls.ResourceUtility.GetImage("expand.png",
                this, typeof(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.
            string taskResultsPanelHtml = this.GetControlHtml(this);
            taskResultsPanelHtml = taskResultsPanelHtml.Replace("\"", "\\\"");
            taskResultsPanelHtml = taskResultsPanelHtml.Replace("\r", "");
            taskResultsPanelHtml = taskResultsPanelHtml.Replace("\n", "");

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

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

            this.CallbackResults.Add(initTaskResultsPanelCallbackResult);
        }

        // 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 static void RegisterScripts(ESRI.ArcGIS.ADF.Web.UI.WebControls.CompositeControl registeringControl)
        {
            if (!registeringControl.Page.ClientScript.IsStartupScriptRegistered("TaskResultsPanelScript"))
                registeringControl.Page.ClientScript.RegisterStartupScript(registeringControl.GetType(),
                    "TaskResultsPanelScript", FindNearTask_CSharp.ResourceUtility.GetJavascript(registeringControl, 
                    "TaskResultsPanel.js", registeringControl.GetType()));
        }

        #endregion

        #region Private Methods

        #region Table Creation Methods

        private System.Collections.Generic.List<int> GetDisplayColumnIndexes()
        {
            System.Collections.Generic.List<int> DisplayColumnIndexList =
                new System.Collections.Generic.List<int>();

            // Get the contents template for the panel's associated GraphicsLayer.  This will contain
            // information about the layer's display columns.
            string layerContentsTemplate = this.GraphicsLayer.GetContentsTemplate(true,
            System.Drawing.Color.White, true, null);
            
            // Iterate through the GraphicsLayer's columns building a list of the indexes of those that
            // are to be displayed
            for (int i = 0; i < this.GraphicsLayer.Columns.Count; i++)
            {
                // Get the curernt column
                System.Data.DataColumn currentColumn = this.GraphicsLayer.Columns[i];

                // Get the column's visibility from its extended properties
                string visibility = currentColumn.ExtendedProperties[
                    ESRI.ArcGIS.ADF.Web.Constants.ADFVisibility] as string;

                // Get the display name of the field
                string fieldName = !string.IsNullOrEmpty(currentColumn.Caption) ? 
                    currentColumn.Caption : currentColumn.ColumnName;

                // If the column is not supposed to be displayed, skip to the next
                if ((visibility != null && !System.Convert.ToBoolean(visibility)) ||
                currentColumn.DataType == typeof(ESRI.ArcGIS.ADF.Web.Geometry.Geometry) ||
                (!layerContentsTemplate.Contains(fieldName)))
                    continue;

                // Add the current column index to the list
                DisplayColumnIndexList.Add(i);
            }

            return DisplayColumnIndexList;
        }

        // Creates the header row for the panel's data table
        private System.Web.UI.WebControls.TableRow CreateHeaderRow(
            System.Collections.Generic.List<int> displayColumnIndexList,
            ref System.Collections.Generic.List<float> columnWidthList,
            ref System.Collections.Generic.List<float> rowHeightList)
        {
            System.Web.UI.WebControls.TableRow tableRow = new System.Web.UI.WebControls.TableRow();

            // Create the column for checkoxes to allow for selection/deselection of results
            if (this.GraphicsLayer != null)
            {
                // Initialize a cell for the column header
                System.Web.UI.WebControls.TableCell 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
                this.CopyFont(tableCell.Font, this.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
                System.Drawing.SizeF cellSize = this.GetTextScreenSize(tableCell.Text, tableCell.Font);
                rowHeightList.Add(cellSize.Height);
                columnWidthList.Add(cellSize.Width);
            }

            // Loop through the display column indexes, making a header cell for each
            foreach (int index in displayColumnIndexList)
            {
                // Initialize a cell for the column header
                System.Web.UI.WebControls.TableCell 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
                this.CopyFont(tableCell.Font, this.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.
                System.Data.DataColumn currentColumn = this.GraphicsLayer.Columns[index];
                tableCell.Text = (string.IsNullOrEmpty(currentColumn.Caption)) ? 
                    currentColumn.ColumnName : currentColumn.Caption;

                // Get the width and height of the text in the cell and add these to the row height
                // and column width lists
                System.Drawing.SizeF cellSize = this.GetTextScreenSize(tableCell.Text, tableCell.Font);
                rowHeightList[0] = cellSize.Height > rowHeightList[0] ? cellSize.Height : rowHeightList[0];
                columnWidthList.Add(cellSize.Width);
            }
            return tableRow;
        }

        // Creates the data rows for the panel's data table
        private System.Web.UI.WebControls.TableRow[] CreateDataRows(
            System.Collections.Generic.List<int> displayColumnIndexList,
            ref System.Collections.Generic.List<float> columnWidthList,
            ref System.Collections.Generic.List<float> rowHeightList)
        {
            // JavaScript to highlight and un-highlight the table row and corresponding graphic feature.  
            // Wired to mouseover and mouseout.
            string setHighlightJavaScript = @"
                try {{
                    this.style.backgroundColor = '{0}';
                    var graphicFeatureGroup = $find('{1}');
                    var graphicFeature = graphicFeatureGroup.get({2});
                    graphicFeature.set_highlight({3});
                }}
                catch (ex) {{
                }}";

            // JavaScript to zoom to the feature corresponding to the clicked row.
            string zoomToFeatureJavaScript = @"
                try {{
                    // Get the envelope of the feature
                    var map = $find('{0}');
                    var graphicFeatureGroup = $find('{1}');
                    var graphicFeature = graphicFeatureGroup.get({2});
                    var geometry = graphicFeature.get_geometry();
                    var envelope = geometry.getEnvelope();

                    // Expand the envelope so some area round the feature is included
                    var envelopeDivisor = 3;
                    var xExpansionFactor = envelope.get_width() / envelopeDivisor;
                    var yExpansionFactor = envelope.get_height() / envelopeDivisor;

                    // Update the envelope with the expansion factor
                    var xMax = envelope.get_xmax() + xExpansionFactor;
                    envelope.set_xmax(xMax);

                    var xMin = envelope.get_xmin() - xExpansionFactor;
                    envelope.set_xmin(xMin);

                    var yMax = envelope.get_ymax() + yExpansionFactor;
                    envelope.set_ymax(yMax);

                    var yMin = envelope.get_ymin() - yExpansionFactor;
                    envelope.set_ymin(yMin);

                    // Zoom the map to the envelope
                    map.zoomToBox(envelope, false);
                }}
                catch (ex) {{
                }}";

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

            // Get the client ID of the GraphicsLayer associated with the panel
            string graphicsLayerClientID = this.MapInstance.GetGraphicsLayerClientID(this.GraphicsLayer);
            System.Web.UI.WebControls.TableRow[] tableRowArray = new System.Web.UI.WebControls.TableRow[this.GraphicsLayer.Rows.Count];
            System.Web.UI.WebControls.TableRow tableRow;

            // Loop through the rows (i.e. features) in the GraphicsLayer, creating a row in the rows array for each
            for (int i = 0; i < this.GraphicsLayer.Rows.Count; ++i)
            {               
                // 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",
                    graphicsLayerClientID, i, "true"));
                tableRow.Attributes.Add("onmouseout", string.Format(setHighlightJavaScript, "white",
                    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, this.MapInstance.ClientID,
                    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
                System.Data.DataRow currentRow = this.GraphicsLayer.Rows[i];

                // Create a cell to hold the checkbox that toggles whether the corresponding feature is selected
                if (GraphicsLayer != null)
                {
                    // Instantiate and initialize the styling of the cell
                    System.Web.UI.WebControls.TableCell 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.
                    System.Web.UI.HtmlControls.HtmlInputCheckBox checkBox = 
                        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
                    string clientAction = string.Format(toggleFeatureJavaScript, graphicsLayerClientID, i, 
                        checkBox.ClientID);
                    checkBox.Attributes.Add("onclick", clientAction);

                    // Add the checkbox to the current cell
                    tableCell.Controls.Add(checkBox);
                }

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

                // Loop through the indexes of the display columns creating a table cell and adding the 
                // corresponding feature data for each
                foreach (int index in displayColumnIndexList)
                {
                    // Instantiate the cell and initialize its styling
                    System.Web.UI.WebControls.TableCell 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
                    this.CopyFont(tableCell.Font, this.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))
                        tableCell.Text = "&nbsp;";

                    // Get the size of the cell given its text
                    System.Drawing.SizeF cellSize = this.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)
                        rowHeightList.Add(cellSize.Height);
                    else
                        rowHeightList[rowIndex] = cellSize.Height > rowHeightList[rowIndex] ? 
                            cellSize.Height : rowHeightList[rowIndex];

                    // Only update the column width if the width of the current cell is greater than the stored 
                    // column width
                    columnWidthList[columnIndex] = cellSize.Width > columnWidthList[columnIndex] ? 
                        cellSize.Width : columnWidthList[columnIndex];

                    columnIndex++;
                }
            }

            return tableRowArray;
        }

        #endregion

        // Retrieves the GraphicsLayer associated with the panel based on the MapInstance, ResourceName, and
        // LayerName properties
        private ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer GetGraphicsLayer()
        {
            ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer featureGraphicsLayer = null;

            // Make sure a resource name and layer name have been defined.  These are initialized in SetLayer.
            if (!string.IsNullOrEmpty(this.ResourceName) && !string.IsNullOrEmpty(this.LayerName))
            {
                // Get the resource item corresponding to the resource name
                ESRI.ArcGIS.ADF.Web.UI.WebControls.MapResourceItem mapResourceItem =
                    this.MapInstance.MapResourceManagerInstance.ResourceItems.Find(this.ResourceName);

                // Make sure the resource item was found
                if (mapResourceItem != null && mapResourceItem.Resource != null)
                {
                    // Get a reference to the resource underlying the resource item as a graphics resource
                    ESRI.ArcGIS.ADF.Web.DataSources.Graphics.MapResource graphicsResource =
                        mapResourceItem.Resource as ESRI.ArcGIS.ADF.Web.DataSources.Graphics.MapResource;

                    // Make sure the resource could be referenced as a graphics resource
                    if (graphicsResource != null)
                    {
                        // Loop through the tables and find the one matching the panel's layer name.  This will be
                        // the associated GraphicsLayer.
                        foreach (System.Data.DataTable table in graphicsResource.Graphics.Tables)
                        {
                            if (table.TableName == this.LayerName)
                            {
                                featureGraphicsLayer = table as ESRI.ArcGIS.ADF.Web.Display.Graphics.FeatureGraphicsLayer;
                                break;
                            }
                        }
                    }
                }
            }

            return featureGraphicsLayer;
        }

        // Gets the screen size of the passed-in text in the passed-in font
        private System.Drawing.SizeF GetTextScreenSize(string text, System.Web.UI.WebControls.FontInfo fontInfo)
        {
            System.Drawing.SizeF size;
            float emSize = System.Convert.ToSingle(fontInfo.Size.Unit.Value + 1);
            emSize = emSize == 0 ? 12 : emSize;

            System.Drawing.Font font = new System.Drawing.Font(fontInfo.Name, emSize);
            System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(1000, 100);
            System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap);          

            size = graphics.MeasureString(text, font);
            graphics.Dispose();
            return size;
        }

        // Copies the properties of one FontInfo to another
        private void CopyFont(System.Web.UI.WebControls.FontInfo targetFont, System.Web.UI.WebControls.FontInfo sourceFont)
        {
            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;
        }

        // Retrieves the HTML of a WebControl
        private string GetControlHtml(System.Web.UI.Control webControl)
        {
            // Instantaite an HtmlTextWriter object
            System.IO.StringWriter stringWriter = new System.IO.StringWriter();
            System.Web.UI.HtmlTextWriter 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();
        }

        #endregion
    }
}