動的に生成した TextBox コントロールの Text プロパティに初期値を設定するタイミングによって、TextChanged イベントの発生の仕方が異なる理由を調べていく過程で、変更系イベント発生のメカニズムが理解できたので、忘れないように書いておきます。
例として、Page_Load イベントハンドラで動的に TextBox を生成して PlaceHolder に追加するケースを考えます。
その際、以下のように、Text プロパティの初期値を、ケース (1) PlaceHolder に Add した後、ケース (2) Add する前の 2 通りのタイミングで設定します。そうすると、ケース (1) の TextChanged イベントの発生が期待と異なる動きをします(詳細後述)。ケース (2) は期待通りになります。
(普通、ケース (1) では txtbox.Text = "AAA"; を if (!Page.IsPostBack) { } で囲います。そうすれば問題は出ないのですが、その話はちょっと置いておいて・・・)
protected void Page_Load(object sender, EventArgs e)
{
// ケース (1)
TextBox txtbox = new TextBox();
txtbox.ID = "TextBox1";
txtbox.TextChanged += new EventHandler(TextChangedEvent);
PlaceHolder1.Controls.Add(txtbox);
txtbox.Text = "AAA";
// ケース (2)
txtbox = new TextBox();
txtbox.ID = "TextBox2";
txtbox.Text = "BBB";
txtbox.TextChanged += new EventHandler(TextChangedEvent);
PlaceHolder1.Controls.Add(txtbox);
}
ケース (1) は、初期画面では "AAA" が表示され、変更してポストバックすれば変更後の値が表示されます。ただし、一旦 "AAA" から変更すると、その後はポストバック前後で変更しなくても毎回 TextChanged イベントが発生します。"AAA" に戻してポストバックすれば、TextChanged イベントは発生しません。要するに、ポストする文字列が "AAA" でない限り TextChanged イベントが発生するということです。
ケース (2) は、初期画面では "BBB" が表示され、変更してポストバックすれば変更後の値が表示されます(ここまでは前者と同じ)。その後は、ポストバック前後の値に違いがある場合のみ TextChanged イベントが発生します。(これが期待された動きです)
何故、このような違いがあるのでしょう? 理由は以下の通りです。
この例では、ポストバック時に、Page.Load イベント ⇒ LoadViewState メソッド ⇒ LoadPostData メソッド ⇒ RaisePostDataChangedEvent メソッドの順序で処理が行われます。具体的には以下の通りです。
-
LoadViewState メソッド: Page.Load イベントで、ViewState の中に Text プロパティのデータが入っている場合、TextBox を PlaceHolder に Add するタイミングでこのメソッドが呼ばれ Text プロパティを ViewState の値に書き換える。
(注:ケース (2) の場合、初期画面では ViewState に Text プロパティのデータはないのでポストバック時にこのメソッドは呼ばれません。何故 ViewState に無いかはこの記事の下の方の「捕捉その1」を見てください)
-
LoadPostData メソッド: Text プロパティの値とポストされた値を比較。異なっていると Text プロパティをポストされた値で書き換えて ture を返す。
-
RaisePostDataChangedEvent メソッド: ture が返された TextBox の TextChanged イベントを発生させる。
ケース (1) では、1 と 2 の間で毎回 Text プロパティを "AAA" に書き換えています。従って、"AAA" とポストされた値を比較することになります。
ケース (2) では、1 の前で Text プロパティを "BBB" に設定するものの、1 で ViewState の値に書き換えています。従って、ViewState の値とポストされた値を比較することになります。
普通はケース (1) のコードような設定はしないと思いますので、ここで述べたことはあまり役に立たないかも知れませんね。でも、変更系イベント発生のメカニズムがこうなっているということは知っておいて損はないかも。
補足その1
ASP.NET は、ページの状態を保存してポストバック時に復元するため、ViewState と呼ばれる機能を持っています。
サーバーから、以下のような隠しフィールドが HTML コードに含まれてブラウザに送信されてきます。その value 属性に、送信時のページの状態(コントロールのプロパティ値など)が含まれており、ポストバックの際は同じ情報がサーバーに返されます。
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwU..." />
LoadViewState メソッドは、隠しフィールドの value 情報を用いて、コントロールのプロパティ値を、前回の送信時の値に書き換えます。
なお、必ず ViewState にポストバック前のデータがあるというわけではないので注意してください。
例えば、ケース (2) では初期画面の ViewState に Text プロパティのデータはありません。何故なら、静的に <asp:TextBox Text="BBB" .../> としたのと同様に、ポストバックの際は初期値の "BBB" を取得できるからです。また、その後ユーザーがブラウザ上で初期値 "BBB" を変更しない限り、何度ポストバックを繰り返しても ViewState に Text プロパティの値は保持されません。
ケース (1) では TextBox を PlaceHolder に Add した後 Text プロパティをデフォルトの初期値 "" から "AAA" に変更していますので、初期画面で ViewState に Text プロパティのデータ "AAA" が入ります。
ケース (1)、ケース (2) とも、ユーザーがブラウザに表示されたテキストボックスの値を変更してポストバックすると、ポストした値が ViewState に保持されます。
補足その2
普通の TextBox コントロールを使って LoadViewState メソッド ⇒ LoadPostData メソッド ⇒ RaisePostDataChangedEvent メソッドの順序で処置が行われることを確認することはできません。
確認するためには、TextBox コントロールを継承して、上記のメソッドを override したカスタムコントロールを作り、それにブレークポイントを設定して、デバッガで追いかけます。
カスタムコントロールは、以下のようになります(名前空間の using 宣言は省略しています)。
namespace CustomWebFormsControls
{
public class MyTextBox : TextBox
{
protected override bool LoadPostData(string postDataKey,
NameValueCollection postCollection)
{
String presentValue = Text;
String postedValue = postCollection[postDataKey];
if (presentValue == null ||
!presentValue.Equals(postedValue))
{
Text = postedValue;
return true;
}
return false;
}
protected override void RaisePostDataChangedEvent()
{
OnTextChanged(EventArgs.Empty);
}
protected override void LoadViewState(object savedState)
{
if (savedState != null)
{
base.LoadViewState(savedState);
}
}
}
}
補足その3
コントロールの動的作成で Microsoft が推奨しているのは、マイクロソフトサポートオンライン で述べられているように、Page_Init メソッド内だそうです。
Page_Init メソッド内で上記のように TextBox を動的に追加すると、Page_Init 完了後 ⇒ LoadViewState(ViewSate にデータがある場合のみ) ⇒ LoadPostData ⇒ Page_Load ⇒ RaisePostDataChangedEvent という順序でメソッドが実行されます。
Page_Load で TextBox を追加した場合と異なり、ケース (1) のコードでも txtbox.Text = "AAA" とした後で LoadViewState メソッドが実行されますので、ポストバック前後の値に違いがある場合のみ TextChanged イベントが発生するという期待された動作になります。
補足その4
上記は、Page_Load イベントハンドラで、動的に配置した TextBox コントロールの話です。
静的に配置したコントロールについては、LoadViewState メソッド、LoadPostData メソッドは Page.Load イベントより前に実行されます。(Page_Init で動的に追加した場合と同じ)