none
Ajaxタイマーで動的に追加したコントロールのイベントが取れない RRS feed

  • 質問

  • お世話になっております。ASP.NET3.5 C# VS2008

    以下のようにAjaxタイマーで動的にコントロールを追加した時に
    そのイベントが取れずに悩んでいます。
    Tickの時に条件によっては更新せずにReturnしたい状況もありますので、

    UpdateMode="Conditional"
    ChildrenAsTriggers="false"
    UpdatePanel1.Update();

    は、このままにしたいのですが可能でしょうか。
    宜しくお願い致します。

     


    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="false">
     <ContentTemplate>
      <asp:Timer ID="Timer1" runat="server" ontick="Timer1_Tick" Interval="5000" EnableViewState="true" >
      </asp:Timer>
      <asp:Panel ID="Panel1" runat="server">
      </asp:Panel>
     </ContentTemplate>
    </asp:UpdatePanel>


    public partial class WebForm3 : System.Web.UI.Page
    {
     protected void Page_Load( object sender, EventArgs e )
     {
     }
     protected void Timer1_Tick( object sender, EventArgs e )
     {
      Button button = new Button();
      button.Text = DateTime.Now.ToString();
      button.Click += new EventHandler( button_Click );
      Panel1.Controls.Add( button );

      UpdatePanel1.Update();
     }
     void button_Click( object sender, EventArgs e )
     {
        // ここに飛んでこない
     }
    }

     

    2009年12月18日 1:14

