title: GridView with Pager TopAndBottom
body: <p>I have extended the standard GridView control in ASP.NET. Basically it is just the same, but paging and sorting is done using callbacks.</p>
  
  <p>I have successfully created my own PagerTemplate, but it only works when PagerSettings.Position is set to Top. I want to have the pager at both the top and the bottom.</p>
  
  <p>I get an HttpException saying: The table must contain row sections in order of header, body, then footer.</p>
  
  <p>Anyone know what this could be?</p>
  
  <p>I already tried calling the MakeAccessible() method, but that doesn't help.</p>
  
  <p>Code: </p>
  
  <pre><code>private int _IdColumnIndex = -1;
  
  public override object DataSource
  {
    get
    {
      return HttpContext.Current.Session[this.ClientID + "_DataSource"];
    }
    set
    {
      if (!value.GetType().Equals(typeof(DataView)))
        throw new ArgumentException("Only System.Data.DataView is accepted as a DataSource for DataGridView!");
  
      HttpContext.Current.Session[this.ClientID + "_DataSource"] = value;
    }
  }
  
  /// &lt;summary&gt;
  /// Sets the default settings.
  /// &lt;/summary&gt;
  private void SetDefaultSettings()
  {
    base.AllowPaging = true;
    base.AllowSorting = true;
    base.ShowHeader = true;
    base.ShowFooter = true;
    base.PagerTemplate = new GridPagerTemplate(PAGER_ID, this);
    base.PagerSettings.Position = PagerPosition.TopAndBottom;
    base.PageSize = 10;
  
    AddSelectionCheckbox();
  }
  
  //private void MakeAccessible()
  //{
  //  if (this.Rows.Count &gt; 0)
  //  {
  //    //This replaces &lt;td&gt; with &lt;th&gt; and adds the scope attribute 
  //    this.UseAccessibleHeader = true;
  
  //    //This will add the &lt;thead&gt; and &lt;tbody&gt; elements
  //    this.HeaderRow.TableSection = TableRowSection.TableHeader;
  
  //    //This adds the &lt;tfoot&gt; element. Remove if you don't have a footer row 
  //    this.FooterRow.TableSection = TableRowSection.TableFooter;
  //  }
  //} 
  
  private void AddSelectionCheckbox()
  {
    TemplateField tf = new TemplateField();
    tf.HeaderText = "";
    tf.ItemTemplate = new CheckboxTemplate(ROW_CHECKBOX_ID);
  
    base.Columns.Add(tf);
  }
  
  protected override void OnInit(EventArgs e)
  {
    SetDefaultSettings();
  
    base.DataBound += new EventHandler(DataGridView_DataBound);
    base.RowDataBound += new GridViewRowEventHandler(DataGridView_RowDataBound);
  
    base.OnInit(e);
  }
  
  protected override void OnPreRender(EventArgs e)
  {
    base.OnPreRender(e);
    //this.PaintRows();
    //this.FormatCells();
  
    // Get JavaScript for this DataGridView, and include it on the page.
    string scriptUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(), "Yakinix.UI.Controls.DataGridView.js");
    Page.ClientScript.RegisterClientScriptInclude("DataGridView", scriptUrl);
  
    string js = "function DGV_MakeCallback(argument)" +
                "{" +
                    "__theFormPostData = '';" +
                    "__theFormPostCollection = new Array();" +
                    "WebForm_InitCallback();" +
                    GetCallbackEventReference("DGV_Callback") +
                ";}";
  
    Page.ClientScript.RegisterClientScriptBlock(typeof(DataGridView), typeof(DataGridView).FullName + "DGV_MakeCallback", js, true);
  
    if (this.HeaderRow != null)
      this.HeaderRow.TableSection = TableRowSection.TableHeader;
  }
  
  private string GetCallbackEventReference(string func)
  {
    string callbackEventReference = Page.ClientScript.GetCallbackEventReference(this, "argument", func, "'" + ClientID + "'", false);
  
    return callbackEventReference;
  }
  
  public override void RenderBeginTag(HtmlTextWriter writer)
  {
    writer.RenderBeginTag(HtmlTextWriterTag.Div); // &lt;div&gt;
    base.RenderBeginTag(writer);
  }
  
  public override void RenderEndTag(HtmlTextWriter writer)
  {
    base.RenderEndTag(writer);
    writer.RenderEndTag(); // &lt;/div&gt;
  }
  
  /// &lt;summary&gt;
  /// Renders the gridview control and strips whitespace. It also
  /// applies the right formatting.
  /// &lt;/summary&gt;
  public string RenderHtml()
  {
    using (StringWriter sw = new StringWriter(CultureInfo.InvariantCulture))
    {
      //this.PaintRows();
      //this.FormatCells();
  
      if (this.HeaderRow != null)
        this.HeaderRow.TableSection = TableRowSection.TableHeader;
  
      this.UseAccessibleHeader = true;
      this.RenderControl(new HtmlTextWriter(sw));
      string html = sw.ToString();
  
      return html;
    }
  }
  
  // TODO: Style all rows for the column matching current SortExpression.
  protected void DataGridView_RowDataBound(object sender, GridViewRowEventArgs e)
  {
    //DataGridView gridView = (DataGridView)sender;
  
    //if (gridView.SortExpression.Length &gt; 0)
    //{
    //  int cellIndex = -1;
  
    //  //  find the column index for the corresponding sort expression
    //  foreach (DataControlField field in gridView.Columns)
    //  {
    //    if (field.SortExpression == gridView.SortExpression)
    //    {
    //      cellIndex = gridView.Columns.IndexOf(field);
    //      break;
    //    }
    //  }
  
    //  if (cellIndex &gt; -1)
    //  {
    //    if (e.Row.RowType == DataControlRowType.Header)
    //    {
    //      //  this is a header row,
    //      //  set the sort style
    //      e.Row.Cells[cellIndex].CssClass += (gridView.SortDirection == SortDirection.Ascending ? " sortascheader" : " sortdescheader");
    //    }
    //    else if (e.Row.RowType == DataControlRowType.DataRow)
    //    {
    //      //  this is a data row
    //      e.Row.Cells[cellIndex].CssClass += (e.Row.RowIndex % 2 == 0 ? " sortaltrow" : "sortrow");
    //    }
    //  }
    //}
  
    RenderDetailsRow(e);
  }
  
  /// &lt;summary&gt;
  /// Renders the details row, which is hidden when rendered. Clicking a details link, displays the row and more detailed data is shown, and is editable.
  /// &lt;/summary&gt;
  /// &lt;param name="e"&gt;The &lt;see cref="System.Web.UI.WebControls.GridViewRowEventArgs"/&gt; instance containing the event data.&lt;/param&gt;
  private void RenderDetailsRow(GridViewRowEventArgs e)
  {
    // Only add a details row to DataRows
    if (false &amp;&amp; e.Row.RowType == DataControlRowType.DataRow)
    {
      Table table = e.Row.Parent as Table;
  
      if (table != null)
      {
        GridViewRow row = new GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal);
        row.Style[HtmlTextWriterStyle.Display] = "none";
  
        TableCell rowCell = new TableCell();
        rowCell.ColumnSpan = this.Columns.Count;
        rowCell.Height = new Unit(100);
  
        row.Cells.Add(rowCell);
        table.Rows.Add(row);
  
        HtmlAnchor htmlAhref = new HtmlAnchor();
        htmlAhref.HRef = "javascript:void(0);";
        htmlAhref.Attributes.Add("onclick", "ToggleElement('" + row.ClientID + "');");
        htmlAhref.InnerText = "Test";
  
        e.Row.Cells[e.Row.Cells.Count - 1].Controls.Add(htmlAhref);
      }
    }
  }
  
  protected void DataGridView_DataBound(object sender, EventArgs e)
  {
    FillPagerDetails(base.TopPagerRow);
    FillPagerDetails(base.BottomPagerRow);
  }
  
  private void FillPagerDetails(GridViewRow pagerRow)
  {
    if (pagerRow != null)
    {
      // Retrieve the DropDownList and Label controls from the row.
      DropDownList pageList = pagerRow.Cells[0].FindControl(PAGER_ID) as DropDownList;
  
      if (pageList != null)
      {
        pageList.Attributes.Add("onchange", "DataGridViewPage(this.value, '" + ClientID + "');");
  
        // Add the number of pages to the ListBox
        for (int i = 0; i &lt; base.PageCount; i++)
        {
          int pageNumber = i + 1;
  
          // Create a ListItem that represents a page
          ListItem item = new ListItem(String.Format("{0}", pageNumber), i.ToString());
  
          // If the page is already selected, make sure the 
          // ListBox select the selected page
          if (i == base.PageIndex)
            item.Selected = true;
  
          // Add the ListItem object to the Items collection of the DropDownList.
          pageList.Items.Add(item);
        }
      }
    }
  }
  
  protected override void InitializeRow(GridViewRow row, DataControlField[] fields)
  {
    base.InitializeRow(row, fields);
  
    if (row.RowType == DataControlRowType.Header)
    {
      for (int i = 0; i &lt; row.Cells.Count; i++)
      {
        row.Cells[i].Controls.Clear();
  
        if (row.Cells[i].Text.Equals("CHKCOL", StringComparison.Ordinal))
        {
          CheckBox chkSelectAll = new CheckBox();
          chkSelectAll.ID = "chkSelectAllRows";
          row.Cells[i].Controls.Add(chkSelectAll);
        }
        else
        {
          row.Cells[i].Text = String.Format(CultureInfo.InvariantCulture, "&lt;a href=\"javaScript:DataGridViewSort('{0}', '{2}');void(0)\" title=\"{1}\"&gt;{1}&lt;/a&gt;", fields[i].SortExpression, fields[i].HeaderText, this.ClientID);
        }
      }
      //if (ViewState["SortExpression"] != null)
      //{
      //  int index;
      //  index = 0;// GetHeaderCellIndex(ViewState["SortExpression"].ToString());
      //  if (index != -1)
      //  {
      //    //Literal literal;
      //    //if ((SortDirection)ViewState["SortDirection"] == SortDirection.Ascending)
      //    //{
      //    //  if (string.IsNullOrEmpty(SortAscendingImageUrl))
      //    //  {
      //    //    literal = new Literal();
      //    //    literal.Text = "&amp;#8593;";
      //    //    row.Cells[index].Controls.Add(literal);
      //    //  }
      //    //  else
      //    //  {
      //    //    sortImage.ImageUrl = SortAscendingImageUrl;
      //    //    row.Cells[index].Controls.Add(sortImage);
      //    //  }
      //    //}
      //    //else
      //    //{
      //    //  if (string.IsNullOrEmpty(SortDescendingImageUrl))
      //    //  {
      //    //    literal = new Literal();
      //    //    literal.Text = "&amp;#8595;";
      //    //    row.Cells[index].Controls.Add(literal);
      //    //  }
      //    //  else
      //    //  {
      //    //    sortImage.ImageUrl = SortDescendingImageUrl;
      //    //    row.Cells[index].Controls.Add(sortImage);
      //    //  }
      //    //}
      //  }
      //}
    }
    else if (row.RowType == DataControlRowType.DataRow)
    {
      for (int i = 0; i &lt; row.Cells.Count; i++)
      {
        if (fields[i].HeaderText.Equals("id", StringComparison.InvariantCultureIgnoreCase))
        {
          _IdColumnIndex = i;
          break;
        }
      }
    }
  }
  
  protected override void OnRowCreated(GridViewRowEventArgs e)
  {
    base.OnRowCreated(e);
  
    if (e.Row.RowType == DataControlRowType.Header)
    {
      //HtmlImage img = new HtmlImage();
      //img.ID = "lbCheckAll";
      //img.Src = "/img/btn_slct_all.gif";
      //img.Alt = "Select all";
      //img.Attributes.Add("onclick", "SelectAllRows('" + this.ClientID + "',true);return false;");
      //e.Row.Cells[0].Controls.Add(img);
    }
    else if (e.Row.RowType == DataControlRowType.DataRow)
    {
      //AddCheckbox(e);
    }
    else if (e.Row.RowType == DataControlRowType.Footer)
    {
      //e.Row.Cells[0].Text = "sdsds";
    }
  }
  
  protected override void OnRowDataBound(GridViewRowEventArgs e)
  {
    base.OnRowDataBound(e);
  
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
      //AddCheckbox(e);
  
      if (_IdColumnIndex != -1)
        e.Row.Cells[_IdColumnIndex].Text = "&lt;a href=\"Details.aspx?id=" + e.Row.Cells[_IdColumnIndex].Text + "\"&gt;Edit&lt;/a&gt;";
    }
  }
  
  public override void Sort(string sortExpression, SortDirection sortDirection)
  {
    DataView dv = this.DataSource as DataView;
  
    if (dv != null)
    {
      dv.Sort = sortExpression + " " + (sortDirection == SortDirection.Ascending ? "ASC" : "DESC");
    }
  }
  
  #region ICallbackEventHandler Members
  
  private string callbackArg = String.Empty;
  
  string ICallbackEventHandler.GetCallbackResult()
  {
    // Save state with PageStatePersister and place it to Page.ClientState
    System.Reflection.MethodInfo mi = typeof(Page).GetMethod("SaveAllState", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
    mi.Invoke(this.Page, null);
  
    // Get serialized viewstate from Page's ClientState
    System.Reflection.PropertyInfo stateProp = typeof(Page).GetProperty("ClientState", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
    string state = stateProp.GetValue(this.Page, null).ToString();
  
    return callbackArg + "¤#¤" + state;
  }
  
  void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
  {
    try
    {
      // Load XML from client
      XmlDocument doc = new XmlDocument();
      doc.LoadXml(eventArgument.Replace("&amp;", "&amp;amp;"));
      string pagerId = String.Empty;
  
      if (doc.DocumentElement.Name == "callback")
      {
        string gridId = doc.DocumentElement["arguments"]["gridId"].InnerText;
  
        switch (doc.DocumentElement["action"].InnerText)
        {
          case "sort":
            string sortExpression = doc.DocumentElement["arguments"]["sortExpression"].InnerText;
            Sort(sortExpression.Split(' ')[0].Trim(), sortExpression.Split(' ')[1].Trim().Equals("ASC") ? SortDirection.Ascending : SortDirection.Descending);
            break;
  
          case "page":
            string pageIndex = doc.DocumentElement["arguments"]["pageIndex"].InnerText;
            this.PageIndex = Convert.ToInt32(pageIndex);
            pagerId = base.BottomPagerRow.Cells[0].FindControl(PAGER_ID).ClientID;
            break;
  
          case "search":
            break;
        }
  
        this.DataBind();
        callbackArg = String.Format("{0}¤|¤{1}¤|¤{2}", gridId, this.RenderHtml(), pagerId);
      }
  
      doc = null;
    }
    catch (Exception ex)
    {
      callbackArg = String.Format(AJAX_ERROR_FORMAT, ex.Message + "\n\n" + ex.StackTrace);
    }
    finally
    {
      if (callbackArg.Equals(string.Empty))
      {
        callbackArg = String.Format(AJAX_ERROR_FORMAT, "An AJAX Error occurred!");
      }
    }
  }
  
  #endregion
  </code></pre>