通用ASPNET數據分頁控件
對於幾乎所有的數據表現Web應用來說組織好數據的顯示方式避免給用戶帶來混亂的感覺就是最主 要的目標之一每個頁面顯示條記錄當然是可以接受的但每頁顯示條記錄就很容易給用戶帶來不便了將數據分成多個頁面顯示即對數據進行分 頁是解決此類問題的最常見的辦法
一慨述
ASPNET本身只提供了一個支持數據分頁的控件即 DataGrid分頁控件不過它比較適合Intranet環境使用對於Internet環境來說DataGrid分頁控件提供的功能似乎不足以構造 出靈活的Web應用其中一個原因是DataGrid控件對Web設計者放置分頁控件的位置和分頁控件的外觀都有限制例如DataGrid控件不允 許垂直放置分頁控件另一個能夠發揮分頁技術優勢的控件是RepeaterWeb開發者可以利用Repeater控件快速配置數據的顯示方式但分頁功 能卻需要開發者自己實現數據源在不斷地變化數據表現方式也千差萬別如果針對這些不斷變動的條件分別定制分頁控件顯然太浪費時間了構造一個不限於 特定表現控件的通用分頁控件將極大地有利於節省時間
一個優秀的通用數據控件不僅提供常規的分頁功能而且還要能夠
⑴ 提供首頁上一頁下一頁末頁分頁導航按鈕
⑵ 根據數據顯示情況調整自身的狀態即具有數據敏感性如果分頁控件被設置成每頁顯示個記錄但實際上只有個記錄那麼分頁控件不應該顯示出來在數 據分成多頁顯示的情況下第一個頁面的首頁上一頁按鈕不應顯示出來最後一個頁面的下一頁末頁按鈕也不應該顯示出來
⑶ 不能依賴於特定的數據顯示控件
⑷ 具有適應各種現有將有數據源的能力
⑸ 應當能夠方便地配置顯示方式輕松地集成到各種應用之中
⑹ 當分頁就緒時提醒其他控件
⑺ 即使是缺乏經驗的Web設計者也要能夠毫無困難地使用
⑻ 提供有關分頁信息的屬性數據
目前市場上已經有一些提供上述功能的商業性控件不過都價格不菲對於許多開發者來說自己構造一個通用的分頁控件是最理想的選擇
圖一顯示了本文通用分頁控件的運行界面其中用於顯示的控件是一個Repeater控件分頁控件由兩類部件構成四個導航按鈕一組頁面編號鏈接
用戶可以方便地改換顯示控件改變分頁控件本身的外觀例如在圖一中和分頁控件協作的顯示控件可發換成一個DataGrid控件頁面編號鏈接和四個導航按鈕分兩行顯示
ASPNET 支持創建定制Web控件的三種方式用戶控件復合控件自定義控件第三種控件即自定義控件的名稱很容易引起誤解實際上所有這三種控件都應該算是自 定義控件復合控件和微軟所謂的自定義控件的不同之處在於前者要用到CreateChildControls()方法 CreateChildControls()方法允許控件根據某些事件重新繪制自身對於本文的通用分頁器我們將使用復合控件
下面的UML序列圖概括了通用分頁控件的一般機制
雖然我們的目標是讓通用分頁控件不依賴於表現數據的控件但很顯然總得有某種方法讓分頁控件訪問數據每一個從Control類繼承的控件都提供一個 DataBinding事件我們把分頁器本身注冊成DataBinding事件的監聽器分頁器就可以獲知數據的情況並修改數據由於所有從 Control類繼承的控件都有這個DataBinding事件所以分頁器控件達到了不依賴於特定數據表現控件的目標——換句話說分頁器控件可以綁定 到所有從Control類派生的控件即它能夠綁定到幾乎所有的Web控件
二核心功能
當表現控件觸發DataBinding事件分頁控件就可以獲取DataSource屬性遺憾的是微軟沒有提供所 有數據綁定類實現的接口諸如IdataSourceProvider之類而且並非所有從Control或WebControl類繼承的控件都有一個 DataSource屬性因此向上定型成Control類沒有意義唯一可行的辦法是通過Reflection API直接操作DataSoruce屬性在討論事件句柄方法之前應該指出的是為了注冊事件句柄首先必須獲得一個表現控件的引用分頁控件顯露了一 個簡單的字符串屬性BindToControl
public string BindToControl
{
get
{
if (_bindcontrol == null)
throw new NullReferenceException(
在使用分頁控件之前
請先通過設置BindToControl屬性綁定到一個控件
);
return _bindcontrol;}
set
{_bindcontrol=value;}
}
這個方法非常重要所以最好能夠拋出一個含義更明確的信息而不是拋出標准的NullReferenceException異常在分頁控件的 OnInit方法中我們解析了對表現控件的引用本例應當用OnInit事件句柄(而不是構造函數)來確保JIT編譯的aspx頁面已經設置了 BindToControl
protected override void OnInit(EventArgs e)
{
_boundcontrol = Parent
FindControl(BindToControl);
BoundControl
DataBinding += new EventHandler(BoundControl_DataBound);
base
OnInit(E);
}
搜索表現控件的操作通過搜索分頁控件的Parent控件完成在這裡Parent就是頁面本身按照這種方式使用Parent比較危險舉例來說如 果分頁控件嵌入到了另一個控件之中例如嵌入到了Table控件之中則Parent引用實際上將是一個對Table控件的引用由於 FindControl方法只搜索當前的控件集合除非表現控件就在該集合之中否則不可能搜索到一種比較安全的方法是遞歸地搜索各個控件集合直至找 到目標控件為止
找到BoundControl之後我們將分頁控件注冊成為DataBinding事件的監聽器由於分頁控件要操作數 據源所以該事件句柄應當是調用鏈中的最後一個這一點很重要不過只要表現控件在OnInit事件句柄中注冊DataBinding的事件句柄(默認 行為)分頁控件操作數據源時就不會出現問題
DataBound事件句柄負責獲取表現控件的DataSource屬性
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提供的數據源還是自定義的數據源
為了提供一個健壯的可伸縮的接插式體系結構我們將利用[GoF] Builder模式構造出一個解決方案
IDataSourceAdapter接口定義了分頁控件操作數據所需的最基本的元素相當於插頭
publicinterface IDataSourceAdapter
{
int TotalCount
{get;}
object GetPagedData(int start
int end);
}
TotalCount屬性返回在處理數據之前數據源所包含元素的總數而GetPagedData方法返回原始數據的一個子集例如假設數據源是一個 包含個元素的數組分頁控件將數據顯示成每頁個元素則第一頁的元素子集是數組元素第二頁的元素子集是數組元素 DataViewAdapter提供了一個DataView類型的插頭
internal class DataViewAdapter:IDataSourceAdapter
{
private DataView _view;
internal DataViewAdapter(DataView view)
{
_view = view;
}
public int TotalCount
{
get
{return (_view == null) ?
: _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
]
Row);
}
return table;
}
}
DataViewAdapter實現了IDataSourceAdapter的GetPagedData方法該GetPagedData克隆原始的 DataTable將原始DataTable中的數據導入到新的DataTable該類的可見性有意地設置成internal目的是為了向Web開 發者隱藏實現細節進而通過Builder類提供一個更簡單的接口
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;}
}
AdapterBuilder抽象類為IdataSourceAdapter類型提供了一個更容易管理的接口由於提高了抽象程度我們不必再直接使用 IdataSourceAdapter同時AdapterBuilder還提供了在分頁數據之前執行預處理的指令另外該Builder還使得實際的 實現類例如DataViewAdapter對分頁控件的用戶透明
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;}
}
}
From:http://tw.wingwit.com/Article/program/ASP/201311/21745.html