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

ASP.NET 伺服器控件開發基礎 (15):複合控件核心 - CreateChildControls 與子控件生命週期

     編輯:關於ASP.NET

前言:複合控件是封裝 UI 邏輯的利器

複合控件 (CompositeControl) 將多個現有控件組合封裝,對外呈現單一控件介面。ASP.NET 2.0 引入 CompositeControl 基類 (繼承 WebControl、實作 INamingContainer),大幅簡化複合控件開發。本篇深度解析 CreateChildControls、EnsureChildControls、子控件生命週期管理。

CompositeControl 基類優勢


public abstract class CompositeControl : WebControl, INamingContainer
{
    // 核心方法:建立子控件 (必須覆寫)
    protected abstract void CreateChildControls();
    
    // 確保子控件已建立 (內部自動呼叫 CreateChildControls)
    protected void EnsureChildControls()
    {
        if (!ChildControlsCreated)
            CreateChildControls();
    }
    
    // 屬性:子控件是否已建立
    protected bool ChildControlsCreated { get; private set; }
    
    // 渲染:自動確保子控件建立後渲染
    protected override void Render(HtmlTextWriter writer)
    {
        EnsureChildControls();
        base.Render(writer);
    }
    
    // 預渲染:確保子控件建立
    protected override void OnPreRender(EventArgs e)
    {
        EnsureChildControls();
        base.OnPreRender(e);
    }
}

核心模式:CreateChildControls 實作標準流程


public class LoginCompositeControl : CompositeControl
{
    // 子控件欄位 (私有,封裝內部結構)
    private TextBox _txtUsername;
    private TextBox _txtPassword;
    private Button _btnLogin;
    private Label _lblMessage;
    private RequiredFieldValidator _valUsername;
    private RequiredFieldValidator _valPassword;
    
    // 公開屬性:暴露必要設定
    public string UsernameLabelText { get; set; } = "帳號:";
    public string PasswordLabelText { get; set; } = "密碼:";
    public string LoginButtonText { get; set; } = "登入";
    public string ValidationGroup { get; set; } = "LoginGroup";
    
    // 事件
    public event EventHandler LoginClick;
    
