The Command event of dynamically loaded controls

D

Donald Xie

Hi,

I noticed an interesting effect when working with controls that are
dynamically loaded. For instance, on a web form with a PlaceHolder control
named ImageHolder, I dynamically add an image button at runtime:

//----- Code snippet
protected System.Web.UI.WebControls.PlaceHolder ImageHolder;

private void Page_Load(object sender, System.EventArgs e)
{
if (!IsPostBack)
LoadDynamicImageButton();
}

private void ImageButton_Click(object sender,
System.Web.UI.ImageClickEventArgs e)
{
Response.Write("Image button clicked");
}

private void LoadDynamicImageButton()
{
ImageButton btn = new ImageButton();
btn.ImageUrl = "Images/Minus.png";
btn.Click += new ImageClickEventHandler(ImageButton_Click);
ImageHolder.Controls.Add(btn);
}
//----- End code snippet

The first time the page loads, the image button is created. When I click it,
it reloads the page but doesn't trigger its ImageClickEventHandler. If I
forcefully run LoadDynamicImageButton(), i.e., remove the IsPostBack check
in Page_Load() to recreate the image, its ImageClickEventHandler is
triggered. This happens with the Command event as well.

It seems that on post-back, if a dynamically loaded control is not loaded,
then its event handler is not wired. Its properties such as CommandName and
CommandArgument, OTOH, persists from the last load.

The problem is that if I dynamically load up the PlaceHolder control with
different controls according to the button clicked, this behavior forces all
those controls to be loaded twice - first to load all default controls just
to wire up their event handlers, then to reload them again according to the
event. There are also other factors, such as the assigning a differnet
ImageUrl, that seem to affect whether or not the event handler is triggered.
I'm still working on it to hopefully isolate the problem.

Is there a workaround?

Thanks,
Donald Xie
 
S

Steven Cheng[MSFT]

Hi Donald,

Thanks for posting in the community!
From your description, you dynamically add some command controls such as
button or ImageButton into a webform within a placeholder.(in the page_load
event when the page is first loaded). Also, you registered a "Click" event
handler for the dynamicly added control. However, you found that when you
clicked the control , its event handler hadn't been called and if you did
the adding and registering event handler operation every time the page is
loaded in Page_load, it worked well, yes?

I've tested the code you provided and also tried using other controls such
as Button and did encoutnered the same problem. Currently, I am finding
proper resource to assist you and we will update as soon as posible. In the
meantime, if you have any new findings, please feel free to post here.


Regards,

Steven Cheng
Microsoft Online Support

Get Secure! www.microsoft.com/security(This posting is provided "AS IS",
with no warranties, and confers no rights.)
 
M

Mike Moore [MSFT]

Hi Donald,

This is normal behavior and results from the way ASP.NET remembers the old
value for that text box in order to determine if the new value is different.

Each time the form is sent to the browser, ASP.NET stores the current text
box value (and other controls) in a hidden field which it sends to the
browser. When the browser posts the page back, the hidden field is sent to
the server along with the rest of the form. ASP.NET parses the list of old
values from the hidden field and attempts to associate each value with a
control. Then it compares the new value of the control with the old value
and triggers events.

The text box must be recreated to allow ASP.NET to associate it with the
old value from the hidden field. Only then can ASP.NET compare the new and
old values.

Here is a code sample which demonstrates one text box that correctly
triggers the changed event and three text boxes which fail for each of
three reasons.


