您將需要用到的專屬資料繫結控制項。Fritz Onion
代碼下載位置: ExtremeASPNET2008_03.exe
(192 KB)
Browse the Code
Online
目錄 ListView 基礎
ListView 和 CSS
分頁
排序、編輯、插入和刪除
分組
開始執行 ListView
隨 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 內容放在 ItemTemplate 裡。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
字串的預設值。)在第一個樣本中,我將 PlaceHolder 控制項的執行個體放置在模板中,即我希望注入 ItemTemplate
內容的位置。請注意:儘管必須支援子控制項,但並沒有限制必須使用什麼類型的控制項作為預留位置——ID 才是重要。例如,我可以使用伺服器端表格行代替
PlaceHolder 控制項編寫 LayoutTemplate,實現同樣的效果:
複製代碼
<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
的內容。您不是只能使用表格——您可以將任何希望呈現的 HTML 放置在 LayoutTemplate 中,只要注入 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 和 CSSASP.NET 開發人員長久以來在建立 CSS
驅動的網站時都受到單獨控制項的限制。許多預設控制項呈現內聯樣式,或者難於使 CSS 類與其 HTML 輸出部分相關聯。實際上 Microsoft 在 2006 年
4 月發行名為“CSS 控制項適配器”的工具包,該工具包為幾個完全由 CSS 驅動的控制項(包括
GridView)提供了可選呈現機制,協助糾正該問題(有關詳細資料,請參見 2006 年 10 月的“非常 ASP.NET”專欄 msdn.microsoft.com/msdnmag/issues/06/10/ExtremeASPNET)。這些備用呈現機制從未併入完整版當中,所以需要單獨安裝且缺少設計人員支援。ListView 通過讓您完全控制何時何地應用樣式表,使您在網站裡利用 CSS
變得更為簡捷。一種常見的情形是開發人員為特定頁面手動預先設計,通常包含 HTML 和 CSS。採用傳統的 GridView
呈現資料表的特定設計總是很難保證正確,因為 GridView 類僅提供用於修改 HTML 結果的有限掛接集。我見過許多開發人員經曆過的實驗和錯誤,將樣式屬性應用到網格,查看頁面源以準確理解樣式放置的位置,並反覆實驗直到網格能夠按要求呈現為止。使用
ListView,您不必再做這些猜測工作了,因為現在您可以控制布局和內容。例如,假設提供給您的表格需要按照
圖 4 所示的方式顯示,並使用由
.htm 和 .css 檔案組成的設計,如
圖 5中所示。 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>
CSS
複製代碼
.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 中選取適當部分,並將其置於的對應模板中,來快速建立能夠精確按照
HTML/CSS 組合要求呈現的 ListView,如
圖 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
以便與 ListView 關聯。在這種特例中,NumericPagerField
精確顯示了我想要的介面——就是一系列顯示為超連結的數字,可以導航至頁面。DataPager 支援三種類型的欄位:
- NumericPagerField 顯示 1 2 3... 分頁介面。
- NextPreviousPagerField
顯示“Next”(下一頁)、“Previous”(上一頁)、“First”(第一頁)和“Last”(最後一頁)按鈕在行間往複。
- 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
回傳模式發送。該變化帶來的另一個好處是:它讓用戶端能夠建立到資料繫結 ListView
控制項中特定頁面的書籤,因為可以在 URL 中看到頁面編號。請注意,如果切換到這種 GET 通訊模式,那由 ASP.NET AJAX UpdatePanel
控制項使用的 POST 回傳掛接機制將無法截取分頁請求,並會將其變為非同步回傳:
複製代碼
<asp:DataPager ID="_moviesGridDataPager" runat="server"
QueryStringField="pageNum" >
<Fields>
<asp:NumericPagerField />
</Fields>
</asp:DataPager>
請注意:因為 DataPager 完全依賴 ListView 執行實際的資料分頁,而 ListView
又依賴 DataSource 控制項,所以對於其它資料繫結控制項也存在相同的分頁限制。例如,對於 SqlDataSource 控制項,僅當其設定為 DataSet
模式時分頁才能正常工作,這意味著需要將整個結果集載入到記憶體中才能執行分頁。當然,您可以使用自訂 DataSource 控制項或使用
ObjectDataSource 控制項自訂自己的分頁。
排序、編輯、插入和刪除如果 ListView 不具備支援排序和完備的建立、讀取、更新和刪除 (CRUD)
操作,那麼它是不完整的。它實現每種命令的方式與 FormView 控制項實現命令的方式相似。因為 ListView 完全由模板驅動,所以對於其模板中將 CommandName
屬性設定為以下七種特定命令字串之一的按鈕,能夠予以識別:Cancel(取消)、Delete(刪除)、Select(選擇)、Edit(編輯)、Insert(插入)、Update(更新)和
Sort(排序)。每個命令都會啟動 ListView 中的相應操作——這樣如果希望為 ListView 添加排序,那隻需要在 LayoutTemplate
中放置一個按鈕(
圖 8 的樣本中使用 LinkButton),將其 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 中指定帶有 itemPlaceholder ID 的控制項——該控制項現在需要位於 GroupTemplate 之內。而是需要在
LayoutTemplate 中指定帶有 groupPlaceholder ID 的控制項(可以通過設定 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
結果 Web 頁面中的 GroupTemplate
行 (單擊該映像獲得較大視圖)這與使用 DataList 完成的工作非常相似,但因為使用的是
ListView,所以可以像前面所示的網格呈現一樣輕鬆添加分頁和排序功能,而使用 DataList
完成這些工作則非常複雜。用於本文的下載程式碼封裝含實現了分頁和排序功能的樣本供您參考。
開始執行 ListView您可能希望通過使用 Visual Studio 2008 中的設計器來試用 ListView
控制項,該設計器可以提供五種不同的布局供您選擇:網格、平鋪、項目符號清單、流動和單行。您可以快速查看可用的各種布局選項——但 ListView
真正強大的功能在於您對它所呈現 HTML 的控制,所以在實際的項目中您很可能要自行構建 LayoutTemplate。您是否最後決定今後每次遇到資料繫結都使用
ListView 呢?雖然可能有點過頭——但知道您會這樣做我還是很高興。我想今後我還會更多地研究這個靈活的資料繫結控制項。
請將您想向 Fritz 詢問的問題和提出的意見發送至 xtrmaspt@microsoft.com.
Fritz Onion 是 Pluralsight(一家 Microsoft
.NET 培訓供應商)的創始人之一,負責主持 Web 開發課題。Fritz 是
《Essential ASP.NET》和《Essential
ASP.NET 2.0》的作者。要瞭解本作者,請登入 pluralsight.com/fritz。