How can I create buttons and hook up events from postback - c#

I need to generate buttons initially based on quite a processor and disk intensive search. Each button will represent a selection and trigger a postback. My issue is that the postback does not trigger the command b_Command. I guess because the original buttons have not been re-created. I cannot affort to execute the original search in the postback to re-create the buttons so I would like to generate the required button from the postback info.
How and where shoud I be doing this? Should I be doing it before Page_Load for example? How can I re-construct the CommandEventHandler from the postback - if at all?
namespace CloudNavigation
{
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
// how can I re-generate the button and hook up the event here
// without executing heavy search 1
}
else
{
// Execute heavy search 1 to generate buttons
Button b = new Button();
b.Text = "Selection 1";
b.Command += new CommandEventHandler(b_Command);
Panel1.Controls.Add(b);
}
}
void b_Command(object sender, CommandEventArgs e)
{
// Execute heavy search 2 to generate new buttons
Button b2 = new Button();
b2.Text = "Selection 2";
b2.Command += new CommandEventHandler(b_Command);
Panel1.Controls.Add(b2);
}
}
}

The b_Command Event Handler method is not being executed because on post back buttons are not being recreated (since they are dynamically generated). You need to re-create them every time your page gets recreated but in order to do this you need to explicitly cache information somewhere in state.
If this a page-scoped operation easiest way is to store it in the ViewState (as strings - if you start loading the ViewState with objects you'll see performance go down) so that you can check it on next load (or any other previous event) and re-create buttons when reloading the page.
If the operation is session-scoped, you can easily store an object (array or whatever) in session and retrieve it on next Load (or Init) to re-create your controls.
This scenario means that you need just to store some info about your button in your b_Command EventHandler instead of creating and adding buttons since if you do so you'll lose relative information in the next postback (as it is happening now).
so your code would become something like:
namespace CloudNavigation
{
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
this.recreateButtons();
}
else
{
// Execute heavy search 1 to generate buttons
Button b = new Button();
b.Text = "Selection 1";
b.Command += new CommandEventHandler(b_Command);
Panel1.Controls.Add(b);
//store this stuff in ViewState for the very first time
}
}
void b_Command(object sender, CommandEventArgs e)
{
//Execute heavy search 2 to generate new buttons
//TODO: store data into ViewState or Session
//and maybe create some new buttons
}
void recreateButtons()
{
//retrieve data from ViewState or Session and create all the buttons
//wiring them up to eventHandler
}
}
}
If you don't want to call recreateButtons on page load you can do it on PreLoad or on Init events, I don't see a difference since you'll be able to access ViewState/Session variables everywhere (on Init viewstate is not applied but you can access it to re-create your dynamic buttons).
Someone will hate this solution but as far as I know the only way to retain state data server-side is ViewState - Session - Page.Transfer or client-side cookies.

The buttons need to be created before the load event, or state won't be wired up correctly. Re-create your buttons in Init() instead.
As for how to do this without re-running the search, I suggest you cache the results somewhere. The existence of a result set in the cache is how your button code in the Init() event will know it needs to run.
Alternatively, you could place the buttons on the page statically. Just put enough there to handle whatever the search returns. If you're thinking that maybe that would be way too many items, then ask your self this: will your users really want to sort through that many items? Maybe you should consider paging this data, in which case static buttons aren't as big a deal any more.

What happens when the postback event handling tries to find the control it dosen't exists on the collection.
Checkout Denis DynamicControlsPlaceholder # http://www.denisbauer.com/ASPNETControls/DynamicControlsPlaceholder.aspx
Hope it helps
Bruno Figueiredo
http://www.brunofigueiredo.com

Does your ASPX have the event handler wired up?
<asp:Button id="btnCommand" runat="server" onClick="b_Command" text="Submit" />

I agree with Joel about caching the search results. As for the buttons you can create them dynamically at the init or load phases of the page lifecycle but be aware that if you remove a button and then add it back programmatically you will mess up your state.
In one of my projects we have a dynamic form that generates field son the fly and the way we make it work is through an array that is stored in the cache or in the viewstate for the page. The array contains the buttons to display and on each page load it re-creates the buttons so that state can be loaded properly into them. Then if I need more buttons or a whole new set I flag the hide value in the array and add a new set of values in the array for the new set of corresponding buttons. This way state is not lost and the buttons continue to work.
You also need to ensure that you add a handler for the on_click event for your buttons if you create them programmatically which I think I see in your code up at the top.

