打造一個通用ASP.NET數據分頁控件 |
發佈於:2006-8-15 17:23:08 已被閱讀: <script src="http://www.dwww.cn/hits.asp?ID=2804" type="text/javascript"></script> 138 33 |
對於幾乎所有的數據表現Web應用來說,組織好數據的顯示方式、避免給用戶帶來混亂的感覺就是最主要的目標之一。每個頁面顯示20條記錄當然是可以接受的,但每頁顯示10000條記錄就很容易給用戶帶來不便了。將數據分成多個頁面顯示,即對數據進行分頁,是解決此類問題的最常見的辦法。 一、慨述 ASP.NET本身只提供了一個支持數據分頁的控件,即DataGrid分頁控件,不過它比較適合Intranet環境使用,對於Internet環境來說,DataGrid分頁控件提供的功能似乎不足以構造出靈活的Web應用。其中一個原因是,DataGrid控件對Web設計者放置分頁控件的位置和分頁控件的外觀都有限制,例如,DataGrid控件不允許垂直放置分頁控件。另一個能夠發揮分頁技術優勢的控件是Repeater,Web開發者可以利用Repeater控件快速配置數據的顯示方式,但分頁功能卻需要開發者自己實現。數據源在不斷地變化,數據表現方式也千差萬別,如果針對這些不斷變動的條件分別定製分頁控件,顯然太浪費時間了,構造一個不限於特定表現控件的通用分頁控件將極大地有利於節省時間。 一個優秀的通用數據控件不僅提供常規的分頁功能,而且還要能夠: ⑴ 提供“首頁”、“上一頁”、“下一頁”、“末頁”分頁導航按鈕。 ⑵ 根據數據顯示情況調整自身的狀態,即具有數據敏感性。如果分頁控件被設置成每頁顯示10個記錄,但實際上只有9個記錄,那麼分頁控件不應該顯示出來;在數據分成多頁顯示的情況下,第一個頁面的“首頁”、“上一頁”按鈕不應顯示出來,最後一個頁面的“下一頁”、“末頁”按鈕也不應該顯示出來。 ⑶ 不能依賴於特定的數據顯示控件。 ⑷ 具有適應各種現有、將有數據源的能力。 ⑸ 應當能夠方便地配置顯示方式,輕鬆地集成到各種應用之中。 ⑹ 當分頁就緒時,提醒其他控件。 ⑺ 即使是缺乏經驗的Web設計者,也要能夠毫無困難地使用。 ⑻ 提供有關分頁信息的屬性數據。 目前市場上已經有一些提供上述功能的商業性控件,不過都價格不菲。對於許多開發者來說,自己構造一個通用的分頁控件是最理想的選擇。 圖一顯示了本文通用分頁控件的運行界面,其中用於顯示的控件是一個Repeater控件。分頁控件由兩類部件構成:四個導航按鈕,一組頁面編號鏈接。
二、核心功能 public string BindToControl
{ get { if (_bindcontrol == null) throw new NullReferenceException("在使用分頁控件之前,請先通過設置BindToControl屬性綁定到一個控件。"); return _bindcontrol;} set{_bindcontrol=value;} }
protected override void OnInit(EventArgs e)
{ _boundcontrol = Parent.FindControl(BindToControl); BoundControl.DataBinding += new EventHandler(BoundControl_DataBound); base.OnInit(e); ... }
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("分頁控件要求表現控件必需包含一個DataSource。"); object data = _datasource.GetGetMethod().Invoke(sender,null); _builder = Adapters[data.GetType()]; if (_builder == null) throw new NullReferenceException("沒有安裝適當的適配器來處理下面的數據源類型:"+data.GetType()); _builder.Source = data; ApplyDataSensitivityRules(); BindParent(); RaiseEvent(DataUpdate,this); }
在DataBound中,我們嘗試通過Reflection API獲得DataSource屬性,然後返回實際數據源的一個引用。現在雖然已經獲知了數據源,但分頁控件還必須知道如何操作該數據源。爲了讓分頁控件不依賴於特定的表現控件,問題複雜了很多。不過,如果讓分頁控件依賴於特定的數據源,那就背離了設計一個靈活的分頁控件的目標。我們要通過一個接插式的體系結構來確保分頁控件能夠處理各種數據源,無論是.NET提供的數據源,還是自定義的數據源。
publicinterface IDataSourceAdapter
{ int TotalCount{get;} object GetPagedData(int start,int end); }
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; } }
public abstract class AdapterBuilder
{ private object _source; private void CheckForNull() { if (_source == null) throw new NullReferenceException("必須提供一個合法的數據源"); } public virtual object Source { get { CheckForNull(); return _source;} set { _source = value; CheckForNull(); } } public abstract IDataSourceAdapter Adapter{get;} }
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 { // 延遲實例化 if (_adapter == null) { _adapter = new DataViewAdapter((DataView)Source); } return _adapter; } } public override IDataSourceAdapter Adapter { get{return ViewAdapter;} } }
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;} } }
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("分頁控件要求表現控件必需包含一個DataSource。"); object data = _datasource.GetGetMethod().Invoke(sender,null); _builder = Adapters[data.GetType()]; if (_builder == null) throw new NullReferenceException("沒有安裝適當的適配器來處理下面的數據源類型:"+data.GetType()); _builder.Source = data; ApplyDataSensitivityRules(); BindParent(); RaiseEvent(DataUpdate,this); }
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()); }
private void BindParent()
{ _datasource.GetSetMethod().Invoke(BoundControl, new object[]{_builder.Adapter.GetPagedData(StartRow,ResultsToShow*CurrentPage)}); }
三、界面設計
public ImageButton FirstButton{get {return First;}}
public ImageButton LastButton{get {return Last;}} public ImageButton PreviousButton{get {return Previous;}} public ImageButton NextButton{get {return Next;}}
[Template Container(typeof(LayoutContainer))]
public ITemplate Layout { get{return (_layout;} set{_layout =value;} } public class LayoutContainer:Control,INamingContainer { public LayoutContainer() {this.ID = "Page";} }
<LAYOUT>
<asp:ImageButton id="First" Runat="server" imageUrl="play2L_dis.gif" AlternateText="首頁"></asp:ImageButton> <asp:ImageButton id="Previous" Runat="server" imageUrl="play2L.gif" AlternateText="上一頁"></asp:ImageButton> <asp:ImageButton id="Next" Runat="server" imageUrl="play2.gif" AlternateText="下一頁"></asp:ImageButton> <asp:ImageButton id="Last" Runat="server" imageUrl="play2_dis.gif" AlternateText="末頁"></asp:ImageButton> <asp:Panel id="Pager" Runat="server"></asp:Panel> </LAYOUT>
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); }
protected override void OnInit(EventArgs e)
{ _boundcontrol = Parent.FindControl(BindToControl); BoundControl.DataBinding += new EventHandler(BoundControl_DataBound); InstantiateTemplate(); Controls.Add(_container); base.OnInit(e); }
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.ImageUrl="play2.gif"; First.ID="First"; First.AlternateText="首頁";First.ImageUrl="play2L_dis.gif"; Last.ID = "Last"; Last.AlternateText ="末頁";Last.ImageUrl="play2_dis.gif"; Previous.ID="Previous"; Previous.AlternateText="上一頁";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); } }
[TemplateContainer(typeof(LayoutContainer))]
public ITemplate Layout { get{return (_layout == null)? new DefaultPagerLayout():_layout;} set{_layout =value;} }
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;} }
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;} }
雖然ASP.NET定義了一些默認的樣式,不過對於分頁控件的用戶它們可能不是很實用。用戶可以通過自定義樣式來調整分頁控件的外觀。 public Style UnSelectedPagerStyle {get {return UnselectedPager;}}
public Style SelectedPagerStyle {get {return SelectedPager;}} UnSelectedPagerStyle提供了頁面編號未選中時所用的樣式,而SelectedPagerStyle提供了頁面編號被選中時所用的樣式。 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); } GeneratePagers方法動態地創建所有頁面編號,頁面編號是LinkButton類型的按鈕。各個頁面編號的標籤和ID屬性通過循環賦值,同時,點擊事件被綁定到適當的事件句柄。最後,頁面編號被加入到一個容器控件——在本例中是一個Panel對象。按鈕ID起到了標識哪一個按鈕觸發點擊事件的作用。下面是事件句柄的定義: 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(); } 這些事件句柄都相似,它們首先更改分頁控件的當前頁面,然後刷新綁定的控件。 private void Update()
{ if (!HasParentControlCalledDataBinding) return; ApplyDataSensitivityRules(); BindParent(); BoundControl.DataBind(); } 首先,分頁控件通過調用HasParentControlCalledDataBinding方法檢查是否已經初始化了必要的適配器。如果是,則將前面指出的根據數據顯示情況自動調整控件的規則應用到當前的控件,這些規則使得分頁控件根據BoundControl中數據的不同情況表現出不同的行爲。雖然這些規則由分頁控件內部控制,但必要時可以用[GoF] State模式方便地移出到控件之外。 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(); } ApplyDataSensitivityRules方法實施預定義的規則,諸如IsPagerVisible、IsPreviousVisible和IsNextVisible。默認情況下,分頁控件將啓用這些規則,但用戶可以通過設置IsDataSensitive屬性來關閉這些規則。 至此爲止,分頁控件的顯示部分基本設計完畢。最後剩下的結束工作是提供幾個事件句柄,使得用戶能夠在各種分頁控件事件出現時進行必要的調整。 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;}} } 由於分頁控件需要返回自定義的事件參數,所以我們定義了一個專用的PageChangedEventArgs類。PageChangedEventArgs類返回PagedEventInvoker類型,PagedEventInvoker類型是可能觸發事件的控件的枚舉量。爲了處理自定義的事件參數,我們定義了一個新的delegate,即PageDelegate。事件按照下面的形式定義: public event PageDelegate PageChanged;
public event EventHandler DataUpdate; 當事件沒有對應的事件監聽器時,ASP.NET會拋出一個異常。分頁控件定義了下列RaiseEvent方法。 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); } } 現在事件句柄可以通過調用各個RaiseEvent方法來觸發事件了。 四、應用實例 至此爲止,分頁控件的設計已經全部完成,可以正式使用了。要使用該分頁控件,只要把它綁定到一個表現控件即可。 <asp:Repeater ID="repeater" Runat="server">
<ItemTemplate> 列1: <%# Convert.ToString(DataBinder.Eval(Container.DataItem,"Column1"))%> <br> 列2: <%# Convert.ToString(DataBinder.Eval(Container.DataItem,"Column2"))%> <br> 列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> 上面的aspx頁面將分頁控件綁定到一個Repeater控件,設置每頁顯示的記錄數量爲2,選中的頁面編號顏色爲黃色,使用默認的佈局,效果如圖一。下面是另一個例子,它將分頁控件綁定到一個DataGrid,效果如圖二。 <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="首頁"></asp:ImageButton> <asp:ImageButton id="Previous" Runat="server" imageUrl="play2L.gif" AlternateText="上一頁"></asp:ImageButton> <asp:ImageButton id="Next" Runat="server" imageUrl="play2.gif" AlternateText="下一頁"></asp:ImageButton> <asp:ImageButton id="Last" Runat="server" imageUrl="play2_dis.gif" AlternateText="末頁"></asp:ImageButton> <asp:Panel id="Pager" Runat="server"></asp:Panel> </LAYOUT> </cc1:Pager>
|
打造一個通用ASP.NET數據分頁控件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.