トップ回答者
Ajaxタイマーで動的に追加したコントロールのイベントが取れない

質問
-
お世話になっております。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 )
{
// ここに飛んでこない
}
}
回答
-
> 実際はデータベースから取得したレコードの数だけ
> コントロールを生成しないといけません。コントロールを生成するタイミングは 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
-
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
-
今さらながらですが、先にアップしたコードに見落としがあることに気がつきました。
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
-
> 上記の件、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
すべての返信
-
> 実際はデータベースから取得したレコードの数だけ
> コントロールを生成しないといけません。コントロールを生成するタイミングは 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
-
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
-
ご回答ありがとうございました。
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";
}
} -
今さらながらですが、先にアップしたコードに見落としがあることに気がつきました。
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
-
SurferOnWww様
コメントありがとうございます。
動的にコントロールを生成するのは
Page_Loadでやらないといけないということで、
複雑なページですとものすごく重い処理になります。
この処理をAjaxタイマーによるポーリングで毎回更新しているとパフォーマンスに影響しますので
Tickの時にフィルタリングして負荷を減らしたいのですがうまく行きません。
上記のSession[ "Ticked" ] を使った方法でも
ボタンの場合はうまく行ってもCheckBox.CheckedChangedイベントを取ろうと思うと
なぜかAjaxが落ちてしまってうまく行きません。
エラーメッセージ↓
Microsoft JScript 実行時エラー: Sys.WebForms.PageRequestManagerServerErrorException:
このページの状態情報は無効です。壊れている可能性があります。
結局はTickのたびにPageLoadで全てのコントロールを作り直して
UpdatePanel1.Update() しないといけないようです。
Ajaxでは頻繁に更新してもチカチカしないのが売りなんだと思いますが、
UpdatePanelの中に画像が入っていると途端にチカチカします。(JavaScriptベースなので当然の結果?)
なんとか負荷を減らすよい方法は無いでしょうか。
AjaxタイマーでTickはするけどポストバックはしないなんてのは不可能ですよね? -
> 上記の件、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