Here is a sample with custom viewstate handling (note that buttons have EnableViewState = false):
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Execute heavy search 1 to generate buttons
ButtonTexts = new ButtonState[] {
new ButtonState() { ID = "Btn1", Text = "Selection 1" }
};
}
AddButtons();
}
void b_Command(object sender, CommandEventArgs e)
{
TextBox1.Text = ((Button)sender).Text;
// Execute heavy search 2 to generate new buttons
ButtonTexts = new ButtonState[] {
new ButtonState() { ID = "Btn1", Text = "Selection 1" },
new ButtonState() { ID = "Btn2", Text = "Selection 2" }
};
AddButtons();
}
private void AddButtons()
{
Panel1.Controls.Clear();
foreach (ButtonState buttonState in this.ButtonTexts)
{
Button b = new Button();
b.EnableViewState = false;
b.ID = buttonState.ID;
b.Text = buttonState.Text;
b.Command += new CommandEventHandler(b_Command);
Panel1.Controls.Add(b);
}
}
private ButtonState[] ButtonTexts
{
get
{
ButtonState[] list = ViewState["ButtonTexts"] as ButtonState[];
if (list == null)
ButtonTexts = new ButtonState[0];
return list;
}
set { ViewState["ButtonTexts"] = value; }
}
[Serializable]
class ButtonState
{
public string ID { get; set; }
public string Text { get; set; }
}

Related

Enabling/disabling button

I am having trouble getting my 'start' button to show when I select either of the radio boxes.
Ideally, when one of the boxes is selected, the 'start' button will enable and allow to be clicked.
Here is my code for the form, as I am relatively new to C# I'm not sure if I'm posting all of the code you need, I'll post more if required.
public partial class mainForm : Form
{
public mainForm() {
InitializeComponent();
}
private void label1_Click(object sender, EventArgs e) {
}
private void mainForm_Load(object sender, EventArgs e) {
title.Font = new Font("Arial", 10, FontStyle.Bold);
}
private void startButton_Click(object sender, EventArgs e) {
if (radioDice.Checked) {
startButton.Enabled = true; //Activates 'start' button
whichDiceGameForm GameForm = new whichDiceGameForm();
GameForm.Show();
}
if (radioCard.Checked) {
startButton.Enabled = true; //Activates 'start' button
whichCardGame GameForm = new whichCardGame();
GameForm.Show();
}
}
}
[Posting for a friend.]
You have placed your enable code in the Button's Click event Handler while you should do it on your checkboxes changed.
Take this code :
if (radioDice.Checked)
{
startButton.Enabled = true;
}
to radioDice checkbox's changed event handler and this one :
if (radioCard.Checked)
{
startButton.Enabled = true; //Activates 'start' button
}
to radioCard checkbox's changed event handler .
Man, seriously?
ANSWER:
You're trying to enable DISABLED button when clicking on that button. You cannot click DISABLED button. What's more - you're duplicating your code.
Button should be always enabled. You only have two choices. Every choice enabled button. So it should be always enabled. No matter the choice. If there is something hidden and button may be disabled, then enable the button in Radio Click event.
Additional information about your code:
Now. About code duplication. Look what you're doing in startButton_Click. You have duplicated code.
You can do something like:
BaseGameForm f = null;
if(radioDice.Checked)
f = new DiceGameForm();
else
if(radioCard.Checked)
f = new CardGameForm();
f.Show();
(BaseGameForm is base form for every game)
But this is not good solution. Better solution is (somewhere in construtor):
radioDice.Tag = new DiceGameForm();
radioCard.Tag = new CardGameForm();
Then in Start button click you look for checked radio:
foreach(Control c in selectGameTypeGroupBox.Controls) //you could do this using LINQ
{
if((c is RadioButton) && ((RadioButton)c).Checked)
{
((Form)c.Tag).Show();
}
}
But this is still not good solution, because you're creating all game forms at startup and this is stupid.
So the better solution would be to keep game form class name in your radio Tag property and then create object of this class using reflection and Activator.
But this is still not the best solution. But I assume that this is one of your first applications so I won't be telling you now about separating gui from logic. If you want to know more - read on the Internet. Or just ask.

Custom control events don't fire when control collection is cached?

