C# Dynamically created LinkButton Command Event Handler - c#

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.

Related

linkbuttons postback before executing click event code?

I am trying to add a basic switch to my site in order to switch between static and responsive layouts.
I have two linkbuttons at the bottom of my page:
<div id="toggleView">
<asp:linkbutton ID="lbtnMobile" runat="server" Visible="false">Switch to Mobile site</asp:linkbutton>
<asp:linkbutton ID="lbtnFull" runat="server" >Switch to Full site</asp:linkbutton>
</div>
They both have a very similar OnClick event.
protected void lbtnFull_Click(object sender, EventArgs e)
{
c.ViewChange = true;
Session["Customer"] = c;
}
protected void lbtnMobile_Click(object sender, EventArgs e)
{
c.ViewChange = false;
Session["Customer"] = c;
}
The events should set a boolean in a class file (User.vb) between true or false and then save the session, on postback the Page_Load event is supposed to read this boolean and use it to adjust the Viewport meta tag:
protected void Page_Load(object sender, System.EventArgs e)
{
//Other Stuff in here, irrelevant to current question
HtmlMeta view = new HtmlMeta();
view.Name = "viewport";
if (c.ViewChange = false)
{
view.Content = "width=device-width, initial-scale=1";
lbtnFull.Visible = true;
lbtnMobile.Visible = false;
}
else
{
view.Content = "width=1040px, initial-scale=1";
lbtnFull.Visible = false;
lbtnMobile.Visible = true;
}
MetaPlaceHolder.Controls.Add(view);
}
However, when I click on the "Switch to Full Site" linkbutton, the page will postback but nothing will have changed. Does the postback get triggered too early somehow?
The page load event will happen BEFORE your click event. Reference this here.
This means your check for the ViewChange will happen before you set it in the OnClick handler.
You should change
if (c.ViewChange = false)
to
if (c.ViewChange == false)
for something to happen. But I think it won't be what you expect. Because page_load is executed before click event. You may move some code from page_load to click event handlers.
When ever you postback the Page_Load always get called. So, the code mentioned inside Page_Load would always get executed.
protected void Page_Load(object sender, System.EventArgs e)
{
... All your mentioned code will be executed.
}
Therefore, you won't find any change in your HTML page currently viewed in a browser because at postback initial content also got executed. You need to wrap your content inside !IsPostBack to make it work properly.
Thus, modify you code in following way.
protected void Page_Load(object sender, System.EventArgs e)
{
if(!IsPostback)
{
... All your mentioned code will be executed during normal load.
}
}
Also, you need to add some extra code in LinkButton click event i.e. what to show and what to hide.
Firstly your implementation in the Page_Load isn't very clear.
Nevertheless this is what I recommend, from what I've understod:
As the page load will get executed before the post-back event like the buton or link click, you need persist the value of the class object
Make a protected property of type of your class (where you store/manage the ViewChange atribute)
The property should internally (in the get & set), hold/persist the value in session/viewstate (similar to what you've written)
The setting and reading should only be by referring the property directly (and not how you've done the click-event)
On clicking of the button and post setting the new value, you will have to redirect to the same page, as only then the Page_Load event will get the new boolean value that you've just changed in the click-event; (Page_Load occurs befoer the any other post-back event)
An alternative to the fresh redirection is that, you could make a function that has the view changing logic (as depicted in your Page_Load code), and this function should be called on your button/link click event (post boolean value change) and also in the Page_Load event, but within the "!IsPostBack" block
Hope this helps you.

How to find out the name of the caller object during a function?

I'm trying to create a "list" using linklabels to identify attachments (in a mail client). So, I've this to create the links:
Label newLabel = new LinkLabel();
newLabel.Name = "anexo" + Convert.ToString(anexos_file.Count); //anexos_file is a list where all the attachments Paths exist
newLabel.Text = Path.GetFileName(file);
newLabel.Left = bt_anexos.Left;
newLabel.Top = label2.Top;
newLabel.Width = 150;
newLabel.AutoSize = true;
newLabel.Click += new System.EventHandler(Click_anexo); //Click_anexo is the name of the function
Now I need to know how do I make a function that, when I click the link, deletes the link itself.
So, any help?
in the Click_anexo delegate you have to have sender parameter.
That parameter is of object type, but it is actually the control that raised that event.
Just cast it to the type you need and you done.
private void Click_anexo(object sender, EventArgs arg)
{
}
Object sender parameter contains information about the control which fired this event. Cast sender as Label
LinkLabel lbl = (LinkLabel)sender;
and use it
lbl.Visible = false;
I think making it invisible is as good as deleted.
To respond to your statement and to clarify some of my comments.
To delete I just add: this.Controls.RemoveByKey(lbl.Name);
All you are doing here is removing your created control from its ControlCollection. The Control is still present and if you are creating a lot of these they will still be hanging around in memory. If you plan to reuse these controls then this fine, but if they are just for a onetime use you will be causing a memory leak. The way I would do it would be remove the eventhandler and dispose of the object like this:
private void Click_anexo(object sender, EventArgs e)
{
LinkLabel lbl = (LinkLabel)sender;
lbl.Click -= new EventHandler(Click_anexo);
lbl.Dispose();
}

Hooking up generic event handlers to multiple controls of the same type

I have a WinForms app that contains many NumericUpDown controls. In a nutshell, if my users enter a value into the control and then delete the text, I want to restore it (the text) when the control loses focus. So I decided that I'd check .Text when the control loses focus and if it's empty, I set .Text = .Value.ToString().
I'm doing this in the Leave event handler and it works just fine. But as I said, I have many of these controls (18, to be exact). I don't like creating 18 Leave event handlers that all do the same thing so I created a generic one like this:
private void numericUpDown_GenericLeave(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(((NumericUpDown)sender).Text))
((NumericUpDown)sender).Text = ((NumericUpDown)sender).Value.ToString();
}
I started to hook up all of the controls to this generic event handler but I quickly got tired of doing this:
numericUpDown1.Leave += numericUpDown_GenericLeave;
numericUpDown2.Leave += numericUpDown_GenericLeave;
numericUpDown3.Leave += numericUpDown_GenericLeave;
...
numericUpDown18.Leave += numericUpDown_GenericLeave;
So I thought I'd create a function that would return a list of all the controls of a specified type and then loop through that list and hookup the event handlers. That function looks like this:
public static List<Control> GetControlsOfSpecificType(Control container, Type type)
{
var controls = new List<Control>();
foreach (Control ctrl in container.Controls)
{
if (ctrl.GetType() == type)
controls.Add(ctrl);
controls.AddRange(GetControlsOfSpecificType(ctrl, type));
}
return controls;
}
I call the function like this:
var listOfControls = GetControlsOfSpecificType(this, typeof(NumericUpDown));
foreach (var numericUpDownControl in listOfControls)
{
numericUpDownControl.Leave += numericUpDown_GenericLeave;
}
When I run my app, however, I don't see the expected behavior that occurs when I manually hookup each control to the generic event handler. This code is currently in the constructor of my form and I've tried calling it before as well as after the call to InitializeComponent() but neither one seems to be working. I get no error of any kind, I just don't see the behavior that I was expecting. I have a breakpoint set inside the generic event handler but the debugger never breaks so it seems like the event handler isn't being hooked up correctly. Does anyone know why this might be or how I can troubleshoot it further? Thanks!
EDIT
I just realized that the call to:
var listOfControls = GetControlsOfSpecificType(this, typeof(NumericUpDown));
was happening before the call to InitializeComponent() so of course the list of controls being returned was empty. DOH! Thanks for all the replys. I apologize for wasting everyones time. :-(
You're passing this to your method, which is presumably a reference to your form. Your method will only catch the controls that are placed directly on your form. Any NumericUpDown controls that are not directly on the form (i.e. they're sitting on a panel or something) will be missed.
Why not create a user control that has a NumericUpDown control in it.
Then handle this is in the user control events.
This worked for me:
private decimal _previous = 0;
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
if (((NumericUpDown)sender).Text.Length > 0)
{
_previous = this.numericUpDown1.Value;
}
}
private void UserControl1_Leave(object sender, EventArgs e)
{
if (this.numericUpDown1.Text == "")
{
this.numericUpDown1.Value = _previous;
this.numericUpDown1.Text = System.Convert.ToString(_previous);
}
}
Just note that the Leave event is on the user control not on the updown control itself.
Question answered. See Edit above. Thanks to bsegraves for pointing me in the right direction.

