[ASP.NET]如何透過動態新增的控制項,再去動態新增控制項

  • 22535
  • 0

[ASP.NET]如何透過動態新增的控制項,再去動態新增控制項

前言
這是一篇MSDN forum上,發問人提出的疑問,請參考:
asp.net 為什麼不能兩個按鈕事件?。

其實在ASP.NET webform上,要透過server端的code來動態產生控制項,是一件吃力的事。因為http是無狀態的,postback後要記住『值』,ASP.NET幾乎都是仰賴ViewState。雖然不是做不到,但是當動態新增控制項完之後,如果還要與UI上其他東西互動,或是需求異動後,某一個動態新增的控制項又要額外做某些功能,不管是在開發、維護或是偵錯上,這都是一件相當吃力的事。

除非,這樣的動態新增控制項滿足幾個基本要件,不然我自己還是偏向避開太動態的設計。

  1. 只有呈現資料,沒有互動的潛藏需求。
  2. 最多兩層的動態產生,不然程式碼會變得相當冗長跟硬幹。
  3. 動態新增的部分是user control,因為邏輯都被封裝在裡面了,就像widget, webpart, igoogle,每一塊都可以獨立作業。


雖然我不是挺喜歡這樣的設計方式,但是既然在Forum上已經不只一個人問過了,我就隨手把sample code做個記錄,方便真的避不開這需求的人可以快速的參考一下。

Sample Code

.aspx

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Panel ID="Panel1" runat="server">
            <asp:Label ID="Label1" runat="server" Text="Button1 Group"></asp:Label>
        </asp:Panel>
        <asp:Panel ID="Panel2" runat="server">
            <asp:Label ID="Label2" runat="server" Text="Button2 Group"></asp:Label>            
        </asp:Panel>
        <asp:Label ID="result" runat="server" Text="result"></asp:Label>
    </div>
    </form>
</body>
</html>

.aspx.cs

public partial class DynamicAddingCOntrols : System.Web.UI.Page
{
    /// <summary>
    /// 用來判斷是否為第二群組的按鈕觸發的postback,如果是,記得也要再generate一次第二群組的按鈕,只要出現過一次,之後的postback理論上都要再出現
    /// </summary>
    private bool Myflag
    {
        get
        {
            if (this.ViewState["Myflag"] == null)
            {
                this.ViewState["Myflag"] = false;
            }
            return Convert.ToBoolean(this.ViewState["Myflag"]);
        }
        set
        {
            this.ViewState["Myflag"] = value;
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        List<string> firstButtonClientIdCollection = new List<string>();

        for (int i = 0; i < 3; i++)
        {
            Button button = new Button();
            button.ID = "Button1_" + i.ToString();
            firstButtonClientIdCollection.Add(button.ClientID);
            button.UseSubmitBehavior = false;
            button.Text = button.ID;
            button.Click += new EventHandler(button_Click);
            this.Panel1.Controls.Add(button);
        }

        if (IsPostBack)
        {
            //代表是first button group觸發的postback
            if (firstButtonClientIdCollection.IndexOf(Convert.ToString(this.Request.Form["__EVENTTARGET"])) != -1 || this.Myflag)
            {
                this.GenerateSecondButtonGroup();
            }
        }

    }

    /// <summary>
    /// 產生第二組button group
    /// </summary>
    private void GenerateSecondButtonGroup()
    {
        this.Myflag = true;
        for (int i = 0; i < 3; i++)
        {
            Button button = new Button();
            button.ID = "Button2_" + i.ToString();
            button.Text = button.ID;
            button.Click += new EventHandler(button_Click);
            this.Panel2.Controls.Add(button);
        }
    }

    /// <summary>
    /// button的event handler,紀錄是哪一顆按鈕觸發的postback
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void button_Click(object sender, EventArgs e)
    {
        this.result.Text = string.Format("觸發的ButtonID:{0}", (sender as Button).ID);
    }
}


結論
幾個重點提醒:

  1. 動態新增控制項,因為http無狀態,所以每一次的page life cycle都應該要做Controls.Add的動作,至於上面的值如何在postback之間記住,那就是控制項本身透過ViewState會去記值了,developer就不用擔心這問題,只要位置放對就可以。
  2. 怎麼讀動態新增控制項上面的值?基本上只能透過FindControl或是Request.Form[]去讀,所以ID的命名通常都要按照一定的rule。
  3. 以這篇文章有兩層動態新增控制項為例,很多東西是得透過Request.Form[“__EVENTTARGET”]去攔截判斷是什麼東西觸發的postback,再轉發(呼叫)要處理的事情。很多東西寫在對應的control event,會因為page life cycle的事件順序而導致潛藏的bug。
  4. 必要時可以用ViewState來記住一些size比較小的值,例如我們的event handler中,需要記住某些東西給下一次用的情況。


這篇範例只是一些概念上的組合和呈現,相信我,實際上會碰到的需求都沒這麼簡單,而且需求很容易發散,屆時就會收不了網,被質疑為什麼這樣的功能這麼難做。(尤其是你的PM, SA不懂Web的時候)

 


blog 與課程更新內容,請前往新站位置:http://tdd.best/