I have a custom control "BedGrid" that contains a collection of custom controls, each of which has a click handler on them. In the Page_Load event of my Parent page, I generate a collection of BedGrids, and wire them up to an event handler. This all works fine, when I generate the BedGrids on each Page_Load... The grandchild is clicked, fires the event up to the BedGrid, which alerts my Parent Page and everything goes as planned.
The problem is, it's slow.. Generating all those custom controls on each Page_Load doesn't make sense (especially with trips to the backend). So, I want to cache the collection of BedGrids like so:
protected void Page_Load(object sender, EventArgs e)
{
DrawBedGrids();
}
protected void DrawBedGrids()
{
if (CachedBedgrids == null)
{
CachedBedgrids = new List<BedGrid>();
//Hit DB here and generate list of buildings....
foreach (Building b in buildings)
{
BedGrid bg = new BedGrid(b);
bg.RaiseAlertParentPage += new EventHandler(BedGrid_Clicked);
CachedBedgrids.Add(bg);
}
}
else
{
foreach (BedGrid bg in CachedBedgrids)
{
somePanel.Controls.Add(bg);
}
}
}
protected List<BedGrid> CachedBedgrids
{
get
{
try { return (List<BedGrid>)Session["CachedBedgrids"]; }
catch { return null; }
}
set { Session["CachedBedgrids"] = value; }
}
And it all breaks.. The events never fire... Even if I add
bg.RaiseAlertParentPage += new EventHandler(BedGrid_Clicked);
to the "else" right before I add the BedGrid to the panel..
What am I missing? All of this is happening in Page_Load, so why is the event not firing? Everything else is fine, meaning that the controls and their children draw properly..
The reason this doesn't work is because the controls must be recreated on the post back, and events wired up, for them to fire.
See, since the server is stateless, to fire an event on an object, that object needs recreated AND readded to the form.
How about cache the database result instead and continue the loop otherwise to build new, add, and hookup the controls?

State Of Variables

In "edmx" page I have button control with event "NextButton_Click" for click. When I click this button the variables "index" doesn't want to change to "40" and the "text"
variable doesn't want to change to "active". These variables are always in the same state "text" is always equal to "start" and "index" is always equal to "10". Why they don't want to change with (index = 40;
text = "active";) as I wrote in the click button event method ?
public partial class CountriesTowns : System.Web.UI.Page
{
int index = 10;
string text = "start";
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
index = 20;
text = "stop";
}
}
//click next button
protected void NextButton_Click(object sender, EventArgs e)
{
Response.Write(index);
Response.Write(text);
index = 40;
text = "active";
}
HTTP is stateless, every object like your index or text(and even all controls) are destroyed at the end of the page's life-cycle. So they are always initialized with their default value.
int index = 10;
string text = "start";
You can use a control(f.e. a TextBox or a HiddenField) to persist their value across postbacks.
But there are other options:
Nine Options for Managing Persistent User State in Your ASP.NET Application
You are not persisting the updated state of the index and text variables between post backs. As such, since a new instance of CountriesTowns is created per request, the values are re-initialised to their default values.
Every time you click a button, you cause what is known as a Postback. A Postback does not just run your click code... it also rebuilds your entire page. To do this, it creates a brand new instance of your Page class, which is then destroyed as soon as the html for your new page is completed. It has to do this because the original instance of your Page class was also destroyed as soon the html was rendered.

Dynamically Added DropDownlists Are Not Firing SelectedIndexChanged Event [duplicate]

