首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

ASP.NET 自定義伺服器控制項開發實戰:分頁控制項完整實作 (CompositeControl)

     編輯:ASP.NET 基礎

為什麼選擇 Custom Server Control?

類型優點缺點適用場景
User Control (.ascx)開發快、像設計頁面、支援視覺化設計師跨專案重用麻煩 (需複製檔案)、無法進 Toolbox、事件模型受限單專案內重複 UI 區塊
Custom Server Control (.cs)編譯成 DLL、進 Toolbox 拖拉、完整事件/屬性/狀態管理、跨專案引用需手寫 Render 邏輯、學習曲線較高**元件庫、商業控制項、跨專案共用**

本文採用 **CompositeControl** 基類,組合多個子控制項 (Label、LinkButton、TextBox、Button) 實作功能完整的分頁控制項。

控制項效果預覽

分頁控制項運行效果

一、專案建立與基礎結構

  1. 新建 **ASP.NET Server Control** 專案
  2. 加入項目 → **ASP.NET Server Control** → 命名 `PageOn.cs`
  3. 繼承 `CompositeControl`,標註屬性:
[DefaultProperty("PageSize")]
[ToolboxData("<{0}:PageOn runat=server Width=100%>")]
public class PageOn : CompositeControl
{
    // 子控制項欄位
    Label lblMessage;
    LinkButton btnFirst, btnPrev, btnNext, btnLast;
    TextBox txtGoPage;
    Button btnGo;
    // ...
}

二、核心屬性定義 (支援 ViewState 持久化)

/// <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%>")]
    public class PageOn : CompositeControl
    {
        // 子控制項欄位...
        // 屬性 (RowCount, CurPage, PageCount, PageSize)...
        // CreateChildControls()...
        // RenderContents()...
        // TagKey...
        // RecPageChanged 事件...
        // OnBubbleEvent()...
    }
}

總結

透過繼承 `CompositeControl`,封裝子控制項建立、ViewState 屬性管理、Table 版面渲染、Command 事件冒泡與自定義事件對外通知,完成一個**可拖曳、可重用、可客製化**的分頁伺服器控制項。此模式可擴展至任意複雜度的組合式控制項 (如進階搜尋面板、資料編輯表單、儀表板元件),是建構企業級控制項庫的核心技能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved