| 導購 | 订阅 | 在线投稿
分享
 
 
 

創建用于 ASP.NET 的分頁程序控件[MSDN]

2008-06-01 02:09:00  編輯來源:互聯網  简体版  手機版  評論  字體: ||
 
  摘要:解決向任何 asp.net 控件添加分頁功能的問題。還爲開發複合 ASP.NET 控件提供了很多有用的提示和技巧。

  下載本文的源代碼(英文)。(請注重,在示例文件中,程序員的注釋使用的是英文,本文中將其譯爲中文是爲了便于讀者理解。)

  從程序員的角度來看,Microsoft® SQL Server™ 查詢的最大缺陷之一就是返回的行數通常比應用程序的用戶界面實際可以容納的行數要多得多。這種尴尬情形經常將開發人員陷于困境。開發人員是應該創建一個非常長的頁面,讓用戶花時間去滾動浏覽,還是應該通過設置一個手動分頁機制來更好地解決這個問題?

  哪種解決方案更好,在很大程度上取決于要檢索的數據的特性。由多個項目(如搜索結果)組成的較長列表,最好通過各頁大小相等、每頁相對較短的多個頁面顯示。由單個項目(如文章的文本)組成的較長列表,假如整個插入在一個頁面中,使用起來會更方便。最後得出的分析結果是,應該根據應用程序的總體用途來做決定。那麽,Microsoft® ASP.NET 是如何解決數據分頁問題的呢?

  ASP.NET 提供了功能強大的數據綁定控件,以便將查詢結果格式化爲 Html 標記。但是,這些數據綁定控件中只有一種控件(即 DataGrid 控件)本來就支持分頁。其他控件(如 DataList、Repeater 或 CheckBoxList)則不支持分頁。這些控件及其他列表控件不支持分頁,不是因爲它們在結構上不支持分頁,而是因爲它們與 DataGrid 不同,不包含任何處理分頁的特定代碼。但是,處理分頁的代碼是相當樣板化的,可以添加到所有這些控件中。

  Scott Mitchell 在最近的一篇題目爲「Creating a Pageable, Sortable DataGrid」(英文)的文章中,介紹了 DataGrid 分頁。該文還引用了 Web 上的其他有用信息,爲您提供了有關網格分頁基礎知識和其他信息。假如想查看如何使 DataList 控件可以進行分頁的示例,可以查看此文章(英文)。該文演示了如何創建一個自定義的 DataList 控件,該控件具有當前索引和頁面大小屬性,並可以啓動頁面更改事件。

  同樣的代碼也可以用于滿足其他列表控件(如 ListBox 和 CheckBoxList)的分頁需要。不過,向各個控件添加分頁功能實際上並不是一種非常好的做法,因爲,如上所述,分頁代碼是相當樣板化的。因此,對于聰明的程序員來說,有什麽方法比使用一種新的通用分頁程序控件來實現所有這些控件的分頁功能更好的呢?

  本文中將建立一個分頁程序控件,它將使合作者列表控件能夠對 SQL Server 的查詢結果進行分頁。該控件名爲 SqlPager,它支持兩種類型的合作者控件 - 列表控件和基礎數據列表控件。

  SqlPager 控件的顯著特點

  SqlPager 控件是一個 ASP.NET 複合控件,包含一個單行表格。該行又包含兩個單元格 - 導航條和頁面描述符。該控件的用戶界面呈條形,理想情況下,其寬度與合作者控件的寬度相同。導航條部分提供了可單擊的元素,以便在頁面之間移動;頁面描述符部分爲用戶提供了有關當前顯示的頁面的一些反饋信息。

