asp tutorials, asp.net tutorials, sample code, and Microsoft news from 15Seconds
Data Access  |   Troubleshooting  |   Security  |   Performance  |   ADSI  |   Upload  |   Email  |   Control Building  |   Component Building  |   Forms  |   XML  |   Web Services  |   ASP.NET  |   .NET Features  |   .NET 2.0  |   App Development  |   App Architecture  |   IIS  |   Wireless
 
Pioneering Active Server
 Power Search





Active News
15 Seconds Weekly Newsletter
• Complete Coverage
• Site Updates
• Upcoming Features

More Free Newsletters
Reference
News
Articles
Archive
Writers
Code Samples
Components
Tools
FAQ
Feedback
Books
Links
DL Archives
Community
Messageboard
List Servers
Mailing List
WebHosts
Consultants
Tech Jobs
15 Seconds
Home
Site Map
Press
Legal
Privacy Policy
internet.commerce














internet.com
IT
Developer
Internet News
Small Business
Personal Technology

Search internet.com
Advertise
Corporate Info
Newsletters
Tech Jobs
E-mail Offers

HardwareCentral
Compare products, prices, and stores at Hardware Central!

Creating a Generic Pager Control
By Tomasz Kaszuba
Rating: 4.1 out of 5
Rate this article


  • email this article to a colleague
  • suggest an article



    Effectively showing data so it doesn't confuse the end user is a main objective in developing almost all Web data presentation applications. Showing 20 records on one page is bearable but showing 10,000 certainly can be confusing. Splitting the data across several pages, or paging the data, is a commonly employed solution to this problem.

    ASP.NET provides only one control that supports paging, the DataGrid. The DataGrid pager control is fine for intranet applications but for public applications, the DataGrid pager doesn't provide much of the functionality needed to make flexible Web applications. For one thing, the DataGrid control limits the Web designer in where he can place the pager or what it should look like. For example, the DataGrid certainly doesn't allow the designer the option of placing the pager vertically. Another control that can benefit from paging is the repeater control. The repeater control allows the Web developer to quickly configure how data is shown, but the paging behavior must be implemented by the Web developer. Implementing a custom pager for different controls that change depending on the data source or the presentation can be time consuming. A generic pager control that's not bound to a specific presentation control is a great time saver. A good generic pager control is more than just a data pager; it should also provide the following functionality:

    1. Provide First, Previous, Next, Last and paging buttons
    2. Be sensitive to the data. If the pager is set to show 10 records per page and only nine are shown, then the pager shouldn't be visible. On the first page the Previous and First buttons shouldn't be shown. On the last page the Next and the Last methods shouldn't be shown.
    3. Independent of the control that's in charge of the presentation
    4. The ability to handle a variety of current and future data sources
    5. Easily configurable presentation to integrate with custom applications
    6. Notify other controls when paging is taking place
    7. Easy to use even by inexperienced Web designers
    8. Provide properties to relevant paging data

    There are a few commercial pagers available that provide such functionality, but they don't come cheap. For cash-strapped Web companies creating a custom pager control becomes a necessity.

    ASP.NET provides three ways to create custom Web controls: user controls, composite controls, and custom controls. The third type of control, custom control, is a bit misleading. All of the mentioned controls are actually custom controls but what makes the composite control different from the custom control is the dependence on the CreateChildControls() method, which allows the control to re-render itself based on raised events. For the generic pager, the composite control model is chosen.

    The following sequence diagram outlines the general mechanism of the pager control.

    Even thought the pager control aims to be independent of the presentation control, it must have some way to access the data. Every class that derives from the Control class provides a DataBinding event. By registering itself as a listener to the DataBinding event, the pager can listen-in and make changes to the data. Since all controls that derive from the Control class posses this event, the pager control achieves independence from the presentation control. In other words, any control that derives form the Control class, almost all Web controls, can be bound to. Once the presentation control raises the DataBinding event, the pager control can intercept the DataSource property. Unfortunately MS does not provide an interface that all data bound classes implement, such as an IdataSourceProvider, and not all controls derived from the Control or WebControl class provide a DataSource property, so upcasting to the Control class isn't an option. The only alternative is to use reflection to manipulate the DataSource property directly. Before discussing the event handler method, it should be pointed out that in order to register as an event listener, a reference must be established to the presentation control. The pager control exposes a simple string property, BindToControl, which the Web developer can set through code or through the aspx page to bind the DataSource to the presentation control.

    publicstring BindToControl

    {

    get

          {

    if (_bindcontrol == null)

                thrownew NullReferenceException("You must bind to a control through the BindToControl property before you use the pager");

    return _bindcontrol;

    }

          set{_bindcontrol=value;}

    }

    This method is important enough that it's a good idea to throw a more meaningful message than a standard NullReferenceException. In the pager's OnInit event handler the call to resolve the reference to the presentation control is made. The OnInit event handler must be used (instead of the constructor) to make sure that the JIT compiled aspx page has set the BindToControl method.

    protectedoverridevoid OnInit(EventArgs e)

    {

          _boundcontrol = Parent.FindControl(BindToControl);

    BoundControl.DataBinding += new EventHandler(BoundControl_DataBound);

          base.OnInit(e);

    }

    The search for the presentation control is made by searching the pager's Parent control, which in the case of this article is the main page template. There is much danger in using the Parent property in this manner. If, for example, the pager were to be imbedded into another control, such as a Table control, the call to the Parent property would return a reference to the Table control. Since the FindControl method only searches the current control collection, the presentation control won't be found unless it's in that collection. A safer method is to recursively search through each control's control collection until the control is found.

    Once the BoundControl is found, the pager is registered as listener to the DataBinding event. Since the pager control manipulates the data source, it's important that this event handler be the last in the calling chain. As long as the presentation control registers its event handlers for the DataBinding event in the OnInit event handler (the default), there won't be a problem when the pager manipulates the data source.

    The DataBound event handler takes care of the acquisition of the DataSource property of the presentation control.

    privatevoid BoundControl_DataBound(object sender,System.EventArgs e)

    {

          Type type = sender.GetType();

          _datasource = type.GetProperty("DataSource");

          if (_datasource == null)

                thrownew NotSupportedException("The Pager control doesn't support controls that don't contain a datasource");

    object data = _datasource.GetGetMethod().Invoke(sender,null);

          BindParent();

    }

    Through reflection, the call is made to invoke the Get part of the DataSource property and return a reference to the actual data source. The data source is now known but the pager still needs to know how to manipulate it. A lot of effort went into making the pager presentation independent. Making it data source dependent would defeat the purpose of constructing a flexible control. A pluggable architecture ensures that the pager control will be able to handle all sorts of different data sources, .NET provided or custom.

    The perfect solution to provide a robust, scalable and pluggable architecture can be achieved by using the [GoF] builder pattern.

    The IDataSourceAdapter interface defines the most basic element, or plug, the pager needs to manipulate the data.

    publicinterface IDataSourceAdapter

    {

          int TotalCount{get;}

          object GetPagedData(int start,int end);

    }

    The TotalCount property returns the total number of elements in the data source before manipulating the data while the GetPagedData method manipulates the data source by returning a subset of the original data. For example: if the data source is a simple array containing 20 elements and the pager shows 10 elements per page, then a subset of this data would be elements 0-9 for page one and 10-19 for page two. A plug for a DataView type is provided by the DataViewAdapter.

    internalclass DataViewAdapter:IDataSourceAdapter

    {

          private DataView _view;

          internal DataViewAdapter(DataView view)

          {

                _view = view;

          }

          publicint TotalCount

          {

                get{return (_view == null) ? 0 : _view.Table.Rows.Count;}         }

          publicobject GetPagedData(int start, int end)

          {

                DataTable table = _view.Table.Clone();

                for (int i = start;i<=end && i<= TotalCount;i++)

                {

                      table.ImportRow(_view[i-1].Row);

                }

                return table;

          }

    }

    The DataViewAdapter implements the IDataSourceAdapter's GetPagedData method by cloning the original DataTable and then importing rows from the original DataTable to the cloned table. The class's visibility is intentionally set to internal in order to hide the implementation from the Web developer and provide an easier interface through the Builder classes.

    publicabstractclass AdapterBuilder

    {

          privateobject _source;

          privatevoid CheckForNull()

          {

    if (_source == null) thrownew NullReferenceException("You must provide a valid source");

          }

          publicvirtualobject Source

          {

                get

                {

                      CheckForNull();

                      return _source;}

                set

                {

                      _source = value;

                      CheckForNull();

                }

          }

          publicabstract IDataSourceAdapter Adapter{get;}

    }

    The abstract AdapterBuilder class provides a more manageable interface to the IdataSourceAdapter type. By employing an extra level of abstraction, instead of using the IDataSourceAdapter directly, it provides an extra layer where pre processing instructions, before paging the data, can take place. The builder also allows the actual implementation, such as the DataViewAdapter to be hidden away from the pager user.

    publicclass DataTableAdapterBuilder:AdapterBuilder

    {

          private DataViewAdapter _adapter;

          private DataViewAdapter ViewAdapter

          {

                get

                {

                      if (_adapter == null)

                      {

                            DataTable table = (DataTable)Source;

    _adapter = new DataViewAdapter(table.DefaultView);

                      }

                      return _adapter;

                }

          }

          publicoverride IDataSourceAdapter Adapter

          {

                get{return ViewAdapter;}

          }

    }

     

    publicclass DataViewAdapterBuilder:AdapterBuilder

    {

          private DataViewAdapter _adapter;

          private DataViewAdapter ViewAdapter

          {

                get

                {   //lazy instantiate

                      if (_adapter == null)

                      {

    _adapter = new DataViewAdapter((DataView)Source);

                      }

                      return _adapter;

                }

          }

          publicoverride IDataSourceAdapter Adapter

          {

                get{return ViewAdapter;}

          }

    }

    The DataView type and the DataTable type are so closely related that it might make sense to make a general DataAdapter. It would be enough to add another constructor that handles a DataTable. Alas when the user needs different functionality for a DataTable, the entire class would have to be replaced or inherited. By constructing a new builder that uses the same IdataSourceAdapter, the user has more freedom on how they choose to implement the adapter.

    In the pager control, the look-up of the proper builder is handled by a type safe collection.

    publicclass AdapterCollection:DictionaryBase

    {

          privatestring GetKey(Type key)

          {

                return key.FullName;

          }

          public AdapterCollection()

          {}

          publicvoid Add(Type key,AdapterBuilder value)

          {

                Dictionary.Add(GetKey(key),value);

          }

          publicbool Contains(Type key)

          {

                return Dictionary.Contains(GetKey(key));

          }

          publicvoid Remove(Type key)

          {

                Dictionary.Remove(GetKey(key));

          }

    public AdapterBuilder this[Type key]

          {

                get{return (AdapterBuilder)Dictionary[GetKey(key)];}

                set{Dictionary[GetKey(key)]=value;}

          }

    }

    The AdapterCollection relies on the type of the DataSource, which fits in perfectly with the BoundControl_DataBound method. The index key used is the Type.FullName method, ensuring the index key is unique for each type. This puts the responsibility on the AdapterCollection to contain only one builder for a given type. Adding the builder look-up to the BoundControl_DataBound method results in the following:

    public AdapterCollection Adapters

    {

          get{return _adapters;}

    }

    privatebool HasParentControlCalledDataBinding

    {

          get{return _builder != null;}

    }

     

    privatevoid BoundControl_DataBound(object sender,System.EventArgs e)

    {

          if (HasParentControlCalledDataBinding) return;

          Type type = sender.GetType();

          _datasource = type.GetProperty("DataSource");

          if (_datasource == null)

                thrownew NotSupportedException("The Pager control doesn't support controls that don't contain a datasource");

          object data = _datasource.GetGetMethod().Invoke(sender,null);

     

          _builder = Adapters[data.GetType()];

          if (_builder == null)

                thrownew NullReferenceException("There is no adapter installed to handle a datasource of type "+data.GetType());

          _builder.Source = data;

     

          BindParent();

    }

    The BoundControl_DataBound method also checks to see if the builder is already created with the HasParentControlCalledDataBinding. If it has, then it doesn't go through the exercise of finding the proper builder again. This of course assumes that the user doesn't call DataBinding with different DataSources, which of course they shouldn't. The Adapters table is initialized in the constructor.

    public Pager()

    {

          _adapters = new AdapterCollection();

          _adapters.Add(typeof(DataTable),new DataTableAdapterBuilder());

          _adapters.Add(typeof(DataView),new DataViewAdapterBuilder());

    }

         

    The last method to implement is to call the BindParent to manipulate and return the data.

     

    privatevoid BindParent()

    {

          _datasource.GetSetMethod().Invoke(BoundControl,

    new object[] { _builder.Adapter.GetPagedData( StartRow,ResultsToShow*CurrentPage)});

    }

    The method is pretty simple since the actual manipulation of the data is done by the Adapter. Once finished, reflection is again used but this time to set the DataSource property of the presentation control. The behavior of the pager control is now nearly finished, but without proper presentation, it is not very useful.

    As stated earlier the best way to achieve a solid separation of presentation from logic is to use templates, or more specifically the Itemplate interface. Indeed, MS realizes the power of templates and employs them almost everywhere, even in the page parser itself. Templates unfortunately aren't the easiest mechanism, and they take a while to learn, but there are many tutorials available that should ease the learning curve. Getting back to the pager control, the pager control contains the following buttons, First, Previous, Next, Last plus the individual pagers. The four navigation buttons are chosen from the ImageButton class instead of the LinkButton class. From a professional Web design point of view an image is more often useful than just a link.

    public ImageButton FirstButton{get {return First;}}

    public ImageButton LastButton{get {return Last;}}

    public ImageButton PreviousButton{get {return Previous;}}

    public ImageButton NextButton{get {return Next;}}

    The individual pagers are created dynamically since they depend on the datasource and how many records and pagers should be shown per page. The pagers will be added to a Panel which allows the Web designer to specify where he would like to show the pagers. More on the creation of the pagers later, for now the pager control needs to provide a template to allow the user to customize the look and feel of the pager.

    [TemplateContainer(typeof(LayoutContainer))]

    public ITemplate Layout

    {

          get{return (_layout;}

          set{_layout =value;}

    }

     

    publicclass LayoutContainer:Control,INamingContainer

    {

          public LayoutContainer()

          {this.ID = "Page";}

    }

    The LayoutContainer class provides a holder for the template. The addition of the custom ID in a template container is always a good idea since it prevents problems that arise with events and how they're called by the page. The following UML diagram defines the presentation of the pager control.

    The first step in creating a template is to define a simple layout in the aspx page:

    <Layout>

    <asp:ImageButtonid="First"Runat="server" AlternateText="first"/>

    <asp:ImageButtonid="Previous"Runat="server"AlternateText="previous"/>

       <asp:ImageButtonid="Next"Runat="server"AlternateText="next"/>

       <asp:ImageButtonid="Last"Runat="server"AlternateText="last"/>

       <asp:PanelID="Pager"Runat="server"/>

    </Layout>

    For the purpose of this example, the layout doesn't contain any formatting, such as tables etc ... But they can and should be included as shown later.

    The Itemplate interface provides only one method, InstantiateIn, which parses the template and binds it with the holder.

    privatevoid InstantiateTemplate()

    {

          _container = new LayoutContainer();

          Layout.InstantiateIn(_container);

          First = (ImageButton)_container.FindControl("First");

          Previous = (ImageButton)_container.FindControl("Previous");

          Next = (ImageButton)_container.FindControl("Next");

          Last = (ImageButton)_container.FindControl("Last");

          Holder = (Panel)_container.FindControl("Pager");

    this.First.Click += new System.Web.UI.ImageClickEventHandler(this.First_Click);

    this.Last.Click += new System.Web.UI.ImageClickEventHandler(this.Last_Click);

    this.Next.Click += new System.Web.UI.ImageClickEventHandler(this.Next_Click);

    this.Previous.Click += new System.Web.UI.ImageClickEventHandler(this.Previous_Click);

    }

    The first thing the page control's InstatiateTemplate method does is instantiate the template, Layout.InstantiateIn(_container). The container is just another control that is used like any other control. Making use of this fact, the InstantiateTemplate method finds the four navigational buttons and the panel needed to hold the individual pagers. The buttons are found by their IDs. This is a small limitation placed on the pager control. The navigational buttons MUST have the pre-defined IDs, "First","Previous","Next","Last", "Pager", or they won't be found. Unfortunately this is the only alternative with the presentation structure chosen. The other alternative is for each of the buttons to inherit from the ImageButton class thereby defining a new type. Since each button will be of a different type then a recursive search through the container could be implemented to find a particular type forgoing the need to name the buttons properly. But with proper documentation such a small requirement shouldn't pose problems.

    Once the four buttons are found, then the proper event handlers are bound to them. A very important decision must be made as to when the InstantiateTemplate should be called. Normally such a method would be called in the CreateChildControls method, since it basically does just that, creates child controls. Since the pager control doesn't ever change its child controls, then it doesn't need the functionality provided by the CreateChildControls method to change its rendering state based on some event. The quicker the child controls get rendered the better. A good place then to call the instantiate method is in the OnInit event.

    protectedoverridevoid OnInit(EventArgs e)

    {

          _boundcontrol = Parent.FindControl(BindToControl);

    BoundControl.DataBinding += new EventHandler(BoundControl_DataBound);

          InstantiateTemplate();

          Controls.Add(_container);

          base.OnInit(e);

    }

    The OnInit method also performs a very important act, it adds the container to the pager control. Without adding the container to the pager's control collection then the template wouldn't be shown since the Render method would never be called. A template can also be defined programmatically by implementing the Itemplate interface. In step with providing a good flexible control, this feature can be used to provide a default template in case the user doesn't provide one in the aspx page.

    publicclass DefaultPagerLayout:ITemplate

    {

          private ImageButton Next;

          private ImageButton First;

          private ImageButton Last;

          private ImageButton Previous;

          private Panel Pager;

         

          public DefaultPagerLayout()

          {

                Next = new ImageButton();

                First = new ImageButton();

                Last = new ImageButton();

                Previous = new ImageButton();

                Pager = new Panel();

     

                Next.ID="Next"; Next.AlternateText="Next";

                First.ID="First"; First.AlternateText="First";

                Last.ID = "Last"; Last.AlternateText ="Last";

                Previous.ID="Previous"; Previous.AlternateText="Previous";

                Pager.ID="Pager";

          }

          publicvoid InstantiateIn(Control control)

          {

                control.Controls.Clear();

                Table table = new Table();

                table.BorderWidth = Unit.Pixel(0);

                table.CellSpacing= 1;

                table.CellPadding =0;

                TableRow row = new TableRow();

                row.VerticalAlign = VerticalAlign.Top;

                table.Rows.Add(row);

                TableCell cell = new TableCell();

                cell.HorizontalAlign = HorizontalAlign.Right;

                cell.VerticalAlign = VerticalAlign.Middle;

                cell.Controls.Add(First);

                cell.Controls.Add(Previous);

                row.Cells.Add(cell);

                cell = new TableCell();

                cell.HorizontalAlign= HorizontalAlign.Center;

                cell.Controls.Add(Pager);

                row.Cells.Add(cell);

                cell = new TableCell();

                cell.VerticalAlign = VerticalAlign.Middle;

                cell.Controls.Add(Next);

                cell.Controls.Add(Last);

                row.Cells.Add(cell);

                control.Controls.Add(table);

          }

    }

    The DefaultPagerLayout implements all the navigation elements programmatically that were added in the aspx page but this time it formats the elements with a normal HTML table. Now if the user forgets to implement their presentation template, then a default template will be provided for them.

    [TemplateContainer(typeof(LayoutContainer))]

    public ITemplate Layout

    {

          get{return (_layout == null)? new DefaultPagerLayout():_layout;}

          set{_layout =value;}

    }

    Getting back to generating the individual pagers, the pager control needs to first establish some useful properties that will tell the control how many individual pagers to generate.

    publicint CurrentPage

    {

          get

          {

                string cur = (string)ViewState["CurrentPage"];

    return (cur == string.Empty || cur ==null)? 1 : int.Parse(cur);

          }

          set

          {

                ViewState["CurrentPage"] = value.ToString();}

          }

    }

    publicint PagersToShow

    {

          get{return _results;}

          set{_results = value;}

    }

    publicint ResultsToShow

    {

          get{return _resultsperpage;}

          set{_resultsperpage = value;}

    }

    The CurrentPage property stores actually that, the current page in the ViewState of the pager, while the PagersToShow method and the ResultsToShow method define properties that allow the user to define how many pagers to show and how many results to show per page. The default is set to 10.

    privateint PagerSequence

    {

          get

          {

    return Convert.ToInt32(Math.Ceiling((double)CurrentPage / (double)PagersToShow));

    }

    }

    The NumberofPagersToGenerate returns the current number of pagers that should be generated.

    privateint NumberOfPagersToGenerate

    {

          get{return PagerSequence*PagersToShow;}

    }

     

             

    The NumberofPagersToGenerate returns the current number of pagers that should be generated.

     

    privateint TotalPagesToShow

    {

    get{return Convert.ToInt32(Math.Ceiling((double)TotalResults/ (double)ResultsToShow));

    }

    }

    publicint TotalResults

    {

          get{return _builder.Adapter.TotalCount;}

    }

    The TotalPagesToShow method returns the total number of pages that should be shown adjusted by the users predefined ResultsToShow property.

    ASP.NET defines default styles, which might not be very useful to the user employing the pager control. Defining custom styles allows the user to customize the pagers look.

    public Style UnSelectedPagerStyle {get {return UnselectedPager;}}

    public Style SelectedPagerStyle {get {return SelectedPager;}}

    The UnSelectedPagerStyle provides a style that will be used by the unselected individual pagers, while the SelectedPagerStyle provides a style that will be used by the selected individual pagers.

    privatevoid GeneratePagers(WebControl control)

    {

          control.Controls.Clear();

          int pager = (PagerSequence-1)* PagersToShow +1;

     

    for (;pager<=NumberOfPagersToGenerate && pager<=TotalPagesToShow;pager++)

          {

                LinkButton link = new LinkButton();

                link.Text = pager.ToString();

                link.ID = pager.ToString();

                link.Click += new EventHandler(this.Pager_Click);

                if (link.ID.Equals(CurrentPage.ToString()))

                      link.MergeStyle(SelectedPagerStyle);

                else

                      link.MergeStyle(UnSelectedPagerStyle);

                control.Controls.Add(link);

                control.Controls.Add(new LiteralControl("&nbsp;"));

          }

    }

     

    privatevoid GeneratePagers()

    {

          GeneratePagers(Holder);

    }

    The GeneratePagers method dynamically creates all the needed individual pagers as buttons of type LinkButton. The text and the ID property of the individual pagers are assigned the current pager number controlled by the loop; the click event is bound to the proper event handler and the proper styles are set. In the end the pagers are added to the proper housing control which in this case is the Panel object. The button ID serves as a way of identifying which button fired the click event. Defining the event handlers:

    privatevoid Pager_Click(object sender, System.EventArgs e)

    {

          LinkButton button = (LinkButton) sender;

          CurrentPage = int.Parse(button.ID);

          Update();

    }

     

    privatevoid Next_Click(object sender, System.Web.UI.ImageClickEventArgs e)

    {

          if (CurrentPage<TotalPagesToShow)

                CurrentPage++;

          Update();

    }

    privatevoid Previous_Click(object sender, System.Web.UI.ImageClickEventArgs e)

    {

          if (CurrentPage > 1)

                CurrentPage--;

          Update();

    }

    privatevoid First_Click(object sender, System.Web.UI.ImageClickEventArgs e)

    {

          CurrentPage = 1;

          Update();

    }

    privatevoid Last_Click(object sender, System.Web.UI.ImageClickEventArgs e)

    {

          CurrentPage = TotalPagesToShow;

    Update();

    }

    Each of the event handlers takes care of setting the current page of the pager control and then update the bound control.

    privatevoid Update()

    {

          if (!HasParentControlCalledDataBinding) return;

          ApplyDataSensitivityRules();

          BindParent();

          BoundControl.DataBind();

    }

    First off, the pager control checks that the control has gone through the necessary steps to initialize the proper adapters by calling the HasParentControlCalledDataBinding method. If it has, then it applies something which was outlined in the specifications as data sensitivity rules. Data sensitivity rules allow the pager control to behave differently based on the data in the BoundControl. The pager control controls data sensitivity internally, but they could easily be moved outside by using the [GoF] State pattern.

    publicbool IsDataSensitive

    {

          get{return _isdatasensitive;}

          set{_isdatasensitive = value;}

    }

     

    privatebool IsPagerVisible

    {

          get{return (TotalPagesToShow != 1) && IsDataSensitive;}

    }

     

    privatebool IsPreviousVisible

    {

          get

          {

                return (!IsDataSensitive)? true:

                      (CurrentPage != 1);

          }

    }

     

    privatebool IsNextVisible

    {

          get

          {

                return (!IsDataSensitive)? true:

                      (CurrentPage != TotalPagesToShow);

          }

    }

     

    privatevoid ApplyDataSensitivityRules()

    {

          FirstButton.Visible = IsPreviousVisible;

          PreviousButton.Visible = IsPreviousVisible;

          LastButton.Visible = IsNextVisible;

          NextButton.Visible = IsNextVisible;

          if (IsPagerVisible) GeneratePagers();

    }

    The ApplyDataSensitivityRules applies the predefined rules such as IsPagerVisible, IsPreviousVisible and IsNextVisible. By default data sensitivity is turned on. Users can turn it off by setting the IsDataSensitive property. The presentation of the pager control is now finished. The last finishing touch is to provide events so that the user can make all the necessary adjustments on different pager events.

    publicdelegatevoid PageDelegate(object sender,

    PageChangedEventArgs e);

     

    publicenum PagedEventInvoker{Next,Previous,First,Last,Pager}

     

    publicclass PageChangedEventArgs:EventArgs

    {

          privateint newpage;

          private Enum invoker;

     

          public PageChangedEventArgs(int newpage):base()

          {

                this.newpage = newpage;

          }

    public PageChangedEventArgs(int newpage,PagedEventInvoker invoker)

          {

                this.newpage = newpage;

                this.invoker = invoker;

          }

          publicint NewPage {get{return newpage;}}

          public Enum EventInvoker{get{return invoker;}}

    }

    Since the pager control needs to return custom event arguments, a special class PageChangedEventArgs is built. The PageChangedEventArgs class returns the type of PagedEventInvoker, which is a simple enumeration of the possible controls that could raise the event, and the new page number. To handle the custom event arguments a new delegate is defined, PageDelegate. The different events are defined in the following manor:

    publicevent PageDelegate PageChanged;

    publicevent EventHandler DataUpdate;

    When an event doesn't have a listener attached to it, ASP.NET raises an exception to counteract this behavior; the pager control defines the following RaiseEvent methods.

    privatevoid RaiseEvent(EventHandler e,object sender)

    {

          this.RaiseEvent(e,this,null);

    }

     

    privatevoid RaiseEvent(EventHandler e,object sender, PageChangedEventArgs args)

    {

          if(e!=null)

          {

                e(sender,args);

          }

    }

    privatevoid RaiseEvent(PageDelegate e,object sender)

    {

          this.RaiseEvent(e,this,null);

    }

     

    privatevoid RaiseEvent(PageDelegate e,object sender, PageChangedEventArgs args)

    {

          if(e!=null)

          {

                e(sender,args);

          }

    }

    The event handlers can now raise events (or bubble) by making calls to the RaiseEvents methods.

    privatevoid Pager_Click(object sender, System.EventArgs e)

    {

          LinkButton button = (LinkButton) sender;

          CurrentPage = int.Parse(button.ID);

    RaiseEvent(PageChanged, this,new PageChangedEventArgs(CurrentPage,PagedEventInvoker.Pager));

          Update();

    }

     

    privatevoid Update()

    {

          if (!HasParentControlCalledDataBinding) return;

          ApplyDataSensitivityRules();

          BindParent();

          _boundcontrol.DataBind();

          RaiseEvent(DataUpdate,this);

    }

    For simplicity sake the other event handlers aren't shown, but they raise the PageChanged event in a similar way. The pager control is now ready to use. To use it the Web designer needs only to bind it to a presentation control.

    <asp:RepeaterID="repeater"Runat="server">

          <ItemTemplate>

    Column 1: <%# Convert.ToString(DataBinder.Eval( Container.DataItem,"Column1"))%><br>

    Column 2: <%# Convert.ToString(DataBinder.Eval (Container.DataItem,"Column2"))%><br>

    Column 3: <%# Convert.ToString(DataBinder.Eval( Container.DataItem,"Column3"))%><br>

                <hr>

          </ItemTemplate>

    </asp:Repeater>

     

    <cc1:Pagerid="pager"ResultsToShow="2"runat="server"BindToControl="repeater">

          <SelectedPagerStyleBackColor="Yellow"/>

    </cc1:Pager>

    The above aspx page binds the pager to a Repeater control, sets the number of results to show per page to 2, and sets the selected pager color to yellow. The default layout is used. The following test application binds the pager control to a DataGrid.

    <asp:DataGridID="Grid"Runat="server"/>

     

    <cc1:Pagerid="PagerGrid"ResultsToShow="2"runat="server"BindToControl="Grid">

          <SelectedPagerStyleBackColor="Red"/>

          <Layout>

    <asp:ImageButtonid="First"Runat="server"AlternateText="first"/>

    <asp:ImageButtonid="Previous"Runat="server"AlternateText="previous"/>

    <asp:ImageButtonid="Next"Runat="server"AlternateText="next"/>

    <asp:ImageButtonid="Last"Runat="server"AlternateText="last"/>

                <asp:PanelID="Pager"Runat="server"/>

          </Layout>

    </cc1:Pager>

    The code behind tests the pluggable architecture.

    protected Pager pager;

    protected Repeater repeater;

    protected Pager PagerGrid;

    protected DataGrid Grid;

     

    privatevoid Page_Load(object sender, System.EventArgs e)

    {

          pager.Adapters.Remove(typeof(DataTable));

    pager.Adapters.Add(typeof(DataTable),new DataTableAdapterBuilder());

          DataTable table = GenerateDataTable();

          repeater.DataSource = table;

          repeater.DataBind();

          Grid.DataSource = table;

          Grid.DataBind();

    }

    private DataTable GenerateDataTable()

    {

          DataTable table = new DataTable();

          table.Columns.Add("Column1");

          table.Columns.Add("Column2");

          table.Columns.Add("Column3");

          for (int i=0;i<20;i++)

          {

                DataRow row = table.NewRow();

                row[0] = "Row"+(i+1);

                table.Rows.Add(row);

          }

          return table;

    }

    The test applications show that the pager control is independent from the presentation controls, can easily handle different data sources, and is very easy to use. For other tests of the pagers functionality please download the zip file which contains all the tests and all the relevant source code.

    Even though developing custom Web controls can have a significant learning curve, taking the time to learn it has untold benefits. By providing reusable components, the developer, with a bit of extra work, can turn a normal web control into a multi-use generic control, increasing output ten fold. The pager control is just one example of how to create a control that is independent of current and future presentation needs.

    About the Author

    Tom is a senior web developer for Poland's biggest online bank, Inteligo. He's developed for over 5 years in Java and .NET on projects ranging from portals, CMS, distributed systems, banking systems, CRM's and telecom applications. In his spare time he develops open source software, such as the NetAr (http://sourceforge.net/projects/netarcomponents/) Portal components. He can be reached at tomasz.kaszuba@inteligo.pl.

    References

    - Thinking in C# by Bruce Eckel
    - Design Patterns By Erich Gamma et al, Addisson Wesley
    - Developing Microsoft ASP.NET Server Controls and Components by Nikhil Kothari, Vandana Datye

  • Rate This Article
    Not HelpfulMost Helpful
    1 2 3 4 5
    Other Articles
    Apr 21, 2004 - Creating a Web Custom Control
    Conrad Jalali shows how to build Web custom controls by creating one that displays checkboxes in a categorized, hierarchical view.
    [Read This Article]  [Top]
    Mar 15, 2004 - Creating a Popup Date Picker
    Conrad Jalali shows how to extend the functionality of the ASP.NET Calendar control to remove some of the annoying postback delays that occur when populating a text box with a date from a popup calendar.
    [Read This Article]  [Top]
    Oct 23, 2003 - Creating a Custom .NET Web Control With Events
    This article covers some of the essentials of building reusable Web controls. Learn how to create a Web control and its custom events and event arguments, as well as how to use the control properly within a page.
    [Read This Article]  [Top]
    Jun 10, 2003 - Hosting .NET Windows Forms Controls in IE
    Windows Forms within Web pages work in a manner similar to Java applets. Thiru Thangarathinam shows how to host Windows Forms controls in Internet Explorer and how to utilize .NET Code Access Security to configure what the control can do when running within the browser.
    [Read This Article]  [Top]
    May 6, 2003 - Building a Simple Mask Control
    James Culshaw shares his experiences building his first working custom control, a basic mask control that allows input of time values, and offers advice and tips to those just starting out.
    [Read This Article]  [Top]
    Mar 3, 2003 - Creating a Server Control for JavaScript Testing
    Learn how to create an ASP.NET server control that detects if a browser supports JavaScript AND if JavaScript is enabled.
    [Read This Article]  [Top]
    Jan 21, 2003 - Protecting Users from Suspect Textual Data
    Learn the ins and outs of composite controls by creating an application that prevents users from submitting offending text.
    [Read This Article]  [Top]
    Nov 26, 2002 - Introduction to the .NET Speech SDK
    Rob Chartier offers a tour of the .NET Speech SDK Beta 2 and its use of the industry specification SALT. The article shows you how to create a basic speech-enabled Web user control.
    [Read This Article]  [Top]
    Aug 27, 2002 - Creating "Self Populating" Controls
    In this article, Luther Stanton shows how to combine inheritance and server controls to create a self-populating drop-down-list control.
    [Read This Article]  [Top]
    Apr 30, 2002 - Building ASP.NET User and Server Controls, Part 2
    Solomon Shaffer explains custom controls, describes the complexities and issues surrounding building such controls, and walks through a useful example.
    [Read This Article]  [Top]
    Mailing List
    Want to receive email when the next article is published? Just Click Here to sign up.

    Support the Active Server Industry

    internet.commediabistro.comJusttechjobs.comGraphics.com

    Search:

    WebMediaBrands Corporate Info

    Legal Notices, Licensing, Reprints, Permissions, Privacy Policy.
    Advertise | Newsletters | Shopping | E-mail Offers | Freelance Jobs