For almost all data presentation web applications, organizing the way data is displayed and avoiding confusing feelings to users is one of the main goals. Displaying 20 records per page is certainly acceptable, but displaying 10,000 records per page can easily cause inconvenience to users. Splitting the data into multiple pages for display, that is, paging the data, is the most common way to solve such problems.
1. Overview
ASP.NET itself only provides one control that supports data paging, namely the DataGrid paging control. However, it is more suitable for use in Intranet environments. For Internet environments, the functions provided by the DataGrid paging control do not seem to be enough to construct a flexible Web application. One of the reasons is that the DataGrid control imposes restrictions on where Web designers can place paging controls and on the appearance of paging controls. For example, the DataGrid control does not allow vertical placement of paging controls. Another control that can take advantage of paging technology is Repeater. Web developers can use the Repeater control to quickly configure the display method of data, but the paging function requires developers to implement it themselves. Data sources are constantly changing, and data presentation methods vary widely. It would obviously be a waste of time to customize paging controls for these changing conditions. Constructing a universal paging control that is not limited to specific presentation controls will greatly help save time.
An excellent universal data control not only provides regular paging functions, but also can:
⑴ Provide "Home Page", "Previous Page", "Next Page" and "Last Page" paging navigation buttons.
⑵ Adjust its status according to the data display situation, that is, it has data sensitivity. If the paging control is set to display 10 records per page, but there are actually only 9 records, then the paging control should not be displayed; when the data is divided into multiple pages for display, the "Home" and "Top" of the first page The "Page" button should not be displayed, nor should the "Next Page" and "Last Page" buttons of the last page be displayed.
⑶ Cannot rely on specific data display controls.
⑷ Ability to adapt to various existing and future data sources.
⑸ The display mode should be easily configured and easily integrated into various applications.
⑹ When paging is ready, remind other controls.
⑺ Even inexperienced web designers should be able to use it without difficulty.
⑻ Provide attribute data about paging information.
There are currently some commercial controls on the market that provide the above functions, but they are all expensive. For many developers, constructing a universal paging control yourself is the ideal choice.
Figure 1 shows the running interface of the universal paging control in this article, in which the control used for display is a Repeater control. The paging control consists of two types of components: four navigation buttons and a set of page number links.
Users can easily change the display controls and change the appearance of the paging control itself. For example, the display control that cooperates with the paging control is replaced by a DataGrid control, and the page number link and four navigation buttons are displayed in two rows.
ASP.NET supports three ways to create custom Web controls: user controls, composite controls, and custom controls. The name of the third type of control, the custom control, is easily misleading. In fact, all three controls should be considered custom controls. The difference between composite controls and Microsoft's so-called custom controls is that the former requires the CreateChildControls() method. The CreateChildControls() method allows the control to redraw itself based on certain events. For this article's universal pager, we will use a composite control.
The following UML sequence diagram outlines the general mechanism of the universal paging control.
Although our goal is to make the universal paging control independent of the control that represents the data, it is obvious that there must be some way for the paging control to access the data. Every control that inherits from the Control class provides a DataBinding event. We register the pager itself as a listener for the DataBinding event, so the pager can learn about the data and modify the data. Since all controls that inherit from the Control class have this DataBinding event, the pager control achieves the goal of not relying on a specific data presentation control - in other words, the pager control can be bound to all controls that derive from the Control class. That is, it can be bound to almost all Web controls.
2. Core functions
When the presentation control triggers the DataBinding event, the paging control can obtain the DataSource property. Unfortunately, Microsoft does not provide interfaces that all data binding classes implement, such as IdataSourceProvider, and not all controls that inherit from the Control or WebControl class have a DataSource property, so there is no point in upcasting to the Control class, the only feasible way The method is to directly operate the DataSoruce property through the Reflection API. Before discussing the event handler methods, it should be noted that in order to register an event handler, you must first obtain a reference to the presentation control. The paging control exposes a simple string property BindToControl:
public string BindToControl
{
get
{
if (_bindcontrol == null)
throw new NullReferenceException("Before using the paging control, please bind to a control by setting the BindToControl property.");
return _bindcontrol;}
set{_bindcontrol=value;}
}
This method is very important, so it is best to throw a more clear message instead of throwing the standard NullReferenceException. In the OnInit method of the paging control, we resolve the reference to the presentation control. This example should use the OnInit event handler (rather than the constructor) to ensure that the JIT compiled aspx page has BindToControl set.
protected override void OnInit(EventArgs e)
{
_boundcontrol = Parent.FindControl(BindToControl);
BoundControl.DataBinding += new EventHandler(BoundControl_DataBound);
base.OnInit(e);
...
}
The operation of searching the presentation control is completed by searching the Parent control of the paging control. Here, the Parent is the page itself. It is dangerous to use Parent in this way. For example, if the paging control is embedded in another control, such as a Table control, the Parent reference will actually be a reference to the Table control. Since the FindControl method only searches the current control collection, it is impossible to search unless the presentation control is in the collection. A safer approach is to recursively search through the collection of controls until the target control is found.
After finding the BoundControl, we register the paging control as a listener for the DataBinding event. Since the paging control operates on a data source, it is important that this event handler is the last one in the call chain. However, as long as the presentation control registers the DataBinding event handler in the OnInit event handler (the default behavior), there will be no problem when the paging control operates the data source.
The DataBound event handler is responsible for obtaining the DataSource property of the presentation control.
private void BoundControl_DataBound(object sender,System.EventArgs e)
{
if (HasParentControlCalledDataBinding) return;
Type type = sender.GetType();
_datasource = type.GetProperty("DataSource");
if (_datasource == null)
throw new NotSupportedException("The paging control requires that the presentation control must contain a DataSource.");
object data = _datasource.GetGetMethod().Invoke(sender,null);
_builder = Adapters[data.GetType()];
if (_builder == null)
throw new NullReferenceException("The appropriate adapter is not installed to handle the following data source type: "+data.GetType());
_builder.Source = data;
ApplyDataSensitivityRules();
BindParent();
RaiseEvent(DataUpdate,this);
}
In DataBound, we try to get the DataSource property through the Reflection API and then return a reference to the actual data source. Now that the data source is known, the paging control must also know how to operate on the data source. In order to make the paging control not dependent on a specific presentation control, the problem is much more complicated. However, making the paging control dependent on a specific data source defeats the goal of designing a flexible paging control. We need to use a plug-in architecture to ensure that the paging control can handle various data sources, whether it is the data source provided by .NET or a custom data source.
In order to provide a robust, scalable pluggable architecture, we will construct a solution using the [GoF] Builder pattern.
Figure 4
IDataSourceAdapter interface defines the most basic elements required for paging control to operate data, which is equivalent to a "plug".
publicinterface IDataSourceAdapter
{
int TotalCount{get;}
object GetPagedData(int start,int end);
}
The TotalCount property returns the total number of elements contained in the data source before processing the data, while the GetPagedData method returns a subset of the original data. For example: assuming the data source is an array containing 20 elements, the paging control displays the data as 10 elements per page. elements, then the subset of elements on the first page is array elements 0-9, and the subset of elements on the second page is array elements 10-19. DataViewAdapter provides a DataView type plug:
internal class DataViewAdapter:IDataSourceAdapter
{
private DataView _view;
internal DataViewAdapter(DataView view)
{
_view = view;
}
public int TotalCount
{
get{return (_view == null) ? 0 : _view.Table.Rows.Count;}
}
public object 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;
}
}
DataViewAdapter implements the GetPagedData method of IDataSourceAdapter, which clones the original DataTable and imports the data in the original DataTable into the new DataTable. The visibility of this class is intentionally set to internal in order to hide implementation details from web developers and provide a simpler interface through the Builder class.
public abstract class AdapterBuilder
{
private object _source;
private void CheckForNull()
{
if (_source == null) throw new NullReferenceException("A legal data source must be provided");
}
public virtual object Source
{
get
{
CheckForNull();
return _source;}
set
{
_source = value;
CheckForNull();
}
}
public abstract IDataSourceAdapter Adapter{get;}
}
The AdapterBuilder abstract class provides a more manageable interface for the IdataSourceAdapter type. Due to the increased level of abstraction, we no longer have to use IdataSourceAdapter directly. At the same time, AdapterBuilder also provides instructions for performing preprocessing before paging data. In addition, this Builder also makes the actual implementation class, such as DataViewAdapter, transparent to users of the paging control:
public class DataTableAdapterBuilder:AdapterBuilder
{
private DataViewAdapter _adapter;
private DataViewAdapter ViewAdapter
{
get
{
if (_adapter == null)
{
DataTable table = (DataTable)Source;
_adapter = new DataViewAdapter(table.DefaultView);
}
return _adapter;
}
}
public override IDataSourceAdapter Adapter
{
get
{
return ViewAdapter;
}
}
}
public class DataViewAdapterBuilder:AdapterBuilder
{
private DataViewAdapter _adapter;
private DataViewAdapter ViewAdapter
{
get
{ // Delayed instantiation
if (_adapter == null)
{
_adapter = new DataViewAdapter((DataView)Source);
}
return _adapter;
}
}
public override IDataSourceAdapter Adapter
{
get{return ViewAdapter;}
}
}
The DataView type and the DataTable type are so closely related that it may make sense to construct a generic DataAdapter. In fact, just adding another constructor that handles the DataTable is enough. Unfortunately, when users need different functionality to deal with a DataTable, the entire class must be replaced or inherited. If we construct a new Builder that uses the same IdataSourceAdapter, the user has more freedom in choosing how to implement the adapter.
In the paging control, the operation of finding the appropriate Builder class is completed by a type-safe collection.
public class AdapterCollection:DictionaryBase
{
private string 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;}
}
}
AdapterCollection relies on the DataSource type, and DataSource is cleverly introduced through BoundControl_DataBound. The index key used here is the Type.FullName method, which ensures the uniqueness of the index key of each type. At the same time, it also assigns the responsibility of ensuring that there is only one Builder for each type to the AdapterCollection. Add the Builder search to the BoundControl_DataBound method and the results are as follows:
public AdapterCollection Adapters
{
get{return _adapters;}
}
private bool HasParentControlCalledDataBinding
{
get{return _builder != null;}
}
private void BoundControl_DataBound(object sender,System.EventArgs e)
{
if (HasParentControlCalledDataBinding) return;
Type type = sender.GetType();
_datasource = type.GetProperty("DataSource");
if (_datasource == null)
throw new NotSupportedException("The paging control requires that the presentation control must contain a DataSource.");
object data = _datasource.GetGetMethod().Invoke(sender,null);
_builder = Adapters[data.GetType()];
if (_builder == null)
throw new NullReferenceException("The appropriate adapter is not installed to handle the following data source type: "+data.GetType());
_builder.Source = data;
ApplyDataSensitivityRules();
BindParent();
RaiseEvent(DataUpdate,this);
}
The BoundControl_DataBound method uses HasParentControlCalledDataBinding to check whether the Builder has been created. If so, it will no longer perform the operation of finding the appropriate Builder. The initialization of the Adapters table is done in the constructor:
public Pager()
{
SelectedPager=new System.Web.UI.WebControls.Style();
UnselectedPager = new System.Web.UI.WebControls.Style();
_adapters = new AdapterCollection();
_adapters.Add(typeof(DataTable),new DataTableAdapterBuilder());
_adapters.Add(typeof(DataView),new DataViewAdapterBuilder());
}
The last method to be implemented is BindParent, which is used to process and return data.
private void BindParent()
{
_datasource.GetSetMethod().Invoke(BoundControl,
new object[]{_builder.Adapter.GetPagedData(StartRow,ResultsToShow*CurrentPage)});
}
This method is very simple, because the data processing is actually done by the Adapter. After this process is completed, we will use the Reflection API again, but this time to set the DataSource property of the presentation control.
3. Interface Design
So far, the core functions of the paging control have been almost implemented, but if there is a lack of appropriate presentation methods, the paging control will not be very useful.
In order to effectively separate the presentation method from the program logic, the best way is to use templates, or to be more specific, use the Itemplate interface. In fact, Microsoft clearly understands the power of templates and uses them almost everywhere, even in the page parser itself. Unfortunately, templates are not as simple a concept as some people think, and it takes some time to truly grasp its essence. Fortunately, there is a lot of information in this area, so I won't go into details here. Back to the paging control, it has four buttons: home page, previous page, next page, last page, and of course the number of each page. The four navigation buttons are selected from the ImageButton class instead of the LinkButton class. From a professional Web design perspective, graphic buttons are obviously more useful than monotonous links.
public ImageButton FirstButton{get {return First;}}
public ImageButton LastButton{get {return Last;}}
public ImageButton PreviousButton{get {return Previous;}}
public ImageButton NextButton{get {return Next;}}
Page numbers are dynamically constructed because they depend on the number of records in the data source and the number of records displayed on each page. The page number will be added to a Panel, and web designers can use the Panel to specify where to display the page number. The process of creating page numbers will be discussed in detail later. Now we need to provide a template for the paging control so that users can customize the appearance of the paging control.
[Template Container(typeof(LayoutContainer))]
public ITemplate Layout
{
get{return (_layout;}
set{_layout =value;}
}
public class LayoutContainer:Control,INamingContainer
{
publicLayoutContainer()
{this.ID = "Page";}
}
The LayoutContainer class provides a container for templates. Generally speaking, it is always good to add a custom ID to the template container, which will avoid problems when handling events and making page calls. The following UML diagram describes the presentation mechanism of the paging control.
Figure 5
The first step in creating a template is to define the layout in the aspx page:
<LAYOUT>
<asp:ImageButton id="First" Runat="server" imageUrl="play2L_dis.gif"
AlternateText="Home"></asp:ImageButton>
<asp:ImageButton id="Previous" Runat="server" imageUrl="play2L.gif"
AlternateText="Previous page"></asp:ImageButton>
<asp:ImageButton id="Next" Runat="server" imageUrl="play2.gif"
AlternateText="Next page"></asp:ImageButton>
<asp:ImageButton id="Last" Runat="server" imageUrl="play2_dis.gif"
AlternateText="Last page"></asp:ImageButton>
<asp:Panel id="Pager" Runat="server"></asp:Panel>
</LAYOUT>
This layout example does not contain any format elements, such as tables, etc. Of course, actual applications can (and should) add format elements, please see more instructions later.
The Itemplate interface only provides one method InstantiateIn, which parses the template and binds the container.
private void 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 control's InstatiateTemplate method does is instantiate the template, that is, call Layout.InstantiateIn(_container). A container is actually a kind of control, and its usage is similar to other controls. The InstantiateTemplate method uses this feature to find the four navigation buttons and the Panel used to hold the page number. Navigation buttons are found by their IDs. This is a small restriction on paging controls: navigation buttons must have specified IDs, which are First, Previous, Next, and Last. In addition, the ID of Panel must be Pager, otherwise it will Not found. Unfortunately, this seems to be the better approach for the presentation mechanism we've chosen; but hopefully, with proper documentation, this minor limitation won't cause any problems. Another alternative is to let each button inherit from the ImageButton class, thereby defining a new type; since each button is a different type, a recursive search can be implemented in the container to find Various specific buttons, eliminating the need to use the button's ID attribute.
After you find the four buttons, bind the appropriate event handlers to them. An important decision must be made here, namely when to call InstantiateTemplate. Generally, this type of method should be called in the CreateChildControls method, because the main purpose of the CreateChildControls method is this type of task of creating child controls. Since the paging control never modifies its child controls, it does not need the functionality provided by CreateChildControls to modify the display state based on some event. The faster the child control is displayed, the better, so the ideal place to call the InstantiateTemplate method is in the OnInit event.
protected override void OnInit(EventArgs e)
{
_boundcontrol = Parent.FindControl(BindToControl);
BoundControl.DataBinding += new EventHandler(BoundControl_DataBound);
InstantiateTemplate();
Controls.Add(_container);
base.OnInit(e);
}
In addition to calling the InstantiateTemplate method, the OnInit method also has another important task of adding the container to the paging control. If you do not add the container to the pager's control collection, the template will not be displayed because the Render method will never be called.
Templates can also be defined programmatically by implementing the Itemplate interface. In addition to being a measure to increase flexibility, this feature can also provide a default template for use when the user does not provide a template through an aspx page.
public class 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 page";Next.ImageUrl="play2.gif";
First.ID="First"; First.AlternateText="Home";First.ImageUrl="play2L_dis.gif";
Last.ID = "Last"; Last.AlternateText = "Last page"; Last.ImageUrl="play2_dis.gif";
Previous.ID="Previous"; Previous.AlternateText="Previous page";Previous.ImageUrl="play2L.gif";
Pager.ID="Pager";
}
public void 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);
}
}
DefaultPagerLayout provides all the navigation elements programmatically and adds them to the aspx page, but this time the navigation elements are formatted with standard HTML tables. Now, if the user does not provide a presentation template, the program will automatically provide a default template.
[TemplateContainer(typeof(LayoutContainer))]
public ITemplate Layout
{
get{return (_layout == null)? new DefaultPagerLayout():_layout;}
set{_layout =value;}
}
Let’s take a look at the process of generating each page number. The paging control first needs to determine some property values, and use these property values to determine how many different page numbers are to be generated.
public int CurrentPage
{
get
{
string cur = (string)ViewState["CurrentPage"];
return (cur == string.Empty || cur ==null)? 1 : int.Parse(cur);
}
set
{
ViewState["CurrentPage"] = value.ToString();}
}
public int PagersToShow
{
get{return _results;}
set{_results = value;}
}
public int ResultsToShow
{
get{return _resultsperpage;}
set{_resultsperpage = value;}
}
CurrentPage actually saves the current page in the ViewState of the page number. The properties defined by the PagersToShow method allow the user to specify how many pages to display, while the properties defined by ResultsToShow allow the user to specify how many records to display on each page. The default value is 10.
NumberofPagersToGenerate returns the current number of page numbers that should be generated.
private int PagerSequence
{
get
{
return Convert.ToInt32
(Math.Ceiling((double)CurrentPage/(double)PagersToShow));}
}
private int NumberOfPagersToGenerate
{
get{return PagerSequence*PagersToShow;}
}
private int TotalPagesToShow
{
get{return Convert.ToInt32(Math.Ceiling((double)TotalResults/(double)_resultsperpage));}
}
public int TotalResults
{
get{return _builder.Adapter.TotalCount;}
}
The TotalPagesToShow method returns the total number of pages to be displayed, adjusted by the user's preset ResultsToShow property.
Although ASP.NET defines some default styles, they may not be very practical for users of paging controls. Users can adjust the appearance of the paging control through custom styles.
public Style UnSelectedPagerStyle {get {return UnselectedPager;}}
public Style SelectedPagerStyle {get {return SelectedPager;}}
UnSelectedPagerStyle provides the style used when the page number is not selected, and SelectedPagerStyle provides the style used when the page number is selected.
private void 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(" "));
}
}
private void GeneratePagers()
{
GeneratePagers(Holder);
}
The GeneratePagers method dynamically creates all page numbers, which are buttons of type LinkButton. The label and ID attributes of each page number are assigned through a loop, and at the same time, the click event is bound to the appropriate event handler. Finally, the page number is added to a container control - in this case, a Panel object. The button ID serves to identify which button triggered the click event. The following is the definition of event handler:
private void 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();
}
private void Next_Click(object sender, System.Web.UI.ImageClickEventArgs e)
{
if (CurrentPage<TotalPagesToShow)
CurrentPage++;
RaiseEvent(PageChanged, this,new PageChangedEventArgs(CurrentPage,PagedEventInvoker.Next));
Update();
}
private void Previous_Click(object sender, System.Web.UI.ImageClickEventArgs e)
{
if (CurrentPage > 1)
CurrentPage--;
RaiseEvent(PageChanged, this,new PageChangedEventArgs(CurrentPage,PagedEventInvoker.Previous));
Update();
}
private void First_Click(object sender, System.Web.UI.ImageClickEventArgs e)
{
CurrentPage = 1;
RaiseEvent(PageChanged, this,new PageChangedEventArgs(CurrentPage,PagedEventInvoker.First));
Update();
}
private void Last_Click(object sender, System.Web.UI.ImageClickEventArgs e)
{
CurrentPage = TotalPagesToShow;
RaiseEvent(PageChanged, this,new PageChangedEventArgs(CurrentPage,PagedEventInvoker.Last));
Update();
}
These event handlers are similar in that they first change the current page of the paging control and then refresh the bound control.
private void Update()
{
if (!HasParentControlCalledDataBinding) return;
ApplyDataSensitivityRules();
BindParent();
BoundControl.DataBind();
}
First, the paging control checks whether the necessary adapters have been initialized by calling the HasParentControlCalledDataBinding method. If so, apply the previously pointed out rules for automatically adjusting controls based on data display conditions to the current control. These rules make the paging control behave differently based on the different conditions of the data in the BoundControl. Although these rules are controlled internally by the paging control, they can be easily moved out of the control using the [GoF] State mode when necessary.
public bool IsDataSensitive
{
get{return _isdatasensitive;}
set{_isdatasensitive = value;}
}
private bool IsPagerVisible
{
get{return (TotalPagesToShow != 1) && IsDataSensitive;}
}
private bool IsPreviousVisible
{
get
{
return (!IsDataSensitive)? true:
(CurrentPage != 1);
}
}
private bool IsNextVisible
{
get
{
return (!IsDataSensitive)? true:
(CurrentPage != TotalPagesToShow);
}
}
private void ApplyDataSensitivityRules()
{
FirstButton.Visible = IsPreviousVisible;
PreviousButton.Visible = IsPreviousVisible;
LastButton.Visible = IsNextVisible;
NextButton.Visible = IsNextVisible;
if (IsPagerVisible) GeneratePagers();
}
The ApplyDataSensitivityRules method implements predefined rules such as IsPagerVisible, IsPreviousVisible and IsNextVisible. By default, the paging control will have these rules enabled, but the user can turn them off by setting the IsDataSensitive property.
So far, the display part of the paging control has been basically designed. The last remaining finishing work is to provide several event handlers so that users can make necessary adjustments when various paging control events occur.
public delegate void PageDelegate(object sender,PageChangedEventArgs e);
public enum PagedEventInvoker{Next,Previous,First,Last,Pager}
public class PageChangedEventArgs:EventArgs
{
private int newpage;
private Enum invoker;
public PageChangedEventArgs(int newpage):base()
{
this.newpage = newpage;
}
public PageChangedEventArgs(int newpage,PagedEventInvoker invoker)
{
this.newpage = newpage;
this.invoker = invoker;
}
public int NewPage {get{return newpage;}}
public Enum EventInvoker{get{return invoker;}}
}
Since the paging control needs to return custom event parameters, we define a dedicated PageChangedEventArgs class. The PageChangedEventArgs class returns the PagedEventInvoker type, which is an enumerator of the controls that may trigger the event. In order to handle custom event parameters, we define a new delegate, PageDelegate. The event is defined in the following form:
public event PageDelegate PageChanged;
public event EventHandler DataUpdate;
When an event does not have a corresponding event listener, ASP.NET will throw an exception. The paging control defines the following RaiseEvent methods.
private void RaiseEvent(EventHandler e,object sender)
{
this.RaiseEvent(e,this,null);
}
private void RaiseEvent(EventHandler e,object sender, PageChangedEventArgs args)
{
if(e!=null)
{
e(sender,args);
}
}
private void RaiseEvent(PageDelegate e,object sender)
{
this.RaiseEvent(e,this,null);
}
private void RaiseEvent(PageDelegate e,object sender, PageChangedEventArgs args)
{
if(e!=null)
{
e(sender,args);
}
}
Event handlers can now trigger events by calling individual RaiseEvent methods.
4. Application examples
At this point, the design of the paging control has been completed and can be officially used. To use the paging control, simply bind it to a presentation control.
<asp:Repeater ID="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:Pager id="pager" ResultsToShow="2" runat="server" BindToControl="repeater">
<SELECTEDPAGERSTYLE BackColor="Yellow" />
</cc1:Pager>
The above aspx page binds the paging control to a Repeater control, sets the number of records displayed on each page to 2, the color of the selected page number is yellow, and uses the default layout. The effect is as shown in Figure 1. Below is another example, which binds the paging control to a DataGrid, as shown in Figure 2.
<asp:DataGrid ID="Grid" Runat="server"></asp:DataGrid>
<cc1:Pager id="PagerGrid" ResultsToShow="2" runat="server" BindToControl="Grid">
<SELECTEDPAGERSTYLE BackColor="Red"></SELECTEDPAGERSTYLE>
<LAYOUT>
<asp:ImageButton id="First" Runat="server" imageUrl="play2L_dis.gif" AlternateText="Home"></asp:ImageButton>
<asp:ImageButton id="Previous" Runat="server" imageUrl="play2L.gif" AlternateText="Previous page"></asp:ImageButton>
<asp:ImageButton id="Next" Runat="server" imageUrl="play2.gif" AlternateText="Next page"></asp:ImageButton>
<asp:ImageButton id="Last" Runat="server" imageUrl="play2_dis.gif" AlternateText="Last page"></asp:ImageButton>
<asp:Panel id="Pager" Runat="server"></asp:Panel>
</LAYOUT>
</cc1:Pager>
The test shows that the paging control does not depend on specific presentation controls. It can easily handle different data sources and is easy to use. Readers are asked to download the source code at the end of this article to see the complete example.
Although learning to develop custom Web controls is not an easy task, the benefits of mastering this skill are self-evident. With a slight increase in workload, developers can change ordinary Web controls into multi-purpose ones. Universal controls improve work efficiency dozens of times. The paging control in this article is just one example of creating universal controls to meet existing and future performance needs.
http://www.cnblogs.com/niit007/archive/2006/08/13/475501.html