| 類型 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| User Control (.ascx) | 開發快、像設計頁面、支援視覺化設計師 | 跨專案重用麻煩 (需複製檔案)、無法進 Toolbox、事件模型受限 | 單專案內重複 UI 區塊 |
| Custom Server Control (.cs) | 編譯成 DLL、進 Toolbox 拖拉、完整事件/屬性/狀態管理、跨專案引用 | 需手寫 Render 邏輯、學習曲線較高 | **元件庫、商業控制項、跨專案共用** |
本文採用 **CompositeControl** 基類,組合多個子控制項 (Label、LinkButton、TextBox、Button) 實作功能完整的分頁控制項。

[DefaultProperty("PageSize")]
[ToolboxData("<{0}:PageOn runat=server Width=100%>{0}:PageOn>")]
public class PageOn : CompositeControl
{
// 子控制項欄位
Label lblMessage;
LinkButton btnFirst, btnPrev, btnNext, btnLast;
TextBox txtGoPage;
Button btnGo;
// ...
}
/// <summary> 總記錄數 </summary>
public int RowCount
{
get
{
object o = ViewState["RowCount"];
return (o == null || (int)o < 0) ? 0 : (int)o;
}
set
{
ViewState["RowCount"] = Math.Max(0, value);
RecreateChildControls(); // 觸發重建
}
}
/// <summary> 當前頁碼 (1-based) </summary>
public int CurPage
{
get
{
object o = ViewState["CurPage"];
return (o == null || (int)o < 1) ? 1 : (int)o;
}
set
{
int v = Math.Max(1, value);
ViewState["CurPage"] = (v > PageCount) ? PageCount : v;
}
}
/// <summary> 總頁數 (唯讀) </summary>
public int PageCount => (RowCount + PageSize - 1) / PageSize;
/// <summary> 每頁筆數 </summary>
public int PageSize
{
get
{
object o = ViewState["PageSize"];
return (o == null || (int)o < 1) ? 15 : (int)o;
}
set
{
if (value > 0) { ViewState["PageSize"] = value; RecreateChildControls(); }
}
}
protected override void CreateChildControls()
{
Controls.Clear();
// 訊息標籤
lblMessage = new Label { ID = "lblMessage" };
lblMessage.Text = $"當前第{CurPage}頁 共{PageCount}頁 共{RowCount}條記錄";
Controls.Add(lblMessage);
// 首頁
btnFirst = new LinkButton { ID = "btnFirst", Text = "首頁", CommandName = "first" };
btnFirst.Enabled = CurPage > 1;
Controls.Add(btnFirst);
// 上一頁
btnPrev = new LinkButton { ID = "btnPrev", Text = "上一頁", CommandName = "prev" };
btnPrev.Enabled = CurPage > 1;
Controls.Add(btnPrev);
// 下一頁
btnNext = new LinkButton { ID = "btnNext", Text = "下一頁", CommandName = "next" };
btnNext.Enabled = CurPage < PageCount;
Controls.Add(btnNext);
// 末頁
btnLast = new LinkButton { ID = "btnLast", Text = "末頁", CommandName = "last" };
btnLast.Enabled = CurPage < PageCount;
Controls.Add(btnLast);
// 跳轉輸入框 (僅允許數字)
txtGoPage = new TextBox { ID = "txtGoPage", TabIndex = 1 };
txtGoPage.Attributes["onkeyup"] = "this.value=this.value.replace(/\\D/g,'')";
txtGoPage.Attributes["onafterpaste"] = "this.value=this.value.replace(/\\D/g,'')";
Controls.Add(txtGoPage);
// GO 按鈕
btnGo = new Button { ID = "btnGo", Text = "GO", CommandName = "go", TabIndex = 2 };
Controls.Add(btnGo);
base.CreateChildControls();
}
protected override void RenderContents(HtmlTextWriter output)
{
// 外層 Table > Tr > 兩個 Td (左:訊息, 右:導航)
output.RenderBeginTag(HtmlTextWriterTag.Table);
output.RenderBeginTag(HtmlTextWriterTag.Tr);
// 左側:訊息
output.AddStyleAttribute(HtmlTextWriterStyle.TextAlign, "left");
output.RenderBeginTag(HtmlTextWriterTag.Td);
output.Write(" ");
lblMessage.RenderControl(output);
output.RenderEndTag(); // Td
// 右側:導航按鈕群
output.AddStyleAttribute(HtmlTextWriterStyle.TextAlign, "right");
output.RenderBeginTag(HtmlTextWriterTag.Td);
btnFirst.RenderControl(output); output.Write(" ");
btnPrev.RenderControl(output); output.Write(" ");
btnNext.RenderControl(output); output.Write(" ");
btnLast.RenderControl(output); output.Write("到");
output.AddStyleAttribute(HtmlTextWriterStyle.Width, "30px");
txtGoPage.RenderControl(output);
output.Write("頁");
btnGo.RenderControl(output);
output.Write(" ");
output.RenderEndTag(); // Td
output.RenderEndTag(); // Tr
output.RenderEndTag(); // Table
}
// 指定最外層標籤為
protected override HtmlTextWriterTag TagKey => HtmlTextWriterTag.Table;
五、事件冒泡處理
// 1. 定義委托與事件
public delegate void PageOnEventHandler(object sender, EventArgs args);
public event PageOnEventHandler RecPageChanged;
// 2. 重寫 OnBubbleEvent 攔截子控制項 Command 事件
protected override bool OnBubbleEvent(object source, EventArgs args)
{
if (!(args is CommandEventArgs cea)) return base.OnBubbleEvent(source, args);
bool handled = false;
switch (cea.CommandName)
{
case "first": CurPage = 1; handled = true; break;
case "prev": if (CurPage > 1) CurPage--; handled = true; break;
case "next": if (CurPage < PageCount) CurPage++; handled = true; break;
case "last": CurPage = PageCount; handled = true; break;
case "go":
if (int.TryParse(txtGoPage.Text.Trim(), out int pg))
CurPage = pg;
handled = true;
break;
}
if (handled)
{
RecPageChanged?.Invoke(this, args);
RecreateChildControls(); // 更新 UI
return true;
}
return base.OnBubbleEvent(source, args);
}
六、使用方式
1. 專案加入參考
- 右鍵 Toolbox → **Choose Items** → Browse 選取編譯好的 DLL
- 或直接拖曳 DLL 到 Toolbox
2. 頁面拖曳控制項
<cc1:PageOn ID="PageOn1" runat="server" PageSize="15" />
3. 後台訂閱事件並繫結資料
protected void Page_Load(object sender, EventArgs e)
{
PageOn1.RecPageChanged += PageOn1_RecPageChanged;
if (!IsPostBack) BindData();
}
void PageOn1_RecPageChanged(object sender, EventArgs args)
{
BindData(); // 重新查詢當前頁資料
}
private void BindData()
{
int total;
var data = GetPagedData(PageOn1.CurPage, PageOn1.PageSize, out total);
PageOn1.RowCount = total;
GridView1.DataSource = data;
GridView1.DataBind();
}
七、樣式客製化建議
- 暴露子控制項屬性:`public LinkButton BtnFirst => btnFirst;` 讓使用者自行設定 `CssClass`、`ForeColor`
- 或新增 `CssClass`、`ButtonCssClass`、`CurrentPageCssClass` 等樣式屬性,在 `RenderContents` 套用
- 支援 `SkinID` 整合 Theme
八、完整代碼結構
namespace CustomControl
{
public delegate void PageOnEventHandler(object sender, EventArgs args);
[DefaultProperty("PageSize")]
[ToolboxData("<{0}:PageOn runat=server Width=100%>{0}:PageOn>")]
public class PageOn : CompositeControl
{
// 子控制項欄位...
// 屬性 (RowCount, CurPage, PageCount, PageSize)...
// CreateChildControls()...
// RenderContents()...
// TagKey...
// RecPageChanged 事件...
// OnBubbleEvent()...
}
}
總結
透過繼承 `CompositeControl`,封裝子控制項建立、ViewState 屬性管理、Table 版面渲染、Command 事件冒泡與自定義事件對外通知,完成一個**可拖曳、可重用、可客製化**的分頁伺服器控制項。此模式可擴展至任意複雜度的組合式控制項 (如進階搜尋面板、資料編輯表單、儀表板元件),是建構企業級控制項庫的核心技能。
欄目導航
Copyright © 程式碼之家 All Rights Reserved