由 斯科特·米切爾
為了提供彈性,GridView 會提供TemplateField,其會使用範本呈現。 範本可以包含靜態 HTML、Web 控制項和數據系結語法的組合。 在本教學課程中,我們將檢查如何使用TemplateField,以使用 GridView 控件達到更高的自定義程度。
簡介
GridView 是由一組字段所組成,這些欄位會指出要包含在轉譯輸出中的屬性 DataSource
,以及數據的顯示方式。 最簡單的欄位類型是 BoundField,其會將數據值顯示為文字。 其他欄位類型會使用替代 HTML 元素來顯示數據。 例如,CheckBoxField 會轉譯為複選框,其核取狀態取決於指定數據欄位的值;ImageField 會轉譯影像來源以指定的數據欄位為基礎。 可以使用 HyperLinkField 和 ButtonField 字段類型來轉譯狀態相依於基礎數據域值的超連結和按鈕。
雖然 CheckBoxField、ImageField、HyperLinkField 和 ButtonField 字段類型允許替代數據檢視,但它們在格式化方面仍然相當有限。 CheckBoxField 只能顯示單一複選框,而 ImageField 只能顯示單一影像。 如果特定欄位需要根據不同的數據域值來顯示某些文字、複選框 和 影像,該怎麼辦? 或者,如果我們想要使用 CheckBox、Image、HyperLink 或 Button 以外的 Web 控件來顯示數據,該怎麼辦? 此外,BoundField 會將其顯示限制為單一數據欄位。 如果我們想要在單一 GridView 數據行中顯示兩個或多個數據域值,該怎麼辦?
為了配合此層級的彈性,GridView 會提供TemplateField,其會使用 範本轉譯。 範本可以包含靜態 HTML、Web 控制項和數據系結語法的組合。 此外,TemplateField 擁有多種範本,可用來自訂不同情況下的呈現。 例如,ItemTemplate
預設用於呈現每個行的單元格,而在編輯數據時可使用 EditItemTemplate
範本來自訂介面。
在本教學課程中,我們將檢查如何使用TemplateField,以使用 GridView 控件達到更高的自定義程度。 在 上述教學課程中 ,我們已瞭解如何使用 DataBound
和 RowDataBound
事件處理程式,根據基礎數據自定義格式設定。 另一個根據基礎數據自定義格式的方法,是從範本內呼叫格式化方法。 我們也會在此教學課程中查看這項技術。
在這個教學課程中,我們將使用 TemplateFields 來客製化員工名單的外觀。 具體來說,我們會列出所有員工,但會在一個數據行中顯示員工的名字和姓氏、他們在行事歷控件中的僱用日期,以及一個狀態數據行,指出他們在公司雇用的天數。
圖 1:三個 TemplateField 用來自訂顯示(按此檢視全尺寸圖片)
步驟 1:將數據系結至 GridView
在您需要使用TemplateFields來自定義外觀的報告案例中,我發現最容易從建立只包含 BoundFields 的 GridView 控件開始,然後視需要新增 TemplateFields 或將現有的 BoundFields 轉換為 TemplateFields。 因此,讓我們透過設計工具將 GridView 新增至頁面,並將它系結至會傳回員工清單的 ObjectDataSource,來開始本教學課程。 這些步驟會為每個員工欄位建立具有 BoundFields 的 GridView。
開啟GridViewTemplateField.aspx
頁面並將 GridView 從 [工具箱] 拖曳至設計工具。 從 GridView 的智慧標籤中選擇新增一個新的 ObjectDataSource 控制項,以調用 EmployeesBLL
類別的 GetEmployees()
方法。
圖 2:新增叫用 GetEmployees()
方法的 ObjectDataSource 控件 (按兩下以檢視完整大小的影像)
以這種方式系結 GridView 會自動為每個員工屬性新增 BoundField:EmployeeID
、LastName
、FirstName
、Title
、HireDate
、ReportsTo
和 Country
。 針對此報表,我們不要費心顯示 EmployeeID
、 ReportsTo
或 Country
屬性。 若要移除這些 BoundFields,您可以:
- 使用 [欄位] 對話框,按下 GridView 智慧標記中的 [編輯資料行] 連結,以顯示此對話方塊。 接下來,從左下方清單選取 BoundFields,然後按兩下紅色 X 按鈕以移除 BoundField。
- 在「原始檢視」中手動編輯 GridView 的宣告式語法,並刪除您想移除之 BoundField 的
<asp:BoundField>
元素。
移除 EmployeeID
、 ReportsTo
和 Country
BoundFields 之後,GridView 的標記看起來應該像這樣:
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False" DataKeyNames="EmployeeID"
DataSourceID="ObjectDataSource1">
<Columns>
<asp:BoundField DataField="LastName" HeaderText="LastName"
SortExpression="LastName" />
<asp:BoundField DataField="FirstName" HeaderText="FirstName"
SortExpression="FirstName" />
<asp:BoundField DataField="Title" HeaderText="Title"
SortExpression="Title" />
<asp:BoundField DataField="HireDate" HeaderText="HireDate"
SortExpression="HireDate" />
</Columns>
</asp:GridView>
花點時間在瀏覽器中檢視我們的進度。 此時,您應該會看到一個數據表,其中包含每個員工的記錄和四個數據行:一個用於員工的姓氏,一個用於他們的名字,一個用於他們的職稱,另一個用於他們的僱用日期。
圖 3: 每個員工的 LastName
、 FirstName
、 Title
和 HireDate
欄位已顯示 (點擊以檢視完整大小的影像)
步驟 2:在單一數據行中顯示名字和姓氏
目前,每個員工的名字和姓氏都會顯示在個別的數據行中。 或許可以考慮將它們合併成單一欄位。 若要達成此目的,我們需要使用TemplateField。 我們可以新增TemplateField、加入所需的標記和數據系結語法,然後刪除 FirstName
和 LastName
BoundFields,或者我們可以將 BoundField 轉換成 FirstName
TemplateField、編輯 TemplateField 以包含 LastName
值,然後移除 LastName
BoundField。
這兩種方法都會達到相同的結果,但就個人而言,我喜歡盡可能將 BoundFields 轉換成 TemplateField,因為轉換會自動新增 ItemTemplate
和 EditItemTemplate
與 Web 控件和數據系結語法,以模擬 BoundField 的外觀和功能。 優點是,我們需要對 TemplateField 執行較少的工作,因為轉換程式會為我們執行一些工作。
若要將現有的 BoundField 轉換成 TemplateField,請按兩下 GridView 智慧標記中的 [編輯數據行] 連結,並顯示 [欄位] 對話框。 選取 [BoundField] 以從左下角的清單轉換,然後按兩下右下角的 [將此字段轉換成TemplateField] 連結。
圖 4:從 [字段] 對話框將 BoundField 轉換成 TemplateField (按兩下以檢視完整大小的影像)
繼續將 FirstName
BoundField 轉換成 TemplateField。 在這項變更之後,「Designer」沒有明顯差異。 這是因為將 BoundField 轉換成 TemplateField 會建立 TemplateField,以維護 BoundField 的外觀和風格。 儘管設計師目前沒有視覺差異,但此轉換程式已以下列 TemplateField 語法取代 BoundField 的宣告式語法 <asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" />
:
<asp:TemplateField HeaderText="FirstName" SortExpression="FirstName">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
如您所見,TemplateField 由兩個範本組成:一個 ItemTemplate
,其中有 Label,其 Text
屬性設為 FirstName
數據欄位的值,以及一個 EditItemTemplate
,這裡有 TextBox 控件,其 Text
屬性也設為 FirstName
數據欄位的值。 數據系結語法 - <%# Bind("fieldName") %>
指出數據欄位 fieldName
系結至指定的 Web 控件屬性。
若要將LastName
數據欄位值新增至此TemplateField,我們需要在ItemTemplate
中新增另一個標籤Web控制件,並將其Text
屬性綁定至LastName
。 這可以透過手動或透過設計工具來完成。 若要手動執行,只需將適當的宣告式語法新增至 ItemTemplate
:
<asp:TemplateField HeaderText="FirstName" SortExpression="FirstName">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:Label>
<asp:Label ID="Label2" runat="server"
Text='<%# Bind("LastName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
若要透過設計工具新增它,請按下 GridView 智慧標記中的 [編輯範本] 連結。 這會顯示 GridView 的範本編輯介面。 在這個介面的 smart tag 中,是 GridView 裡的範本清單。 由於我們目前只有一個 TemplateField,因此下拉式清單中所列出的範本是針對FirstName
的模板範本,以及EmptyDataTemplate
和PagerTemplate
的模板範本。 如果指定,EmptyDataTemplate
範本會用於當系結至 GridView 的數據沒有結果時轉譯 GridView 的輸出;PagerTemplate
範本會用於當 GridView 支援分頁時轉譯分頁介面。
圖 5:GridView 的範本可以透過設計工具編輯 (按兩下以檢視完整大小的影像)
若要在 LastName
TemplateField 中顯示 FirstName
,請將 [標籤] 控件從工具箱拖曳到 GridView 範本編輯介面中的 FirstName
TemplateField ItemTemplate
。
圖 6:將標籤 Web 控件新增至 FirstName
TemplateField 的 ItemTemplate (按兩下以檢視完整大小的影像)
此時,新增至 TemplateField 的標籤 Web 控件已將其 Text
屬性設定為 「Label」。 我們需要變更此屬性,讓此屬性改為系結至數據欄位的值 LastName
。 若要完成此動作,請點擊 Label 控制項的智慧標記,然後選擇 [編輯資料綁定] 選項。
圖 7:從標籤的智慧標記中選擇 [編輯資料系結] 選項 (按兩下以檢視完整大小的影像)
這會顯示 [DataBindings] 對話框。 從這裡,您可以從左側清單中選取要參與數據系結的屬性,然後選擇要從右邊的下拉式清單中將數據系結至的欄位。 從左側選擇Text
屬性,從右側選擇LastName
欄位,然後點擊 [確定]。
圖 8:將 Text
屬性系結至 LastName
資料欄位 (按兩下以檢視完整大小的影像)
備註
[DataBindings] 對話框可讓您指出是否要執行雙向數據系結。 如果您將此保持未核取狀態,則會使用資料系結語法 <%# Eval("LastName")%>
,而不是 <%# Bind("LastName")%>
。 這兩種方法都適用於本教學課程。 插入和編輯數據時,雙向數據系結會變得很重要。 不過,只要顯示數據,任一種方法都同樣正常運作。 我們將在未來的教學課程中詳細討論雙向數據系結。
請花點時間透過瀏覽器查看此頁面。 如您所見,GridView 仍然包含四個欄。不過,FirstName
欄現在會同時列出 和 FirstName
的數據域值。
圖 9: FirstName
和 LastName
值都會顯示在單一數據列中(按兩下以檢視完整大小的影像)
若要完成第一個步驟,請移除 LastName
BoundField,並將 TemplateField 的 FirstName
屬性重新命名HeaderText
為 “Name”。 在這些變更之後,GridView 的宣告式標記看起來應該如下所示:
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False" DataKeyNames="EmployeeID"
DataSourceID="ObjectDataSource1">
<Columns>
<asp:TemplateField HeaderText="Name" SortExpression="FirstName">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:Label>
<asp:Label ID="Label2" runat="server"
Text='<%# Eval("LastName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Title" HeaderText="Title"
SortExpression="Title" />
<asp:BoundField DataField="HireDate" HeaderText="HireDate"
SortExpression="HireDate" />
</Columns>
</asp:GridView>
圖 10:每個員工的名字和姓氏會顯示在一個數據行中(按兩下以檢視完整大小的影像)
步驟 3:使用行事曆控件顯示HiredDate
欄位
在 GridView 中以文字顯示資料域值就像使用 BoundField 一樣簡單。 不過,在某些案例中,數據最好是使用特定的 Web 控件來表示,而不只是文字。 TemplateFields 可以自定義數據的顯示。 例如,我們可以顯示行事曆(使用 行事歷控件)並醒目提示其雇用日期,而不是將員工的僱用日期顯示為文字。
若要達成此目的,請先將 HiredDate
BoundField 轉換成 TemplateField。 只要移至 GridView 的智慧標記,然後按兩下 [編輯資料行] 連結,就會顯示 [字段] 對話方塊。 選取 HiredDate
BoundField,然後點擊“將此欄位轉換成 TemplateField”。
圖 11:將 HiredDate
BoundField 轉換成 TemplateField (按兩下以檢視完整大小的影像)
如我們在步驟 2 中所見,這會將 BoundField 取代為 TemplateField,該 TemplateField 中包含一個 Label 和一個 TextBox,這兩者的 ItemTemplate
屬性使用數據系結語法 EditItemTemplate
來系結至 Text
的值。
若要將文字取代為 Calendar 控制件,請移除標籤並新增行事歷控件來編輯範本。 從設計工具中選取 GridView 的智慧標記中的 [編輯範本],然後從下拉式清單中選擇 HireDate
TemplateField 的 ItemTemplate
。 接下來,刪除 [卷標] 控件,並將 [行事曆] 控件從 [工具箱] 拖曳至範本編輯介面。
圖 12:將行事歷控件新增至 HireDate
TemplateField 的 ItemTemplate
(按兩下以檢視完整大小的影像)
此時,GridView 中的每個數據列都會在其 HiredDate
TemplateField 中包含 Calendar 控件。 不過,員工的實際 HiredDate
值不會在 Calendar 控件的任何位置設定,導致每個 Calendar 控件預設顯示目前的月份和日期。 若要解決此問題,我們需要將每位員工的 HiredDate
指派給行事歷控件的 SelectedDate 和 VisibleDate 屬性。
從行事曆控件的智慧標記中,選擇 [編輯 DataBindings]。 接下來,將 SelectedDate
和 VisibleDate
屬性系結至 HiredDate
數據欄位。
圖 13:將 SelectedDate
和 VisibleDate
屬性系結至 HiredDate
資料欄位(按一下即可查看完整大小的圖像)
備註
行事歷控件的選取日期不一定可見。 例如,行事曆可能會有 1999 年 8 月 1 日作為選取的日期,但會顯示目前的月份和年份。 選取的日期和可見日期是由行事曆控件的 SelectedDate
和 VisibleDate
屬性所指定。 由於我們想要同時選取員工的 HiredDate
,並確定它已顯示,我們需要將這兩個屬性系結至 HireDate
數據欄位。
在瀏覽器中檢視頁面時,行事歷現在會顯示員工僱用日期的月份,並選取該特定日期。
圖 14:員工的HiredDate
顯示在日曆控制項中(點擊檢視完整大小圖片)
備註
與我們目前看到的所有範例相反,在本教學課程中,我們沒有將 GridView 的屬性設定為EnableViewState
。 此決定的原因是按兩下 [行事曆] 控制的日期會造成回傳,將 [行事曆] 的選取日期設定為剛按下的日期。 不過,如果 GridView 的檢視狀態已停用,則每次回傳時,GridView 的資料都會重新綁定到其基礎資料來源,這會導致行事曆的選定日期被重設為員工的預設日期,並覆寫用戶選擇的日期。
在本教學中,這是一個無關緊要的討論,因為使用者無法更新員工的 HireDate
。 最好設定行事歷控件,使其日期無法選取。 不論怎樣,本教學課程都會示範在某些情況下必須啟用檢視狀態,才能提供特定功能。
步驟 4:顯示員工為公司工作天數
到目前為止,我們看到了TemplateFields的兩個應用程式:
- 將兩個或多個數據域值合併成一個數據行,以及
- 使用 Web 控制件而非文字來表示資料域值
第三個用處是使用 TemplateFields 顯示有關 GridView 基礎數據的元數據。 例如,除了顯示員工的僱用日期之外,我們也可能想要有一個數據行來顯示他們在工作中的總天數。
另一個使用 TemplateFields 的情境是當基礎數據需要在網頁報表中以不同於其在資料庫中的格式顯示時。 假設Employees
數據表有一個字段,用於儲存代表員工性別的字元,該字段可能包含Gender
或M
。 在網頁中顯示這項資訊時,我們可能會想要將性別顯示為「男性」或「女性」,而不只是 「M」或「F」。
這兩種情境都可以透過在 ASP.NET 頁面的後端程式類別中建立 格式化方法 來處理(或者在獨立的類別庫中實作該方法,並從模板中呼叫)。 使用稍早所見的相同數據系結語法,從範本叫用這類格式化方法。 格式化方法可以採用任意數目的參數,但必須傳回字串。 這個傳回的字串是插入範本的 HTML。
為了說明這個概念,我們來增強我們的教學,展示一個列出員工在職天數總數的欄位。 這個格式化方法將接收一個 Northwind.EmployeesRow
物件,並傳回員工受僱天數作為字串。 這個方法可以新增至 ASP.NET 頁面的程式代碼後置類別,但 必須 標示為 protected
或 public
,才能從範本存取。
protected string DisplayDaysOnJob(Northwind.EmployeesRow employee)
{
// Make sure HiredDate is not null... if so, return "Unknown"
if (employee.IsHireDateNull())
return "Unknown";
else
{
// Returns the number of days between the current
// date/time and HireDate
TimeSpan ts = DateTime.Now.Subtract(employee.HireDate);
return ts.Days.ToString("#,##0");
}
}
HiredDate
由於欄位可以包含NULL
資料庫值,因此我們必須先確定該值不是NULL
在繼續計算之前。
HiredDate
如果值為 NULL
,我們只會傳回字串 「Unknown」;如果不是 NULL
,我們會計算目前時間與HiredDate
值之間的差異,並傳回天數。
若要使用此方法,我們需要使用數據系結語法,從 GridView 中的 TemplateField 叫用它。 首先,按兩下 GridView 智慧標記中的 [編輯資料行] 連結並新增TemplateField,以將新的TemplateField新增至 GridView。
圖 15:將新的 TemplateField 新增至 GridView (按兩下以檢視完整大小的影像)
將這個新的 TemplateField HeaderText
屬性設定為 「Days on the Job」,並將其 ItemStyle
的 HorizontalAlign
屬性設定為 Center
。 若要從範本呼叫 DisplayDaysOnJob
方法,請新增 ItemTemplate
,並使用下列數據系結語法:
<%# DisplayDaysOnJob((Northwind.EmployeesRow)
((System.Data.DataRowView) Container.DataItem).Row) %>
Container.DataItem
傳回對應至系結於 DataRowView
的 DataSource
記錄的 GridViewRow
物件。 其 Row
屬性會傳回強型別 Northwind.EmployeesRow
,並將其傳遞至 DisplayDaysOnJob
方法。 此數據系結語法可以直接出現在 ItemTemplate
中(如下列宣告式語法所示),也可以指派給 Text
標籤 Web 控件的 屬性。
備註
或者,我們可以不傳入 EmployeesRow
實例,而是使用 HireDate
傳入 <%# DisplayDaysOnJob(Eval("HireDate")) %>
值。 不過,Eval
方法會傳回 object
,因此我們必須更改 DisplayDaysOnJob
方法的簽名,以接受類型為 object
的輸入參數。 我們無法盲目地將Eval("HireDate")
呼叫轉換成DateTime
,因為HireDate
表格中的Employees
列可能包含NULL
值。 因此,我們需要接受 object
作為 DisplayDaysOnJob
方法的輸入參數,檢查是否有資料庫 NULL
值(可以使用 Convert.IsDBNull(objectToCheck)
完成),然後據以繼續。
由於這些微妙之處,我選擇傳遞整個 EmployeesRow
實例。 在下一個教學課程中,我們將看到一個更合適的範例,展示如何使用 Eval("columnName")
語法來將輸入參數傳遞至格式化方法。
以下顯示 GridView 在新增 TemplateField 之後的宣告式語法,以及 DisplayDaysOnJob
從 ItemTemplate
呼叫的方法:
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False" DataKeyNames="EmployeeID"
DataSourceID="ObjectDataSource1">
<Columns>
<asp:TemplateField HeaderText="Name" SortExpression="FirstName">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server"
Text='<%# Bind("FirstName") %>'></asp:Label>
<asp:Label ID="Label2" runat="server"
Text='<%# Eval("LastName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Title" HeaderText="Title"
SortExpression="Title" />
<asp:TemplateField HeaderText="HireDate"
SortExpression="HireDate">
<EditItemTemplate>
<asp:TextBox ID="TextBox2" runat="server"
Text='<%# Bind("HireDate") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Calendar ID="Calendar1" runat="server"
SelectedDate='<%# Bind("HireDate") %>'
VisibleDate='<%# Eval("HireDate") %>'>
</asp:Calendar>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Days On The Job">
<ItemTemplate>
<%# DisplayDaysOnJob((Northwind.EmployeesRow)
((System.Data.DataRowView) Container.DataItem).Row) %>
</ItemTemplate>
<ItemStyle HorizontalAlign="Center" />
</asp:TemplateField>
</Columns>
</asp:GridView>
圖 16 顯示透過瀏覽器檢視時已完成的教學課程。
圖 16:顯示員工在作業上的天數(按兩下以檢視完整大小的影像)
總結
GridView 控件中的 TemplateField 允許比其他欄位控制項更彈性地顯示數據。 TemplateFields 適用於下列情況:
- 多個數據欄位必須顯示在一個 GridView 資料行中
- 數據最好使用 Web 控制件來表示,而不是純文字
- 輸出取決於基礎數據,例如顯示元數據或重新格式化數據
除了自定義數據的顯示之外,TemplateFields 也可用來自定義用於編輯和插入數據的使用者介面,如未來教學課程所示。
接下來的兩個教學課程會繼續探索範本,從在DetailsView中使用TemplateFields開始。 接下來,我們將轉向 FormView,其會使用範本取代欄位,在數據的版面配置和結構中提供更大的彈性。
快樂的程序設計!
關於作者
斯科特·米切爾,七本 ASP/ASP.NET 書籍和 4GuysFromRolla.com 創始人的作者,自1998年以來一直與Microsoft Web 技術合作。 斯科特擔任獨立顧問、教練和作家。 他的最新書是 Sams 教你自己學 ASP.NET 2.0 在24小時內。 可以透過 mitchell@4GuysFromRolla.com 聯絡他。
特別感謝
本教學系列已由許多熱心的評論者審閱。 本教學課程的主要檢閱者是 Dan Jagers。 有興趣檢閱我即將推出的 MSDN 文章嗎? 如果是,請在 mitchell@4GuysFromRolla.com給我留言。