創建用于 ASP.NET 的分頁程序控件[MSDN]


  圖 1:Visual Studio .NET 網頁設計器中顯示的 SqlPager 控件

  與 DataGrid 控件的嵌入式分頁程序一樣,SqlPager 控件具有兩種導航模式,即下一頁/上一頁和數字頁面。此外,其非凡屬性 PagerStyle 使您能夠選擇更方便的樣式。該控件與列表控件協同工作。您可以通過 ControlToPaginate 字符串屬性爲分頁程序指定一個這樣的合作者控件。 SqlPager1.ControlToPaginate = "ListBox1";

  

  一般情況下,分頁程序首先獲取 SQL Server 的查詢結果,預備一個適當的記錄頁面,然後通過合作者控件的 DataSource 屬性顯示該頁面。當用戶單擊以查看新頁面時,分頁程序將檢索請求的數據並再次通過合作者控件來顯示數據。分頁機制對于列表控件是完全透明的。列表控件的數據源是通過編程方式進行更新的,任何時候都只包含適合當前頁面的記錄。

  控件的分頁引擎具有多個 public 屬性,如 CurrentPageIndex、ItemsPerPage 和 PageCount,通過這些屬性來獲取並設置當前頁面的索引、每個頁面的大小以及要顯示的頁面的總數。分頁程序治理數據檢索和分頁所需的任何邏輯。

  SelectCommand 屬性設置獲取數據所用的命令文本。ConnectionString 屬性定義數據庫的名稱和位置以及連接憑據。執行查詢時采用的方式取決于 PagingMode 屬性的值。該屬性的可能值爲與其同名的 PagingMode 枚舉的值 - Cached 和 NonCached。假如選擇 Cached 選項,則將使用數據適配器和 DataTable 對象檢索整個結果集。可以選擇將結果集放置在 ASP.NET 的 Cache 對象中,該結果集可以重複使用直到過期。假如選擇 NonCached 選項,則查詢只檢索適合當前頁面的記錄。這時,ASP.NET 的 Cache 中不放置任何數據。NonCached 模式與 DataGrid 控件的自定義分頁模式幾乎相同。

  

   下表列出 SqlPager 控件的全部編程接口。

  表 1:SqlPager 控件的編程接口名稱類型說明CacheDuration屬性指示數據在 ASP.NET 的緩存中保留的秒數。只用于 Cached 模式。默認值爲 60 秒。ConnectionString屬性用來訪問所選擇的 SQL Server 數據庫的連接字符串。ControlToPaginate屬性同一個 .aspx 頁面中的控件 ID,它將顯示分頁程序檢索的記錄頁面。這是合作者控件。CurrentPageIndex屬性獲取並設置基于 0 的頁面索引。 ItemsPerPage屬性獲取並設置每頁要顯示的記錄數量。默認值爲每頁顯示 10 個項目。PagerStyle屬性該值指示分頁程序用戶界面的樣式。它可以爲 PagerStyle 枚舉值:NextPRev 和 NumericPages 之一。在 NextPrev 模式中,將顯示 VCR 式的按鈕,來轉到第一頁、上一頁、下一頁和最後一頁。而在 NumericPages 模式中,將顯示一個下拉列表,列出所有可用頁面的索引。PagingMode屬性該值指示檢索數據的方式。它可以爲 PagingMode 枚舉值:Cached 和 NonCached 之一。假如爲 Cached,則將使用數據適配器,且整個結果集將臨時放置在 ASP.NET 緩存中。假如爲 NonCached,則只檢索當前頁面中的記錄。在這種情況下,不進行緩存。SelectCommand屬性用來進行查詢的命令文本。最好爲 SELECT-FROM-WHERE 形式。不支持 ORDER BY 子句。排序是通過 SortField 屬性另外指定的。SortField屬性用來排序的字段的名稱。此字段用于爲查詢提供動態的 ORDER BY 子句。排序是由 SQL Server 執行的。 ClearCache方法刪除存儲在 ASP.NET 緩存中的任何數據。PageIndexChanged事件默認事件,當分頁程序移動到另一個頁面時發生。事件的數據結構爲 PageChangedEventArgs 類,包含舊頁面和新頁面的索引。

  由于 SqlPager 控件繼續了 WebControl,因此它也具有很多與 UI 相關的屬性來治理字體、邊框和顔色。

  生成 SqlPager 控件

  將作爲複合控件來生成 SqlPager 控件並讓其繼續 WebControl 類。複合控件是 ASP.NET 服務器控件所特有的,它是由一個或多個服務器控件組成。 public class SqlPager : WebControl, INamingContainer

  { ... }

  

  除非生成完全自定義的控件或擴展現有的控件,否則,創建新控件時,大多數時間實際上是在生成複合控件。要創建 SqlPager,組合一個 Table 控件,並根據分頁程序的樣式,組合幾個 LinkButton 控件或者一個 DropDownList 控件。

  生成複合控件時,需要注重幾條原則。首先,需要覆蓋 CreateChildControls protected 方法。CreateChildControls 方法是從 Control 繼續來的,當服務器控件爲了顯示而要創建子控件時或在返回後,將調用此方法。 protected override void CreateChildControls()

  {

   // 清除現有的子控件及其 ViewState

   Controls.Clear();

   ClearChildViewState();

   // 生成控件樹

   BuildControlHierarchy();

  }

  

  覆蓋此方法時,需要執行幾項重要的操作。創建並初始化任何所需的子控件實例並將它們添加到父控件的 Controls 集合中。但是,生成新控件樹之前,應該刪除任何現有的子控件並清除子控件可能留下的任何 ViewState 信息。

  複合組件還需要實現 INamingContainer 接口,以便 ASP.NET 運行時可以爲其創建一個新的命名範圍。這就確保了複合控件中的所有控件都具有唯一的名稱。這還將確保能夠自動處理子控件的返回數據。

  對于 SqlPager 控件來說,成爲命名容器非常重要。事實上,SqlPager 包含一些 LinkButton 控件,並且需要獲取並處理其單擊事件以便導航頁面。正如 ASP.NET 頁面中的任何其他控件一樣,LinkButton 也被賦予了一個 ID,用于標識處理返回事件的控件。

  處理返回事件時,ASP.NET 運行時試圖查找事件的目標 ID 與主窗體的任何直接子控件之間是否存在匹配關系。LinkButton 是分頁程序的子控件,因此不能運行其服務器端的代碼。這是否意味著只有窗體的直接子控件才能啓動並處理服務器事件?當然不是,只要您使用命名容器。

  通過使 SqlPager 控件實現 INamingContainer 接口,可以將嵌入式鏈接按鈕的實際 ID 從「First」更改爲「SqlPager1:First」。當用戶單擊以查看新頁面時,返回事件將 SqlPager1:First 作爲目標控件。實際上,運行時用來識別目標控件的算法比上面介紹的要複雜一些。運行時將事件目標控件的名稱看作是用冒號分隔開的字符串。實際上,這種匹配是在窗體的子控件和用冒號分隔開的字符串(如 SqlPager1:First)的第一個標記之間進行的。由于分頁程序是窗體的子控件,因此匹配時會成功,分頁程序獲取單擊事件。假如您認爲這種解釋不夠充分或者令人費解,只要下載 SqlPager 控件的源代碼,刪除 INamingContainer 標記接口並進行重新編譯即可。您將看到分頁程序能夠返回,但不能內部處理單擊事件。

  

   INamingContainer 接口是一個不具備方法的標記接口,其實現只需在類聲明中指定名稱即可,無需進行任何其他操作。

  複合控件的另一個重要方面是,它們通常不需要自定義邏輯來進行顯示。複合控件的顯示遵循其組成控件的顯示。生成複合控件時,通常無需覆蓋 Render 方法。

  控件的 SqlPager 樹由一個包含兩個單元格的單行表格組成。該表格繼續了分頁程序的大部分可視設置,如前景顔色和背景顔色、邊框、字體信息和寬度等。第一個單元格包含導航條,其結構取決于 PagerStyle 屬性的值。假如分頁程序的樣式爲 NextPrev,則導航條將由四個 VCR 式的按鈕組成。否則,它將由一個下拉列表組成。private void BuildControlHierarchy()

  {

   // 生成環境表格(一行,兩個單元格)

   Table t = new Table();

   t.Font.Name = this.Font.Name;

   t.Font.Size = this.Font.Size;

   t.BorderStyle = this.BorderStyle;

   t.BorderWidth = this.BorderWidth;

   t.BorderColor = this.BorderColor;

   t.Width = this.Width;

   t.Height = this.Height;

   t.BackColor = this.BackColor;

   t.ForeColor = this.ForeColor;

   // 生成表格中的行

   TableRow row = new TableRow();

   t.Rows.Add(row);

   // 生成帶有導航條的單元格

   TableCell cellNavBar = new TableCell();

   if (PagerStyle == this.PagerStyle.NextPrev)

   BuildNextPrevUI(cellNavBar);

   else

   BuildNumericPagesUI(cellNavBar);

   row.Cells.Add(cellNavBar);

   // 生成帶有頁面索引的單元格

   TableCell cellPageDesc = new TableCell();

   cellPageDesc.HorizontalAlign = HorizontalAlign.Right;

   BuildCurrentPage(cellPageDesc);

   row.Cells.Add(cellPageDesc);

   // 將表格添加到控件樹

   this.Controls.Add(t);

  }

  

  將各個控件添加到正確的 Controls 集合對于分頁程序的正確顯示極其重要。最外層的表格必須添加到分頁程序的 Controls 集合中。鏈接按鈕和下拉列表必須添加到相應表格單元格的 Controls 集合中。

  下面給出了用來生成鏈接按鈕導航條的代碼。每個按鈕都顯示有一個 Webdings 字符,可以根據需要禁用,並被綁定到內部的 Click 事件處理程序。 private void BuildNextPrevUI(TableCell cell)

  {

   bool isValidPage = ((CurrentPageIndex >=0) &&

   (CurrentPageIndex <= TotalPages-1));

   bool canMoveBack = (CurrentPageIndex>0);

   bool canMoveForward = (CurrentPageIndex<TotalPages-1);

   // 顯示 << 按鈕

   LinkButton first = new LinkButton();

   first.ID = "First";

   first.Click += new EventHandler(first_Click);

   first.Font.Name = "webdings";

   first.Font.Size = FontUnit.Medium;

   first.ForeColor = ForeColor;

   first.ToolTip = "第一頁";

   first.Text = "7";

   first.Enabled = isValidPage && canMoveBack;

   cell.Controls.Add(first);

   :

  }

  

  分頁程序的另一種樣式(在下拉列表中列出數字頁面)的生成方法如下所示:private void BuildNumericPagesUI(TableCell cell)

  {

   // 顯示一個下拉列表

   DropDownList pageList = new DropDownList();

   pageList.ID = "PageList";

   pageList.AutoPostBack = true;

   pageList.SelectedIndexChanged += new EventHandler(PageList_Click);

   pageList.Font.Name = this.Font.Name;

   pageList.Font.Size = Font.Size;

   pageList.ForeColor = ForeColor;

  

   if (TotalPages <=0 CurrentPageIndex == -1)

   {

   pageList.Items.Add("No pages");

   pageList.Enabled = false;

   pageList.SelectedIndex = 0;

   }

   else // 填充列表

   {

   for(int i=1; i<=TotalPages; i++)

   {

   ListItem item = new ListItem(i.ToString(), (i-1).ToString());

   pageList.Items.Add(item);

   }

   pageList.SelectedIndex = CurrentPageIndex;

   }

  }

  

  

   所有事件處理程序(Click 和 SelectedIndexChanged)最終都會更改當前顯示的頁面。這兩種方法都會調用一個公用的 private 方法 GoToPage。private void first_Click(object sender, EventArgs e)

  {

   GoToPage(0);

  }

  private void PageList_Click(object sender, EventArgs e)

  {

   DropDownList pageList = (DropDownList) sender;

   int pageIndex = Convert.ToInt32(pageList.SelectedItem.Value);

   GoToPage(pageIndex);

  }

  private void GoToPage(int pageIndex)

  {

   // 預備事件數據

   PageChangedEventArgs e = new PageChangedEventArgs();

   e.OldPageIndex = CurrentPageIndex;

   e.NewPageIndex = pageIndex;

   // 更新當前的索引

   CurrentPageIndex = pageIndex;

   // 啓動頁面更改事件

   OnPageIndexChanged(e);

   // 綁定新數據

   DataBind();

  }

  

  其他導航按鈕的處理程序與 first_Click 的區別僅在于它們傳遞給 GoToPage 方法的頁碼不同。GoToPage 方法負責處理 PageIndexChanged 事件,並負責啓動數據綁定過程。它預備事件數據(舊頁面和新頁面索引)並觸發事件。GoToPage 被定義爲 private,但是可以使用 CurrentPageIndex 屬性通過編程的方式更改顯示的頁面。 public int CurrentPageIndex

  {

   get {return Convert.ToInt32(ViewState["CurrentPageIndex"]);}

   set {ViewState["CurrentPageIndex"] = value;}

  }

  

  與表 1 中所列的所有屬性一樣,CurrentPageIndex 屬性的實現方法也相當簡單。它將其內容保存到 ViewState 中並從中進行還原。在數據綁定過程中,會驗證和使用頁面索引。

  數據綁定過程

  DataBind 方法是所有 ASP.NET 控件公用的,對于數據綁定控件來說,它將觸發用戶界面的刷新以反映新數據。SqlPager 控件根據 SelectCommand 和 ConnectionString 屬性的值,使用此方法啓動數據檢索操作。不言而喻,假如這些屬性中的任何一個爲空,該過程將終止。同樣,假如合作者控件不存在,數據綁定過程將被取消。要查找合作者控件,DataBind 方法使用 Page 類中的 FindControl 函數。由此可見,合作者控件必須爲主窗體的直接子控件。

  進行分頁顯示的控件不能爲任意的 ASP.NET 服務器控件。它必須爲列表控件或基本數據列表控件。更一般來說,合作者控件必須具有 DataSource 屬性並實現 DataBind 方法。可能進行分頁的控件實際上只需要滿足這些要求。Microsoft® .NET Framework 中所有繼續 ListControl 或 BaseDataList 的控件都滿足第一個要求;而所有 Web 控件通過設計都滿足 DataBind 要求。使用當前的實現方法,無法使用 SqlPager 控件來對 Repeater 進行分頁。Repeater 與合作者控件 DataList 和 DataGrid 不同,不繼續 BaseDataList,也不提供列表控件的功能。下表列出了可以使用 SqlPager 進行分頁的控件。

  表 2:可以由 SqlPager 控件進行分頁的數據綁定控件 控件說明CheckBoxList從 ListControl 派生而來,顯示爲複選框列表。 DropDownList從 ListControl 派生而來,顯示爲字符串下拉列表。ListBox從 ListControl 派生而來,顯示爲字符串可滾動列表。RadioButtonList從 ListControl 派生而來,顯示爲單選按鈕列表。DataList從 BaseDataList 派生而來,顯示爲模板化數據項目列表。DataGrid從 BaseDataList 派生而來,顯示爲數據項目的表格網格。DataGrid 是唯一一個內置有功能強大的分頁引擎的 ASP.NET 控件。

  以下代碼說明由 SqlPager 控件實現的數據綁定過程。 public override void DataBind()

  {

   // 啓動數據綁定事件

   base.DataBinding();

   // 數據綁定後必須重新創建控件

   ChildControlsCreated = false;

   // 確保控件存在且爲列表控件

   _controlToPaginate = Page.FindControl(ControlToPaginate);

   if (_controlToPaginate == null)

   return;

   if (!(_controlToPaginate is BaseDataList

   _controlToPaginate is ListControl))

   return;

   // 確保具有足夠的連接信息並指定查詢

   if (ConnectionString == "" SelectCommand == "")

   return;

   // 獲取數據

   if (PagingMode == PagingMode.Cached)

   FetchAllData();

   else

   FetchPageData();

   // 將數據綁定到合作者控件

   BaseDataList baseDataListControl = null;

   ListControl listControl = null;

   if (_controlToPaginate is BaseDataList)

   {

   baseDataListControl = (BaseDataList) _controlToPaginate;

   baseDataListControl.DataSource = _dataSource;

   baseDataListControl.DataBind();

   return;

   }

   if (_controlToPaginate is ListControl)

   {

   listControl = (ListControl) _controlToPaginate;

   listControl.Items.Clear();

   listControl.DataSource = _dataSource;

   listControl.DataBind();

   return;

   }

  }

  

  

   根據 PagingMode 屬性的值調用不同的獲取例程。在任何情況下,結果集都綁定到 PagedDataSource 類的實例上。此類提供了一些用來對數據進行分頁的功能。非凡是,當整個數據集都存儲在緩存中時,該類將自動檢索當前頁面的記錄並返回布爾值,來提供有關第一頁和最後一頁的信息。稍後將回來介紹此類的內部結構。在上述列表中,幫助程序的 PagedDataSource 對象是由 _dataSource 變量表示的。

  然後,SqlPager 控件經過計算得出合作者控件的類型,並將 PagedDataSource 對象的內容綁定到合作者控件的 DataSource 屬性。

  有時,上述的 DataBind 方法還將 ChildControlsCreated 屬性重新設置爲 false。那麽,爲什麽要這樣做呢?

  當包含分頁程序的頁面返回時,所有控件都要重新創建;分頁程序也不例外。通常情況下,所有控件及其子控件都是在預備顯示頁面之前創建的。在每個控件接收到 OnPreRender 通知之前的一瞬間,protected EnsureChildControls 方法將被調用,這樣,每個控件都可以生成自己的控件樹。此事件發生後,數據綁定過程完成,新數據已存儲到緩存中。

  但是,當由于單擊分頁程序的一個組成控件而使頁面返回時(即用戶單擊以更改頁面),就會生成分頁程序的控件樹,這時遠未達到顯示階段。非凡是,當處理相關的服務器端事件時,就必須生成控件樹,因而是在數據綁定開始之前生成控件樹。困難在于,數據綁定將修改頁面索引,而這必須反映在用戶界面中。假如不采取某些對策的話,當用戶切換到另一頁時,分頁程序中的頁面索引將不會被刷新。

  有各種方法可以解決此問題,但重要的是要弄清楚問題及其真正的原因。您可以避免生成控件樹,並在 Render 方法中生成所有輸出。另外,您還可以修改樹中受數據綁定更改影響的部分。本文選擇了第三種方法,這種方法需要較少的代碼,而且,不管控件的用戶界面的哪個部分受到數據綁定更改的影響,都能夠解決問題。通過將 ChildControlsCreated 屬性設置爲 false,可以使以前創建的任何控件樹無效。這樣,在顯示之前將重新創建控件樹。

  分頁引擎

  SqlPager 控件支持兩種檢索數據的方法 - 緩存和非緩存。假如采用前者,選擇命令原樣執行,整個結果集將綁定到在內部進行分頁的數據源對象上。PagedDataSource 對象將自動返回適合特定頁面的記錄。PagedDataSource 類也是在 DataGrid 默認分頁機制背後運行的系統組件。

  檢索所有記錄但只顯示適合頁面的幾個記錄,這通常不是一種明智的方法。由于 Web 應用程序的無狀態特性,事實上,每次請求頁面時都可能運行大量的查詢。要使操作有效,采用緩存的數據檢索方法必須依靠于某種緩存對象,ASP.NET 的 Cache 對象就是一個很好的候選對象。緩存技術的使用加快了應用程序的運行速度,但其提供的數據快照不能反映最新的更改。另外,它需要使用 Web 服務器上的較多內存。而且荒謬的是,假如大量的數據按會話緩存的話,甚至可能造成很大的問題。Cache 容器對于應用程序來說是全局的;假如數據按會話存儲在其中,還需生成會話特有的項目名稱。

  Cache 對象的上面是完全支持過期策略的。換句話說,存儲在緩存中的數據在一段時間後可以自動被釋放。以下代碼說明 SqlPager 類中用來獲取數據並將其存儲在緩存中的一個 private 方法。private void FetchAllData()

  {

   // 在 ASP.NET Cache 中查找數據

   DataTable data;

   data = (DataTable) Page.Cache[CacheKeyName];

   if (data == null)

   {

   // 使用 order-by 信息修改 SelectCommand

   AdjustSelectCommand(true);

   // 假如數據過期或從未被獲取,則轉到數據庫

   SqlDataAdapter adapter = new SqlDataAdapter(SelectCommand,

   ConnectionString);

   data = new DataTable();

   adapter.Fill(data);

   Page.Cache.Insert(CacheKeyName, data, null,

   DateTime.Now.AddSeconds(CacheDuration),

   System.Web.Caching.Cache.NoSlidingEXPiration);

   }

  

   // 配置分頁的數據源組件

   if (_dataSource == null)

   _dataSource = new PagedDataSource();

   _dataSource.DataSource = data.DefaultView;

   _dataSource.AllowPaging = true;

   _dataSource.PageSize = ItemsPerPage;

   TotalPages = _dataSource.PageCount;

   // 確保頁面索引有效

   ValidatePageIndex();

   if (CurrentPageIndex == -1)

   {

   _dataSource = null;

   return;

   }

   // 選擇要查看的頁面

   _dataSource.CurrentPageIndex = CurrentPageIndex;

  }

  

  控件和請求的緩存項目名稱是唯一的。它包括頁面的 URL 和控件的 ID。在指定的時間(以秒計算)內,數據被綁定到緩存。要使項目過期,必須使用 Cache.Insert 方法。以下較簡單的代碼將項目添加到緩存,但不包括任何過期策略。Page.Cache[CacheKeyName] = data;

  

  

   PagedDataSource 對象通過其 DataSource 屬性獲取要進行分頁的數據。值得注重的是,PagedDataSource 類的 DataSource 屬性只接受 IEnumerable 對象。DataTable 不滿足此要求,這就是爲什麽采取 DefaultView 屬性的原因。

  SelectCommand 屬性確定在 SQL Server 數據庫上運行的查詢。此字符串最好爲 SELECT-FROM-WHERE 形式。不支持 ORDER BY 子句,假如指定了該子句,將被刪除。這正是 AdjustSelectCommand 方法所做的。使用 SortField 屬性可以指定任何排序信息。AdjustSelectCommand 方法本身將根據 SortField 的值添加一個正確的 ORDER BY 子句。這樣做有什麽原因嗎?

  當分頁程序以 NonCached 模式工作時,原始的查詢將被修改以確保只檢索當前頁面的記錄。在 SQL Server 上執行的實際查詢文本將采取以下形式。SELECT * FROM

  (SELECT TOP ItemsPerPage * FROM

  (SELECT TOP ItemsPerPage*CurrentPageIndex * FROM

  (SelectCommand) AS t0

  ORDER BY SortField ASC) AS t1

  ORDER BY SortField DESC) AS t2

  ORDER BY SortField

  

  該查詢彌補了 SQL Server 2000 中 ROWNUM 子句的缺陷,並且對記錄進行重新排序,使得只有 x 項目的「第 n 個」塊經過正確排序後返回。您可以指定基礎查詢,分頁程序將它分解爲多個較小的頁面。只有適合某個頁面的記錄被返回。正如您看到的那樣,除了查詢命令以外,上述查詢需要處理排序字段。這就是爲什麽另外添加了 SortField 屬性。此代碼的唯一缺陷是默認情況爲升序排序。通過使 ASC/DESC 要害字參數化,可以使此代碼真的非常完美:private void FetchPageData()

  {

   // 需要經過驗證的頁面索引來獲取數據。

   // 還需要實際的頁數來驗證頁面索引。

   AdjustSelectCommand(false);

   VirtualRecordCount countInfo = CalculateVirtualRecordCount();

   TotalPages = countInfo.PageCount;

   // 驗證頁碼(確保 CurrentPageIndex 有效或爲「-1」)

   ValidatePageIndex();

   if (CurrentPageIndex == -1)

   return;

   // 預備並運行命令

   SqlCommand cmd = PrepareCommand(countInfo);

   if (cmd == null)

   return;

   SqlDataAdapter adapter = new SqlDataAdapter(cmd);

   DataTable data = new DataTable();

   adapter.Fill(data);

   // 配置分頁的數據源組件

   if (_dataSource == null)

   _dataSource = new PagedDataSource();

   _dataSource.AllowCustomPaging = true;

   _dataSource.AllowPaging = true;

   _dataSource.CurrentPageIndex = 0;

   _dataSource.PageSize = ItemsPerPage;

   _dataSource.VirtualCount = countInfo.RecordCount;

   _dataSource.DataSource = data.DefaultView;

  }

  

  在 NonCached 模式中,PagedDataSource 對象並不提供整個數據源,因此不能計算出要進行分頁的總頁數。進而必須對 AllowCustomPaging 屬性進行標記,並提供數據源中的實際記錄數量。通常,實際數量是使用 SELECT COUNT(*) 查詢進行檢索的。此模型與 DataGrid 的自定義分頁幾乎相同。此外,PagedDataSource 對象中選擇的當前頁面索引通常爲 0,因爲實際上已經存儲了一頁記錄。

  SqlPager 控件的實現方法就介紹到這裏,下面我們介紹一下它的使用方法。

  使用 SqlPager 控件

  假設存在一個包含 ListBox 控件的示例頁面。要使用分頁程序,請確保 .aspx 頁面正確地注冊了該控件的程序集。 <%@ Register TagPrefix="expo" Namespace="DevCenter" Assembly="SqlPager" %>

  

  控件的標記取決于實際設置的屬性。以下標記是一個合理的示例:<asp:listbox runat="server" id="ListBox1"

   Width="300px" Height="168px"

   DataTextField="companyname" />

  <br>

  <expo:SqlPager runat="server" id="SqlPager1" Width="300px"

   ControlToPaginate="ListBox1"

   SelectCommand="SELECT customerid, companyname FROM customers"

   ConnectionString="SERVER=localhost;DATABASE=northwind;UID=..."

   SortKeyField="companyname" />

  <br>

  <asp:button runat="server" id="LoadFirst1" Text="加載第一頁" />

  

  

   除了分頁程序以外,頁面還包含一個列表框和一個按鈕。列表框將顯示每個頁面的內容;按鈕只用于首次填充列表框。該按鈕具有一個單擊事件處理程序,定義如下。private void LoadFirst1_Click(object sender, EventArgs e) {

   SqlPager1.CurrentPageIndex = 0;

   SqlPager1.DataBind();

  }

  

  圖 2 顯示操作中的頁面。

創建用于 ASP.NET 的分頁程序控件[MSDN]


  圖 2:與 ListBox 控件協同工作的 SqlPager 控件。

  使用 DataList 控件可以生成一個更有意思的示例。目標是使用分頁程序浏覽每個 Northwind 職員的個人記錄。該 DataList 如以下列表所示。<asp:datalist runat="server" id="DataList1" Width="300px"

   Font-Names="Verdana" Font-Size="8pt">

  <ItemTemplate>

  <table bgcolor="#f0f0f0" style="font-family:verdana;font-size:8pt;">

   <tr><td valign="top">

   <b><%# DataBinder.Eval(Container.DataItem, "LastName") + ", " +

   DataBinder.Eval(Container.DataItem, "firstname") %></b></td></tr>

  

   <tr><td>

   <span style="color:blue;"><i>

   <%# DataBinder.Eval(Container.DataItem, "Title")%></i></span>

   <p><img style="float:right;" src='image.aspx?

   id=<%# DataBinder.Eval(Container.DataItem, "employeeid")%>' />

   <%# DataBinder.Eval(Container.DataItem, "Notes") %></td></tr>

  </table>

  </ItemTemplate>

  </asp:datalist>

  

  表格的第一行顯示職員的姓名和職務,然後是相片,相片四周是注釋。相片是使用特定的 .aspx 頁面檢索的,返回從數據庫中獲取的 JPEG 數據。

  分頁程序可以放置在頁面中的任何位置。本例中將它放置在合作者 DataList 控件上方並緊挨著合作者控件。

