WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

変更系イベント発生のメカニズム

by WebSurfer 2010年8月3日 12:36

動的に生成した 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 メソッドの順序で処理が行われます。具体的には以下の通りです。

  1. LoadViewState メソッド: Page.Load イベントで、ViewState の中に Text プロパティのデータが入っている場合、TextBox を PlaceHolder に Add するタイミングでこのメソッドが呼ばれ Text プロパティを ViewState の値に書き換える。

    (注:ケース (2) の場合、初期画面では ViewState に Text プロパティのデータはないのでポストバック時にこのメソッドは呼ばれません。何故 ViewState に無いかはこの記事の下の方の「捕捉その1」を見てください)
  2. LoadPostData メソッド: Text プロパティの値とポストされた値を比較。異なっていると Text プロパティをポストされた値で書き換えて ture を返す。
  3. 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 で動的に追加した場合と同じ)

Tags: ,

ASP.NET

GridView の列名の取得

by WebSurfer 2010年8月2日 12:35

GridView の n 番目(スタートは n = 0)の列名は、通常、GridView1.HeaderRow.Cells[n].Text のようにして取得できますが、AllowSorting プロパティが true になっていると、それでは取得できません(結果は "" になります)。

何故なら、GridView1.HeaderRow.Cells[n] には LinkButton コントロールが配置され、LinkButton の Text プロパティに列名が設定されるからです。

その場合は、以下のようにして取得できます。

string headerText;
foreach (Control ctl in GridView1.HeaderRow.Cells[n].Controls)
{
  if (ctl is LinkButton)
  {
    headerText = ((LinkButton)ctl).Text; 
    break;
  }
}

たぶん、GridView1.HeaderRow.Cells[n].Controls[0] が LinkButton になると思いますが、保証の限りではないので、ちょっと冗長かもしれませんが、上記のようにループで回して取得するように考えました。

Tags: ,

ASP.NET

ユーザー登録時のプロファイル情報取得

by WebSurfer 2010年8月1日 16:26

CreateUserWizard を利用して新規ユーザー登録をする際に、ユーザーの漢字の氏名、住所などのユーザープロファイル情報を取得し、ASP.NET プロファイルを使用して保存する方法の紹介です。

プロファイル情報の取得

MSDN ライブラリの CreateUserWizard クラス の 3 番目のコード例に、ユーザーの氏名をプロファイル情報として取得する方法が書かれています。

でも、これではうまくいきません。以下の問題があります。

  1. web.config に <anonymousIdentification enabled="true" /> が必須(サンプルには書いてない)。
  2. サンプルには <add name="userName" /> としか書いてないが、それでは allowAnonymous がデフォルトの false となり、Profile.SetPropertyValue メソッドで、ProviderException 例外がスローされる。
  3. allowAnonymous を true にすると成功するように見えるが、匿名ユーザーの UserId の方にのみプロファイルデータが登録され、新規認証済みユーザーの UserId には登録されない。結果、新規認証済みユーザーの ID とパスワードで再ログインしてもプロファイルデータは取得できない。

上記の問題を解決して MSDN ライブラリのサンプルを書き換えると、以下のようになります。なお、サンプルの userName は KanjiName に変更していますので注意してください。

web.config の設定

<anonymousIdentification  enabled="true" />

<profile>
    <properties>
        <add name="KanjiName" />
    </properties>
</profile>

.aspx のコード

<%@ page language="C#"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">

  void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
  {
    ProfileCommon profile = 
      (ProfileCommon)ProfileCommon.Create(
        CreateUserWizard1.UserName, true);
    profile.KanjiName = lastName.Text + " " + firstName.Text;
    profile.Save();
  }
    
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
  <head id="Head1" runat="server">
    <title>CreateUserWizard.CreatedUser サンプル</title>
  </head>
  <body>
    <form id="form1" runat="server">
      <div>
        <asp:createuserwizard 
          id="CreateUserWizard1" 
          oncreateduser="CreateUserWizard1_CreatedUser"
          runat="server">
          <wizardsteps>
            <asp:wizardstep 
              ID="Wizardstep1" 
              runat="server" 
              steptype="Start" 
              title="Identification">
              氏名を記入してください:<br />
              <table width="100%">
                <tr>
                  <td>
                    姓:</td>
                  <td>
                    <asp:textbox id="lastName" runat="server" />
                  </td>
               </tr>
               <tr>
                  <td>
                    名:</td>
                  <td>
                    <asp:textbox id="firstName" runat="server" />
                  </td>
                </tr>
              </table>
            </asp:wizardstep>
            <asp:createuserwizardstep 
                ID="Createuserwizardstep1" 
                runat="server" 
                title="Sign Up for Your New Account">
            </asp:createuserwizardstep>
          </wizardsteps>
        </asp:createuserwizard>
      </div>
    </form>
  </body>
</html>

なお、allowAnonymous="true" としておくと、aspnet_Users に匿名ユーザーが残ってしまうことがあるので、ユーザー登録のときに記入してもらう漢字名などのデータは、allowAnonymous="false" としておくのが正解のようです。

また、例えば買い物籠のように、最初は匿名ユーザーとして入力され、ある時点で認証ユーザーとなって購入に進むような場合は、MSDN ライブラリの ASP.NET プロファイル プロパティのユーザー ID の例に示すように Global.asax の Profile_OnMigrateAnonymous イベントハンドラで処置するのが適当かもしれません。

Tags: ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年5月  >>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar