Form(思歸):動態控制項的狀態問題

來源:互聯網
上載者:User

在論壇上,動態控制項好象是永久的話題。大家都知道要動態控制項起作用,PostBack時需要重建或裝載(LoadControl),而且需要深入瞭解其狀態的變化過程。

有個同事另謀高就,要離開我們工作的地方了。我給她出了一道出門考題,同時也叫其他手下一起參加。這題目是這樣的:

下面兩頁差別很小,就是一句語句的前後次序有所不同,但PostBack後顯示效果有所不同,請解釋為什麼顯示效果不同,並且解釋正確顯示的那頁(你知道是哪頁,對嗎?)中Response.Write的輸出結果

TestDyn1.aspx:

<html>

<body>

 <form id="form1" runat="server">

  <asp:Button id="btn" runat="server" Text="Click Me" OnClick="Button_Click" /> <br/>

  靜態: <asp:DropDownList id="ddlStatic" runat="server">

  <asp:ListItem Text="1" Value="1" />

  <asp:ListItem Text="2" Value="2" />

  <asp:ListItem Text="3" Value="3" />

       </asp:DropDownList> <br/>

  動態:

 </form>

</body>

</html>

<script language="C#" runat="server">

void Page_Load(Object sender, EventArgs e)

{

   DropDownList ddlDynamic = new DropDownList();

   ddlDynamic.ID = "ddlDynamic";

   form1.Controls.Add(ddlDynamic);

   if (!IsPostBack)

   {

 for (int i=1; i <=3; i++)

  ddlDynamic.Items.Add(new ListItem(i.ToString(), i.ToString()));

   }

  

 

   if (IsPostBack)

   {

    Response.Write("[Page_Load]靜態:" + ddlStatic.SelectedIndex + "<BR>");

 Response.Write("[Page_Load]動態:" + ddlDynamic.SelectedIndex + "<BR>");

   }

}

void Button_Click(Object sender, EventArgs e)

{

 DropDownList ddlDynamic = (DropDownList)form1.FindControl("ddlDynamic");

      Response.Write("[Button_Click]靜態:" + ddlStatic.SelectedIndex + "<BR>");

 Response.Write("[Button_Click]動態:" + ddlDynamic.SelectedIndex + "<BR>");

}

</script>

TestDyn2.aspx:

<html>

<body>

 <form id="form1" runat="server">

  <asp:Button id="btn" runat="server" Text="Click Me" OnClick="Button_Click" /> <br/>

  靜態: <asp:DropDownList id="ddlStatic" runat="server">

  <asp:ListItem Text="1" Value="1" />

  <asp:ListItem Text="2" Value="2" />

  <asp:ListItem Text="3" Value="3" />

       </asp:DropDownList> <br/>

  動態:

 </form>

</body>

</html>

<script language="C#" runat="server">

void Page_Load(Object sender, EventArgs e)

{

   DropDownList ddlDynamic = new DropDownList();

   ddlDynamic.ID = "ddlDynamic";

   if (!IsPostBack)

   {

 for (int i=1; i <=3; i++)

  ddlDynamic.Items.Add(new ListItem(i.ToString(), i.ToString()));

   }

   form1.Controls.Add(ddlDynamic);

 

   if (IsPostBack)

   {

    Response.Write("[Page_Load]靜態:" + ddlStatic.SelectedIndex + "<BR>");

 Response.Write("[Page_Load]動態:" + ddlDynamic.SelectedIndex + "<BR>");

   }

}

void Button_Click(Object sender, EventArgs e)

{

 DropDownList ddlDynamic = (DropDownList)form1.FindControl("ddlDynamic");

      Response.Write("[Button_Click]靜態:" + ddlStatic.SelectedIndex + "<BR>");

 Response.Write("[Button_Click]動態:" + ddlDynamic.SelectedIndex + "<BR>");

}

</script>

 

瞭解ViewState與控制項生命週期的人,對這題自然不在話下。鑒於Lostinet對此有深入研究,本題禁止他參與,

註:本題是從我在CSDN上回答過的一個問題改編而來的


要理解第一個問題,即,對於一個動態產生的DropDownList對象,為什麼先添加ListItem,後調用父控制項的Controls.Add,其狀態並沒有儲存,其關鍵在於理解TrackViewState的調用以及動態控制項加入父控制項的Controls後的階段“追趕”過程。

如果你在文檔裡查詢Control的TrackViewState方法描述,其中說到,只有調用該方法後,view-state的變化才會存到伺服器控制項的StateBag對象裡去,這樣才會在下一次的PostBack後的LoadViewState中恢複到原來狀態。

大家都知道每個控制項一般都會經曆如下幾個階段 (抄自《Developing Microsoft ASP.NET Server Controls and Components》 一書第九章)

1。Instantiate

2。Initialize

3。Begin Tracking View State

4。Load View State (postback only)

5。Load Postback Data (postback only)

6。Load

7。Raise Changed Events (postback only, optional)

8。Raise Postback Events (postback only, optional)

9。PreRender

10。SaveViewState

11。Render

12。Unload

13。Dispose

在頁面裡declared的伺服器控制項,譬如,例子中的ddlStatic,其TrackViewState方法是在Init階段後面調用的,其後的變化將儲存到StateBag裡去,但其前的變化不會儲存。如果你用Paul Wilson的ViewState Parser查看該例的ViewState,你是看不到其狀態的。但假如在此之後,你改變其狀態,那麼這些狀態也許就會儲存到ViewState去(取決於該對象是否override了SaveViewState)。

那動態控制項呢?其一開始是建立對象,處於原始狀態,當它被加到父控制項的Controls裡時,父控制項會根據其當前的control階段來調用該子控制項的一些方法,讓子控制項趕上父控制項的control階段 (這些方法可以從上個貼裡leighsword和microhelper貼的Control的AddedControl方法裡看到,在此就不重複了,而且也不用看那些方法)。為什麼要這樣呢?這應該跟整個頁面的生命週期有關吧。

打個比方,不是很恰當,但湊合著吧,這好象是複製一個人後,讓他快速經曆嬰兒,童年,少年,青年,。。。直至趕上被複製人目前的階段為止。

但大概來講,當我們在Page_Load裡調用form1.Controls.Add()時,父控制項form1處於Load階段(上面的第六行),它就會調用下拉框的一些方法讓它經過Init->Load狀態,其中的一個結果是在Init後面調用了TrackViewState,DropDownList的父類ListControl, override了TrackViewState,在其中調用了Items(ListItemCollection類)對象的TrackViewState。其結果是,如果你在form1.Controls.Add()之後改變動態DropDownList控制項的Items的話,那些ListItem就會被儲存下來,因為ListItemCollection對象 override 了 SaveViewState() 。而在form1.Controls.Add()之前添加的ListItem則不會被儲存下來。

其實解決TestDyn2.aspx中的問題有個現成的答案,即去除 if (!IsPostBack):

DropDownList ddlDynamic = new DropDownList();

ddlDynamic.ID = "ddlDynamic";

for (int i=1; i <=3; i++)

  ddlDynamic.Items.Add(new ListItem(i.ToString(), i.ToString()));  

form1.Controls.Add(ddlDynamic);

這樣,跟ddlStatic一樣,每次都產生ListItem對象

第二個問題,即為什麼動態產生的DropDownList控制項在PostBack後在Page_Load裡其選擇的項沒有被設定,再供大家研究。

上次的第二個問題是,為什麼動態產生的DropDownList控制項,在PostBack後,在Page_Load裡其選擇的項沒有被設定。

拿TestDyn1.aspx為例,如果你在第一個(靜態)下拉框裡選擇2,在第二個(動態)下拉框裡選擇3,然後按Click Me按鈕,你的輸出是這樣的

[Page_Load]靜態:1

[Page_Load]動態:0

[Button_Click]靜態:1

[Button_Click]動態:2

不管你選什麼,第二項總是

[Page_Load]動態:0

即,動態下拉框的選項在Page_Load沒有被正確設定,但在Button的Click事件裡被正確設定了。

大家知道,表單控制項(TextBox, CheckBox, DropDownList, ListBox,....) 的輸入值或被選狀態與ViewState無關,而是在Load Postback Data階段被設定的,因為它們都實現了IPostBackDataHandler介面。

上次說到動態控制項被加入父控制項的Controls集合時,會通過階段“追趕(catch-up)”過程來趕上父控制項當前的階段,如果你仔細看一下前一個貼裡leighsword和microhelper貼的Control的AddedControl方法,你將看到

control.InitRecursive(control1);

...

control.LoadViewStateRecursive(obj1);

...

control.LoadRecursive();

...

並沒有涉及Load Postback Data。那麼這個階段是什麼時候被執行的呢?如果你參考Reflector(也可以參考上一個貼的2個回貼)裡System.Web.UI.Page的ProcessRequestMain()方法,在去掉了那些Trace語句後是這樣的:

base.InitRecursive(null);

if (this.IsPostBack)

{

      this.LoadPageViewState();

    //注意,這裡是._requestValueCollection

      this.ProcessPostData(this._requestValueCollection, true); //第二個參數表明是否是在Load前調用的

}

  

base.LoadRecursive();

if (this.IsPostBack)

{

//注意,這裡是._leftoverPostData,即,尚未被處理的PostData

 this.ProcessPostData(this._leftoverPostData, false);

 

 this.RaiseChangedEvents();

 

 this.RaisePostBackEvent(this._requestValueCollection);

 

}

base.PreRenderRecursiveInternal();

this.SavePageViewState();

base.RenderControl(this.CreateHtmlTextWriter(this.Response.Output));


ProcessPostData會根據Request.Form裡每對名字/值,看是否有實現了IPostBackDataHandler介面的對應名字的控制項,有的話,就會調用該控制項的LoadPostData方法,譬如DropDownList的LoadPostData是這樣的

bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection)

{

      string[] textArray1 = postCollection.GetValues(postDataKey);

      if (textArray1 != null)

      {

            int num1 = this.Items.FindByValueInternal(textArray1[0]);

            if (this.SelectedIndex != num1)

            {

                  this.SelectedIndex = num1;

                  return true;

            }

      }

      return false;

}

 

從上面可見,ProcessPostData在Load前被執行了一次,在Load後又會被執行一次。看上去有點怪,但這正是系統給你的方便,允許你在Load裡動態產生控制項,並讓那些實現了IPostBackDataHandler介面的控制項擷取使用者輸入的值或選擇的狀態。

在我們當前的情形下,我們的動態控制項是在Load裡產生的,錯過了第一次ProcessPostData,所以在Page_Load裡其選項還沒有被正確設定,但第二次ProcessPostData讓其擷取了使用者輸入的值或選擇的狀態,所以在Button的Click事件裡被正確設定了。

這也意味著,如果我們的表單控制項是在Load之後產生的,譬如你的控制項是在PreRender事件裡產生的,

void Page_PreRender(Object sender, EventArgs e)

{

   DropDownList ddlDynamic2 = new DropDownList();

   ddlDynamic2.ID = "ddlDynamic2";

   form1.Controls.Add(ddlDynamic2);

   if (!IsPostBack)

   {

      for (int i=1; i <=3; i++)

          ddlDynamic2.Items.Add(new ListItem(i.ToString(), i.ToString()));

   }

   else

     Response.Write("[Page_Load]動態2:" + ddlDynamic2.SelectedIndex + "<BR>");

}

那麼儘管它可以恢複ViewState,但因為它錯過了2次ProcessPostData機會,它不可能擷取使用者輸入的值或選擇的狀態。同時這些控制項也不會觸發Changed Events 與 Postback Events。 當然,你盡可以使用Request.Form來擷取使用者輸入的值或選擇的狀態,但這跟我們的討論無關。

所以,如果你需要產生動態控制項,而且需要擷取使用者佈建的輸入值或觸發Changed Events 與 Postback Events事件的話,最好在Load階段或之前產生。

關於ASP.NET裡Page事件及其次序的細節,請參考MVP Paul Wilson的文章

Page Events: Order and PostBack

也請參考

The ASP.NET Page Object Model

Page Life-cycle in ASP.NET

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.