創建用于 ASP.NET 的分頁程序控件[MSDN]


  圖 3:SqlPager 對 DataList 控件進行分頁

  將 SqlPager 控件與 DataGrid 控件一起使用有意義嗎?這要視情況而定。DataGrid 已經與一個基于本文中使用的 PagedDataSource 對象的嵌入式分頁引擎一起工作。因此,假如您需要對以表格格式顯示的單個記錄集合進行分頁時,就無需使用 SqlPager。但是,對于重要/複雜的方案,將這兩個控件一起使用並不是一種牽強的想法。例如,假如您要向前一個屏幕快照添加一個 DataGrid 來顯示由職員治理的訂單,則可以在同一格頁面上放置兩個相關的分頁引擎,一個要對職員進行分頁,另一個用于滾動相關訂單。

  小結

  不管您要生成哪種類型的應用程序(Web 應用程序、Microsoft® Windows® 應用程序或 Web 服務),您都很難下載和緩存要顯示的整個數據源。有時,測試環境會使您相信這種解決方案真是太棒了,非常可取。但是測試環境也會誤導。數據源的大小非常要害,應用程序的規模越大,數據源的大小越要害。

  在 ASP.NET 中,只有 DataGrid 控件具有內置的分頁功能。但是,分頁引擎是由相當樣板化的代碼實現的,只要進行少量的處理,就可以進行推廣並用于多個不同的控件。本文介紹的 SqlPager 控件就實現了此目標。它關注下載數據,將數據分成多個頁面並通過合作者控件顯示。該控件可以檢索並緩存整個數據集,或者僅在 SQL Server 中查詢要在選定的頁面中顯示的幾個記錄。我說的是 SQL Server,這是另一個重點。SqlPager 只能用于 SQL Server,不能用于通過 OLE DB 或 ODBC 來檢索數據。也不能使用它訪問 Oracle 或 DB2 存檔。

  要生成真正的通用 SQL 分頁程序組件,應該形成數據訪問層,並生成一種能夠使用相應的數據提供程序創建連接、命令和適配器的工廠類。此外,要注重爲各種 SQL 源設置一個分頁引擎是最糟糕的做法。這裏介紹的方法只適用于 SQL Server 7.0 和更高版本。TOP 子句在不同的環境下具有不同的特性。使用服務器遊標和臨時表格,它可適用于較大範圍的 DBMS 系統。但那將使代碼更爲複雜。

  作者簡介

  Dino Esposito 是一位來自意大利羅馬的培訓教師和顧問。他是 Wintellect(英文)小組的成員,專門研究 ASP.NET 和 ADO.NET,主要在歐洲和美國從事教學和咨詢工作。此外,Dino 還負責治理 Wintellect 的 ADO.NET 課件,並爲 MSDN Magazine(英文)的「Cutting Edge」專欄撰寫文章。
 
