I'm doing some test with dynamic controls. Here the code:
ASPX page
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True"
onselectedindexchanged="DropDownList1_SelectedIndexChanged">
<asp:ListItem Value="0">Nothing</asp:ListItem>
<asp:ListItem Value="1">Two buttons</asp:ListItem>
</asp:DropDownList>
<asp:Panel ID="Panel1" runat="server">
</asp:Panel>
</div>
</form>
</body>
</html>
Code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (ViewState["CreateDynamicButton"] != null)
{
CreateControls();
}
}
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
CreateControls();
}
void Button_Click(object sender, EventArgs e)
{
Button b = (Button)sender;
Response.Write("You clicked the button with ID " + b.ID);
}
private void CreateControls()
{
if (DropDownList1.SelectedValue.Equals("1"))
{
Panel1.Controls.Clear();
Button b1 = new Button();
b1.ID = "b1";
b1.Text = "Button 1";
Button b2 = new Button();
b2.ID = "b2";
b2.Text = "Button 2";
b1.Click += new EventHandler(Button_Click);
b2.Click += new EventHandler(Button_Click);
Panel1.Controls.Add(b1);
Panel1.Controls.Add(b2);
ViewState["CreateDynamicButton"] = true;
}
}
}
This code works but as you can see I remove all controls in the Panel1.Controls before add the buttons becouse when I choose to create them for the second time I get an exeption for duplicated controls ID.
I think that for two buttons the operation is very fast but with a larger number of controls the elaboration time will be longer.
Can you suggest me a better way to re-generate controls after PostBack without this workaround?
For starters, if you're trying to remove the controls (in this case buttons) for good, are you best off using the Dispose method? clearing the panel of the control won't dispose of it, which would explain the already in use ID's. You can do a simple loop to do this, regardless of how many controls are in the panel;
foreach(Control control in Container)
{
control.Dispose();
}
also, to create buttons i see you're just naming them btn1, btn2 and so on. You could do this in a loop too;
for(int i = 0; i >= yourInt; i++)
{
Button b = new Button();
b.ID = "b" + i;
b.Text = "Button " + i;
}
}
Create a class variable private bool ControlsCreated = false;
In your CreateControls method then check
if (!ControlsCreated) {
//your code to create controls
}
This makes sure the controls are created only once. If at any point later you need to recreate the controls (dropdownlist value changed), just clear the container and set ControlsCreated to false.
Related
I am very new to ASP.NET but I have background in C and C#. I am trying to make web application which will be connected with my database. I did that and that is OK. The problem occurred when I added buttons for moving forward and backward through the database... It was working fine but only for one click! Then I thought that probably I have bug in the code and I decided to do it in easy way.
I created new web form with just one button and one label, but behavior is still the same- after first click event is not fired again. Any help?
My code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace DummyTest
{
public partial class WebForm1 : System.Web.UI.Page
{
int pom = 0;
protected void Page_Load(object sender, EventArgs e)
{
Label1.Text = Convert.ToString(pom);
}
protected void Button1_Click(object sender, EventArgs e)
{
pom++;
Label1.Text = Convert.ToString(pom);
}
}
}
and source
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs"
Inherits="DummyTest.WebForm1" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
Dummy Test
<br />
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Button" />
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
<br />
</div>
</form>
</body>
Your Webform class gets recreated on every request - that's just how it works. You need to persist the field pom in some other way. You could add it to the session state, but that affects scalability of the application as you then hold state on the server.
It is already persisted by the label control, so you could do something like this in your click event:
var pom = Int.Parse(Label1.Text);
pom++;
Label1.Text = pom.ToString();
However, this will not always be the case for something you want to persist. In these cases I would add a hidden field to the html which holds it. My WebForms is a little rusty, but in your markup:
<asp:HiddenField id="pom" value="0" />
then pull the value out and increment in the click event as I've done above.
Whenever you click your int pom = 0 get initialized to zero and then you set value that is why it looks it calling once. either make it static or better to try:
protected void Button1_Click(object sender, EventArgs e)
{
Label1.Text = "" + (Convert.ToInt32(Label1.Text) + 1);
}
You could try moving your initialisation into an IsPostBack check, so reloading the page does not re-intitialise the variable to 0. The IsPostBack value indicates whether the page is being rendered for the first time or is being loaded in response to a postback and a button click event triggers a postback.
Additional
The IsPostBack fires at a specific stage in the page lifecycle
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
int pom = 0;
}
Label1.Text = Convert.ToString(pom);
}
To some extent it depends what the purpose of the pom variable is. If it is tracking clicks per user then the above should help. However if you're tracking clicks across all users then you should consider something else (probably using the global.asax)
It is working now! Here is code for Up and Down.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace DummyTest
{
public partial class WebForm1 : System.Web.UI.Page
{
static int pom;
protected void Page_Load(object sender, EventArgs e)
{
if (Page.IsPostBack == false)
{
pom = 0;
Label1.Text = Convert.ToString(pom);
}
}
protected void Button1_Click(object sender, EventArgs e)
{
var pom = Int32.Parse(Label1.Text);
pom++;
Label1.Text = Convert.ToString(pom);
}
protected void Button2_Click(object sender, EventArgs e)
{
var pom = Int32.Parse(Label1.Text);
pom--;
Label1.Text = Convert.ToString(pom);
}
}
}
I've created a very simple bit of code in C# to display data in an asp:Table. On the initial page load all looks great. I've used an AJAX timer to refresh every 15 seconds to keep the data up to date. The problem is after it refreshes the table shows all the data duplicated. I've tried debugging and again all looks good until the AJAX refresh and then it seems to jump all over the place in the C# code rather than following the expected sequence.
Hopefully I've not approached this in completely the wrong way but if I have please tell me! Is there a way to prevent/fix this duplication?
I've replicated the problem code here in a very simple form. Default.aspx:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="testASPTables._default" %>
<!DOCTYPE html>
<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:ScriptManager>
<asp:Table ID="Table1" runat="server">
</asp:Table>
<asp:Timer ID="Timer1" runat="server" Interval="15000" OnTick="Timer1_Tick">
</asp:Timer>
</div>
</form>
</body>
</html>
...and the codebehind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace testASPTables
{
public partial class _default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
TableRow[] tRows = new TableRow[5];
TableCell[] tCells = new TableCell[5];
for (int i = 0; i < 5; i++)
{
tRows[i] = new TableRow();
Table1.Rows.Add(tRows[i]);
tCells[i] = new TableCell();
tRows[i].Cells.Add(tCells[i]);
tCells[i].Text = "Cell Number: " + i.ToString();
}
}
protected void Timer1_Tick(object sender, EventArgs e)
{
Page_Load(sender, e);
}
}
}
You're calling Page_Load twice. Page_Load runs on postback, then you call it again from Timer1_Click.
You need to always be concious of whether the page is in a postback or not and how Page_Load works.
Here's what I'd do.
protected void Page_Load(object sender, EventArgs e)
{
if(!IsPostBack)
{
LoadTableData();
}
}
protected void LoadTableData()
{
Table1.Rows.Clear(); //I added this
TableRow[] tRows = new TableRow[5];
TableCell[] tCells = new TableCell[5];
for (int i = 0; i < 5; i++)
{
tRows[i] = new TableRow();
Table1.Rows.Add(tRows[i]);
tCells[i] = new TableCell();
tRows[i].Cells.Add(tCells[i]);
tCells[i].Text = "Cell Number: " + i.ToString();
}
}
protected void Timer1_Tick(object sender, EventArgs e)
{
LoadTableData();
}
Oh, and in addition to that, you'd need to clear the existing table rows.
By the way, this is very inefficient. You're reloading the entire table every 15 seconds. Multiple that by a few clients and you're practically performing a DoS attack on yourself. Consider using SignalR to push new changes to the clients as rows are added to the underlying data.
I write one example to create own control on ASP.NET Froms. The controls very simple- combobox and button. User need choose value and when after he submit the button, the value from combobox need display in label.
So. Code of my Control:
public class MyControl:Control,IPostBackEventHandler
{
protected override void Render(HtmlTextWriter writer)
{
writer.AddAttribute("size","1");
writer.AddAttribute("ID","List2");
writer.AddAttribute("name", "ListYear");
writer.RenderBeginTag(HtmlTextWriterTag.Select);
for (int i = 1950; i < DateTime.Now.Year; i++)
{
writer.RenderBeginTag(HtmlTextWriterTag.Option);
writer.WriteEncodedText(i.ToString());
writer.RenderEndTag();
}
writer.RenderEndTag();
writer.AddAttribute("type","submit");
writer.AddAttribute("value","ClickMe");
writer.AddAttribute("name","BtnChange");
writer.RenderBeginTag(HtmlTextWriterTag.Input);
writer.RenderEndTag();
base.Render(writer);
}
public delegate void OnClickEventHandler(object sender, EventArgs args);
public event OnClickEventHandler Click;
public void RaisePostBackEvent(string eventArgument)
{
Click(this, new EventArgs());
}
}
The Page ASP:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="TestMyControl.aspx.cs" Inherits="Hello.TestMyControl" %>
<%# Register assembly="Hello" namespace="Hello" tagPrefix="MyContrl" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="Label1" runat="server" Text="Label" Visible="False"></asp:Label>
<br />
<MyContrl:MyControl runat="server" OnClick="Unnamed1_OnClick" ID="Control1"></MyContrl:MyControl>
</div>
</form>
</body>
</html>
And in the end Event function:
protected void Unnamed1_OnClick(object sender, EventArgs args)
{
Label1.Visible = true;
Label1.Text="You choose "+Control1.????+" year";
}
What substitute for a question mark that take the value from the list?
P.S. Something strange is going on. Because when I click the button, the handler is not called, and I can not get into Unnamed1_OnClick
Since you have set the value on an attribute, to retrieve it you need to access Attributes property
Make your control inherit from HtmlControl
public class MyControl : HtmlControl, IPostBackEventHandler
{
...
On your page
<MyContrl:MyControl runat="server" OnClick="Unnamed1_OnClick" ID="Control1"></MyContrl:MyControl>
On your code
Label1.Text = Control1.Attributes["value"];
You can debug this line to see all available attributes
You would need to pass the name of the combobox and its the value of the text that is selected.
Like this:
protected void Unnamed1_OnClick(object sender, EventArgs args)
{
Label1.Visible = true;
Label1.Text="You choose "+ myCustomControl.SelectedItem.Value.ToString()
+ " year";
}
(Sorry. I misread the initial post and edited my code accordingly once I realized my mistake.)
Add select list to your user control with name="YourSelectList"
then in the click event handler
protected void Unnamed1_OnClick(object sender, EventArgs args)
{
Label1.Visible = true;
Label1.Text="You choose "+Control1.YourSelectList.SelectedValue.ToString()+" year";
}
I have created a TextBox dynamically, and i am getting the value of the textbox when i click the button. But the value entered in the dynamic textbox gets empty when i click the button.
Below is my ASPX Code:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="Reports.WebForm1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
</div>
<asp:DropDownList ID="DropDownList1" runat="server"
onselectedindexchanged="DropDownList1_SelectedIndexChanged" AutoPostBack = "true">
<asp:ListItem>Text</asp:ListItem>
<asp:ListItem>Check</asp:ListItem>
</asp:DropDownList>
<asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="GetTextBoxValue" />
</form>
</body>
</html>
CodeBehind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Reports
{
public partial class WebForm1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
TextBox TB = new TextBox();
TB.ID = "abc";
form1.Controls.Add(TB);
Response.Write(Request.Form["abc"]);
}
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
createcontrol();
}
protected void createcontrol()
{
if (DropDownList1.SelectedValue.ToLower().Trim() == "text")
{
TextBox TB = new TextBox();
TB.ID = "abc";
form1.Controls.Add(TB);
}
}
}
}
When you use dynamic controls in .NET and want their values to be accessible after postback you need to create them within the Page_Init method of the page. In essence its not working because ViewState has already been set by the time you created the controls. See this guide for detailed info on this topic https://web.archive.org/web/20211020131055/https://www.4guysfromrolla.com/articles/081402-1.aspx . To fix the problem elevate your Textbox instantiation code to the Page_Init method and all should be well.
Basically, in a nutshell, the problem is that dynamically generated triggers for an UpdatePanel can no longer be found (by ASP.NET) as soon as I add them as children of a custom control.
Since the amount of code I'm working on is quite substantial I've recreated the problem on a smaller scale, which will make it easier to debug.
The error thrown in my face is:
A control with ID 'theTrigger' could not be found for the trigger in UpdatePanel 'updatePanel'.
I'm not sure whether this implementation of a "custom control" is the right way to go about it, but I did not write the original implementation: I'm working with code written by a previous developer to which I cannot make large modifications. It looks a little unusual to me, but, alas, this is what I've been given.
Default.aspx
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TestWeb.Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Panel runat="server" ID="panel">
</asp:Panel>
<asp:ScriptManager ID="scriptManager" runat="server"></asp:ScriptManager>
<asp:UpdatePanel runat="server" ID="updatePanel" UpdateMode="Conditional">
<ContentTemplate>
<asp:Label ID="lblSomething" runat="server"></asp:Label>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>
</body>
</html>
Default.aspx.cs
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace TestWeb
{
public partial class Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
UselessTableWrapper table = new UselessTableWrapper();
TableRow tr = new TableRow();
TableCell td = new TableCell();
LinkButton button1 = new LinkButton { ID = "theTrigger", Text = "Click Me" };
button1.Click += button1_Click;
td.Controls.Add(button1);
tr.Controls.Add(td);
table.AddRow(tr);
panel.Controls.Add(table);
// ### uncomment these lines (and comment the one above) to see it working
// ### without the custom control
/*
Table realTable = new Table();
realTable.Controls.Add(tr);
panel.Controls.Add(realTable);
*/
updatePanel.Triggers.Add(new AsyncPostBackTrigger { ControlID = "theTrigger", EventName = "Click" });
scriptManager.RegisterAsyncPostBackControl(button1);
}
protected void button1_Click(object sender, EventArgs e)
{
lblSomething.Text = "Random number: " + new Random().Next(100);
updatePanel.Update();
}
}
}
MyControl.cs
using System;
using System.Web.UI.WebControls;
namespace TestWeb
{
public class UselessTableWrapper : WebControl
{
private Table table = new Table();
protected override void OnPreRender(EventArgs e)
{
Controls.Add(table);
}
public void AddRow(TableRow row)
{
table.Controls.Add(row);
}
}
}
Any ideas would be greatly appreciated.
Edit
I've tried switching the OnPreRender event for this (found in a tutorial):
protected override void RenderContents(HtmlTextWriter writer)
{
writer.BeginRender();
table.RenderControl(writer);
writer.EndRender();
base.RenderContents(writer);
}
... hoping that it would fix it, but it does not.
this is the approach that I've taken with loading a ascx web control inside an aspx control from the code behind.
In the control:
namespace dk_admin_site.Calculations
{
public partial class AssignedFieldCalculation : System.Web.UI.UserControl
{
public static AssignedFieldCalculation LoadControl(Calculation initialData)
{
var myControl = (AssignedFieldCalculation) ((Page) HttpContext.Current.Handler).LoadControl(#"~\\Calculations\AssignedFieldCalculation.ascx");
myControl._initialData = initialData;
return myControl;
}
private Calculation _initialData;
public Calculation Data { get { return _initialData; } }
protected void Page_Load(object sender, EventArgs e) {}
}
}
in the web form code behind:
protected void Page_Load(object sender, EventArgs e)
{
if (this.IsPostBack)
{
if (ScriptManager1.AsyncPostBackSourceElementID.StartsWith("ctl00$MainContent$calc") && ScriptManager1.AsyncPostBackSourceElementID.EndsWith("$btnRemoveCalculationFromField"))
{
//do something on the postback
}
else if (ScriptManager1.AsyncPostBackSourceElementID.StartsWith("ctl00$MainContent$calc") && (ScriptManager1.AsyncPostBackSourceElementID.EndsWith("$btnMoveCalculationUp") || ScriptManager1.AsyncPostBackSourceElementID.EndsWith("$btnMoveCalculationDown")))
{
//do something on the postback
}
}
foreach (Calculation calc in calculationCollection)
{
AssignedFieldCalculation asCalc = AssignedFieldCalculation.LoadControl(calc);
asCalc.ID = "calc" + calc.UniqueXKey;
pnlFieldCalculations.Controls.Add(asCalc);
foreach (Control ct in asCalc.Controls)
{
if (ct.ID == "btnMoveCalculationDown" || ct.ID == "btnMoveCalculationUp" || ct.ID == "btnRemoveCalculationFromField")
{
ScriptManager1.RegisterAsyncPostBackControl(ct);
}
}
}
}
A few things to note:
You need to make each control ID unique when adding it to the asp:Panel (called pnlFieldCalculations).
The LoadControl method allows you to pass initial arguments