**** HTML
<form id="Form1" method="post" runat="server">
<P>This sample contains a series of text boxes added to the page<BR>
programmatically with Controls.Add. The variations in these<BR>
text boxes demonstrate how to (and how not to) add a control<BR>
so that it can fire an event.<BR>
<BR>
</P>
<asp:panel id="Panel1" runat="server">
<P>
<asp:Label id="Label5" runat="server" Width="115px"> With
ViewState:</asp:Label>
<asp:placeHolder id="PlaceHolder1"
runat="server"></asp:placeHolder>&nbsp;
<asp:Label id="Label1" runat="server" Width="368px"
EnableViewState="False"></asp:Label></P>
<P>
<asp:Label id="Label6" runat="server" Width="115px" Height="19"> No
ViewState:</asp:Label>
<asp:placeHolder id="PlaceHolder2"
runat="server"></asp:placeHolder>&nbsp;
<asp:Label id="Label2" runat="server" Width="368px"
EnableViewState="False"></asp:Label></P>
<P>
<asp:Label id="Label7" runat="server" Width="115px"
Height="19px">Controls.AddAt:</asp:Label>
<asp:Label id="Label3" runat="server" Width="368px"
EnableViewState="False"></asp:Label></P>
<P>
<asp:Label id="Label8" runat="server" Width="115px"
Height="19px">!IsPostBack:</asp:Label>
<asp:placeHolder id="PlaceHolder4"
runat="server"></asp:placeHolder>&nbsp;
<asp:Label id="Label4" runat="server" Width="368px"
EnableViewState="False"></asp:Label></P>
</asp:panel>
<P><asp:button id="Button1" runat="server" Text="Submit" Width="100px"
Height="27px"></asp:button></P>
<P><hr></P>
<P>As you can see through experimenting with the above, the text<BR>
box labeled "With ViewState" is the only one that works properly.<BR>
<BR>
The "No ViewState" and the "Controls.AddAt" text boxes both<BR>
fire their changed events whenever they contain text, even if that<BR>
text has not changed.<BR>
<BR>
The "!IsPostBack" text box simply disappears upon postback.</P>
</form>


**** CODE
private void Page_Load(object sender, System.EventArgs e)
{
System.Web.UI.WebControls.TextBox MyTextBox;

MyTextBox = new TextBox();
MyTextBox.ID = "TextBox1";
MyTextBox.EnableViewState=true;
PlaceHolder1.Controls.Add(MyTextBox);
MyTextBox.TextChanged += new
System.EventHandler(this.TextBox_TextChanged);

MyTextBox = new TextBox();
MyTextBox.ID = "TextBox2";
MyTextBox.EnableViewState=false;
PlaceHolder2.Controls.Add(MyTextBox);
MyTextBox.TextChanged += new
System.EventHandler(this.TextBox_TextChanged);

MyTextBox = new TextBox();
MyTextBox.ID = "TextBox3";
MyTextBox.EnableViewState=true;
Panel1.Controls.AddAt(15,MyTextBox);
MyTextBox.TextChanged += new
System.EventHandler(this.TextBox_TextChanged);

if(!IsPostBack)
{
MyTextBox = new TextBox();
MyTextBox.ID = "TextBox4";
MyTextBox.EnableViewState=true;
PlaceHolder4.Controls.Add(MyTextBox);
MyTextBox.TextChanged += new
System.EventHandler(this.TextBox_TextChanged);
}
}

private void TextBox_TextChanged(object sender, System.EventArgs e)
{
TextBox txtBoxSender = (TextBox)sender;
string strTextBoxID = txtBoxSender.ID;

switch(strTextBoxID)
{
case "TextBox1":
Label1.Text = "Changed";
break;
case "TextBox2":
Label2.Text = "Changed";
break;
case "TextBox3":
Label3.Text = "Changed";
break;
case "TextBox4":
Label4.Text = "Changed";
break;
}
}


---
Please see these articles for more information.

Adding Controls to a Web Forms Page Programmatically
http://msdn.microsoft.com/library/en-us/vbcon/html/vbtskaddingcontrolstowebf
ormspageprogrammatically.asp

HOW TO: Dynamically Create Controls in ASP.NET by Using Visual C# .NET
http://support.microsoft.com/default.aspx?scid=kb;EN-US;317794

Does this answer your question?

Thank you, Mike
Microsoft, ASP.NET Support Professional

Microsoft highly recommends to all of our customers that they visit the
http://www.microsoft.com/protect site and perform the three straightforward
steps listed to improve your computer’s security.

This posting is provided "AS IS", with no warranties, and confers no rights.


--------------------
 
D

Donald Xie

Thanks for the excellent explanation, Mike. Yes that answers my question.

Now I'm trying to find a good solution for my problem. In a nutshell, I need
to load a hierarchical list of folders and files from a database and display
them on a tree - very much the same as the MSDN Library deeptree.asp. The
number of nodes is potentially large, so I'll only load all top level
folders plus folders in the branch selected by the user. Only one branch can
be expanded each time. My users can click a folder node to expand or
collapse it, based on the selected folder's state.

