全能的資料繫結控制項 - ListView (含DataPager)

全能的資料繫結控制項 - ListView

原文:http://msdn2.microsoft.com/zh-tw/magazine/cc337898.aspx

 

隨附於 Visual Studio® 2008 的 ASP.NET 3.5 版引進了新的資料繫結控制項 -- ListView。我了解您正在想什麼:我們為何還要在 ASP.NET 中加入另一個資料繫結控制項?畢竟在顯示資料集合時,我們已經有超過 10 個控制項可供選用,其中包括半退休的 DataGrid、改進過的新 GridView、一直很可靠且單純的 Repeater、獨特且很有彈性的 DataList、方便的 FormView,以及有一點多餘的同層級控制項 DetailsView。當然還有單一維度的清單控制項:BulletedList、ListBox、DropDownList、RadioButtonList 及 CheckBoxList。

這是因為 ListView 可以真正取代 ASP.NET 中的其他所有資料繫結控制項。對的,就是一切所有的資料繫結控制項。使用 ListView 控制項,您就可以避免使用以上清單中的其他每一個控制項。比起先前的控制項,ListView 還可以簡化數個資料繫結工作,包括 CSS 樣式、彈性分頁,以及排序、插入、刪除及更新等輔助功能。

讓我們從說明 ListView 的一般使用模型開始,然後我會介紹此控制項的功能,並說明其彈性及威力。閱讀本專欄之後,您就可以決定要在 ASP.NET 工具箱中保留多少個資料繫結控制項。

ListView 基本概念

ListView 是一種以樣板導向的控制項,這表示它預設並不會呈現任何東西 -- 您必須以樣板的形式,完整指定希望它呈現的 HTML。與大多數樣板控制項一樣,ItemTemplate 也將成為您大部分工作的重點,因為這就是您放置 HTML 內容的位置,且會針對繫結資料集的每一個資料列重覆執行。

ListView 中的新功能以及使它能夠在控制項中脫穎而出的原因 -- 就是引進了 LayoutTemplate。LayoutTemplate 就是可在最上層定義的 HTML,這會輸出成控制項呈現資訊的一部分。例如,如果您希望 ListView 會呈現出資料表,就可以在 LayoutTemplate 中納入最上層的 <table>,也可以納入 <thead> 項目,然後由 ItemTemplate 呈現資料列和儲存格,如 [圖 1] 所示 (在此案例中,是繫結至包含電影標題和發行日期之簡單資料表的資料來源)。[圖 2] 顯示瀏覽器的呈現情形。

Figure 1 Using LayoutTemplate and ItemTemplate

01 <asp:ListView runat="server" ID="_simpleTableListView"
02   DataSourceID="_moviesDataSource">
03   <LayoutTemplate>
04     <table>
05       <thead>
06         <tr>
07           <th>ID</th>
08           <th>Title</th>
09           <th>Release Date</th>
10         </tr>
11       </thead>
12       <tbody>
13         <asp:PlaceHolder runat="server" ID="itemPlaceholder" />
14       </tbody>
15     </table>
16   </LayoutTemplate>
17   <ItemTemplate>
18     <tr>
19       <td><%# Eval("movie_id") %></td>
20       <td><%# Eval("title") %></td>
21       <td><%# Eval("release_date", "{0:d}") %></td>
22     </tr>
23   </ItemTemplate>
24 </asp:ListView>
 

Figure 2 在資料表中顯示的清單 (按影像可放大)

LayoutTemplate 與 ItemTemplate 之間的關聯,是利用 LayoutTemplate 中伺服器端的單一控制項,並將 ID 設定為 itemPlaceholder 來達成的 (您可以使用 ListView 的 ItemPlaceholderID 屬性,來變更 ID 字串的預設值)。在第一個範例中,我在想要加入 ItemTemplate 內容的樣板位置,使用了放置 PlaceHolder 控制項之執行個體的技巧。請注意,雖然可做為預留位置的控制項型別並沒有限制,但是它必須支援子控制項 -- ID 才是最重要的。例如,我如果使用伺服器端的資料表資料列方式來撰寫 LayoutTemplate,而不使用 PlaceHolder 控制項,也能達到相同的目的:

01 <LayoutTemplate>
02   <table>
03     <thead>
04       <tr>
05         <th>ID</th>
06         <th>Title</th>
07         <th>Release Date</th>
08       </tr>
09     </thead>
10     <tbody>
11       <tr runat="server" ID="itemPlaceholder" />
12     </tbody>
13   </table>
14 </LayoutTemplate>

一般來說,基於兩項理由,我比較喜歡使用泛型的 PlaceHolder 控制項。第一項是因為名稱可以清楚對應。此外,這個控制項不會呈現自己的 HTML,而是會使用 ItemTemplate 的內容加以取代,因此除了在控制項階層中保留位置以外並沒有其他用途,如此使用控制項看起來是比較合乎邏輯的選擇。

當然,ListView 這麼有彈性的原因,是您可以完全控制 LayoutTemplate 的資訊。您並不會受限於只能使用資料表 -- 您可以在 LayoutTemplate 中放置希望呈現的任何 HTML,只要在 itemPlaceholder 控制項位置插入的 ItemTemplate 內容有意義即可。以下是 ListView 繫結至同一個電影資料來源的範例,但是這一回不使用資料表,而是以項目符號清單顯示電影標題和發行日期 (結果顯示於 [圖 3] 中):

Figure 3 相同的清單,不同的格式 (按影像可放大)

01 <asp:ListView runat="server"
02      ID="_simpleTableListView"
03      DataSourceID="_moviesDataSource">
04      <LayoutTemplate>
05        <ul>
06          <asp:PlaceHolder runat="server"
07               ID="itemPlaceholder" />
08        </ul>
09      </LayoutTemplate>
10      <ItemTemplate>
11        <li><%# Eval("title") %>,
12            <%#Eval("release_date","{0:d}") %></li>
13      </ItemTemplate>
14 </asp:ListView>

ListView 和 CSS

ASP.NET 開發人員在著手建立以 CSS 導向的網站時,已長久飽受個別控制項的束縛。許多預設的控制項會呈現內嵌的樣式,或是會使 CSS 類別與其部分 HTML 輸出的關聯很難建立。Microsoft 其實在 2006 年 4 月就推出了名為 CSS Control Adapter Toolkit 的工具組,其中為數個控制項 (包括 GridView) 提供了替代的呈現機制,這些都是完全以 CSS 導向的,以協助排解這個問題 (如需詳細的資訊,請參閱 2006 年 10 月份的 Extreme ASP.NET 專欄,網址為 msdn.microsoft.com/msdnmag/issues/06/10/ExtremeASPNET)。這些替代的呈現方式從未整合到完整的版本中,因此它們依然需要另行安裝,且缺乏設計人員的支援。

ListView 提供了令人耳目一新的單純性,可以在網站中輕鬆運用 CSS,讓您完全控制要在何處與在何時套用樣式表。一個常見的情況,就是開發人員會收到預先設計好的頁面,通常其中包含 HTML 和 CSS。要使用傳統 GridView 呈現資料表的特定設計,以往都很不容易,因為 GridView 類別為修改所產生 HTML 而提供的攔截行為很有限。

我曾看過許多開發人員採用重複嘗試的方式,亦即將樣式屬性套用到方格上,再檢視頁面的來源以確定放置樣式的位置,然後一再重複直到方格呈現所需樣貌為止。使用 ListView 之後,猜測的工作就消失了,因為這樣您就同時需要負責配置以及呈現內容。

舉例來說,假設您收到一個看起來必須如 [圖 4] 所示的資料表,其中的設計包含如 [圖 5] 所示的 .htm 和 .css 檔案。

Figure 5 HTML and CSS for the Table

HTML