回答

  • ちょっと試してみましたが、ダメっぽいです。そもそも Click イベントが発生していないような
    感じです。

    という訳で、代案ですが・・・

    もし、動的に Button を追加する理由が、単に Timer1_Tick 前には Button を表示したくないと
    いうことでしたら、Panel に Button を静的に配置して、Timer1_Tick で Panel.Visible プロパ
    ティの ture/false を切り替えるという手段はいかがですか?

    • 回答としてマーク Myon 2009年12月21日 1:41
    2009年12月19日 0:49
  • > 実際はデータベースから取得したレコードの数だけ
    > コントロールを生成しないといけません。

    コントロールを生成するタイミングは Timer.Tick イベントでなければならないのでしょうか? 

    もし、Page.Load のタイミングで DB のデータを取得可能であれば、Page.Load のハンドラで
    Button を生成して Button.Click にイベントハンドラをフックしてやれば、ボタンクリックで制御
    はそのハンドラに飛んでいきます。

    どうしても Timer.Tick イベントでないとダメということですと、そのタイミングで生成した Button
    コントロールのイベントは利用できず、他の手段を考えるよりなさそうです。例えば、以下のよう
    な手段はいかがですか?


    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    <script runat="server">
        protected void Timer1_Tick(object sender, EventArgs e)
        {
            Button button = new Button();
            button.ID = "Button1";
            button.Text = DateTime.Now.ToString();
            button.OnClientClick =
                String.Format("javascript:SetClickedButtonID('{0}', '{1}');", button.ID, DateTime.Now.ToString());
            PlaceHolder1.Controls.Add(button);       
            UpdatePanel1.Update();
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!String.IsNullOrEmpty(__ClickedButtonID.Value))
            {
                // Button.Click イベントの代わりに、ここでクリックの有無を判定して処置。
                Label1.Text = __ClickedButtonID.Value + " は " + __ClickedTime.Value + " にクリックされました!";
                __ClickedButtonID.Value = String.Empty;
                __ClickedTime.Value = String.Empty;
            }
        }
    </script>

    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ScriptManager ID="ScriptManager1" runat="server" />
            <asp:UpdatePanel ID="UpdatePanel1"
                runat="server"
                UpdateMode="Conditional"
                ChildrenAsTriggers="False">
                <ContentTemplate>
                    <asp:Timer ID="Timer1" runat="server" Interval="5000" OnTick="Timer1_Tick">
                    </asp:Timer>
                    <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
                    <asp:Label ID="Label1" runat="server" Text="Label1"></asp:Label>
                    <input type="hidden" runat="server" name="__ClickedButtonID" id="__ClickedButtonID" value="" />
                    <input type="hidden" runat="server" name="__ClickedTime" id="__ClickedTime" value="" />
                </ContentTemplate>
            </asp:UpdatePanel>
        </div>   
        </form>
    </body>
    </html>
    <script type="text/javascript">
        //<![CDATA[
        function SetClickedButtonID(buttonID, clickedTime) {
            document.form1.__ClickedButtonID.value = buttonID;
            document.form1.__ClickedTime.value = clickedTime;
        }
        //]]>
    </script>

    • 回答としてマーク Myon 2009年12月21日 1:41
    2009年12月20日 6:11
  • ASP.NET から離れて久しいので(for ASP.NET なのに?!)、「こんな風にできませんか」という提案です。

    ASP.NET で、サーバー サイドのイベントが発生するのは、ViewState にある情報よりサーバーが送り出した情報を“再生”し、その情報とポストされた情報の差があるからです。Ajax など、ViewState に載らないコントロールを作る場合は、そのコントロールを、ViewState を再生した後、イベントを発生させるメソッドより先に“再生成”しなければなりません。
    ASP.NET の解説をしているところに、イベントの発生順序も書いてありますから、見てください。
    ご参考→http://blogs.wankuma.com/jitta/archive/2005/11/24/19572.aspx

    たとえば、コントロールを追加した Tick イベントで、セッション情報などに「コントロールを追加した」ことを記録しておき、Load イベントでセッション情報を参照して追加されたコントロールを復元しておく、という方法が考えられます。
    Jitta@わんくま同盟
    • 回答としてマーク Myon 2009年12月21日 1:41
    2009年12月20日 14:27
  • 今さらながらですが、先にアップしたコードに見落としがあることに気がつきました。

    Page_Load の if {  } 内で  __ClickedButtonID.Value を String.Empty に書き換えた
    つもりだったのですが、実は UpdatePanel1.Update() をかけないと Value は書き換えら
    れませんでした。

    従って、次にポストバックされて(Timer.Tick イベントが発生するなどして)、Page_Load
    に制御が飛んだときに if (!String.IsNullOrEmpty(__ClickedButtonID.Value)) の条件
    は false にならず、{  } 内のコードが再度実行されてしまうという不具合がありました。

    というわけで、if の {  } 内に、UpdatePanel1.Update() を追加してやらないとダメです。
    さらに、UpdatePanel1.Update() を追加すると Button が消えてしまうので、Button を生
    成するコードも追加する必要がありました。

    オソマツでした。 m(_._)m

    • 回答としてマーク Myon 2009年12月25日 4:16
    2009年12月23日 2:05
  • > 上記の件、Session変数の方法を使えば

    Session を使わなくても、以下のようにすればよさそうですが? 「1Tick分だけ表示のタイミ
    ングは遅れます」という事も避けられそうですし。すでに対応済みでしたら失礼しました。

    public partial class WebForm3 : System.Web.UI.Page
    {
     protected void Page_Load( object sender, EventArgs e )
     {
       Button button = new Button();
       button.Text = DateTime.Now.ToString();
       button.Click += new EventHandler( button_Click );
       Panel1.Controls.Add( button );
      }
     }

     protected void Timer1_Tick( object sender, EventArgs e )
     {
      // フィルタ処理

      if (フィルタ条件一致)
        UpdatePanel1.Update();
     }

     void button_Click( object sender, EventArgs e )
     {
      ( ( Button )sender ).Text = "Clicked";
     }
    }

    • 回答としてマーク Myon 2009年12月25日 4:16
    2009年12月24日 13:51
  • SurferOnWww様、いつもご回答ありがとうございます。
    試してくださったようで感謝いたします。

    やっぱりダメですか。
    ボタンやパネルは、掲示板の質問用に簡単にしたものでして、
    実際はデータベースから取得したレコードの数だけ
    コントロールを生成しないといけません。
    しかもリアルタイム更新なのでAjaxタイマーも必要です。
    このような要求はあまりないのでしょうか。

     

    • 回答としてマーク Myon 2009年12月21日 1:41
    2009年12月19日 15:23