    // 核心實作:建立子控件樹
    protected override void CreateChildControls()
    {
        Controls.Clear();
        
        // 1. 建立子控件實例
        _txtUsername = new TextBox { ID = "txtUsername", ValidationGroup = ValidationGroup };
        _txtPassword = new TextBox { ID = "txtPassword", TextMode = TextBoxMode.Password, ValidationGroup = ValidationGroup };
        _btnLogin = new Button { ID = "btnLogin", Text = LoginButtonText, ValidationGroup = ValidationGroup };
        _lblMessage = new Label { ID = "lblMessage", CssClass = "message", EnableViewState = false };
        _valUsername = new RequiredFieldValidator { ID = "valUsername", ControlToValidate = "txtUsername", ErrorMessage = "請輸入帳號", ValidationGroup = ValidationGroup, Display = ValidatorDisplay.Dynamic };
        _valPassword = new RequiredFieldValidator { ID = "valPassword", ControlToValidate = "txtPassword", ErrorMessage = "請輸入密碼", ValidationGroup = ValidationGroup, Display = ValidatorDisplay.Dynamic };
        
        // 2. 綁定事件
        _btnLogin.Click += BtnLogin_Click;
        
        // 3. 建立佈局結構 (使用 Table 或 Div)
        Table layout = new Table { CellPadding = 5, CellSpacing = 0 };
        
        // 列 1: 帳號
        TableRow row1 = new TableRow();
        row1.Cells.Add(new TableCell { Text = UsernameLabelText, CssClass = "label" });
        TableCell cell1 = new TableCell();
        cell1.Controls.Add(_txtUsername);
        cell1.Controls.Add(_valUsername);
        row1.Cells.Add(cell1);
        layout.Rows.Add(row1);
        
        // 列 2: 密碼
        TableRow row2 = new TableRow();
        row2.Cells.Add(new TableCell { Text = PasswordLabelText, CssClass = "label" });
        TableCell cell2 = new TableCell();
        cell2.Controls.Add(_txtPassword);
        cell2.Controls.Add(_valPassword);
        row2.Cells.Add(cell2);
        layout.Rows.Add(row2);
        
        // 列 3: 按鈕 + 訊息
        TableRow row3 = new TableRow();
        TableCell cell3 = new TableCell { ColumnSpan = 2, HorizontalAlign = HorizontalAlign.Center };
        cell3.Controls.Add(_btnLogin);
        cell3.Controls.Add(new LiteralControl("
")); cell3.Controls.Add(_lblMessage); row3.Cells.Add(cell3); layout.Rows.Add(row3); // 4. 加入控件樹 Controls.Add(layout); ChildControlsCreated = true; } // 事件處理 private void BtnLogin_Click(object sender, EventArgs e) { // 觸發自訂事件 LoginClick?.Invoke(this, new LoginEventArgs(_txtUsername.Text, _txtPassword.Text)); } // 公開方法:供外部設定訊息 public void ShowMessage(string message, bool isError = false) { EnsureChildControls(); // 確保子控件存在 _lblMessage.Text = message; _lblMessage.CssClass = isError ? "message error" : "message success"; } // 公開屬性:存取子控件值 (封裝內部結構) public string Username { get { EnsureChildControls(); return _txtUsername.Text; } set { EnsureChildControls(); _txtUsername.Text = value; } } public string Password { get { EnsureChildControls(); return _txtPassword.Text; } set { EnsureChildControls(); _txtPassword.Text = value; } } } public class LoginEventArgs : EventArgs { public string Username { get; } public string Password { get; } public LoginEventArgs(string username, string password) { Username = username; Password = password; } }

子控件生命週期管理關鍵點

階段CompositeControl 行為開發者注意事項
Init尚未建立子控件勿在 Init 存取子控件
Load尚未建立 (除非被強制)使用 EnsureChildControls() 保護存取
PreRender自動呼叫 EnsureChildControls()安全存取子控件屬性
Render自動呼叫 EnsureChildControls()安全渲染
Postback 事件已建立 (因 PreRender 觸發)事件處理中可直接存取子控件

進階技巧:延遲建立與條件建立

延遲建立:只有需要時才建立子控件


protected override void CreateChildControls()
{
    // 基礎結構永遠建立
    CreateBaseStructure();
    
    // 昂貴子控件延遲建立
    if (_needAdvancedFeatures)
        CreateAdvancedControls();
}

private void CreateAdvancedControls()
{
    if (_advancedPanel == null)
    {
        _advancedPanel = new Panel { ID = "advancedPanel" };
        // 建立複雜子控件...
        Controls.Add(_advancedPanel);
    }
}

條件建立:根據模式建立不同 UI


public enum DisplayMode { Simple, Advanced, ReadOnly }
public DisplayMode Mode { get; set; } = DisplayMode.Simple;

protected override void CreateChildControls()
{
    Controls.Clear();
    
    switch (Mode)
    {
        case DisplayMode.Simple:
            CreateSimpleLayout();
            break;
        case DisplayMode.Advanced:
            CreateAdvancedLayout();
            break;
        case DisplayMode.ReadOnly:
            CreateReadOnlyLayout();
            break;
    }
    ChildControlsCreated = true;
}

視圖狀態與子控件

CompositeControl 繼承 WebControl,自動擁有視圖狀態。子控件若加入 Controls 集合,其視圖狀態會自動參與頁面視圖狀態管理:

  • 子控件 EnableViewState 預設為 true
  • 父控件 SaveViewState 會遞迴儲存子控件狀態
  • 動態建立的子控件 必須在每次請求重建 (在 CreateChildControls 中),否則視圖狀態無法正確載入

設計時支援:屬性瀏覽器顯示子控件屬性


[Designer(typeof(LoginCompositeControlDesigner))]
public class LoginCompositeControl : CompositeControl { ... }

public class LoginCompositeControlDesigner : ControlDesigner
{
    public override void Initialize(IComponent component)
    {
        base.Initialize(component);
        // 讓設計器識別為複合控件
        SetViewFlags(ViewFlags.TemplateEditing, false);
    }
    
    // 允許在設計時展開子控件屬性
    public override bool AllowResize => true;
}

頁面使用範例


<cc:LoginCompositeControl ID="LoginCtrl1" runat="server" 
    UsernameLabelText="電子郵件:" 
    LoginButtonText="立即登入"
    OnLoginClick="LoginCtrl1_LoginClick" />

<script runat="server">
protected void LoginCtrl1_LoginClick(object sender, LoginEventArgs e)
{
    if (Membership.ValidateUser(e.Username, e.Password))
    {
        FormsAuthentication.RedirectFromLoginPage(e.Username, false);
    }
    else
    {
        ((LoginCompositeControl)sender).ShowMessage("帳號或密碼錯誤", true);
    }
}
</script>

常見陷阱與解決方案

問題原因解決方案
子控件事件不觸發子控件 ID 衝突或未實作 INamingContainer確保父控件實作 INamingContainer,子控件 ID 唯一
視圖狀態遺失動態子控件未在 CreateChildControls 重建所有動態子控件必須在 CreateChildControls 中無條件重建
設計時錯誤CreateChildControls 拋出例外加入 try-catch,設計時回傳預覽 HTML
存取子控件 NullReference未呼叫 EnsureChildControls所有公開屬性/方法存取子控件前呼叫 EnsureChildControls()
驗證控件不工作ValidationGroup 不一致確保所有相關控件 (輸入框、驗證器、按鈕) 使用相同 ValidationGroup

總結

CompositeControl 透過 CreateChildControls + EnsureChildControls 模式,優雅解決了子控件生命週期管理難題:

  1. 統一建立入口:CreateChildControls 封裝所有子控件建立邏輯
  2. 自動保護機制:Render/PreRender 自動確保子控件就緒
  3. 手動保護模式:公開 API 呼叫 EnsureChildControls() 確保安全存取
  4. 視圖狀態自動化:子控件加入 Controls 集合即自動參與視圖狀態
  5. 命名隔離:實作 INamingContainer 避免 ID 衝突

掌握此核心模式,即可開發出健壯、可維護、設計時友善的複合控件。

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