I have a button control. On click of this button I need to add a Link Button dynamically. The Link Button needs an event handler. Hence the dynamic Link button is first added in the Page_Load and cleared and added again in the button click handler. Please read Dynamic Control’s Event Handler’s Working for understanding the business requirement for this.
I have read On postback, how can I check which control cause postback in Page_Init event for identifying the control that caused the postback (inside Page_Load). But it is not working for my scenario.
What change need to be done to confirm whether the postback was caused by link button (inside Page_Load)?
Note: Refer the following for another scenario where it is inevitable https://codereview.stackexchange.com/questions/20510/custom-paging-in-asp-net-web-application
Note 1: I need to get the postback control ID as the first step inside if (Page.IsPostBack). I need to add the dynamic link buttons control only if it is a postback from the button or the link button. There will be other controls that causes postback. For such postbacks we should not execute this code.
Note 2: I am getting empty string for Request["__EVENTARGUMENT"] in the Page_Load
Related Question: By what event, the dynamic controls will be available in the Page (for using in FindControl). #Tung says - "Your GetPostBackControlId method is properly getting the name of the control that caused the postback, but it is unable to find a control with that id through page.FindControl because the linkbutton has not been created yet, and so page does not know of its existence. "
ASPX
<%# Page Language="C#" AutoEventWireup="true" CodeFile="PostbackTest.aspx.cs" Inherits="PostbackTest"
MasterPageFile="~/TestMasterPage.master" %>
<asp:Content ID="myContent" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div id="holder" runat="server">
</div>
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="TestClick" />
</asp:Content>
CODE BEHIND
public partial class PostbackTest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if(Page.IsPostBack)
{
string IDValue = GetPostBackControlId(this.Page);
int x = 0;
holder.Controls.Clear();
LinkButton lnkDynamic = new LinkButton();
lnkDynamic.Click += new EventHandler(LinkClick);
lnkDynamic.ID = "lnkDynamic123";
lnkDynamic.Text = "lnkDynamic123";
holder.Controls.Add(lnkDynamic);
}
}
protected void TestClick(object sender, EventArgs e)
{
holder.Controls.Clear();
LinkButton lnkDynamic = new LinkButton();
lnkDynamic.Click += new EventHandler(LinkClick);
lnkDynamic.ID = "lnkDynamic123";
lnkDynamic.Text = "lnkDynamic123";
holder.Controls.Add(lnkDynamic);
}
protected void LinkClick(object sender, EventArgs e)
{
}
public static string GetPostBackControlId(Page page)
{
if (!page.IsPostBack)
{
return string.Empty;
}
Control control = null;
// First check the "__EVENTTARGET" for controls with "_doPostBack" function
string controlName = page.Request.Params["__EVENTTARGET"];
if (!String.IsNullOrEmpty(controlName))
{
control = page.FindControl(controlName);
}
else
{
// if __EVENTTARGET is null, the control is a button type
string controlId;
Control foundControl;
foreach (string ctl in page.Request.Form)
{
// Handle ImageButton they having an additional "quasi-property" in their Id which identifies mouse x and y coordinates
if (ctl.EndsWith(".x") || ctl.EndsWith(".y"))
{
controlId = ctl.Substring(0, ctl.Length - 2);
foundControl = page.FindControl(controlId);
}
else
{
foundControl = page.FindControl(ctl);
}
if (!(foundControl is Button || foundControl is ImageButton)) continue;
control = foundControl;
break;
}
}
return control == null ? String.Empty : control.ID;
}
}
REFERENCE
On postback, how can I check which control cause postback in Page_Init event
Dynamic Control’s Event Handler’s Working
Understanding the JavaScript __doPostBack Function
Access JavaScript variables on PostBack using ASP.NET Code
How does ASP.NET know which event to fire during a postback?
how to remove 'name' attribute from server controls?
How to use __doPostBack()
A postback in asp.net is done by the java script function __doPostback(source, parameter)
so in your case it would be
__doPostback("lnkDynamic123","") something like this
So in the code behind do the following
var btnTrigger = Request["__EVENTTARGET"];
if(btnTrigger=="lnkDynamic123")
{
}
--- this would tell that it is your linkbutton that causes the postback
You can move the call to the GetPostBackControlId method after the LinkButton has been added to the page:
protected void Page_Load(object sender, EventArgs e)
{
if (Page.IsPostBack)
{
holder.Controls.Clear();
LinkButton lnkDynamic = new LinkButton();
lnkDynamic.Click += new EventHandler(LinkClick);
lnkDynamic.ID = "lnkDynamic123";
lnkDynamic.Text = "lnkDynamic123";
holder.Controls.Add(lnkDynamic);
string IDValue = GetPostBackControlId(this.Page);
if (IDValue == lnkDynamic.ID)
LinkClick(lnkDynamic, new EventArgs());
}
}
Calling the click event handler here also more closely mimics the standard ASP.NET Page Life Cycle, where Postback event handling occurs after the Load event.
Edit:
If the control ID must be determined before the LinkButtons are created, you can create a naming scheme for the link button IDs, e.g. lnkDynamic_1, lnkDynamic_2 etc.
Request["__EVENTTARGET"] will then contain the auto-generated control ID such as “ctl00$mc$lnkDynamic_1”, which you can use to identify which LinkButton caused the postback.
If You're getting the post back control id correctly but FindControl returns nothing, then it's probably because You're using a master page. Basically, someControl.FindControl(id) searches through controls that are in someControl.NamingContainer naming container. But in Your case, the Button1 control is in the ContentPlaceHolder1, which is a naming container, and not directly in the Page naming container, so You won't find it by invoking Page.FindControl. If You can't predict in which naming container the control You're looking for is going to be (e.g. post back can be caused by two different buttons from two different content placeholders), then You can write an extension that'll search for a control recursively, like so:
public static class Extensions
{
public static Control FindControlRecursively(this Control control, string id)
{
if (control.ID == id)
return control;
Control result = default(Control);
foreach (Control child in control.Controls)
{
result = child.FindControlRecursively(id);
if (result != default(Control)) break;
}
return result;
}
}
Use it with caution though, because this method will return the first control that it finds with the specified id (and You can have multiple controls with the same id - but they should be in different naming containers; naming containers are meant to differentiate between controls with same ids, just as namespaces are meant to differentiate between classes with same names).
Alternatively, You could try to use FindControl(string id, int pathOffset) overload, but I think it's pretty tricky.
Also, check this question out.
First approach (wouldn't recommend but it's more flexible)
One completely different approach - although I don't really feel like I should promote it - is to add a hidden field to the form.
The value of this hidden field might be something like false by default.
In case of clicking one of the dynamic buttons which should cause the dynamic controls to be added again, you can simply change the hidden fields value to true on client side before performing the postback (eventually you want/have to modify the client side onclick handler to make this happen).
Of course it would be possible to store more information in such a field, like the controls id and the argument (but you can get those values as described in the other answers). No naming schema would be required in this case.
This hidden field could be "static". So it would be accessible in code behind all time. Anyhow, you might want to implement something to make sure that nobody is playing around with its values and fakes a callback which looks like it originated from one of these dynamic links.
However, this whole approach just helps you getting the id of the control. Until you create the control again, you won't be able to get the instance through NamingContainer.FindControl (as mentioned in the other answers already ;)). And in case you create it, you don't need to find it anymore.
Second approach (might not be suitable due to its contraints)
If you want to do it the clean way, you need to create your controls OnLoad, no matter if something was clicked or not. Additionally, the dynamic controls ID has to be the same as the one you sent to the client in the first place. You subscribe to its Click or Command event and set its visibility to false. Inside the click event handler, you set the senders visibility to true again. This implies, that you don't care if that link is created but instead just don't want to send it to the client. The example below only works for a single link of course (but you could easily modify it to cover a whole group of links).
public void Page_Load(object sender, EventArgs e)
{
LinkButton dynamicButton = new LinkButton();
dynamicButton.ID = "linkDynamic123";
// this id needs to be the same as it was when you
// first sent the page containing the dynamic link to the client
dynamicButton.Click += DynamicButton_Click;
dynamicButton.Visible = false;
Controls.Add(dynamicButton);
}
public void DynamicButton_Click(object sender, EventArgs e)
{
// as you created the control during Page.Load, this event will be fired.
((LinkButton)sender).Visible = true;
}
Related
I'm trying to add a Link to a webform page from a user control. The CSS file contains the styling used by the control. When I try, I get the following exception:
The control collection cannot be modified during DataBind, Init, Load,
PreRender or Unload phase
So, when can anything be added to the controls collection? Seems to me the exception message suggests that all options are off the table. Here's the code:
public partial class AddNoteDlg : UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
RegisterFiles();
}
private void RegisterFiles()
{
Page.ClientScript.RegisterClientScriptInclude(GetType(), "addNoteDlgComponent-1.0.0.js", ResolveUrl("~/Shared/scripts/js/addNoteDlgComponent-1.0.0.js"));
var css = new HtmlLink();
css.Href = ResolveUrl("~/Shared/css/addNoteDlg-0.0.1.css");
css.Attributes["rel"] = "stylesheet";
css.Attributes["type"] = "text/css";
css.Attributes["media"] = "all";
Page.Controls.Add(css);
}
}
Instead, stuck the link tag in the user control markup, as there is no difference, practically speaking.
(The original motivation for doing it programmatically was so I could add it to the header, with Page.Header.Controls.Add(css), allowing me to keep all CSS links in the header.)
I have a class called "is-active" and it has a colored arrow that sticks out from the nav into the main content based on which link the user clicked. The code runs a foreach and pulls all the categories from the database. How do I get the "is-active" class to display only for the current link? I know it works since I put it in the openList control and it displayed on all five categories, I just don't know how to get it to display on only the selected category.
I tried attaching jQuery to do it but adding the linkbutton is done all in the code behind so I am not sure how to attach the two. Is this the only way or is there another way?
Thank you in advance for your help!
Below is my code for the categories and link button:
protected override void CreateChildControls()
{
LiteralControl openingDiv = new LiteralControl("<div id='MainPanel'>");
LiteralControl closingDiv = new LiteralControl("</div>");
this.Controls.Add(openingDiv);
foreach (DataRow dr in ds.Tables[0].Rows)
{
LiteralControl openList = new LiteralControl("<li class='" + dr["CategoryColor"].ToString() + "'>");
LiteralControl closeList = new LiteralControl("</li>");
Label lblNumber = new Label();
LinkButton myLinkButton = new LinkButton();
myLinkButton.Text = "<span class='number'>" + dr["CategoryNumber"] + "</span>"+ dr["CategoryName"].ToString();
myLinkButton.CommandArgument = dr["Category_ID"].ToString();
myLinkButton.Click += myLinkButton_Click;
this.Controls.Add(openList);
this.Controls.Add(myLinkButton);
this.Controls.Add(closeList);
}
this.Controls.Add(closingDiv);
}
void myLinkButton_Click(object sender, EventArgs e)
{
LinkButton btn = (LinkButton)(sender);
Session["CategoryID"] = btn.CommandArgument;
Response.Redirect(Request.RawUrl);
}
Its tricky because your response.redirecting in the button click handler which recreates the pages viewstate.
This means that your page will always appear as fresh and the fact a user has clicked that link has been lost.
as a workaround you could place the link id in a session variable before you response.redirect and then recall it when the page reloads.
then when in your loop, if the session variable matches the current button instance.id you set cssclass equal to is-active.
remember to clear the session variable too after you set the cssclass to is-active to avoid confusion in other pages.
also, you will have to do the id comparison after you add the button to the control tree, because this is where the system automatically generates the id for you. I can give you a full example if you wish.
keep in mind that this is a workaround and a different approach would be best.
by this I mean that if you were to use an ispostback wrapper instead of a redirect, then on postback you could set the id to isactive much easier.
Page lifecycle is an important thing to get your head around in .net if you wish to be more proficient, especially as you mention that your using ajax update panels.
Read this: http://msdn.microsoft.com/en-us/library/ms178472(VS.100).aspx it's a lot of information to take in, so bookmark it for later too as you will use it a lot.
I'm adding validation to an existing asp.net web app built with web form. The input textbox controls I need to validate are created dynamically on the server side.
While creating the textboxes, I can also create RangeValidator controls and set its ControlToValidate to the ID of the textbox.
When validation fails, RangeValidator displays an error message at where the validation control is placed.
But I rather change the border or background-color to red of the textbox instead. How can that be done?
You can add custom logic to your validators both sever side and client side. check this link for help. http://msdn.microsoft.com/en-us/library/f5db6z8k%28v=vs.90%29.aspx
I'm not sure if you want help validating or changing the color (or both), but I just tried this little two-button app and it seems to work. Granted, I'm not using any dynamic control names and banking that "testText" is the first and only "testText" control on the page. You might also want to add some validation to ensure the control exists.
private void button1_Click(object sender, EventArgs e)
{
TextBox tb = new TextBox();
tb.Name = "testText";
this.Controls.Add(tb);
}
private void button2_Click(object sender, EventArgs e)
{
TextBox tb = (TextBox)this.Controls.Find("testText",true)[0];
tb.BackColor = System.Drawing.Color.Red;
}
As Irfan said, you can use a custom validator and implement on the server validate event the logic. However, a more direct aproach would be to check if the page is valid and the check if the rangevalidator is not valid. Keep in mind to set the displaymode to none if you don't want the validator to render any message.
if(!Page.IsValid && !myRangeValidator.IsValid)
{
// simplified, you will need to search for the control in the whole hierarchy
var ctrlToValidate = Page.FindControl(myRangeValidator.ControlToValidate) as WebControl;
if(ctrlToValidate != null)
{
ctrlToValidate.BorderColor = Color.Red;
}
}
this being said, I prefer the custom validator approach. The good thing is you can almost reuse all the code above. Just remove the first if, add the range validation, set the args.IsValid = false and then use the rest of the code.
Loop over all validators and add specific CSS class to controls. I use master page, so need to find controls in content placeholder.
const string ErrorCssClass = "error";
Validate();
if (IsPostBack && !IsValid)
{
var content = Form.FindControl("MainContent") as ContentPlaceHolder;
foreach (BaseValidator validator in Validators)
{
if (validator.IsValid)
continue;
var controlToValidate = content.FindControl(validator.ControlToValidate) as WebControl;
if (controlToValidate != null && !controlToValidate.CssClass.Contains(ErrorCssClass))
controlToValidate.CssClass += " " + ErrorCssClass;
}
}
I'm creating a next/previous function for my repeater using pageddatasource. I added the link button control dynamically in my oninit using the following code.
LinkButton lnkNext = new LinkButton();
lnkNext.Text = "Next";
lnkNext.Click += new EventHandler(NextPage);
if (currentPage != objPagedDataSource.PageCount)
{
pnlMain.Controls.Add(lnkNext);
}
So in my initial page_load, the next link comes up fine. There are 5 pages in my objPagedDataSource. currentPage variable is 1.
The "NextPage" event handler looks like this
public void NextPage(object sender, EventArgs e)
{
if (HttpContext.Current.Request.Cookies["PageNum"] == null)
{
HttpCookie cookie = new HttpCookie("PageNum");
cookie.Value = "1";
}
else
{
HttpCookie cookie = HttpContext.Current.Request.Cookies["PageNum"];
cookie.Value = (Convert.ToInt32(cookie.Value) + 1).ToString();
}
this.BindRepeater();
}
So I am incrementing the cookie I am using to track the page number and then rebinding the repeater.
Here is the main issue. The first time I click Next, it works, it goes to Page 2 without any problems. When on Page 2, I click Next, it goes back to Page 1. Seems like the Next event is not wiring up properly. Not sure why, any ideas?
You need to ensure you're adding your dynamic control to the Page every postback. Dynamic controls often cause much pain - in this case it would probably be much easier to declare the "Next" LinkButton in the markup in the normal way, and just set Visible = false when it isn't required.
While you are loading user controls dynamically you must set ID property without ID property Events will not fire.This is My sample code to call user controls dynamically
private void LoadUserControl()
{
string controlPath = LastLoadedControl;
if (!string.IsNullOrEmpty(controlPath))
{
PlaceHolder1.Controls.Clear();
UserControl uc = (UserControl)LoadControl(controlPath);
uc.ID = "uc"; //Set ID Property here
PlaceHolder1.Controls.Add(uc);
}
}
I have a little problem about using jQuery (I really do not know jQuery but I am forced to use it).
I am using Visual Studio 2008, ASP.NET web app with C#, Telerik controls on my pages. I am also using SqlDataSources (connecting to stored procedures) on my pages
My pages are based on a master and content pages and in content pages I have mutiviews.
In one of the views (inside one of those multiviews) I had made two radcombo boxes for country and city requirement like cascading dropdowns as parent and child combo boxes. I used old way for doing that, I mean I used update panel and in the SelectedIndexChange event of parent RadComboBox (country) I wrote this code:
protected void RadcomboboxCountry_SelectedIndexChanged(object o, RadComboBoxSelectedIndexChangedEventArgs e)
{
hfSelectedCo_ID.Value = RadcomboboxCountry.SelectedValue;
RadcomboboxCity.Items.Clear();
RadcomboboxCity.Items.Add(new RadComboBoxItem(" ...", "5"));
RadcomboboxCity.DataBind();
RadcomboboxCity.SelectedIndex = 0;
}
My child radcombo box can fill by this code, let me tell you how: the child SqlDataSource has a stored procedure that has a parameter and I fill that parameter with this line
hfSelectedCo_ID.Value = RadcbCoNameInInsert.SelectedValue;
RadcbCoNameInInsert.SelectedValue means country ID.
After doing that SelectedIndexChange event of parent RadComboBox (Country) could not be fired therefore I forced to set the AutoPostback property to true.
After doing that, everything was ok until some one told me can you control focus and keydown of your radcombo boxes (when you press the Enter key on the parent combobox [country], so child combobox gets focus -- and when you press upperkey on child radcombobox [city], parent combobox[country] gets the focus - for users that do not want to use mouse for input and choose items).
I told him this is web app, not win form and we can not do that. I googled it and I found jQuery the only way for doing that ... so I started using jQuery. I wrote this code with jQuery for both of them :
<script src="../JQuery/jquery-1.4.1.js" language="javascript" type="text/javascript"></script>
<script type="text/javascript">
$(function() {
$('input[id$=RadcomboboxCountry_Input]').focus();
$('input[id$=RadcomboboxCountry_Input]').select();
$('input[id$=RadcomboboxCountry_Input]').bind('keyup', function(e) {
var code = (e.keyCode ? e.keyCode : e.which);
if (code == 13) { -----------> Enter Key
$('input[id$=RadcomboboxCity_Input]').focus();
$('input[id$=RadcomboboxCity_Input]').select();
}
});
$('input[id$=RadcomboboxCity_Input]').bind('keyup', function(e) {
var code = (e.keyCode ? e.keyCode : e.which);
if (code == 38) { -----------> Upper Key
$('input[id$=RadcomboboxCountry_Input]').focus();
$('input[id$=RadcomboboxCountry_Input]').select();
}
});
});
</script>
This jQuery code worked but autopostback=true of the parent RadComboBox became a problem because when SelectedIndexChange of the parent RadComboBox is fired after that Telerik Skins runs and after that I lost parent combobox focus and we should use mouse but we don't want it....
To fix this problem I decided to set AutoPostback of parent CB to false and convert
protected void RadcomboboxCountry_SelectedIndexChanged(object o, RadComboBoxSelectedIndexChangedEventArgs e)
{
hfSelectedCo_ID.Value = RadcomboboxCountry.SelectedValue;
RadcomboboxCity.Items.Clear();
RadcomboboxCity.Items.Add(new RadComboBoxItem(" ...", "5"));
RadcomboboxCity.DataBind();
RadcomboboxCity.SelectedIndex = 0;
}
to a public non static method without parameters and call it with jQuery like this (I used onclientchanged property of parent combobox like
onclientchanged = "MyMethodForParentCB_InJquery();"
instead of selectedindexchange event):
public void MyMethodForParentCB_InCodeBehind()
{
hfSelectedCo_ID.Value = RadcomboboxCountry.SelectedValue;
RadcomboboxCity.Items.Clear();
RadcomboboxCity.Items.Add(new RadComboBoxItem(" ...", "5"));
RadcomboboxCity.DataBind();
RadcomboboxCity.SelectedIndex = 0;
}
For doing that I read the below manual and do that step by step :
http://www.ajaxprojects.com/ajax/tutorialdetails.php?itemid=732
but this manual is about static methods and this is my new problem ...
When I am using static method like :
public static void MyMethodForParentCB_InCodeBehind()
{
hfSelectedCo_ID.Value = RadcomboboxCountry.SelectedValue;
RadcomboboxCity.Items.Clear();
RadcomboboxCity.Items.Add(new RadComboBoxItem(" ...", "5"));
RadcomboboxCity.DataBind();
RadcomboboxCity.SelectedIndex = 0;
}
I got some errors and this method could not recognize my controls and hidden field...
One of those errors:
Error 2 An object reference is required for the non-static field, method, or property 'Darman.SuperAdmin.Users.hfSelectedCo_ID' C:\Javad\Copy of Darman 6\Darman\SuperAdmin\Users.aspx.cs 231 13 Darman
Any idea or is there any way to call non static methods with jQuery?
(I know we can not do that but is there another way to solve my problem?)
Your problem is related to the interaction between .NET and jQuery. Basically, if you change values in the user interface using jQuery, .NET doesn't know anything about it. If you make an ajax call using jQuery, it doesn't know anything about .NET's controls.
The ajax method you found and started to implement is the right way to go. However, jQuery is going to make a true ajax call. Everything you do in code behind has to exist in that static function. It can create objects and do things with them, but no controls will exist when you enter this function at runtime (unlike using an updatepanel, which walks through the full page lifecycle).
So, something like this is not going to work:
public static void MyMethodForParentCB_InCodeBehind()
{
hfSelectedCo_ID.Value = RadcomboboxCountry.SelectedValue;
RadcomboboxCity.Items.Clear();
RadcomboboxCity.Items.Add(new RadComboBoxItem(" ...", "5"));
RadcomboboxCity.DataBind();
RadcomboboxCity.SelectedIndex = 0;
}
In the case above, you don't have access to any of the controls, so you're basically left with populating the control yourself using jQuery.
You'll need to send the selected value to the static method, create the new list item as a string, and return this to the ajax callback. Within the jQuery ajax callback you'll have to add the item into the list yourself.
public static string MyMethodForParentCB_InCodeBehind( string selectedvalue )
{
string rtrnString = SomeClass.GetValue( selectedvalue );
return rtrnString;
}
The following function in your presentation logic should retrieve this result and add it to your list using jQuery.
function AjaxSucceeded (result)
{
alert(result.d);
// result.d will have the value of the string passed back from the function
// it's up to you to populate the combobox using jQuery.
}
The side effect of doing this is that the .NET control no longer shares the same viewstate that it did before. Meaning, if the page does a postback, the new value entered into your combobox will not be available in codebehind. You most likely won't even get this far as you'll probably get view state errors.
You're kind of in a tough spot. You might want to look into using updatepanels, as you will have access to the controls in code behind.