Since my client doesn't want to use a third party tree control, my first
thought i\was to programmatically create a list of ImageButton/LinkButton
pairs to display the folders. This dynamic control's behavior means that:

1. I'll need to load the folder list in the first pass to wire up the
controls and corresponding event handlers. After it's loaded, the event
handler will fire and give me the selected folder. Then,

2. I'll then load the folder list again with the selected folder
expanded/collapsed.

It doesn't seem like a very efficient solution now because it requires two
trips to the database to load the folder list. Caching doesn't sound very
attractive either because list can be quite big and changes frequently.

Any suggestions are very much appreciated.

Cheers,
Donald Xie

"Mike Moore [MSFT]" said:
Hi Donald,

This is normal behavior and results from the way ASP.NET remembers the old
value for that text box in order to determine if the new value is different.

Each time the form is sent to the browser, ASP.NET stores the current text
box value (and other controls) in a hidden field which it sends to the
browser. When the browser posts the page back, the hidden field is sent to
the server along with the rest of the form. ASP.NET parses the list of old
values from the hidden field and attempts to associate each value with a
control. Then it compares the new value of the control with the old value
and triggers events.

The text box must be recreated to allow ASP.NET to associate it with the
old value from the hidden field. Only then can ASP.NET compare the new and
old values.

Here is a code sample which demonstrates one text box that correctly
triggers the changed event and three text boxes which fail for each of
three reasons.


**** HTML
<form id="Form1" method="post" runat="server">
<P>This sample contains a series of text boxes added to the page<BR>
programmatically with Controls.Add. The variations in these<BR>
text boxes demonstrate how to (and how not to) add a control<BR>
so that it can fire an event.<BR>
<BR>
</P>
<asp:panel id="Panel1" runat="server">
<P>
<asp:Label id="Label5" runat="server" Width="115px"> With
ViewState:</asp:Label>
<asp:placeHolder id="PlaceHolder1"
runat="server"></asp:placeHolder>&nbsp;
<asp:Label id="Label1" runat="server" Width="368px"
EnableViewState="False"></asp:Label></P>
<P>
<asp:Label id="Label6" runat="server" Width="115px" Height="19"> No
ViewState:</asp:Label>
<asp:placeHolder id="PlaceHolder2"
runat="server"></asp:placeHolder>&nbsp;
<asp:Label id="Label2" runat="server" Width="368px"
EnableViewState="False"></asp:Label></P>
<P>
<asp:Label id="Label7" runat="server" Width="115px"
Height="19px">Controls.AddAt:</asp:Label>
<asp:Label id="Label3" runat="server" Width="368px"
EnableViewState="False"></asp:Label></P>
<P>
<asp:Label id="Label8" runat="server" Width="115px"
Height="19px">!IsPostBack:</asp:Label>
<asp:placeHolder id="PlaceHolder4"
runat="server"></asp:placeHolder>&nbsp;
<asp:Label id="Label4" runat="server" Width="368px"
EnableViewState="False"></asp:Label></P>
</asp:panel>
<P><asp:button id="Button1" runat="server" Text="Submit" Width="100px"
Height="27px"></asp:button></P>
<P><hr></P>
<P>As you can see through experimenting with the above, the text<BR>
box labeled "With ViewState" is the only one that works
 
S

Steven Cheng[MSFT]

Hi Donald,


Thank you for the response. Regarding on the issue, we are
finding proper resource to assist you and we will update as soon as posible.


Regards,

Steven Cheng
Microsoft Online Support

Get Secure! www.microsoft.com/security(This posting is provided "AS IS",
with no warranties, and confers no rights.)
 
S

Steven Cheng[MSFT]

Hi Donald,


As for the situation you mentioned in the last reply, here is my
suggestions on it:
1. If we manually build a "tree" programatically via a list of ImageButtons
or LinkButtons, I'm afraid there will cause some performance issues since
every time page loaded , it need to create and add these dynamic controls
so as to associate their states from viewstate, also register the event
handler for them. This will be a large challenge to the serverside.

