出處:http://msdn.microsoft.com/zh-tw/magazine/cc337898.aspx
下載程式碼位址: ExtremeASPNET2008_03.exe (192 KB)
目錄
下載程式碼位址: ExtremeASPNET2008_03.exe (192 KB)
目錄
隨附於 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
<asp:ListView runat="server" ID="_simpleTableListView" DataSourceID="_moviesDataSource"> <LayoutTemplate> <table> <thead> <tr> <th>ID</th> <th>Title</th> <th>Release Date</th> </tr> </thead> <tbody> <asp:PlaceHolder runat="server" ID="itemPlaceholder" /> </tbody> </table> </LayoutTemplate> <ItemTemplate> <tr> <td><%# Eval("movie_id") %></td> <td><%# Eval("title") %></td> <td><%# Eval("release_date", "{0:d}") %></td> </tr> </ItemTemplate> </asp:ListView>
Figure 2 在資料表中顯示的清單 (按影像可放大)
LayoutTemplate 與 ItemTemplate 之間的關聯,是利用 LayoutTemplate 中伺服器端的單一控制項,並將 ID 設定為 itemPlaceholder 來達成的 (您可以使用 ListView 的 ItemPlaceholderID 屬性,來變更 ID 字串的預設值)。在第一個範例中,我在想要加入 ItemTemplate 內容的樣板位置,使用了放置 PlaceHolder 控制項之執行個體的技巧。請注意,雖然可做為預留位置的控制項型別並沒有限制,但是它必須支援子控制項 -- ID 才是最重要的。例如,我如果使用伺服器端的資料表資料列方式來撰寫 LayoutTemplate,而不使用 PlaceHolder 控制項,也能達到相同的目的:
<LayoutTemplate> <table> <thead> <tr> <th>ID</th> <th>Title</th> <th>Release Date</th> </tr> </thead> <tbody> <tr runat="server" ID="itemPlaceholder" /> </tbody> </table> </LayoutTemplate>
一般來說,基於兩項理由,我比較喜歡使用泛型的 PlaceHolder 控制項。第一項是因為名稱可以清楚對應。此外,這個控制項不會呈現自己的 HTML,而是會使用 ItemTemplate 的內容加以取代,因此除了在控制項階層中保留位置以外並沒有其他用途,如此使用控制項看起來是比較合乎邏輯的選擇。
當然,ListView 這麼有彈性的原因,是您可以完全控制 LayoutTemplate 的資訊。您並不會受限於只能使用資料表 -- 您可以在 LayoutTemplate 中放置希望呈現的任何 HTML,只要在 itemPlaceholder 控制項位置插入的 ItemTemplate 內容有意義即可。以下是 ListView 繫結至同一個電影資料來源的範例,但是這一回不使用資料表,而是以項目符號清單顯示電影標題和發行日期 (結果顯示於 [圖 3] 中):
Figure 3 相同的清單,不同的格式 (按影像可放大)
<asp:ListView runat="server" ID="_simpleTableListView" DataSourceID="_moviesDataSource"> <LayoutTemplate> <ul> <asp:PlaceHolder runat="server" ID="itemPlaceholder" /> </ul> </LayoutTemplate> <ItemTemplate> <li><%# Eval("title") %>, <%#Eval("release_date","{0:d}") %></li> </ItemTemplate> </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
<div class="PrettyGrid"> <table cellpadding="0" cellspacing="0" summary=""> <thead> <tr> <th scope="col"><a href="http://.">ID</a></th> <th scope="col"><a href="http://.">Title</a></th> <th scope="col"><a href="http://.">Release date</a></th> </tr> </thead> <tbody> <tr> <td>1</td> <td>Where the Wild Things Are</td> <td>12/15/2008</td> </tr> <!-- ... --> </tbody> </table> <div class="Pagination"> <span>1</span> <a href="http://.">2</a> <a href="http://.">3</a> </div> </div>
.PrettyGrid { width: 100%; } .PrettyGrid div.Pagination, .PrettyGrid div.Pagination a, .PrettyGrid div.Pagination span { color: #00FFFF; background: #284775; font-weight: normal; padding: 2px; } .PrettyGrid table { border: solid 1px #CCCCCC; width: 100%; } /*...*/
Figure 4 資料表的目標設計 (按影像可放大)
您可以迅速建構可呈現與 HTML/CSS 組合完全一樣的 ListView,只要從 HTML 中取得適當的區段,並且將其放置在對應的樣板內,如 [圖 6] 所示。最終的結果看起來會與完全使用 CSS 樣式建立的原始 HTML 一模一樣。若要修改設計也很容易,只要更新 HTML 或對應的 CSS 就可以了。
Figure 6 ListView to Construct the Table
<asp:ListView ID="_moviesGrid" runat="server" DataKeyNames="movie_id" DataSourceID="_moviesDataSource"> <LayoutTemplate> <div class="PrettyGrid"> <table cellpadding="0" cellspacing="0" summary=""> <thead> <tr> <th scope="col"><a href="http://.">ID</a ></th> <th scope="col"><a href="http://.">Title</a></th> <th scope="col"><a href="http://.">Release date</a></th> </tr> </thead> <tbody> <asp:PlaceHolder ID="itemPlaceholder" runat="server" /> </tbody> </table> <div class="Pagination"> <span>1</span> <a href="http://.">2</a> <a href="http://.">3</a> </div> </div> </LayoutTemplate> <AlternatingItemTemplate> <tr class="Alternate"> <td><asp:Label ID="movie_idLabel" runat="server" Text='<%# Eval("movie_id") %>' /></td> <td><asp:Label ID="titleLabel" runat="server" Text='<%# Eval("title") %>' /></td> <td><asp:Label ID="release_dateLabel" runat="server" Text='<%# Eval("release_date", "{0:d}") %>' /> </td> </tr> </AlternatingItemTemplate> <ItemTemplate> <tr> <td><asp:Label ID="movie_idLabel" runat="server" Text='<%# Eval("movie_id") %>' /></td> <td><asp:Label ID="titleLabel" runat="server" Text='<%# Eval("title") %>' /></td> <td><asp:Label ID="release_dateLabel" runat="server" Text='<%# Eval("release_date", "{0:d}") %>' /> </td> </tr> </ItemTemplate> </asp:ListView>
分頁
我在前一節開始使用的原始 HTML 設計,在設計中即隱含有分頁和排序的功能,因此我基於此規格的方格實作工作尚未完成。讓我們從分頁開始,其次再探討排序。
分頁在 ListView 控制項中會透過另一個新控制項 (DataPager) 的引進來完成。藉由將分頁分隔成不同的控制項,DataPager 就會解除分頁 UI 與 ListView 用來呈現資料之項目的聯繫。這就表示您可以將分頁 UI 放在頁面上的任何地方,而且您還可以建立任何數目的 DataPager 控制項。多重分頁控制項的常見用法,是在資料方格的頂端和底部皆提供分頁介面,讓使用者不必捲動方格即可導覽到下一頁 -- 這是很容易用 DataPager 達成的一件事。
讓我們先以前一節的 ListView 範例來實作分頁。建立與 ListView 相關聯的 DataPager 控制項最簡單的方式,就是將 DataPager 控制項嵌入 ListView 的 LayoutTemplate 內:
<asp:ListView ID="_moviesGrid" runat="server" DataKeyNames="movie_id" DataSourceID="_moviesDataSource"> <LayoutTemplate> <!-- ... --> <div class="Pagination"> <asp:DataPager ID="_moviesGridDataPager" runat="server"> <Fields> <asp:NumericPagerField /> </Fields> </asp:DataPager> </div> </LayoutTemplate> </asp:ListView>
藉由將 DataPager 嵌入 ListView 的 LayoutTemplate 內,它們就會隱含地相關聯。另一個選項是將 DataPager 放在頁面上的其他位置,然後將其 PagedControlID 設定為相關聯 ListView 的 ID。
在此案例中,NumericPagerField 會顯示我要的介面 -- 就是一系列的數字,顯示成可巡覽頁面的超連結。DataPager 能支援三種型別的欄位:
- NumericPagerField 會顯示 1 2 3...分頁介面。
- NextPreviousPagerField 會顯示「下一頁」、「上一頁」、「第一頁」及「最後一頁」按鈕,以便在資料列之間反覆執行。
- TemplatePagerField 可以讓您使用 PagerTemplate 來定義分頁介面的設計和功能。
DataPager 控制項採用泛型實作,以提供分頁支援給實作 IPageableItemContainer 介面的任何控制項 (目前 ListView 是實作這個介面的唯一控制項),如下所示:
public interface IPageableItemContainer { event EventHandler<PageEventArgs> TotalRowCountAvailable; void SetPageProperties(int startRowIndex, int maximumRows, bool databind); int MaximumRows { get; } int StartRowIndex { get; } }
[圖 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 控制項所使用的回傳攔截機制,就無法攔截分頁的要求並將其轉換成非同步的回傳:
<asp:DataPager ID="_moviesGridDataPager" runat="server" QueryStringField="pageNum" > <Fields> <asp:NumericPagerField /> </Fields> </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
<asp:ListView ID="_moviesGrid" runat="server" DataKeyNames="movie_id" DataSourceID="_moviesDataSource"> <LayoutTemplate> <div class="PrettyGrid"> <table cellpadding="0" cellspacing="0" summary=""> <thead> <tr> <th scope="col"> <asp:LinkButton ID="_movieIdSortLink" CommandName="Sort" CommandArgument="movie_id" runat="server">ID</asp:LinkButton> </th> <th scope="col"> <asp:LinkButton ID="_titleSortLink" CommandName="Sort" CommandArgument="title" runat="server">Title</asp:LinkButton> </th> <th scope="col"> <asp:LinkButton ID="_releaseDateSortLink" CommandName="Sort" CommandArgument="release_date" runat="server">Release date</asp:LinkButton> </th> </tr> </thead> <!-- ... --> </LayoutTemplate> </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
<asp:ListView ID="_groupListView" runat="server" DataKeyNames="movie_id" DataSourceID="_moviesDataSource" GroupItemCount="4" > <GroupTemplate> <tr> <asp:PlaceHolder runat="server" ID="itemPlaceholder" /> </tr> </GroupTemplate> <LayoutTemplate> <table> <asp:PlaceHolder ID="groupPlaceholder" runat="server" /> </table> </LayoutTemplate> <ItemTemplate> <td> movie_id: <asp:Label ID="_movie_idLabel" runat="server" Text='<%# Eval("movie_id") %>' /> <br /> title: <asp:Label ID="_titleLabel" runat="server" Text='<%# Eval("title") %>' /> <br /> release_date: <asp:Label ID="_release_dateLabel" runat="server" Text='<%# Eval("release_date", "{0:d}") %>' /> <br /> <br /> </td> </ItemTemplate> </asp:ListView>
Figure 11 所產生網頁的 GroupTemplate 資料列 (按影像可放大)
這與您可以用 DataList 執行的作業非常類似,但是因為您使用的是 ListView,所以可以輕易地新增分頁和排序功能,就像先前用方格呈現所做的一樣簡單,這項工作若用 DataList 執行則會相當令人怯步。本文的可下載程式碼含有實作分頁和排序功能的範例,可供您參考。
開始發揮 ListView 的威力
建議您使用 Visual Studio 2008 中的設計工具,來開始試用 ListView 控制項,此舉可讓您從以下五種不同的配置中挑選:方格、並排、項目符號清單、流程及單一資料列。您可以快速地看見不同的配置選項 -- 但是 ListView 真正的威力在於您可以對其呈現之 HTML 執行的控制項,因此您在真實的專案中,很可能需要花費較多的時間自行建立 LayoutTemplate。未來您會不會針對每個資料繫結的情況下都使用 ListView?這樣可能有一點太極端了 -- 但是知道您可以選擇這麼做,將會給您更大的彈性空間。像我就會盡量使用這個彈性的資料繫結控制項。