This question already has an answer here:
Event won't fire to dynamically added control
(1 answer)
Closed 2 years ago.
I saw lots of thing about this topic but i can't fin a solution.
I add many dropdown list with one event but they are not firing SelectedIndexChanged evet.
Here is drplist creator code:
foreach (var row in cmdSelectCats.ExecuteReader())
{
var id = row["ProductCategoryID"].ToString();
var dropDownStatus = new DropDownList {ID = "DrpStatus-" + id};
dropDownStatus.Items.Add(new ListItem("Aktif", "1"));
dropDownStatus.Items.Add(new ListItem("Pasif", "2"));
dropDownStatus.AutoPostBack = true;
dropDownStatus.SelectedIndexChanged += Status_SelectedIndexChanged;
var tableCell = new TableCell();
tableCell.Controls.Add(dropDownStatus);
dropDownStatus.SelectedValue = row["ProductCategoryStatusID"].ToString();
tableRow.Cells.Add(tableCell);
TblCatList.Rows.Add(tableRow);
}
And ofcourse my Event:
public void Status_SelectedIndexChanged(object sender, EventArgs e)
{
//DO SOMETHING
}
What am i missing?
This is a common issue and it's related to the page life cycle:
Take a look at the following questions:
Click events on Array of buttons
Button array disappears after click event
Dynamically create an ImageButton
Now the basic steps to remember when creating dynamic controls are:
Dynamic controls should be created in the PreInit event when you are not working with a master page, if you are, then create the controls in the Init event
Avoid setting properties that can be changed in each post in these events because when the view state is applied (in a post event) the properties will be overridden
Dynamic controls must be created every time the page is posted, avoid this if(!this.IsPostBack) this.CreatemyDynamicControls();
When you create the controls in the PreInit or Init events, their states will be automatically set in a post event, which means in the LoadComplete event your controls will contain their state back even when you create them again in each post and even when you did not explicitly set their state. Note this behavior is different when you are dealing with controls created at design time, in that case, the event where the state has been set is the Load event
Event subscription should occur before the PageLoadComplete or they will not be raised
Consider the following description from MSDN
If controls are created dynamically at run time or declaratively within templates of data-bound controls, their events are initially not synchronized with those of other controls on the page. For example, for a control that is added at run time, the Init and Load events might occur much later in the page life cycle than the same events for controls created declaratively. Therefore, from the time that they are instantiated, dynamically added controls and controls in templates raise their events one after the other until they have caught up to the event during which it was added to the Controls collection.
The above is not so clear to me, but I have found the following. The following TextBox's are created at design time
protected void Page_PreInit(object sender, EventArgs e)
{
this.txtDesignTextBox1.Text = "From PreInit";
this.txtDesignTextBox1.Text += DateTime.Now.ToString();
}
protected void Page_Init(object sender, EventArgs e)
{
this.txtDesignTextBox2.Text = "From Init";
this.txtDesignTextBox2.Text += DateTime.Now.ToString();
}
protected void Page_Load(object sender, EventArgs e)
{
this.txtDesignTextBox3.Text = "From Load";
this.txtDesignTextBox3.Text += DateTime.Now.ToString();
}
At first sight you might think that in every post all the textboxes are updated with the current date, but this is not the case, since they were created at design time they follow strictly the ASP.Net page life-cycle which means, their state is overriden after the PreInit and Init events, only the txtDesignTextBox3 is updated in every post because its Text property is updated after the view state has been set (in the Load event).
But with dynamic controls the behavior is different, remember the MSDN description:
for a control that is added at run time, the Init and Load events might occur much later in the page life cycle
Consider the following:
protected void Page_PreInit(object sender, EventArgs e)
{
var textBox = new TextBox { Text = "From PreInit", Width = new Unit("100%") };
textBox.Text += DateTime.Now.ToString();
this.myPlaceHolder.Controls.Add(textBox);
}
protected void Page_Init(object sender, EventArgs e)
{
var textBox = new TextBox { Text = "From Init", Width = new Unit("100%") };
textBox.Text += DateTime.Now.ToString();
this.myPlaceHolder.Controls.Add(textBox);
}
protected void Page_Load(object sender, EventArgs e)
{
var textBox = new TextBox { Text = "From Load", Width = new Unit("100%") };
textBox.Text += DateTime.Now.ToString();
this.myPlaceHolder.Controls.Add(textBox);
}
In this case, the controls behave slightly different, in this case, in each post, the controls are never updated not even the controls created in the Load event
The reason is their life-cycle events occurs much later in the page-life cycle which means their state is overridden even after the Load event
To solve this, you can use the LoadComplete event, in this event you can change the state of dynamic controls:
protected void Page_LoadComplete(object sender, EventArgs e)
{
var textBox = new TextBox { Text = "From LoadComplete", Width = new Unit("100%") };
textBox.Text += DateTime.Now.ToString();
this.myPlaceHolder.Controls.Add(textBox);
}
In this case, the state will be updated in each post.
However, take in consideration that you should subscribe to dynamic controls events, before the LoadComplete event or they will not be raised.
... I know I hate this kind of behavior, that's why I love MVC
As a quick reference for controls created at design time: Notice how the LoadViewState method is called after the PreInit and Init events but before the Load event. The Load event is considered stable because in this event you can access the view state of your controls. Also notice that the RaisePostBackEvent method represent the control event that caused the post back, this can be, the SelectedIndexChanged, Click, etc this event is handled after the Load event
For a complete detailed specification read the MSDN Page Life-Cycle documentation
I have usually seen this caused by a page lifecycle problem. If the control is only created when an event is fired, then when your index changed event fires the control doesn't exist to bind it to on the postback.
Example:
MyEvent fires. Drop-down created. Event Handler specified.
Index Changed event triggered. Page reloads. Drop-down not found, cannot fire.
You have to ensure the drop-down is created before .NET attemps to handle the event.
You are missing:
1- Override SaveViewState
2- Override LoadViewState
I provide a sample code for this question. I test it. It's work.
ASPX:
<form id="form1" runat="server">
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
<div id="myDiv" runat="server">
</div>
<asp:Label ID="lblDescription" runat="server"></asp:Label>
</form>
Code Behind:
public partial class Default : System.Web.UI.Page
{
private List<string> values = new List<string>();
protected void Page_Load(object sender, EventArgs e)
{
}
protected override object SaveViewState()
{
object baseState = base.SaveViewState();
object[] allStates = new object[2];
allStates[0] = baseState;
allStates[1] = values;
return allStates;
}
protected override void LoadViewState(object savedState)
{
object[] myState = (object[])savedState;
if (myState[0] != null)
base.LoadViewState(myState[0]);
if (myState[1] != null)
{
values = (List<string>)myState[1];
MyRender();
}
}
protected void Button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 10; i++)
{
DropDownList ddl = new DropDownList();
ddl.ID = "ClientID" + i;
ddl.Items.Add("Item 1");
ddl.Items.Add("Item 2");
ddl.AutoPostBack = true;
values.Add(ddl.SelectedValue);
myDiv.Controls.Add(ddl);
}
}
private void MyRender()
{
for (int i = 0; i < values.Count; i++)
{
DropDownList ddl = new DropDownList();
ddl.ID = "ClientID" + i;
ddl.Items.Add("Item 1");
ddl.Items.Add("Item 2");
ddl.AutoPostBack = true;
ddl.SelectedIndexChanged += new EventHandler(ddl_SelectedIndexChanged);
myDiv.Controls.Add(ddl);
}
}
void ddl_SelectedIndexChanged(object sender, EventArgs e)
{
lblDescription.Text = ((DropDownList)sender).ID + ": Selected Index Changed";
}
}

