User Tools

Site Tools


notes:asp.net:custompager

Custom Pager Web Control (ASP.NET)

This is a web control providing a custom pager developed for an on-line bookstore:

using System;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
 
namespace TaraRara.Bookstore.UI.WebControls
{
    public class Pager : Table, INamingContainer
    {
        private const int NumObjectsInViewState = 4;
        private const int DefaultPageSize = 10;
        private const int DefaultGroupSize = 3;
        private const int DefaultTotalItems = 0; // no items by default; it means 0 pages
        private const int DefaultCurrPage = 1;
        private const bool DefaultUseImagesForPageNavigationButtons = false;
 
        private Style pageButtonStyle;
        private Style imageButtonStyle;
        private Style currentButtonStyle;
 
        // populated in CreatePager()
        private List<IPagerButton> buttons = new List<IPagerButton>();
 
        #region Public Properties
        public int CurrentPage
        {
            get
            {
                return CurrPage;
            }
        }
 
        public int TotalItems
        {
            get
            {
                object o = ViewState["TotalItems"];
                if (o == null)
                    return DefaultTotalItems;
                else
                    return (int)ViewState["TotalItems"]; // we assume TotalItems contains an int value
            }
            set
            {
                ViewState["TotalItems"] = value;
            }
        }
 
        /// <summary>
        /// This property is not implemented yet.
        /// </summary>
        public bool UseImagesForPageNavigationButtons
        {
            get
            {
                object o = ViewState["UseImagesForPageNavigationButtons"];
                if (o == null)
                    return DefaultUseImagesForPageNavigationButtons;
                else
                    return (bool)ViewState["UseImagesForPageNavigationButtons"];
            }
            set
            {
                ViewState["UseImagesForPageNavigationButtons"] = value;
            }
        }
 
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [NotifyParentProperty(true)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public virtual Style PageButtonStyle
        {
            get
            {
                if (pageButtonStyle == null)
                {
                    pageButtonStyle = new Style();
 
                    // determine whether view-state change tracking is enabled for the control
                    if (IsTrackingViewState)
                        ((IStateManager)pageButtonStyle).TrackViewState();
                }
 
                return pageButtonStyle;
            }
        }
 
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [NotifyParentProperty(true)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public virtual Style ImageButtonStyle
        {
            get
            {
                if (imageButtonStyle == null)
                {
                    imageButtonStyle = new Style();
 
                    // determine whether view-state change tracking is enabled for the control
                    if (IsTrackingViewState)
                        ((IStateManager)imageButtonStyle).TrackViewState();
                }
 
                return imageButtonStyle;
            }
        }
 
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [NotifyParentProperty(true)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public virtual Style CurrentButtonStyle
        {
            get
            {
                if (currentButtonStyle == null)
                {
                    currentButtonStyle = new Style();
 
                    // determine whether view-state change tracking is enabled for the control
                    if (IsTrackingViewState)
                        ((IStateManager)currentButtonStyle).TrackViewState();
                }
 
                return currentButtonStyle;
            }
        }
        #endregion
 
        #region Protected Properties
        protected int CurrPage
        {
            get
            {
                object o = ViewState["CurrPage"];
                if (o == null)
                    return DefaultCurrPage; // default page number
                else
                    return (int)o; // unbox the current page number
            }
            set
            {
                ViewState["CurrPage"] = value;
            }
        }
 
        protected int PageSize
        {
            get
            {
                object o = ViewState["PageSize"];
                if (o == null)
                    return DefaultPageSize;
                else
                    return (int)ViewState["PageSize"]; // we assume PageSize contains an int value
            }
            set
            {
                ViewState["PageSize"] = value;
            }
        }
 
        protected int GroupSize
        {
            get
            {
                object o = ViewState["GroupSize"];
                if (o == null)
                    return DefaultGroupSize;
                else
                    return (int)ViewState["GroupSize"]; // we assume GroupSize contains an int value
            }
            set
            {
                if (value % 2 == 0)
                    // Pager.GroupSize must be an odd number because we need a single page as the current 
                    // one in the middle of the page group
                    throw new ArgumentException("Pager.GroupSize must be an odd number");
                else
                    ViewState["GroupSize"] = value;
            }
        }
 
        // NumPages is a "derived" property. It calculates the number of pages on the basis of 
        // the number of items.
        // Note: NumPages does not need its own view state as its value is kept indirectly in 
        // TotalItmes and PageSize.
        protected int NumPages
        {
            get
            {
                if (TotalItems > 0)
                    return TotalItems / PageSize + (TotalItems % PageSize != 0 ? 1 : 0);
                else
                    return 0;
            }
        }
        #endregion
 
        #region Overridden Properties
        public override ControlCollection Controls
        {
            get
            {
                EnsureChildControls();
                return base.Controls;
            }
        }
        #endregion
 
        #region Public Methods
        public void InitPager(int pageSize, int groupSize)
        {
            this.PageSize = pageSize;
            this.GroupSize = groupSize;
        }
 
        // called during postback, after data binding, to create a fresh control hierarchy
        public void Refresh()
        {
            CreatePager(CurrPage, GroupSize, NumPages);
        }
 
        // Modifies CurrPage
        public void Reset()
        {
            CurrPage = 1;
        }
 
        public void AdjustCurrentPage()
        {
            // if the current page number is greater then the total number of pages
            // adjust the current page number
            if (CurrPage > NumPages) CurrPage = NumPages;
        }
 
        public void SetPage(PagerButtonEventArgs e)
        {
            ChangeCurrPage(e);
            CreatePager(CurrPage, GroupSize, NumPages);
        }
        #endregion
 
        #region Protected Methods
        // called each and every time; creates the old control hierachy to handle postback
        protected override void CreateChildControls()
        {
            CreatePager(CurrPage, GroupSize, NumPages);
        }
 
        // the first event bubbling handler
        protected override bool OnBubbleEvent(object source, EventArgs args)
        {
            if (args is PagerButtonEventArgs)
            {
                PagerButtonEventArgs e = (PagerButtonEventArgs)args;
                ChangeCurrPage(e);
            }
 
            // bubble further up; this is the default behaviour of OnBubbleEvent()
            return false;
        }
 
        protected override void Render(HtmlTextWriter writer)
        {
            // child controls are already created i.e. we do not need to call CreateChildControls();
            // we apply styles to the child controls in Render() so that style property changes
            // are not persisted in the child controls' ViewState
            foreach (IPagerButton button in buttons)
            {
                if (button.PagerButtonType == PagerButtonType.PageNo)
                    ((WebControl)button).ApplyStyle(pageButtonStyle);
                else if (button.PagerButtonType == PagerButtonType.CurrentPage)
                    ((WebControl)button).ApplyStyle(currentButtonStyle);
                else if (button.PagerButtonType == PagerButtonType.FirstPage ||
                    button.PagerButtonType == PagerButtonType.PreviousPage ||
                    button.PagerButtonType == PagerButtonType.NextPage ||
                    button.PagerButtonType == PagerButtonType.LastPage)
                    ((WebControl)button).ApplyStyle(imageButtonStyle);
                else
                    throw new ArgumentException("Wrong PagerButtonType");
            }
 
            base.Render(writer);
        }
 
        protected override object SaveViewState()
        {
            object[] state = new object[NumObjectsInViewState];
 
            state[0] = base.SaveViewState();
            state[1] = (pageButtonStyle != null ? ((IStateManager)pageButtonStyle).SaveViewState() : null);
            state[2] = (imageButtonStyle != null ? ((IStateManager)imageButtonStyle).SaveViewState() : null);
            state[3] = (currentButtonStyle != null ? ((IStateManager)currentButtonStyle).SaveViewState() : null);
 
            for (int i = 0; i < NumObjectsInViewState; i++)
                if (state[i] != null)
                    return state;
 
            return null;
        }
 
        protected override void LoadViewState(object savedState)
        {
            if (savedState == null)
            {
                base.LoadViewState(null);
                return;
            }
            else
            {
                object[] state = (object[])savedState;
                if (state.Length != NumObjectsInViewState)
                    throw new ArgumentException("Invalid view state");
 
                base.LoadViewState(state[0]);
 
                if (state[1] != null) ((IStateManager)pageButtonStyle).LoadViewState(state[1]);
                if (state[2] != null) ((IStateManager)imageButtonStyle).LoadViewState(state[2]);
                if (state[3] != null) ((IStateManager)currentButtonStyle).LoadViewState(state[3]);
            }
 
        }
 
        protected override void TrackViewState()
        {
            base.TrackViewState();
 
            if (pageButtonStyle != null) ((IStateManager)pageButtonStyle).TrackViewState();
            if (imageButtonStyle != null) ((IStateManager)imageButtonStyle).TrackViewState();
            if (currentButtonStyle != null) ((IStateManager)currentButtonStyle).TrackViewState();
        }
        #endregion
 
        #region Private Methods
        private void ChangeCurrPage(PagerButtonEventArgs e)
        {
            switch (e.ButtonType)
            {
                case PagerButtonType.PageNo:
                    CurrPage = e.PageNo;
                    break;
                case PagerButtonType.PreviousPage:
                    CurrPage--;
                    break;
                case PagerButtonType.NextPage:
                    CurrPage++;
                    break;
                case PagerButtonType.FirstPage:
                    CurrPage = 1;
                    break;
                case PagerButtonType.LastPage:
                    CurrPage = NumPages;
                    break;
            }
        }
 
        private void CreatePager(int currPage, int groupSize, int numPages)
        {
            // Controls.Clear() calls CreateChildControls() internally but only when it is invoked first time.
            Controls.Clear();
            ClearChildViewState();
            buttons.Clear();
 
            // if there is only one page to display or no pages at all - do nothing
            if (numPages <= 1)
                return;
 
            int firstPageNo;
            int lastPageNo;
            DetermineFirstAndLastPageNo(currPage, groupSize, numPages, out firstPageNo, out lastPageNo);
 
            // thanks to the event bubbling mechanism we don't have to consolidate all pager button clicks to 
            // a single event handler such as 
            // firstPageButton.PagerButtonClick += new PagerButtonClickEventHandler(PagerButton_Click);
 
            ImagePagerButton firstPageButton = new ImagePagerButton(Page, PagerButtonType.FirstPage, 
                "go to first page", (currPage > 1));
            buttons.Add(firstPageButton);
 
            ImagePagerButton prevPageButton = new ImagePagerButton(Page, PagerButtonType.PreviousPage, 
                "go to previous", (currPage > 1));
            buttons.Add(prevPageButton);
 
            for (int pageNo = firstPageNo; pageNo <= lastPageNo; pageNo++)
            {
                if (pageNo == currPage)
                {
                    CurrentPagerButton pageButton = new CurrentPagerButton(Page, pageNo.ToString(), pageNo);
                    buttons.Add(pageButton);
                }
                else
                {
                    PagerButton pageButton = new PagerButton(Page, pageNo.ToString(), pageNo);
                    buttons.Add(pageButton);
                }
            }
 
            ImagePagerButton nextPageButton = new ImagePagerButton(Page, PagerButtonType.NextPage, 
                "go to next page", (currPage < numPages));
            buttons.Add(nextPageButton);
 
            ImagePagerButton lastPageButton = new ImagePagerButton(Page, PagerButtonType.LastPage, 
                "go to last page", (currPage < numPages));
            buttons.Add(lastPageButton);
 
            // add buttons to the pager
            TableRow row = new TableRow();
            TableCell td = new TableCell();
 
            // Center pager buttons horizontally and vertically.
            td.HorizontalAlign = HorizontalAlign.Center;
            td.VerticalAlign = VerticalAlign.Middle;
 
            foreach (IPagerButton button in buttons)
            {
                td.Controls.Add((Control)button);
                if (button.PagerButtonType != PagerButtonType.LastPage)
                    td.Controls.Add(new LiteralControl("&nbsp;")); // button separator (just a single space)
            }
 
            row.Cells.Add(td);
            this.Rows.Add(row);
        }
 
        // Determine first and last page to display in a group
        private void DetermineFirstAndLastPageNo(int C, int G, int N, out int F, out int L)
        {
            if (N > G)
            {
                F = C - G / 2;
                if (F <= 0) F = 1;
                L = C + G / 2;
                if (L > N) L = N;
                if (F == 1) L = G;
                if (L == N) F = N - G + 1;
            }
            else
            {
                F = 1;
                L = N;
            }
        }
        #endregion
    }
}

PagerButton is a helper class representing a single button in the Pager:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
 
namespace TaraRara.Bookstore.UI.WebControls
{
    public enum PagerButtonType
    {
        FirstPage,
        PreviousPageGroup, // not implemented
        PreviousPage,
        PageNo,
        CurrentPage,
        NextPage,
        NextPageGroup, // not implemented
        LastPage
    }
 
    public interface IPagerButton
    {
        PagerButtonType PagerButtonType { get; }
    }
 
    #region PagerButton
    /// <summary>
    /// PagerButton supports only PagerButtonType.PageNo. It is derived from the LinkButton class.
    /// </summary>
    public class PagerButton : LinkButton, IPagerButton
    {
        protected int pageNo;
        protected PagerButtonType buttonType;
 
        public PagerButtonType PagerButtonType { get { return buttonType; } }
 
        public PagerButton(Page page, string buttonText, int pageNo)
            : base()
        {
            // Keep the page number to pass as an argument in the Click event.
            this.pageNo = pageNo;
            this.buttonType = PagerButtonType.PageNo; // always set to PagerButtonType.PageNo
 
            // The page number is unique, so we can use it as the control ID.
            this.ID = "Page" + pageNo;
            this.Text = buttonText;
            this.CausesValidation = false;
        }
 
        protected override void OnClick(EventArgs e)
        {
            base.OnClick(e);
 
            // initialize bubbling
            RaiseBubbleEvent(this, new PagerButtonEventArgs(pageNo, buttonType));
        }
    }
    #endregion
 
    #region ImagePagerButton
    /// <summary>
    /// ImagePagerButton is intended to be used as a navigation button i.e. first, prev, next, or last.
    /// </summary>
    public class ImagePagerButton : ImageButton, IPagerButton
    {
        protected PagerButtonType buttonType;
 
        public PagerButtonType PagerButtonType { get { return buttonType; } }
 
        public ImagePagerButton(Page page, PagerButtonType buttonType, string alternateText, bool isEnabled)
            : base()
        {
            if (buttonType == PagerButtonType.PageNo)
                throw new ArgumentException("ImagePagerButton does not support PagerButtonType.PageNo");
 
            // keep the button type to pass as an argument in the Click event
            this.buttonType = buttonType;
 
            // set properties for both enabled and disabled buttons
            this.AlternateText = alternateText;
            this.Enabled = isEnabled;
            this.ImageAlign = ImageAlign.AbsMiddle;
 
            string imageName = "";
 
            // set image button attributes
            switch (buttonType)
            {
                // if images for buttons are not provided, load default images from the Resources folder
                case PagerButtonType.FirstPage:
                    imageName = "pager_button_first";
                    break;
                case PagerButtonType.PreviousPage:
                    imageName = "pager_button_prev";
                    break;
                case PagerButtonType.NextPage:
                    imageName = "pager_button_next";
                    break;
                case PagerButtonType.LastPage:
                    imageName = "pager_button_last";
                    break;
            }
 
            this.ImageUrl = page.ClientScript.GetWebResourceUrl(this.GetType(), 
                "WebControls.Resources." + imageName + "_" + (isEnabled ? "on" : "off") + ".gif");
 
            if (isEnabled)
            {
                this.ID = buttonType.ToString();
                this.CausesValidation = false;
            }
        }
 
        protected override void OnClick(ImageClickEventArgs e)
        {
            base.OnClick(e);
 
            // initialize bubbling
            RaiseBubbleEvent(this, new PagerButtonEventArgs(buttonType));
        }
    }
    #endregion
 
    #region CurrentPagerButton
    /// <summary>
    /// CurrentPagerButton represents the currently selected page. It is derived from the Label class.
    /// </summary>
    public class CurrentPagerButton : Label, IPagerButton
    {
        public PagerButtonType PagerButtonType { get { return PagerButtonType.CurrentPage; } }
 
        public CurrentPagerButton(Page page, string buttonText, int pageNo)
            : base()
        {
            // The page number is unique, so we can use it as the control ID.
            this.ID = "Page" + pageNo;
            this.Text = buttonText;
        }
    }
    #endregion
 
    #region PagerButtonEventArgs
    public class PagerButtonEventArgs : EventArgs
    {
        public readonly int PageNo;
        public readonly PagerButtonType ButtonType;
 
        public PagerButtonEventArgs(PagerButtonType buttonType)
        {
            this.ButtonType = buttonType;
        }
 
        public PagerButtonEventArgs(int pageNo, PagerButtonType buttonType)
        {
            this.PageNo = pageNo;
            this.ButtonType = buttonType;
        }
    }
    #endregion
}

The Pager web control uses the following files:

  • pager_button_first_off.gif
  • pager_button_first_on.gif
  • pager_button_last_off.gif
  • pager_button_last_on.gif
  • pager_button_next_off.gif
  • pager_button_next_on.gif
  • pager_button_prev_off.gif
  • pager_button_prev_on.gif
notes/asp.net/custompager.txt · Last modified: 2020/08/26 (external edit)