Button Submit calls Onload first!

I have a problem with a webform.
My Goal: Intially when a page is loading, it has to load every textbox empty. After filling the info and click submit, it has to get submitted(UpdatePaymentInfo())
Problem: Here, When the user fills the info and clicks Submit,it calls onload function even before the submit button and makes all text box empty.
Here is the code:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
string QueryStringupdatecreditcard1 = Request.QueryString.ToString();
if (String.Equals(QueryStringupdatecreditcard1, "tabID=B"))
{
divTitle.Visible = false;
trmain.Visible = false;
tdOrderSummary.Visible = false;
trCCandBilling.Visible = true;
trtest2.Visible = false;
divUpdatecreditcard.Visible = true;
trusecompaddress.Visible = false;
txtFirstName.Text = "";
txtLastName.Text = "";
txtAddress1.Text = "";
txtAddress2.Text = "";
txtCity.Text = "";
txtZip.Text = "";
txtCardNo.Text = "";
txtVccNumber.Text = "";
trAmountCharged.Visible = false;
}
}
protected void imgbtnSubmit_Click(object sender, ImageClickEventArgs e)
{
try
{
UpdatePaymentInfo();
}
}
Wrap the current contents of your OnLoad method in:
if (!Page.IsPostBack)
{
// Code in here will only be executed when the page is *not* being loaded by a postback
}
This is because, as per the ASP.NET Page Life Cyle, the things that you care about in this instance happen in this order:
Load - During load, if the current request is a postback, control
properties are loaded with information
recovered from view state and control
state.
Postback event handling - If the request is a postback, control event
handlers are called. After that, the
Validate method of all validator
controls is called, which sets the
IsValid property of individual
validator controls and of the page.
So what happens is (somewhat simplified):
You click the image button, triggering the postback.
The data from your form is loaded into your controls.
Your OnLoad method overwrites the values in the controls to clear them.
Your click handler is run, but because of step 3 it sees empty values.
As others have sort-of mentioned, it wouldn't necessarily be a bad thing to refactor your OnLoad method whilst you're doing this. At the moment you seem to have it doing two distinct things:
Clearing the text fields
Setting the visibility of fields
It might be worth separating this into one or two (depending on if the visibility setting and field clearing will be done independently) separate methods and adjusting your OnLoad method so it looks like this:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (!Page.IsInPostBack)
{
SetFieldVisibility();
ClearFields();
}
}
Page_Load always occurs.
See the documentation on the Page Lifecycle
What you need to do is check to see if the Page_Load is being triggered by a Postback.
private void Page_Load(object sender, System.EventArgs e)
{
if(!Page.IsPostBack)
{
///do stuff in here that you want to occur only on the first lad.
}
else
}
// code that you want to execute only if this IS a postback here.
{
}
// do stuff you want to do on Page_Load regardless of postback here.
}
You can use the IsPostBack property of the Page as follows:
protected override void OnLoad(EventArgs e) {
if (!Page.IsPostBack) {
EmptyTextBoxes();
}
}
Have you tried wrapping the form reset code in a check to see if the page is a postback?
if(!Page.IsPostback) {
// Do form reset here
}
You thought about using the IsPostBack page variable?
protected override void OnLoad(EventArgs e)
{
if(!IsPostBack){
//all your logic here.
}
}
if it's the case, you might use a databound control and set it to insert mode

How can I create buttons and hook up events from postback

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; }
}

Categories