摘要:解決向任何 asp.net 控件添加分頁功能的問題。還爲開發複合 ASP.NET 控件提供了很多有用的提示和技巧。 下載本文的源代碼(英文)。(請注重,在示例文件中,程序員的注釋使用的是英文,本文中將其譯爲中文是爲了便于讀者理解。) 從程序員的角度來看,Microsoft® SQL Server™ 查詢的最大缺陷之一就是返回的行數通常比應用程序的用戶界面實際可以容納的行數要多得多。這種尴尬情形經常將開發人員陷于困境。開發人員是應該創建一個非常長的頁面,讓用戶花時間去滾動浏覽,還是應該通過設置一個手動分頁機制來更好地解決這個問題? 哪種解決方案更好,在很大程度上取決于要檢索的數據的特性。由多個項目(如搜索結果)組成的較長列表,最好通過各頁大小相等、每頁相對較短的多個頁面顯示。由單個項目(如文章的文本)組成的較長列表,假如整個插入在一個頁面中,使用起來會更方便。最後得出的分析結果是,應該根據應用程序的總體用途來做決定。那麽,Microsoft® ASP.NET 是如何解決數據分頁問題的呢? ASP.NET 提供了功能強大的數據綁定控件,以便將查詢結果格式化爲 Html 標記。但是,這些數據綁定控件中只有一種控件(即 DataGrid 控件)本來就支持分頁。其他控件(如 DataList、Repeater 或 CheckBoxList)則不支持分頁。這些控件及其他列表控件不支持分頁,不是因爲它們在結構上不支持分頁,而是因爲它們與 DataGrid 不同,不包含任何處理分頁的特定代碼。但是,處理分頁的代碼是相當樣板化的,可以添加到所有這些控件中。 Scott Mitchell 在最近的一篇題目爲「Creating a Pageable, Sortable DataGrid」(英文)的文章中,介紹了 DataGrid 分頁。該文還引用了 Web 上的其他有用信息,爲您提供了有關網格分頁基礎知識和其他信息。假如想查看如何使 DataList 控件可以進行分頁的示例,可以查看此文章(英文)。該文演示了如何創建一個自定義的 DataList 控件,該控件具有當前索引和頁面大小屬性,並可以啓動頁面更改事件。 同樣的代碼也可以用于滿足其他列表控件(如 ListBox 和 CheckBoxList)的分頁需要。不過,向各個控件添加分頁功能實際上並不是一種非常好的做法,因爲,如上所述,分頁代碼是相當樣板化的。因此,對于聰明的程序員來說,有什麽方法比使用一種新的通用分頁程序控件來實現所有這些控件的分頁功能更好的呢? 本文中將建立一個分頁程序控件,它將使合作者列表控件能夠對 SQL Server 的查詢結果進行分頁。該控件名爲 SqlPager,它支持兩種類型的合作者控件 - 列表控件和基礎數據列表控件。 SqlPager 控件的顯著特點 SqlPager 控件是一個 ASP.NET 複合控件,包含一個單行表格。該行又包含兩個單元格 - 導航條和頁面描述符。該控件的用戶界面呈條形,理想情況下,其寬度與合作者控件的寬度相同。導航條部分提供了可單擊的元素,以便在頁面之間移動;頁面描述符部分爲用戶提供了有關當前顯示的頁面的一些反饋信息。 [url=/bbs/detail_1785433.html][img]http://image.wangchao.net.cn/it/1323423379360.gif[/img][/url] 圖 1:Visual Studio .NET 網頁設計器中顯示的 SqlPager 控件 與 DataGrid 控件的嵌入式分頁程序一樣,SqlPager 控件具有兩種導航模式,即下一頁/上一頁和數字頁面。此外,其非凡屬性 PagerStyle 使您能夠選擇更方便的樣式。該控件與列表控件協同工作。您可以通過 ControlToPaginate 字符串屬性爲分頁程序指定一個這樣的合作者控件。 SqlPager1.ControlToPaginate = "ListBox1"; 一般情況下,分頁程序首先獲取 SQL Server 的查詢結果,預備一個適當的記錄頁面,然後通過合作者控件的 DataSource 屬性顯示該頁面。當用戶單擊以查看新頁面時,分頁程序將檢索請求的數據並再次通過合作者控件來顯示數據。分頁機制對于列表控件是完全透明的。列表控件的數據源是通過編程方式進行更新的,任何時候都只包含適合當前頁面的記錄。 控件的分頁引擎具有多個 public 屬性,如 CurrentPageIndex、ItemsPerPage 和 PageCount,通過這些屬性來獲取並設置當前頁面的索引、每個頁面的大小以及要顯示的頁面的總數。分頁程序治理數據檢索和分頁所需的任何邏輯。 SelectCommand 屬性設置獲取數據所用的命令文本。ConnectionString 屬性定義數據庫的名稱和位置以及連接憑據。執行查詢時采用的方式取決于 PagingMode 屬性的值。該屬性的可能值爲與其同名的 PagingMode 枚舉的值 - Cached 和 NonCached。假如選擇 Cached 選項,則將使用數據適配器和 DataTable 對象檢索整個結果集。可以選擇將結果集放置在 ASP.NET 的 Cache 對象中,該結果集可以重複使用直到過期。假如選擇 NonCached 選項,則查詢只檢索適合當前頁面的記錄。這時,ASP.NET 的 Cache 中不放置任何數據。NonCached 模式與 DataGrid 控件的自定義分頁模式幾乎相同。 下表列出 SqlPager 控件的全部編程接口。 表 1:SqlPager 控件的編程接口名稱類型說明CacheDuration屬性指示數據在 ASP.NET 的緩存中保留的秒數。只用于 Cached 模式。默認值爲 60 秒。ConnectionString屬性用來訪問所選擇的 SQL Server 數據庫的連接字符串。ControlToPaginate屬性同一個 .aspx 頁面中的控件 ID,它將顯示分頁程序檢索的記錄頁面。這是合作者控件。CurrentPageIndex屬性獲取並設置基于 0 的頁面索引。 ItemsPerPage屬性獲取並設置每頁要顯示的記錄數量。默認值爲每頁顯示 10 個項目。PagerStyle屬性該值指示分頁程序用戶界面的樣式。它可以爲 PagerStyle 枚舉值:NextPRev 和 NumericPages 之一。在 NextPrev 模式中,將顯示 VCR 式的按鈕,來轉到第一頁、上一頁、下一頁和最後一頁。而在 NumericPages 模式中,將顯示一個下拉列表,列出所有可用頁面的索引。PagingMode屬性該值指示檢索數據的方式。它可以爲 PagingMode 枚舉值:Cached 和 NonCached 之一。假如爲 Cached,則將使用數據適配器,且整個結果集將臨時放置在 ASP.NET 緩存中。假如爲 NonCached,則只檢索當前頁面中的記錄。在這種情況下,不進行緩存。SelectCommand屬性用來進行查詢的命令文本。最好爲 SELECT-FROM-WHERE 形式。不支持 ORDER BY 子句。排序是通過 SortField 屬性另外指定的。SortField屬性用來排序的字段的名稱。此字段用于爲查詢提供動態的 ORDER BY 子句。排序是由 SQL Server 執行的。 ClearCache方法刪除存儲在 ASP.NET 緩存中的任何數據。PageIndexChanged事件默認事件,當分頁程序移動到另一個頁面時發生。事件的數據結構爲 PageChangedEventArgs 類,包含舊頁面和新頁面的索引。 由于 SqlPager 控件繼續了 WebControl,因此它也具有很多與 UI 相關的屬性來治理字體、邊框和顔色。 生成 SqlPager 控件 將作爲複合控件來生成 SqlPager 控件並讓其繼續 WebControl 類。複合控件是 ASP.NET 服務器控件所特有的,它是由一個或多個服務器控件組成。 public class SqlPager : WebControl, INamingContainer { ... } 除非生成完全自定義的控件或擴展現有的控件,否則,創建新控件時,大多數時間實際上是在生成複合控件。要創建 SqlPager,組合一個 Table 控件,並根據分頁程序的樣式,組合幾個 LinkButton 控件或者一個 DropDownList 控件。 生成複合控件時,需要注重幾條原則。首先,需要覆蓋 CreateChildControls protected 方法。CreateChildControls 方法是從 Control 繼續來的,當服務器控件爲了顯示而要創建子控件時或在返回後,將調用此方法。 protected override void CreateChildControls() { // 清除現有的子控件及其 ViewState Controls.Clear(); ClearChildViewState(); // 生成控件樹 BuildControlHierarchy(); } 覆蓋此方法時,需要執行幾項重要的操作。創建並初始化任何所需的子控件實例並將它們添加到父控件的 Controls 集合中。但是,生成新控件樹之前,應該刪除任何現有的子控件並清除子控件可能留下的任何 ViewState 信息。 複合組件還需要實現 INamingContainer 接口,以便 ASP.NET 運行時可以爲其創建一個新的命名範圍。這就確保了複合控件中的所有控件都具有唯一的名稱。這還將確保能夠自動處理子控件的返回數據。 對于 SqlPager 控件來說,成爲命名容器非常重要。事實上,SqlPager 包含一些 LinkButton 控件,並且需要獲取並處理其單擊事件以便導航頁面。正如 ASP.NET 頁面中的任何其他控件一樣,LinkButton 也被賦予了一個 ID,用于標識處理返回事件的控件。 處理返回事件時,ASP.NET 運行時試圖查找事件的目標 ID 與主窗體的任何直接子控件之間是否存在匹配關系。LinkButton 是分頁程序的子控件,因此不能運行其服務器端的代碼。這是否意味著只有窗體的直接子控件才能啓動並處理服務器事件?當然不是,只要您使用命名容器。 通過使 SqlPager 控件實現 INamingContainer 接口,可以將嵌入式鏈接按鈕的實際 ID 從「First」更改爲「SqlPager1:First」。當用戶單擊以查看新頁面時,返回事件將 SqlPager1:First 作爲目標控件。實際上,運行時用來識別目標控件的算法比上面介紹的要複雜一些。運行時將事件目標控件的名稱看作是用冒號分隔開的字符串。實際上,這種匹配是在窗體的子控件和用冒號分隔開的字符串(如 SqlPager1:First)的第一個標記之間進行的。由于分頁程序是窗體的子控件,因此匹配時會成功,分頁程序獲取單擊事件。假如您認爲這種解釋不夠充分或者令人費解,只要下載 SqlPager 控件的源代碼,刪除 INamingContainer 標記接口並進行重新編譯即可。您將看到分頁程序能夠返回,但不能內部處理單擊事件。 INamingContainer 接口是一個不具備方法的標記接口,其實現只需在類聲明中指定名稱即可,無需進行任何其他操作。 複合控件的另一個重要方面是,它們通常不需要自定義邏輯來進行顯示。複合控件的顯示遵循其組成控件的顯示。生成複合控件時,通常無需覆蓋 Render 方法。 控件的 SqlPager 樹由一個包含兩個單元格的單行表格組成。該表格繼續了分頁程序的大部分可視設置,如前景顔色和背景顔色、邊框、字體信息和寬度等。第一個單元格包含導航條,其結構取決于 PagerStyle 屬性的值。假如分頁程序的樣式爲 NextPrev,則導航條將由四個 VCR 式的按鈕組成。否則,它將由一個下拉列表組成。private void BuildControlHierarchy() { // 生成環境表格(一行,兩個單元格) Table t = new Table(); t.Font.Name = this.Font.Name; t.Font.Size = this.Font.Size; t.BorderStyle = this.BorderStyle; t.BorderWidth = this.BorderWidth; t.BorderColor = this.BorderColor; t.Width = this.Width; t.Height = this.Height; t.BackColor = this.BackColor; t.ForeColor = this.ForeColor; // 生成表格中的行 TableRow row = new TableRow(); t.Rows.Add(row); // 生成帶有導航條的單元格 TableCell cellNavBar = new TableCell(); if (PagerStyle == this.PagerStyle.NextPrev) BuildNextPrevUI(cellNavBar); else BuildNumericPagesUI(cellNavBar); row.Cells.Add(cellNavBar); // 生成帶有頁面索引的單元格 TableCell cellPageDesc = new TableCell(); cellPageDesc.HorizontalAlign = HorizontalAlign.Right; BuildCurrentPage(cellPageDesc); row.Cells.Add(cellPageDesc); // 將表格添加到控件樹 this.Controls.Add(t); } 將各個控件添加到正確的 Controls 集合對于分頁程序的正確顯示極其重要。最外層的表格必須添加到分頁程序的 Controls 集合中。鏈接按鈕和下拉列表必須添加到相應表格單元格的 Controls 集合中。 下面給出了用來生成鏈接按鈕導航條的代碼。每個按鈕都顯示有一個 Webdings 字符,可以根據需要禁用,並被綁定到內部的 Click 事件處理程序。 private void BuildNextPrevUI(TableCell cell) { bool isValidPage = ((CurrentPageIndex >=0) && (CurrentPageIndex <= TotalPages-1)); bool canMoveBack = (CurrentPageIndex>0); bool canMoveForward = (CurrentPageIndex<TotalPages-1); // 顯示 << 按鈕 LinkButton first = new LinkButton(); first.ID = "First"; first.Click += new EventHandler(first_Click); first.Font.Name = "webdings"; first.Font.Size = FontUnit.Medium; first.ForeColor = ForeColor; first.ToolTip = "第一頁"; first.Text = "7"; first.Enabled = isValidPage && canMoveBack; cell.Controls.Add(first); : } 分頁程序的另一種樣式(在下拉列表中列出數字頁面)的生成方法如下所示:private void BuildNumericPagesUI(TableCell cell) { // 顯示一個下拉列表 DropDownList pageList = new DropDownList(); pageList.ID = "PageList"; pageList.AutoPostBack = true; pageList.SelectedIndexChanged += new EventHandler(PageList_Click); pageList.Font.Name = this.Font.Name; pageList.Font.Size = Font.Size; pageList.ForeColor = ForeColor; if (TotalPages <=0 CurrentPageIndex == -1) { pageList.Items.Add("No pages"); pageList.Enabled = false; pageList.SelectedIndex = 0; } else // 填充列表 { for(int i=1; i<=TotalPages; i++) { ListItem item = new ListItem(i.ToString(), (i-1).ToString()); pageList.Items.Add(item); } pageList.SelectedIndex = CurrentPageIndex; } } 所有事件處理程序(Click 和 SelectedIndexChanged)最終都會更改當前顯示的頁面。這兩種方法都會調用一個公用的 private 方法 GoToPage。private void first_Click(object sender, EventArgs e) { GoToPage(0); } private void PageList_Click(object sender, EventArgs e) { DropDownList pageList = (DropDownList) sender; int pageIndex = Convert.ToInt32(pageList.SelectedItem.Value); GoToPage(pageIndex); } private void GoToPage(int pageIndex) { // 預備事件數據 PageChangedEventArgs e = new PageChangedEventArgs(); e.OldPageIndex = CurrentPageIndex; e.NewPageIndex = pageIndex; // 更新當前的索引 CurrentPageIndex = pageIndex; // 啓動頁面更改事件 OnPageIndexChanged(e); // 綁定新數據 DataBind(); } 其他導航按鈕的處理程序與 first_Click 的區別僅在于它們傳遞給 GoToPage 方法的頁碼不同。GoToPage 方法負責處理 PageIndexChanged 事件,並負責啓動數據綁定過程。它預備事件數據(舊頁面和新頁面索引)並觸發事件。GoToPage 被定義爲 private,但是可以使用 CurrentPageIndex 屬性通過編程的方式更改顯示的頁面。 public int CurrentPageIndex { get {return Convert.ToInt32(ViewState["CurrentPageIndex"]);} set {ViewState["CurrentPageIndex"] = value;} } 與表 1 中所列的所有屬性一樣,CurrentPageIndex 屬性的實現方法也相當簡單。它將其內容保存到 ViewState 中並從中進行還原。在數據綁定過程中,會驗證和使用頁面索引。 數據綁定過程 DataBind 方法是所有 ASP.NET 控件公用的,對于數據綁定控件來說,它將觸發用戶界面的刷新以反映新數據。SqlPager 控件根據 SelectCommand 和 ConnectionString 屬性的值,使用此方法啓動數據檢索操作。不言而喻,假如這些屬性中的任何一個爲空,該過程將終止。同樣,假如合作者控件不存在,數據綁定過程將被取消。要查找合作者控件,DataBind 方法使用 Page 類中的 FindControl 函數。由此可見,合作者控件必須爲主窗體的直接子控件。 進行分頁顯示的控件不能爲任意的 ASP.NET 服務器控件。它必須爲列表控件或基本數據列表控件。更一般來說,合作者控件必須具有 DataSource 屬性並實現 DataBind 方法。可能進行分頁的控件實際上只需要滿足這些要求。Microsoft® .NET Framework 中所有繼續 ListControl 或 BaseDataList 的控件都滿足第一個要求;而所有 Web 控件通過設計都滿足 DataBind 要求。使用當前的實現方法,無法使用 SqlPager 控件來對 Repeater 進行分頁。Repeater 與合作者控件 DataList 和 DataGrid 不同,不繼續 BaseDataList,也不提供列表控件的功能。下表列出了可以使用 SqlPager 進行分頁的控件。 表 2:可以由 SqlPager 控件進行分頁的數據綁定控件 控件說明CheckBoxList從 ListControl 派生而來,顯示爲複選框列表。 DropDownList從 ListControl 派生而來,顯示爲字符串下拉列表。ListBox從 ListControl 派生而來,顯示爲字符串可滾動列表。RadioButtonList從 ListControl 派生而來,顯示爲單選按鈕列表。DataList從 BaseDataList 派生而來,顯示爲模板化數據項目列表。DataGrid從 BaseDataList 派生而來,顯示爲數據項目的表格網格。DataGrid 是唯一一個內置有功能強大的分頁引擎的 ASP.NET 控件。 以下代碼說明由 SqlPager 控件實現的數據綁定過程。 public override void DataBind() { // 啓動數據綁定事件 base.DataBinding(); // 數據綁定後必須重新創建控件 ChildControlsCreated = false; // 確保控件存在且爲列表控件 _controlToPaginate = Page.FindControl(ControlToPaginate); if (_controlToPaginate == null) return; if (!(_controlToPaginate is BaseDataList _controlToPaginate is ListControl)) return; // 確保具有足夠的連接信息並指定查詢 if (ConnectionString == "" SelectCommand == "") return; // 獲取數據 if (PagingMode == PagingMode.Cached) FetchAllData(); else FetchPageData(); // 將數據綁定到合作者控件 BaseDataList baseDataListControl = null; ListControl listControl = null; if (_controlToPaginate is BaseDataList) { baseDataListControl = (BaseDataList) _controlToPaginate; baseDataListControl.DataSource = _dataSource; baseDataListControl.DataBind(); return; } if (_controlToPaginate is ListControl) { listControl = (ListControl) _controlToPaginate; listControl.Items.Clear(); listControl.DataSource = _dataSource; listControl.DataBind(); return; } } 根據 PagingMode 屬性的值調用不同的獲取例程。在任何情況下,結果集都綁定到 PagedDataSource 類的實例上。此類提供了一些用來對數據進行分頁的功能。非凡是,當整個數據集都存儲在緩存中時,該類將自動檢索當前頁面的記錄並返回布爾值,來提供有關第一頁和最後一頁的信息。稍後將回來介紹此類的內部結構。在上述列表中,幫助程序的 PagedDataSource 對象是由 _dataSource 變量表示的。 然後,SqlPager 控件經過計算得出合作者控件的類型,並將 PagedDataSource 對象的內容綁定到合作者控件的 DataSource 屬性。 有時,上述的 DataBind 方法還將 ChildControlsCreated 屬性重新設置爲 false。那麽,爲什麽要這樣做呢? 當包含分頁程序的頁面返回時,所有控件都要重新創建;分頁程序也不例外。通常情況下,所有控件及其子控件都是在預備顯示頁面之前創建的。在每個控件接收到 OnPreRender 通知之前的一瞬間,protected EnsureChildControls 方法將被調用,這樣,每個控件都可以生成自己的控件樹。此事件發生後,數據綁定過程完成,新數據已存儲到緩存中。 但是,當由于單擊分頁程序的一個組成控件而使頁面返回時(即用戶單擊以更改頁面),就會生成分頁程序的控件樹,這時遠未達到顯示階段。非凡是,當處理相關的服務器端事件時,就必須生成控件樹,因而是在數據綁定開始之前生成控件樹。困難在于,數據綁定將修改頁面索引,而這必須反映在用戶界面中。假如不采取某些對策的話,當用戶切換到另一頁時,分頁程序中的頁面索引將不會被刷新。 有各種方法可以解決此問題,但重要的是要弄清楚問題及其真正的原因。您可以避免生成控件樹,並在 Render 方法中生成所有輸出。另外,您還可以修改樹中受數據綁定更改影響的部分。本文選擇了第三種方法,這種方法需要較少的代碼,而且,不管控件的用戶界面的哪個部分受到數據綁定更改的影響,都能夠解決問題。通過將 ChildControlsCreated 屬性設置爲 false,可以使以前創建的任何控件樹無效。這樣,在顯示之前將重新創建控件樹。 分頁引擎 SqlPager 控件支持兩種檢索數據的方法 - 緩存和非緩存。假如采用前者,選擇命令原樣執行,整個結果集將綁定到在內部進行分頁的數據源對象上。PagedDataSource 對象將自動返回適合特定頁面的記錄。PagedDataSource 類也是在 DataGrid 默認分頁機制背後運行的系統組件。 檢索所有記錄但只顯示適合頁面的幾個記錄,這通常不是一種明智的方法。由于 Web 應用程序的無狀態特性,事實上,每次請求頁面時都可能運行大量的查詢。要使操作有效,采用緩存的數據檢索方法必須依靠于某種緩存對象,ASP.NET 的 Cache 對象就是一個很好的候選對象。緩存技術的使用加快了應用程序的運行速度,但其提供的數據快照不能反映最新的更改。另外,它需要使用 Web 服務器上的較多內存。而且荒謬的是,假如大量的數據按會話緩存的話,甚至可能造成很大的問題。Cache 容器對于應用程序來說是全局的;假如數據按會話存儲在其中,還需生成會話特有的項目名稱。 Cache 對象的上面是完全支持過期策略的。換句話說,存儲在緩存中的數據在一段時間後可以自動被釋放。以下代碼說明 SqlPager 類中用來獲取數據並將其存儲在緩存中的一個 private 方法。private void FetchAllData() { // 在 ASP.NET Cache 中查找數據 DataTable data; data = (DataTable) Page.Cache[CacheKeyName]; if (data == null) { // 使用 order-by 信息修改 SelectCommand AdjustSelectCommand(true); // 假如數據過期或從未被獲取,則轉到數據庫 SqlDataAdapter adapter = new SqlDataAdapter(SelectCommand, ConnectionString); data = new DataTable(); adapter.Fill(data); Page.Cache.Insert(CacheKeyName, data, null, DateTime.Now.AddSeconds(CacheDuration), System.Web.Caching.Cache.NoSlidingEXPiration); } // 配置分頁的數據源組件 if (_dataSource == null) _dataSource = new PagedDataSource(); _dataSource.DataSource = data.DefaultView; _dataSource.AllowPaging = true; _dataSource.PageSize = ItemsPerPage; TotalPages = _dataSource.PageCount; // 確保頁面索引有效 ValidatePageIndex(); if (CurrentPageIndex == -1) { _dataSource = null; return; } // 選擇要查看的頁面 _dataSource.CurrentPageIndex = CurrentPageIndex; } 控件和請求的緩存項目名稱是唯一的。它包括頁面的 URL 和控件的 ID。在指定的時間(以秒計算)內,數據被綁定到緩存。要使項目過期,必須使用 Cache.Insert 方法。以下較簡單的代碼將項目添加到緩存,但不包括任何過期策略。Page.Cache[CacheKeyName] = data; PagedDataSource 對象通過其 DataSource 屬性獲取要進行分頁的數據。值得注重的是,PagedDataSource 類的 DataSource 屬性只接受 IEnumerable 對象。DataTable 不滿足此要求,這就是爲什麽采取 DefaultView 屬性的原因。 SelectCommand 屬性確定在 SQL Server 數據庫上運行的查詢。此字符串最好爲 SELECT-FROM-WHERE 形式。不支持 ORDER BY 子句,假如指定了該子句,將被刪除。這正是 AdjustSelectCommand 方法所做的。使用 SortField 屬性可以指定任何排序信息。AdjustSelectCommand 方法本身將根據 SortField 的值添加一個正確的 ORDER BY 子句。這樣做有什麽原因嗎? 當分頁程序以 NonCached 模式工作時,原始的查詢將被修改以確保只檢索當前頁面的記錄。在 SQL Server 上執行的實際查詢文本將采取以下形式。SELECT * FROM (SELECT TOP ItemsPerPage * FROM (SELECT TOP ItemsPerPage*CurrentPageIndex * FROM (SelectCommand) AS t0 ORDER BY SortField ASC) AS t1 ORDER BY SortField DESC) AS t2 ORDER BY SortField 該查詢彌補了 SQL Server 2000 中 ROWNUM 子句的缺陷,並且對記錄進行重新排序,使得只有 x 項目的「第 n 個」塊經過正確排序後返回。您可以指定基礎查詢,分頁程序將它分解爲多個較小的頁面。只有適合某個頁面的記錄被返回。正如您看到的那樣,除了查詢命令以外,上述查詢需要處理排序字段。這就是爲什麽另外添加了 SortField 屬性。此代碼的唯一缺陷是默認情況爲升序排序。通過使 ASC/DESC 要害字參數化,可以使此代碼真的非常完美:private void FetchPageData() { // 需要經過驗證的頁面索引來獲取數據。 // 還需要實際的頁數來驗證頁面索引。 AdjustSelectCommand(false); VirtualRecordCount countInfo = CalculateVirtualRecordCount(); TotalPages = countInfo.PageCount; // 驗證頁碼(確保 CurrentPageIndex 有效或爲「-1」) ValidatePageIndex(); if (CurrentPageIndex == -1) return; // 預備並運行命令 SqlCommand cmd = PrepareCommand(countInfo); if (cmd == null) return; SqlDataAdapter adapter = new SqlDataAdapter(cmd); DataTable data = new DataTable(); adapter.Fill(data); // 配置分頁的數據源組件 if (_dataSource == null) _dataSource = new PagedDataSource(); _dataSource.AllowCustomPaging = true; _dataSource.AllowPaging = true; _dataSource.CurrentPageIndex = 0; _dataSource.PageSize = ItemsPerPage; _dataSource.VirtualCount = countInfo.RecordCount; _dataSource.DataSource = data.DefaultView; } 在 NonCached 模式中,PagedDataSource 對象並不提供整個數據源,因此不能計算出要進行分頁的總頁數。進而必須對 AllowCustomPaging 屬性進行標記,並提供數據源中的實際記錄數量。通常,實際數量是使用 SELECT COUNT(*) 查詢進行檢索的。此模型與 DataGrid 的自定義分頁幾乎相同。此外,PagedDataSource 對象中選擇的當前頁面索引通常爲 0,因爲實際上已經存儲了一頁記錄。 SqlPager 控件的實現方法就介紹到這裏,下面我們介紹一下它的使用方法。 使用 SqlPager 控件 假設存在一個包含 ListBox 控件的示例頁面。要使用分頁程序,請確保 .aspx 頁面正確地注冊了該控件的程序集。 <%@ Register TagPrefix="expo" Namespace="DevCenter" Assembly="SqlPager" %> 控件的標記取決于實際設置的屬性。以下標記是一個合理的示例:<asp:listbox runat="server" id="ListBox1" Width="300px" Height="168px" DataTextField="companyname" /> <br> <expo:SqlPager runat="server" id="SqlPager1" Width="300px" ControlToPaginate="ListBox1" SelectCommand="SELECT customerid, companyname FROM customers" ConnectionString="SERVER=localhost;DATABASE=northwind;UID=..." SortKeyField="companyname" /> <br> <asp:button runat="server" id="LoadFirst1" Text="加載第一頁" /> 除了分頁程序以外,頁面還包含一個列表框和一個按鈕。列表框將顯示每個頁面的內容;按鈕只用于首次填充列表框。該按鈕具有一個單擊事件處理程序,定義如下。private void LoadFirst1_Click(object sender, EventArgs e) { SqlPager1.CurrentPageIndex = 0; SqlPager1.DataBind(); } 圖 2 顯示操作中的頁面。 [url=/bbs/detail_1785433.html][img]http://image.wangchao.net.cn/it/1323423379490.gif[/img][/url] 圖 2:與 ListBox 控件協同工作的 SqlPager 控件。 使用 DataList 控件可以生成一個更有意思的示例。目標是使用分頁程序浏覽每個 Northwind 職員的個人記錄。該 DataList 如以下列表所示。<asp:datalist runat="server" id="DataList1" Width="300px" Font-Names="Verdana" Font-Size="8pt"> <ItemTemplate> <table bgcolor="#f0f0f0" style="font-family:verdana;font-size:8pt;"> <tr><td valign="top"> <b><%# DataBinder.Eval(Container.DataItem, "LastName") + ", " + DataBinder.Eval(Container.DataItem, "firstname") %></b></td></tr> <tr><td> <span style="color:blue;"><i> <%# DataBinder.Eval(Container.DataItem, "Title")%></i></span> <p><img style="float:right;" src='image.aspx? id=<%# DataBinder.Eval(Container.DataItem, "employeeid")%>' /> <%# DataBinder.Eval(Container.DataItem, "Notes") %></td></tr> </table> </ItemTemplate> </asp:datalist> 表格的第一行顯示職員的姓名和職務,然後是相片,相片四周是注釋。相片是使用特定的 .aspx 頁面檢索的,返回從數據庫中獲取的 JPEG 數據。 分頁程序可以放置在頁面中的任何位置。本例中將它放置在合作者 DataList 控件上方並緊挨著合作者控件。 [url=/bbs/detail_1785433.html][img]http://image.wangchao.net.cn/it/1323423379579.gif[/img][/url] 圖 3:SqlPager 對 DataList 控件進行分頁 將 SqlPager 控件與 DataGrid 控件一起使用有意義嗎?這要視情況而定。DataGrid 已經與一個基于本文中使用的 PagedDataSource 對象的嵌入式分頁引擎一起工作。因此,假如您需要對以表格格式顯示的單個記錄集合進行分頁時,就無需使用 SqlPager。但是,對于重要/複雜的方案,將這兩個控件一起使用並不是一種牽強的想法。例如,假如您要向前一個屏幕快照添加一個 DataGrid 來顯示由職員治理的訂單,則可以在同一格頁面上放置兩個相關的分頁引擎,一個要對職員進行分頁,另一個用于滾動相關訂單。 小結 不管您要生成哪種類型的應用程序(Web 應用程序、Microsoft® Windows® 應用程序或 Web 服務),您都很難下載和緩存要顯示的整個數據源。有時,測試環境會使您相信這種解決方案真是太棒了,非常可取。但是測試環境也會誤導。數據源的大小非常要害,應用程序的規模越大,數據源的大小越要害。 在 ASP.NET 中,只有 DataGrid 控件具有內置的分頁功能。但是,分頁引擎是由相當樣板化的代碼實現的,只要進行少量的處理,就可以進行推廣並用于多個不同的控件。本文介紹的 SqlPager 控件就實現了此目標。它關注下載數據,將數據分成多個頁面並通過合作者控件顯示。該控件可以檢索並緩存整個數據集,或者僅在 SQL Server 中查詢要在選定的頁面中顯示的幾個記錄。我說的是 SQL Server,這是另一個重點。SqlPager 只能用于 SQL Server,不能用于通過 OLE DB 或 ODBC 來檢索數據。也不能使用它訪問 Oracle 或 DB2 存檔。 要生成真正的通用 SQL 分頁程序組件,應該形成數據訪問層,並生成一種能夠使用相應的數據提供程序創建連接、命令和適配器的工廠類。此外,要注重爲各種 SQL 源設置一個分頁引擎是最糟糕的做法。這裏介紹的方法只適用于 SQL Server 7.0 和更高版本。TOP 子句在不同的環境下具有不同的特性。使用服務器遊標和臨時表格,它可適用于較大範圍的 DBMS 系統。但那將使代碼更爲複雜。 作者簡介 Dino Esposito 是一位來自意大利羅馬的培訓教師和顧問。他是 Wintellect(英文)小組的成員,專門研究 ASP.NET 和 ADO.NET,主要在歐洲和美國從事教學和咨詢工作。此外,Dino 還負責治理 Wintellect 的 ADO.NET 課件,並爲 MSDN Magazine(英文)的「Cutting Edge」專欄撰寫文章。
󰈣󰈤
 
 
 
>>返回首頁<<
 
 
 
 
 熱帖排行
 
王朝網路微信公眾號
微信掃碼關註本站公眾號 wangchaonetcn
 
  免責聲明:本文僅代表作者個人觀點,與王朝網絡無關。王朝網絡登載此文出於傳遞更多信息之目的,並不意味著贊同其觀點或證實其描述,其原創性以及文中陳述文字和內容未經本站證實,對本文以及其中全部或者部分內容、文字的真實性、完整性、及時性本站不作任何保證或承諾,請讀者僅作參考,並請自行核實相關內容。
 
© 2005- 王朝網路 版權所有