1

I am trying to extend the asp.net DataPager control into a custom control with predefined templates. This is the desired output

enter image description here

Problems:

  1. When I load the page containing the custom DataPager for the first time, the SelectMethod of my ObjectDataSource is called 3 times.
  2. When I try to load another page of data using the DataPager, the SelectMethod is called twice.
  3. When I try to load another page of data, either by using the Next/Previous buttons or the DropDownList, the page does not change. I ran some debugging and found that it was not passing the correct value to the StartRowIndexParameter of the SelectMethod (it just passed 0 everytime it called the method).

Here's the code for my custom control.

public class DataPagerDDL : DataPager
{
    protected override void RenderContents(HtmlTextWriter writer)
    {
        //add custom template
        TemplatePagerField templateField = new TemplatePagerField();
        templateField.PagerTemplate = new CustomTemplate();
        Fields.Add(templateField);

        //add previous/next page template
        NextPreviousPagerField nextPreviousField = new NextPreviousPagerField();            
        nextPreviousField.ShowFirstPageButton = false;
        nextPreviousField.ShowLastPageButton = false;
        nextPreviousField.PreviousPageText = "<<";
        nextPreviousField.NextPageText = ">>";
        Fields.Add(nextPreviousField);

        base.RenderContents(writer);
    }

    public void cmbPage_SelectedIndexChanged(object sender, EventArgs e)
    {
        DropDownList cmbPage = (DropDownList)sender;
        this.SetPageProperties(cmbPage.SelectedIndex * MaximumRows, MaximumRows, true);
    }
}

public class CustomTemplate : ITemplate
{
    /// <summary>
    /// Insert an instance of text and controls into the specified container.
    /// </summary>
    public void InstantiateIn(Control container)
    {
        DataPagerFieldItem caller = (DataPagerFieldItem)container;
        DataPagerDDL pager = (DataPagerDDL)caller.Parent;
        int totalPages = pager.TotalRowCount / pager.MaximumRows;
        if (pager.TotalRowCount % pager.MaximumRows > 0) totalPages += 1;
        int currentPage = (pager.StartRowIndex / pager.MaximumRows) + 1;

        DropDownList cmbPage = new DropDownList();
        cmbPage.ID = "cmbPage";
        cmbPage.AutoPostBack = true;
        cmbPage.SelectedIndexChanged += new EventHandler(pager.cmbPage_SelectedIndexChanged);
        for (int i = 1; i <= totalPages; i++)
        {
            ListItem item = new ListItem(i.ToString(), i.ToString());
            if (i == currentPage) item.Selected = true;
            cmbPage.Items.Add(item);
        }

        pager.Controls.Add(new LiteralControl("Page "));
        pager.Controls.Add(cmbPage);
        pager.Controls.Add(new LiteralControl(" of " + totalPages.ToString() + " pages | "));
    }
}

And this is what my page looks like:

<asp:ListView ID="ListView1" DataSourceID="ods1" ... >
...
</asp:ListView>

<custom:DataPagerDDL ID="CustomDataPager" runat="server" PagedControlID="ListView1" 
        PageSize="25">
</custom:DataPagerDDL>    

<asp:ObjectDataSource ID="ods1" ... >
</asp:ObjectDataSource>

What should I do to make my custom DataPager work as intended? Thanks in advance! :)

JW Lim
  • 1,794
  • 3
  • 21
  • 41

1 Answers1

2

I suspect you're creating the pager fields too late in the page lifecycle. Try creating them from the Init event of the DataPagerDDL class.

Also, your CustomTemplate should be adding the controls to the container, not the pager.

public class DataPagerDDL : DataPager
{
    protected override void OnInit(EventArgs e)
    {
        CreateDefaultPagerFields();
        base.OnInit(e);
    }

    protected virtual void CreateDefaultPagerFields()
    {
        //add custom template
        TemplatePagerField templateField = new TemplatePagerField();
        templateField.PagerTemplate = new CustomTemplate();
        Fields.Add(templateField);

        //add previous/next page template
        NextPreviousPagerField nextPreviousField = new NextPreviousPagerField();            
        nextPreviousField.ShowFirstPageButton = false;
        nextPreviousField.ShowLastPageButton = false;
        nextPreviousField.PreviousPageText = "<<";
        nextPreviousField.NextPageText = ">>";
        Fields.Add(nextPreviousField);
    }

    public void cmbPage_SelectedIndexChanged(object sender, EventArgs e)
    {
        DropDownList cmbPage = (DropDownList)sender;
        SetPageProperties(cmbPage.SelectedIndex * MaximumRows, MaximumRows, true);
    }
}

public class CustomTemplate : ITemplate
{
    public void InstantiateIn(Control container)
    {
        DataPagerFieldItem caller = (DataPagerFieldItem)container;
        DataPagerDDL pager = (DataPagerDDL)caller.Parent;
        int totalPages = pager.TotalRowCount / pager.MaximumRows;
        if (pager.TotalRowCount % pager.MaximumRows > 0) totalPages += 1;
        int currentPage = (pager.StartRowIndex / pager.MaximumRows) + 1;

        DropDownList cmbPage = new DropDownList();
        cmbPage.ID = "cmbPage";
        cmbPage.AutoPostBack = true;
        cmbPage.SelectedIndexChanged += pager.cmbPage_SelectedIndexChanged;

        for (int i = 1; i <= totalPages; i++)
        {
            ListItem item = new ListItem(i.ToString(), i.ToString());
            if (i == currentPage) item.Selected = true;
            cmbPage.Items.Add(item);
        }

        container.Controls.Add(new LiteralControl("Page "));
        container.Controls.Add(cmbPage);
        container.Controls.Add(new LiteralControl(" of " + totalPages + " pages | "));
    }
}
Richard Deeming
  • 29,830
  • 10
  • 79
  • 151
  • Spot on, thank you! Is it generally advised to set the control's properties on Init? What if the control has additional custom properties e.g. `UserName` property on a `Label`? I have another custom control and I tried setting the properties on Init, but it didn't work. I think it's because the custom property's value (which is set in the HTML markup) is not retrieved until after the Load event. Am I right? – JW Lim Feb 19 '14 at 01:18
  • Setting properties in the control's `Init` event is largely equivalent to setting them in the page markup. The property values won't be stored in `ViewState`, so you'll have to set them on every request; don't wrap the code in an `if (!IsPostBack)` block. When you're dynamically modifying the control tree, the `Init` event is generally the latest point at which you want to make your changes, to ensure that the dynamic controls can participate in the post-back event processing. – Richard Deeming Feb 19 '14 at 12:39
  • Taking the `OnInit` approach solved an unrelated problem for me. I was using `CreateChildControls()` to instantiate the template. This worked fine except in the case where there was no data. For some reason this call would cascade on itself and result in a stack-overflow. Refactoring code to instantiate in the manner of this post solved the issue. – GunnerGuyven May 04 '15 at 14:35