2. Would you like to have a try on the TreeView control in the Microsoft
Internet Explorer WebControls? This treeview control has been added to the
MS's web control collections and have detailed reference and tutorial in
MSDN. Also, the IE webcontrols' source code is opened to public , you're
feel to extend their existed functions. Here is some tech references and
articls on the TreeView web control:

#TreeView WebControl Reference
http://msdn.microsoft.com/library/en-us/dnaspp/html/aspnet-usingtreeviewiewe
bcontrol.asp?frame=true

And here is a test page I used to test dynamically add nodes when a certain
node is expanded. The treeview has
4 hierarchies and each contains 20 nodes. When a node is first time
expanded or selected, it dynamically add its child nodes. Please have a
look to see whether it possible for you to use this TreeView control.

In addition, if you have the "Application Center Test" installed, you may
try having a test on different approachs such as using TreeView or manually
add button lists so as to see the realtime performance. And here are
another two tech articles, one is about the MSDN site's TreeView 's
implementation in C#. I think they may be helpful to you,too.

#Hierarchical Data Binding in ASP.NET
http://msdn.microsoft.com/library/en-us/dnaspp/html/aspn-hierdatabinding.asp
?frame=true

#The MSDN Table of Contents in C#
http://msdn.microsoft.com/library/en-us/dnexxml/html/xml01152001.asp?frame=t
rue


Please check out the above suggestions. If you have any questions or if my
suggestions not quite suitable for your situation, please feel free to post
here.



Regards,

Steven Cheng
Microsoft Online Support

Get Secure! www.microsoft.com/security
(This posting is provided "AS IS", with no warranties, and confers no
rights.)
 
S

Steven Cheng[MSFT]

Hi Donald,


Sorry for forgetting attach My test page's code in the last reply, here is
the Page code:
#If you haven't the IE web control's reference or code, you may get them at
http://www.asp.net/ControlGallery/ControlDetail.aspx?Control=75&tabindex=2

-----------------------------aspx page-----------------------------------
<%@ Register TagPrefix="iewc" Namespace="Microsoft.Web.UI.WebControls"
Assembly="Microsoft.Web.UI.WebControls" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>DynaTree</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema"
content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body>
<form id="Form1" method="post" runat="server">
<table width="500" align="center">
<tr>
<td><FONT face="ËÎÌå">
<iewc:TreeView id="trvMain"
runat="server"></iewc:TreeView></FONT></td>
</tr>
<tr>
<td></td>
</tr>
</table>
</form>
</body>
</HTML>


-------------------------code behind class--------------
public class DynaTree : System.Web.UI.Page
{
protected Microsoft.Web.UI.WebControls.TreeView trvMain;

private void Page_Load(object sender, System.EventArgs e)
{
if(!IsPostBack)
{
LoadData();
BindTreeNodes(trvMain.Nodes,GetChildNodes("0"));
trvMain.AutoPostBack = true;
}
}

protected void LoadData()
{
DataTable tb = new DataTable();
tb.Columns.Add("nid");
tb.Columns.Add("pid");
tb.Columns.Add("name");
tb.Columns.Add("url");
tb.Columns.Add("target");

int i=0;
int j=0;
int k=0;
int l=0;
int nodeindex = 1;



for(i=1;i<=20;i++)
{
DataRow row1 = tb.NewRow();
row1["nid"] = i.ToString();
row1["pid"] = "0";
row1["name"] = "FirstLevelNode_"+nodeindex.ToString();
row1["url"] = "";
row1["target"] = "";
tb.Rows.Add(row1);

for(j=1;j<=20;j++)
{

DataRow row2 = tb.NewRow();
row2["nid"] = i.ToString() + j.ToString();
row2["pid"] = i.ToString();
row2["name"] = "SecondLevelNode_"+j.ToString();
row2["url"] = "";
row2["target"] = "";
tb.Rows.Add(row2);


for(k=1;k<=20;k++)
{

DataRow row3 = tb.NewRow();
row3["nid"] = i.ToString() + j.ToString() + k.ToString();
row3["pid"] = i.ToString() + j.ToString();
row3["name"] = "ThirdLevelNode_"+k.ToString();
row3["url"] = "";
row3["target"] = "";
tb.Rows.Add(row3);

for(l=1;l<20;l++)
{
DataRow row4 = tb.NewRow();
row4["nid"] = i.ToString() + j.ToString() + k.ToString() +
l.ToString();
row4["pid"] = i.ToString() + j.ToString() + k.ToString();
row4["name"] = "ForthLevelNode_"+k.ToString();
row4["url"] = "";
row4["target"] = "";
tb.Rows.Add(row4);
}

}
}
}

Session["TEMP_DATA"] = tb;

}


protected DataRow[] GetChildNodes(string pid)
{
DataTable tb = (DataTable)Session["TEMP_DATA"];
DataRow[] rows = tb.Select("pid = '" + pid +"'");

return rows;
}

protected void BindTreeNodes(TreeNodeCollection nodes, DataRow[] rows)
{


for(int i=0;i<rows.Length;i++)
{
TreeNode node = new TreeNode();
node.ID = rows["nid"].ToString();
node.Text = rows["name"].ToString();
node.NavigateUrl = rows["url"].ToString();
node.Target = rows["target"].ToString();

TreeNode emptynode = new TreeNode();
emptynode.ID = node.ID + "#$#EMPTY";

node.Nodes.Add(emptynode);
nodes.Add(node);
}

}

#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{

InitializeComponent();
base.OnInit(e);
}