すべての返信

  • ちょっと試してみましたが、ダメっぽいです。そもそも Click イベントが発生していないような
    感じです。

    という訳で、代案ですが・・・

    もし、動的に Button を追加する理由が、単に Timer1_Tick 前には Button を表示したくないと
    いうことでしたら、Panel に Button を静的に配置して、Timer1_Tick で Panel.Visible プロパ
    ティの ture/false を切り替えるという手段はいかがですか?

    • 回答としてマーク Myon 2009年12月21日 1:41
    2009年12月19日 0:49
  • SurferOnWww様、いつもご回答ありがとうございます。
    試してくださったようで感謝いたします。

    やっぱりダメですか。
    ボタンやパネルは、掲示板の質問用に簡単にしたものでして、
    実際はデータベースから取得したレコードの数だけ
    コントロールを生成しないといけません。
    しかもリアルタイム更新なのでAjaxタイマーも必要です。
    このような要求はあまりないのでしょうか。

     

    • 回答としてマーク Myon 2009年12月21日 1:41
    2009年12月19日 15:23
  • > 実際はデータベースから取得したレコードの数だけ
    > コントロールを生成しないといけません。

    コントロールを生成するタイミングは Timer.Tick イベントでなければならないのでしょうか? 

    もし、Page.Load のタイミングで DB のデータを取得可能であれば、Page.Load のハンドラで
    Button を生成して Button.Click にイベントハンドラをフックしてやれば、ボタンクリックで制御
    はそのハンドラに飛んでいきます。

    どうしても Timer.Tick イベントでないとダメということですと、そのタイミングで生成した Button
    コントロールのイベントは利用できず、他の手段を考えるよりなさそうです。例えば、以下のよう
    な手段はいかがですか?


    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    <script runat="server">
        protected void Timer1_Tick(object sender, EventArgs e)
        {
            Button button = new Button();
            button.ID = "Button1";
            button.Text = DateTime.Now.ToString();
            button.OnClientClick =
                String.Format("javascript:SetClickedButtonID('{0}', '{1}');", button.ID, DateTime.Now.ToString());
            PlaceHolder1.Controls.Add(button);       
            UpdatePanel1.Update();
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!String.IsNullOrEmpty(__ClickedButtonID.Value))
            {
                // Button.Click イベントの代わりに、ここでクリックの有無を判定して処置。
                Label1.Text = __ClickedButtonID.Value + " は " + __ClickedTime.Value + " にクリックされました!";
                __ClickedButtonID.Value = String.Empty;
                __ClickedTime.Value = String.Empty;
            }
        }
    </script>

    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:ScriptManager ID="ScriptManager1" runat="server" />
            <asp:UpdatePanel ID="UpdatePanel1"
                runat="server"
                UpdateMode="Conditional"
                ChildrenAsTriggers="False">
                <ContentTemplate>
                    <asp:Timer ID="Timer1" runat="server" Interval="5000" OnTick="Timer1_Tick">
                    </asp:Timer>
                    <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
                    <asp:Label ID="Label1" runat="server" Text="Label1"></asp:Label>
                    <input type="hidden" runat="server" name="__ClickedButtonID" id="__ClickedButtonID" value="" />
                    <input type="hidden" runat="server" name="__ClickedTime" id="__ClickedTime" value="" />
                </ContentTemplate>
            </asp:UpdatePanel>
        </div>   
        </form>
    </body>
    </html>
    <script type="text/javascript">
        //<![CDATA[
        function SetClickedButtonID(buttonID, clickedTime) {
            document.form1.__ClickedButtonID.value = buttonID;
            document.form1.__ClickedTime.value = clickedTime;
        }
        //]]>
    </script>

    • 回答としてマーク Myon 2009年12月21日 1:41
    2009年12月20日 6:11
  • ASP.NET から離れて久しいので(for ASP.NET なのに?!)、「こんな風にできませんか」という提案です。

    ASP.NET で、サーバー サイドのイベントが発生するのは、ViewState にある情報よりサーバーが送り出した情報を“再生”し、その情報とポストされた情報の差があるからです。Ajax など、ViewState に載らないコントロールを作る場合は、そのコントロールを、ViewState を再生した後、イベントを発生させるメソッドより先に“再生成”しなければなりません。
    ASP.NET の解説をしているところに、イベントの発生順序も書いてありますから、見てください。
    ご参考→http://blogs.wankuma.com/jitta/archive/2005/11/24/19572.aspx

    たとえば、コントロールを追加した Tick イベントで、セッション情報などに「コントロールを追加した」ことを記録しておき、Load イベントでセッション情報を参照して追加されたコントロールを復元しておく、という方法が考えられます。
    Jitta@わんくま同盟
    • 回答としてマーク Myon 2009年12月21日 1:41
    2009年12月20日 14:27
  • ご回答ありがとうございました。

    Ajaxの問題かと思っていたのですが、
    アドバイスをお聞きしてAjaxは何の関係も無いことがわかりました。
    とにかく、他のコントロールのイベントで、
    動的にコントロールを生成しても、
    そこではイベントハンドラ登録できないのですね。

    ちょっと前に私がこちらで質問させていただいたのも同じ問題でした。
    のみ込みが悪く申し訳ありません。
    http://social.msdn.microsoft.com/Forums/ja-JP/csharpgeneralja/thread/5e77b6f1-061f-4dd8-83a1-95edf273de9c

    コントロールを生成したいタイミングで
    セッション変数なりにフラグを持たせて
    実際にコントロールを作るのは
    Page_LoadやPage_Initでということで理解しました。
    (SurferOnWww様ご提示の方法はこんな方法もあるのかと思い、大変参考になりました。)

    とりあえず、1Tick分だけ表示のタイミングは遅れますが
    以下の方法で解決できました。
    ありがとうございました。


    public partial class WebForm3 : System.Web.UI.Page
    {
     protected void Page_Load( object sender, EventArgs e )
     {
      if( Session[ "Ticked" ] != null )
      {
       Session[ "Ticked" ] = null;

       Button button = new Button();
       button.Text = DateTime.Now.ToString();
       button.Click += new EventHandler( button_Click );
       Panel1.Controls.Add( button );
       UpdatePanel1.Update();
      }
     }

     protected void Timer1_Tick( object sender, EventArgs e )
     {
      // フィルタ処理

      // フィルタ条件一致
      Session[ "Ticked" ] = 1;
     }

     void button_Click( object sender, EventArgs e )
     {
      ( ( Button )sender ).Text = "Clicked";
     }
    }

     

    2009年12月21日 1:41
  • 今さらながらですが、先にアップしたコードに見落としがあることに気がつきました。

    Page_Load の if {  } 内で  __ClickedButtonID.Value を String.Empty に書き換えた
    つもりだったのですが、実は UpdatePanel1.Update() をかけないと Value は書き換えら
    れませんでした。

    従って、次にポストバックされて(Timer.Tick イベントが発生するなどして)、Page_Load
    に制御が飛んだときに if (!String.IsNullOrEmpty(__ClickedButtonID.Value)) の条件
    は false にならず、{  } 内のコードが再度実行されてしまうという不具合がありました。

    というわけで、if の {  } 内に、UpdatePanel1.Update() を追加してやらないとダメです。
    さらに、UpdatePanel1.Update() を追加すると Button が消えてしまうので、Button を生
    成するコードも追加する必要がありました。

    オソマツでした。 m(_._)m

    • 回答としてマーク Myon 2009年12月25日 4:16
    2009年12月23日 2:05
  • SurferOnWww様
    コメントありがとうございます。

    動的にコントロールを生成するのは
    Page_Loadでやらないといけないということで、
    複雑なページですとものすごく重い処理になります。
    この処理をAjaxタイマーによるポーリングで毎回更新しているとパフォーマンスに影響しますので
    Tickの時にフィルタリングして負荷を減らしたいのですがうまく行きません。

    上記のSession[ "Ticked" ] を使った方法でも
    ボタンの場合はうまく行ってもCheckBox.CheckedChangedイベントを取ろうと思うと
    なぜかAjaxが落ちてしまってうまく行きません。

    エラーメッセージ↓
    Microsoft JScript 実行時エラー: Sys.WebForms.PageRequestManagerServerErrorException:
    このページの状態情報は無効です。壊れている可能性があります。

    結局はTickのたびにPageLoadで全てのコントロールを作り直して
    UpdatePanel1.Update() しないといけないようです。
    Ajaxでは頻繁に更新してもチカチカしないのが売りなんだと思いますが、
    UpdatePanelの中に画像が入っていると途端にチカチカします。(JavaScriptベースなので当然の結果?)

    なんとか負荷を減らすよい方法は無いでしょうか。
    AjaxタイマーでTickはするけどポストバックはしないなんてのは不可能ですよね?



    2009年12月23日 10:21
  • 上記の件、Session変数の方法を使えば
    PageLoadでUpdatePanel1.Update()しないといけないということはないようです。
    従って更新したくなければ(画面の変更点がないのであれば)、コントロールは全て作っておいて
    UpdatePanel1.Update()だけを無しにすればよいという結論に至りました。
    今となってはそのためのUpdatePanelという事なんだと思いますが、全然わかっていませんでした。
    一応チカチカの問題も解決しました。
    ありがとうございました。
    2009年12月24日 3:28
  • > 上記の件、Session変数の方法を使えば

    Session を使わなくても、以下のようにすればよさそうですが? 「1Tick分だけ表示のタイミ
    ングは遅れます」という事も避けられそうですし。すでに対応済みでしたら失礼しました。

    public partial class WebForm3 : System.Web.UI.Page
    {
     protected void Page_Load( object sender, EventArgs e )
     {
       Button button = new Button();
       button.Text = DateTime.Now.ToString();
       button.Click += new EventHandler( button_Click );
       Panel1.Controls.Add( button );
      }
     }

     protected void Timer1_Tick( object sender, EventArgs e )
     {
      // フィルタ処理

      if (フィルタ条件一致)
        UpdatePanel1.Update();
     }

     void button_Click( object sender, EventArgs e )
     {
      ( ( Button )sender ).Text = "Clicked";
     }
    }

    • 回答としてマーク Myon 2009年12月25日 4:16
    2009年12月24日 13:51
  • SurferOnWww様

    全く気づいておりませんでしたが、確かにおっしゃるとおりです。
    これでだいぶスッキリしてきました。
    ありがとうございました。

    2009年12月25日 4:15