01 <div class="PrettyGrid">
02   <table cellpadding="0" cellspacing="0" summary="">
03     <thead>
04       <tr>
05         <th scope="col"><a href="http://.">ID</a></th>
06         <th scope="col"><a href="http://.">Title</a></th>
07         <th scope="col"><a href="http://.">Release date</a></th>
08       </tr>
09     </thead>
10     <tbody>
11       <tr>
12         <td>1</td>
13         <td>Where the Wild Things Are</td>
14         <td>12/15/2008</td>
15       </tr>
16   <!-- ... -->
17     </tbody>
18   </table>
19   <div class="Pagination">
20     <span>1</span>
21     <a href="http://.">2</a>
22     <a href="http://.">3</a>
23   </div>
24 </div>

CSS

01 .PrettyGrid
02
{
03   width
: 100%;
04 }
05
06 .PrettyGrid div.Pagination,
07 .PrettyGrid div.Pagination a,
08 .PrettyGrid div.Pagination span
09
{
10   color
: #00FFFF;
11   background
: #284775;
12   font-weight
: normal;
13   padding
: 2px;
14 }
15 .PrettyGrid table
16
{
17   border
: solid 1px #CCCCCC;
18   width
: 100%;
19 }
20 /*...*/

Figure 4 資料表的目標設計 (按影像可放大)

您可以迅速建構可呈現與 HTML/CSS 組合完全一樣的 ListView,只要從 HTML 中取得適當的區段,並且將其放置在對應的樣板內,如 [圖 6] 所示。最終的結果看起來會與完全使用 CSS 樣式建立的原始 HTML 一模一樣。若要修改設計也很容易,只要更新 HTML 或對應的 CSS 就可以了。

Figure 6 ListView to Construct the Table

01 <asp:ListView ID="_moviesGrid" runat="server" DataKeyNames="movie_id"
02   DataSourceID="_moviesDataSource">            
03   <LayoutTemplate>
04     <div class="PrettyGrid">
05       <table cellpadding="0" cellspacing="0" summary="">
06         <thead>
07           <tr>
08             <th scope="col"><a href="http://.">ID</a ></th>
09             <th scope="col"><a href="http://.">Title</a></th>
10             <th scope="col"><a href="http://.">Release date</a></th>
11           </tr>
12         </thead>
13         <tbody>
14           <asp:PlaceHolder ID="itemPlaceholder" runat="server" />  
15         </tbody>
16       </table>
17       <div class="Pagination">
18         <span>1</span>
19         <a href="http://.">2</a>
20         <a href="http://.">3</a>
21       </div>
22     </div>
23   </LayoutTemplate>
24
25   <AlternatingItemTemplate>
26     <tr class="Alternate">
27       <td><asp:Label ID="movie_idLabel" runat="server"
28         Text='<%# Eval("movie_id") %>' /></td>
29       <td><asp:Label ID="titleLabel" runat="server"
30         Text='<%# Eval("title") %>' /></td>
31       <td><asp:Label ID="release_dateLabel" runat="server"
32         Text='<%# Eval("release_date", "{0:d}") %>' />  </td>
33     </tr>
34   </AlternatingItemTemplate>  
35
36   <ItemTemplate>
37     <tr>
38       <td><asp:Label ID="movie_idLabel" runat="server"
39         Text='<%# Eval("movie_id") %>' /></td>
40       <td><asp:Label ID="titleLabel" runat="server"
41         Text='<%# Eval("title") %>' /></td>
42       <td><asp:Label ID="release_dateLabel" runat="server"
43         Text='<%# Eval("release_date", "{0:d}") %>' /> </td>
44     </tr>
45   </ItemTemplate>
46 </asp:ListView>
 

分頁

我在前一節開始使用的原始 HTML 設計,在設計中即隱含有分頁和排序的功能,因此我基於此規格的方格實作工作尚未完成。讓我們從分頁開始,其次再探討排序。

分頁在 ListView 控制項中會透過另一個新控制項 (DataPager) 的引進來完成。藉由將分頁分隔成不同的控制項,DataPager 就會解除分頁 UI 與 ListView 用來呈現資料之項目的聯繫。這就表示您可以將分頁 UI 放在頁面上的任何地方,而且您還可以建立任何數目的 DataPager 控制項。多重分頁控制項的常見用法,是在資料方格的頂端和底部皆提供分頁介面,讓使用者不必捲動方格即可導覽到下一頁 -- 這是很容易用 DataPager 達成的一件事。

讓我們先以前一節的 ListView 範例來實作分頁。建立與 ListView 相關聯的 DataPager 控制項最簡單的方式,就是將 DataPager 控制項嵌入 ListView 的 LayoutTemplate 內:

01 <asp:ListView ID="_moviesGrid"
02   runat="server" DataKeyNames="movie_id"
03   DataSourceID="_moviesDataSource">            
04   <LayoutTemplate>
05     <!-- ... -->
06     <div class="Pagination">
07       <asp:DataPager ID="_moviesGridDataPager" runat="server">
08         <Fields>
09           <asp:NumericPagerField />
10         </Fields>
11       </asp:DataPager>
12     </div>
13   </LayoutTemplate>
14 </asp:ListView>

藉由將 DataPager 嵌入 ListView 的 LayoutTemplate 內,它們就會隱含地相關聯。另一個選項是將 DataPager 放在頁面上的其他位置,然後將其 PagedControlID 設定為相關聯 ListView 的 ID。

在此案例中,NumericPagerField 會顯示我要的介面 -- 就是一系列的數字,顯示成可巡覽頁面的超連結。DataPager 能支援三種型別的欄位:

  • NumericPagerField 會顯示 1 2 3...分頁介面。
  • NextPreviousPagerField 會顯示「下一頁」、「上一頁」、「第一頁」及「最後一頁」按鈕,以便在資料列之間反覆執行。
  • TemplatePagerField 可以讓您使用 PagerTemplate 來定義分頁介面的設計和功能。

DataPager 控制項採用泛型實作,以提供分頁支援給實作 IPageableItemContainer 介面的任何控制項 (目前 ListView 是實作這個介面的唯一控制項),如下所示:

 

1 public interface IPageableItemContainer
2 {
3     event EventHandler<PageEventArgs> TotalRowCountAvailable;
4     void SetPageProperties(int startRowIndex, int maximumRows,
5                            bool databind);
6     int MaximumRows { get; }
7     int StartRowIndex { get; }
8 }

[圖 7] 顯示 ListView、DataPager 以及相關聯之 DataSource 控制項之間的關聯性。DataPager 絕對不會直接與用來填滿 ListView 的 DataSource 互動,但是卻會透過這個介面查詢它需要的資料。

Figure 7 ListView、DataPager 與 DataSource 之間的關聯性 (按影像可放大)

準備分頁時所發生的第一件事,就是 ListView 會向 DataSource 查詢,以了解其是否支援分頁。如果是的話,便會了解其是否可以傳回資料列總數。如果可以的話,ListView 就會擷取資料來源中資料列的總數,然後引發 TotalRowCountAvailable 事件,該事件是隨著其 IPageableItemContainer 介面實作的。任何相關聯的 DataPager 控制項都會訂閱這個事件,並且會使用資料列總數來初始化呈現分頁介面所需要的欄位。然後 DataPager 會叫用 ListView 的 SetPageProperties 方法,來設定初始資料列索引,以及要傳回的資料列數目上限。

當 ListView 從相關聯的資料來源擷取資料時,它會依據 DataPager 所設定的值,僅要求資料列的子集。每當 DataPager 變更其目前的頁面索引 (一般都是由於與使用者的互動),就會再次呼叫 ListView 的 SetPageProperties,以反映目前要擷取的資料列子集。您可以設定 DataPager 控制項的 PageSize 屬性,來變更頁面上所顯示的記錄數目,這個數目會影響它在所對應 ListView 中設定的最大資料列數目資訊。

DataPager 還能支援 QueryStringField 屬性,此屬性徹底的變更了 DataPager 的工作方式。藉由將 QueryStringField 屬性設定為某個字串 (例如 pageNum),就等於是命令 DataPager 發出 HTTP GET 要求,來回應使用者按一下頁面號碼的動作,要求的頁面號碼會透過查詢字串參數來傳送,但會使用您指定的字串,而非傳統的回傳 (POST-Back) 模型。

這項變更有一個有用的副作用,亦即用戶端可以建立書籤,以指向繫結資料之 ListView 控制項中的特定頁面,因為在 URL 中就可以看見頁面號碼。請注意,如果您切換到這種 GET 模型的通訊方式,ASP.NET AJAX UpdatePanel 控制項所使用的回傳攔截機制,就無法攔截分頁的要求並將其轉換成非同步的回傳:

1 <asp:DataPager ID="_moviesGridDataPager" runat="server"
2   QueryStringField="pageNum" >
3   <Fields>
4     <asp:NumericPagerField />
5   </Fields>
6 </asp:DataPager>

請注意,因為 DataPager 完全依賴 ListView 來執行實際的資料分頁,後者又依賴相關聯的 DataSource 控制項,所以對於其他繫結資料之控制項的限制,分頁也會受到相同的限制。例如,唯有當 SqlDataSource 控制項設定為 DataSet 模式時,分頁才能用於 SqlDataSource 控制項,這表示整個結果集會載入記憶體以執行分頁。當然,您也可以用自訂 DataSource 控制項或使用 ObjectDataSource 控制項,來實作自己的自訂分頁。

排序、編輯、插入及刪除

ListView 若不能支援排序,以及建立、讀取、更新及刪除 (CRUD) 等輔助作業,就不算完整。其針對這些命令的實作,皆類似於 FormView 控制項實作命令的方式。

由於 ListView 完全是以樣板導向的,所以能辨認在其樣板內且將 CommandName 屬性設定為以下七個特定命令字串其中之一的某些按鈕:Cancel、Delete、Select、Edit、Insert、Update 及 Sort。每個命令都會初始化 ListView 上的對應動作 -- 因此如果您想要新增排序功能到 ListView,就必須將一個按鈕 ([圖 8] 中的範例使用了 LinkButton) 放進 LayoutTemplate 內,並且將 CommandName 屬性設定為 Sort,以及將 CommandArgument 設定為您想要據以排序資料來源的資料欄名稱。在 [圖 8] 中,我已修改方格中的每個資料欄,使先前的靜態標頭成為可以按一下的連結,來要求 ListView 根據該資料欄排序資料。

Figure 8 Sorting in ListView

01 <asp:ListView ID="_moviesGrid" runat="server" DataKeyNames="movie_id"
02   DataSourceID="_moviesDataSource">            
03   <LayoutTemplate>
04     <div class="PrettyGrid">
05       <table cellpadding="0" cellspacing="0" summary="">
06         <thead>
07           <tr>
08             <th scope="col">
09               <asp:LinkButton ID="_movieIdSortLink"
10                 CommandName="Sort" CommandArgument="movie_id"
11                 runat="server">ID</asp:LinkButton>
12             </th>
13             <th scope="col">
14               <asp:LinkButton ID="_titleSortLink"
15                 CommandName="Sort" CommandArgument="title"
16                 runat="server">Title</asp:LinkButton>
17             </th>
18             <th scope="col">
19               <asp:LinkButton ID="_releaseDateSortLink"
20                 CommandName="Sort" CommandArgument="release_date"
21                 runat="server">Release date</asp:LinkButton>
22             </th>
23           </tr>
24         </thead>  
25     <!-- ... -->
26   </LayoutTemplate>
27 </asp:ListView>

您也可以新增命令按鈕來初始化編輯模式、刪除資料列,或將新的資料列插入資料集內,其中的細節基本上與其他以樣板為基礎的繫結資料之控制項相同 (就像 FormView 和 GridView),所以我就不在此說明。

群組

ListView 最後一項主要的功能,就是能夠將資料群組成子集,非常像 DataList 控制項所提供的功能。DataList 是一種表格式控制項,它會在呈現的資料表的每個儲存格中,呈現單一資料列。您可以藉由設定 RepeatColumns 屬性,來控制要將基礎資料集的多少資料列,群組成單一資料表。

因為 ListView 未限定要呈現資料表,所以它需要用泛型的方法來指定如何將群組項目,以呈現在一起,這就是 GroupTemplate 所執行的作業。[圖 9] 顯示 ListView 內的 LayoutTemplate、GroupTemplate 及 ItemTemplate 項目之間的關聯性。GroupTemplate 可以讓您針對基礎資料集的每 n 個項目指定週邊 HTML,其中 n 是由 ListView 的 GroupItemCount 屬性來指定。

Figure 9 ListView 中的樣板 (按影像可放大)

當您在 ListView 中使用 GroupTemplate 時,不會在 LayoutTemplate 中指定具有 ID 為 itemPlaceholder 的控制項 -- 現在這個控制項必須在 GroupTemplate 中。反之,您會在 LayoutTemplate 中指定具有 ID 為 groupPlaceholder 的控制項 (您可以設定 ListView 的 GroupPlaceholderID 屬性來變更控制項 ID),來說明針對基礎資料集中遇到的每 n 個項目,應該將 GroupTemplate 的內容插入在何處。

例如,[圖 10] 中的 ListView 顯示如何藉由定義 GroupTemplate 來描述資料列,讓 ItemTemplate 只配置儲存格,使得在資料表的每個資料列中,皆顯示來自資料庫的四部電影。結果如 [圖 11] 所示。

 Figure 10 Defining Rows with GroupTemplate

01 <asp:ListView ID="_groupListView" runat="server"
02   DataKeyNames="movie_id" DataSourceID="_moviesDataSource"
03   GroupItemCount="4" >
04   <GroupTemplate>
05     <tr>
06       <asp:PlaceHolder runat="server" ID="itemPlaceholder" />
07     </tr>
08   </GroupTemplate>
09   <LayoutTemplate>
10     <table>
11       <asp:PlaceHolder ID="groupPlaceholder" runat="server" />
12     </table>
13   </LayoutTemplate>
14   <ItemTemplate>
15     <td>
16       movie_id:
17       <asp:Label ID="_movie_idLabel" runat="server"
18         Text='<%# Eval("movie_id") %>' /> <br />
19       title:
20       <asp:Label ID="_titleLabel" runat="server"
21         Text='<%# Eval("title") %>' /> <br />
22       release_date:
23       <asp:Label ID="_release_dateLabel" runat="server"
24         Text='<%# Eval("release_date", "{0:d}") %>' /> <br />
25       <br />
26     </td>
27   </ItemTemplate>
28 </asp:ListView>
 

Figure 11 所產生網頁的 GroupTemplate 資料列 (按影像可放大)

這與您可以用 DataList 執行的作業非常類似,但是因為您使用的是 ListView,所以可以輕易地新增分頁和排序功能,就像先前用方格呈現所做的一樣簡單,這項工作若用 DataList 執行則會相當令人怯步。本文的可下載程式碼含有實作分頁和排序功能的範例,可供您參考。

開始發揮 ListView 的威力

建議您使用 Visual Studio 2008 中的設計工具,來開始試用 ListView 控制項,此舉可讓您從以下五種不同的配置中挑選:方格、並排、項目符號清單、流程及單一資料列。您可以快速地看見不同的配置選項 -- 但是 ListView 真正的威力在於您可以對其呈現之 HTML 執行的控制項,因此您在真實的專案中,很可能需要花費較多的時間自行建立 LayoutTemplate。未來您會不會針對每個資料繫結的情況下都使用 ListView?這樣可能有一點太極端了 -- 但是知道您可以選擇這麼做,將會給您更大的彈性空間。像我就會盡量使用這個彈性的資料繫結控制項。