C# Dynamically created LinkButton Command Event Handler

So I have a weird situation here... I have an System.Web.UI.WebControls.WebParts.EditorPart class. It renders a "Search" button, when you click this button, it's clickHandler method does a DB search, and dynamically creates a LinkButton for each row it returns, sets the CommandName and CommandArgument properties and adds a CommandEventHandler method, then adds the LinkButton control to the page.
The problem is, when you click a LinkButton, its CommandEventHandler method is never called, it looks like the page just posts back to where it was before the ORIGINAL "Search" button was pressed.
I have seen postings saying that you need to add the event handlers in OnLoad() or some other early method, but my LinkButtons haven't even been created until the user tells us what to search for and hits the "Search" button... Any ideas on how to deal with this?
Thanks!
This is my favorite trick :)
Our scenario is to first render a control. Then using some input from the user, render further controls and have them respond to events.
The key here is state - you need to know the state of the control when it arrives at PostBack - so we use ViewState. The issue becomes then a chicken-and-egg problem; ViewState isn't available until after the LoadViewState() call, but you must create the controls before that call to have the events fired correctly.
The trick is to override LoadViewState() and SaveViewState() so we can control things.
(note that the code below is rough, from memory and probably has issues)
private string searchQuery = null;
private void SearchButton(object sender, EventArgs e)
{
searchQuery = searchBox.Text;
var results = DataLayer.PerformSearch(searchQuery);
CreateLinkButtonControls(results);
}
// We save both the base state object, plus our query string. Everything here must be serializable.
protected override object SaveViewState()
{
object baseState = base.SaveViewState();
return new object[] { baseState, searchQuery };
}
// The parameter to this method is the exact object we returned from SaveViewState().
protected override void LoadViewState(object savedState)
{
object[] stateArray = (object[])savedState;
searchQuery = stateArray[1] as string;
// Re-run the query
var results = DataLayer.PerformSearch(searchQuery);
// Re-create the exact same control tree as at the point of SaveViewState above. It must be the same otherwise things will break.
CreateLinkButtonControls(results);
// Very important - load the rest of the ViewState, including our controls above.
base.LoadViewState(stateArray[0]);
}
You need to re-add the dynamically created controls, in the onload, so that they can be in the page hierarchy and fire their event.
LinkButton link= new LinkButton();
link.Command +=new CommandEventHandler(LinkButton1_Command);
protected void LinkButton1_Command(object sender, CommandEventArgs e)
{
try
{
System.Threading.Thread.Sleep(300);
if (e.CommandName == "link")
{
//////////
}
}
catch
{
}
}
A dirty hack I just came up with, is to create dummy LinkButtons with the same IDs as the real buttons.
So let's say you are going to create a LinkButton "foo" at Pre_Render (which is too late), then also create a dummy foo at Page_Load:
var link = new LinkButton();
link.ID = "foo";
link.Click += fooEventHandler;
dummyButtons.Controls.Add(link);
(Where "dummyButtons" is just a PlaceHolder on the page with Visibility set to false.)
It's ugly, but it works.

Categories