private void InitializeComponent()
{
this.trvMain.Expand += new
Microsoft.Web.UI.WebControls.ClickEventHandler(this.trvMain_Expand);
this.trvMain.SelectedIndexChange += new
Microsoft.Web.UI.WebControls.SelectEventHandler(this.trvMain_SelectedIndexCh
ange);
this.Load += new System.EventHandler(this.Page_Load);

}
#endregion

private void trvMain_Expand(object sender,
Microsoft.Web.UI.WebControls.TreeViewClickEventArgs e)
{
TreeNode pnode = ((TreeView)sender).GetNodeFromIndex(e.Node);
TreeNode emptynode = pnode.Nodes[0];

if(emptynode.ID.IndexOf("#$#EMPTY") != -1)
{
pnode.Nodes.Clear();
BindTreeNodes(pnode.Nodes,GetChildNodes(pnode.ID));
}

}


private void trvMain_SelectedIndexChange(object sender,
Microsoft.Web.UI.WebControls.TreeViewSelectEventArgs e)
{
TreeNode oldnode = ((TreeView)sender).GetNodeFromIndex(e.OldNode);
TreeNode newnode = ((TreeView)sender).GetNodeFromIndex(e.NewNode);

if(newnode.Nodes.Count>0)
{
TreeNode emptynode = newnode.Nodes[0];

if(emptynode.ID.IndexOf("#$#EMPTY") != -1)
{
newnode.Nodes.Clear();
BindTreeNodes(newnode.Nodes,GetChildNodes(newnode.ID));
}
}


}


}
 
D

Donald Xie

Hi Steven,

Thanks so much for your suggestions and the sample code - they are exactly
the information I need.

The TreeView control is the more conventional, and the MSDN TOC in C# is
very elegant and certainly well proven. I will go through both to see which
is the best for this application.

The hierarchical data binding approach is in essense what I'm doing
manually, as the depth of each node is variable. However I suspect that it
also suffers the same render-twice problem if we want to do more than just
displaying the nodes and get their state on post back.

Best,
Donald Xie
 
S

Steven Cheng[MSFT]

Hi Donald,


Thanks for your response. I'm glad that my suggestions are helpful to you.
As for the things I mentioned in the last reply, here some of my further
suggestions:
As for the "MSDN TOC in C#", since it is not provided as a Web Server
Control and the DataSource is based on serveral xml files. If you'd like to
use this means, I think you may try dynamically generic the certain XML
files from your own datasource. That'll save your times.

Anyway, which approach to choose all depends on the real time performance.
Hope you'll soon figure out the most appropriate means.



Regards,

Steven Cheng
Microsoft Online Support

Get Secure! www.microsoft.com/security
(This posting is provided "AS IS", with no warranties, and confers no
rights.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top