Repeater 是 ASP.NET 最輕量、彈性最高的資料繫結控制項:
ItemCommand 事件統一處理列內按鈕/連結指令e.Item.FindControl("控制項ID")在 ItemCommand 事件中,透過 e.Item (當前觸發事件的 RepeaterItem) 呼叫 FindControl 取得該列內的任意控制項實例,進而讀取/設定其屬性值。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="RepeaterDemo.aspx.cs" Inherits="Demo.RepeaterDemo" %>
<!DOCTYPE html>
<html>
<head>
<title>Repeater ItemCommand 示範</title>
<style>
.repeater-table { width: 100%; border-collapse: collapse; }
.repeater-table th, .repeater-table td { border: 1px solid #ddd; padding: 8px; }
.repeater-table th { background: #f5f5f5; }
.txt-num { width: 60px; text-align: center; }
</style>
</head>
<body>
<form id="form1" runat="server">
<h3>產品庫存數量調整</h3>
<table class="repeater-table">
<thead>
<tr><th>產品名稱</th><th>目前庫存</th><th>調整數量</th><th>操作</th></tr>
</thead>
<tbody>
<asp:Repeater ID="rptProducts" runat="server" OnItemCommand="rptProducts_ItemCommand">
<ItemTemplate>
<tr>
<td><%# Eval("ProductName") %></td>
<td><%# Eval("StockQty") %></td>
<td>
<asp:TextBox ID="txtAdjustQty" runat="server" CssClass="txt-num" Text="0" />
</td>
<td>
<asp:Button ID="btnUpdate" runat="server" Text="更新"
CommandName="update"
CommandArgument='<%# Eval("ProductID") %>'
CssClass="btn btn-primary" />
</td>
</tr>
</ItemTemplate>
</asp:Repeater>
</tbody>
</table>
<asp:Label ID="lblMsg" runat="server" ForeColor="Green" />
</form>
</body>
</html>
using System;
using System.Data;
using System.Web.UI.WebControls;
namespace Demo
{
public partial class RepeaterDemo : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack) BindRepeater();
}
private void BindRepeater()
{
// 模擬資料來源
DataTable dt = new DataTable();
dt.Columns.Add("ProductID", typeof(int));
dt.Columns.Add("ProductName", typeof(string));
dt.Columns.Add("StockQty", typeof(int));
dt.Rows.Add(1, "筆記型電腦", 50);
dt.Rows.Add(2, "無線滑鼠", 200);
dt.Rows.Add(3, "機械鍵盤", 80);
dt.Rows.Add(4, "27吋螢幕", 30);
rptProducts.DataSource = dt;
rptProducts.DataBind();
}
protected void rptProducts_ItemCommand(object source, RepeaterCommandEventArgs e)
{
if (e.CommandName == "update")
{
// 1. 取得 CommandArgument (主鍵 ID)
int productId = Convert.ToInt32(e.CommandArgument);
// 2. 關鍵:透過 e.Item.FindControl 取得該列內的 TextBox
TextBox txtAdjustQty = e.Item.FindControl("txtAdjustQty") as TextBox;
if (txtAdjustQty != null)
{
// 3. 讀取使用者輸入值
if (int.TryParse(txtAdjustQty.Text.Trim(), out int adjustQty))
{
// 4. 執行業務邏輯 (此處僅示範)
// bool success = ProductService.UpdateStock(productId, adjustQty);
bool success = true; // 模擬成功
if (success)
{
lblMsg.Text = $"✅ 產品 ID {productId} 庫存已調整 {adjustQty} 件";
BindRepeater(); // 重新繫結顯示最新狀態
}
else
{
lblMsg.Text = "❌ 更新失敗,請稍後再試";
}
}
else
{
lblMsg.Text = "❌ 請輸入有效的數字";
}
}
}
}
}
}
protected void rpt_ItemCommand(object source, RepeaterCommandEventArgs e)
{
// DropDownList
DropDownList ddlStatus = e.Item.FindControl("ddlStatus") as DropDownList;
string status = ddlStatus?.SelectedValue;
// CheckBox
CheckBox chkActive = e.Item.FindControl("chkActive") as CheckBox;
bool isActive = chkActive?.Checked ?? false;
// HiddenField (常用於儲存不顯示的主鍵/版本號)
HiddenField hfVersion = e.Item.FindControl("hfVersion") as HiddenField;
int version = Convert.ToInt32(hfVersion?.Value ?? "0");
// Label (唯讀顯示值)
Label lblCreatedDate = e.Item.FindControl("lblCreatedDate") as Label;
DateTime createdDate = DateTime.Parse(lblCreatedDate?.Text ?? DateTime.Now.ToString());
}
<asp:Repeater ID="rptCategory" runat="server" OnItemCommand="rptCategory_ItemCommand">
<ItemTemplate>
<h4><%# Eval("CategoryName") %></h4>
<asp:Repeater ID="rptProducts" runat="server" DataSource='<%# Eval("Products") %>' OnItemCommand="rptProducts_ItemCommand">
<ItemTemplate>
<asp:TextBox ID="txtQty" runat="server" />
<asp:Button ID="btnAdd" runat="server" CommandName="add" CommandArgument='<%# Eval("ProductID") %>' />
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
// 子 Repeater 事件中取得父 Repeater 的控制項
protected void rptProducts_ItemCommand(object source, RepeaterCommandEventArgs e)
{
// 取得父 RepeaterItem
RepeaterItem parentItem = e.Item.NamingContainer as RepeaterItem;
if (parentItem != null)
{
// 取得父層隱藏欄位
HiddenField hfCategoryId = parentItem.FindControl("hfCategoryId") as HiddenField;
int categoryId = Convert.ToInt32(hfCategoryId?.Value ?? "0");
// 取得當前列控制項
TextBox txtQty = e.Item.FindControl("txtQty") as TextBox;
// ... 業務邏輯
}
}
protected void rpt_ItemCommand(object source, RepeaterCommandEventArgs e)
{
switch (e.Item.ItemType)
{
case ListItemType.Item:
case ListItemType.AlternatingItem:
// 一般資料列
TextBox txt = e.Item.FindControl("txtQty") as TextBox;
break;
case ListItemType.Header:
case ListItemType.Footer:
// 標題/底部列 (如 Footer 含新增功能)
TextBox txtNew = e.Item.FindControl("txtNewQty") as TextBox;
break;
case ListItemType.EditItem:
// 編輯模式列 (若支援編輯樣板)
break;
}
}
| 錯誤現象 | 原因 | 解決方式 |
|---|---|---|
FindControl 回傳 null | 1. 控制項 ID 拼錯 2. 控制項不在當前 ItemTemplate 內 3. 控制項被置於 <asp:Panel> 等容器內,需先找容器再找控制項 | 檢查 ID 大小寫;用 e.Item.Controls 遞迴列印控制項樹確認層級 |
| 取得的值為舊值/空值 | 1. 未在 !IsPostBack 時繫結,導致每次 PostBack 重新繫覆蓋使用者輸入2. ViewState 未啟用 | 確認 if (!IsPostBack) Bind();檢查頁面/控制項 EnableViewState="true" |
CommandArgument 為空 | CommandArgument 屬性未設定或 Eval 欄位名錯誤 | 檢查 CommandArgument='<%# Eval("CorrectColumnName") %>' |
| 事件未觸發 | 1. OnItemCommand 未綁定2. Button UseSubmitBehavior="false" 導致不回傳3. 驗證控制項阻擋 PostBack | 檢查繫結;設定 UseSubmitBehavior="true" (預設);檢查 Page.Validate() |
EnableViewState="true" (頁面與控制項皆需開啟)PagedDataSource 或前端分頁插件 (DataTables.js)<%@ OutputCache %> 或資料層快取Repeater 配合 ItemCommand + e.Item.FindControl() 是 ASP.NET Web Forms 處理列表互動最經典、彈性最高的模式。掌握「透過 e.Item 定位當前命名容器,再用 FindControl 取得控制項」這一核心觀念,即可輕鬆處理單列編輯、巢狀列表、多控制項取值等各種複雜業務場景。搭配 CommandName/CommandArgument 傳遞指令與主鍵,程式碼結構清晰、易維護。