Adding asp controls to programmatically added content - c#

What I would like:
In an ideal scenario I would be able to create an anchor with oncommand and commandargument attributes, but if I'm not mistaken that doesn't work and you have to create a control, such as a button, where it will work. My problem then comes from wanting to place that button for each item on the page, as I need something to add the control to, but if I created an anchor with runat="server" and, say, id="try", I can't then do: try.Controls.Add(button) because the anchor 'try' hasn't actually been created yet.
Background:
The majority of content is being added programmatically. A stringbuilder is used to create a string of what will be html displayed on the page. Is it possible to add a control to the page in the middle of this string? OR into an element which is programmatically added this way?
I have tried:
Creating anchors (or otherwise) and targeting the id of those elements and then creating a button as follows, but, because the elements are added programmatically and the number required will vary, the ids will then be try0, try1, etc:
var button = new Button {
CommandArgument = "test",
Text = "Try"
};
button.Command += bt_sendMail_tryDevice_Click;
try.Controls.Add(button);
So I tried variations of the following, where in my aspx page I have a 'dummy' element with the id="try" so it doesn't complain, but I understand why it doesn't like it, at the same time though I don't know how to get around it. (tryCount being an int which increase with each iteration to keep the id unique).
this.FindControl(try.ToString() + tryCount.ToString()).Controls.Add(button);

Its kinda hard to tell what your going for, but I will do my best to get as close as I can. The first thing is you need some control to work as a "container". This can be just about any control you like. In my test for this scenario I did something like this:
<div runat="server" id="ContainerDiv"></div>
The next thing is you need a way to manage your Id. I did this by creating a simple variable and method like so:
private int IdCount = 0;
private string GetNewID()
{
return string.Format("try{0}", IdCount++);
}
Now you say you want an achor tag that also has a CommandName and CommandArgument a LinkButton will do this. You can add a LinkButton to your div above like this:
ContainerDiv.Controls.Add(
new LinkButton()
{
ID = GetNewID(),
CommandName = "DoSomething",
CommandArgument = "arg",
Text= "Try Me",
});
Obviously replacing the CommandName and the rest with the values you really want. Just be sure to call GetNewID() when assigning the ID so they will always be unique.
Controls can be added to a page anywhere as a child of an existing server control, including the page itself, but doing so can be tricky. Be sure to add them to your page as soon as you can (in the page lifecycle) as post back events may not work correctly.
Update
Keeping references to already created elements on your page may simplify your implementation:
public partial class _Default : Page
{
Control containerDiv;
protected void Page_Load(object sender, EventArgs e)
{
this.containerDiv = SomeMethodThatCreatesADiv();
this.Page.Controls.Add(containerDiv);
}
void SomeOtherMethod()
{
this.containerDiv.Controls.Add(
new LinkButton()
{
ID = GetNewID(),
CommandName = "DoSomething",
CommandArgument = "arg",
Text= "Try Me",
});
}
}

Related

How to resolve `Multiple controls with the same ID 'Label1' were found. `?

I am trying to create dynamic label's in my web form app when but seem to be getting this error
Multiple controls with the same ID 'Label1' were found. FindControl requires that controls have unique IDs.'
This is what I have:
<asp:UpdatePanel ID="Panel" runat="server" ChildrenAsTriggers="false" UpdateMode="Conditional">
<ContentTemplate>
<asp:Panel ID="OverViewUpdate" runat="server">
<asp:Label ID="Label" runat="server"></asp:Label>
</asp:Panel>
</ContentTemplate>
</asp:UpdatePanel>
And Code Behind:
for (int i = 1; i <= 20; i++)
{
Label label = new Label();
label.ID = "Label" + i.ToString();
label.Text = "Label" + i;
OverViewUpdate.Controls.Add(label);
}
((Label)OverViewUpdate.FindControl("Label")).Text = Convert.ToString("RoundTripTime: " + reply.RoundtripTime + "ms") + "<br/>";
I am not sure how to resolve this issue?
Thanks
Well, as per comments, it seems perhaps that the page already has a control with that ID. The next problem, if you have to be sure you don't run that code each time - in other words, only add the controls on first page load - not each additional button click and post back.
eg like this:
if Not IsPostBack then
' code here to add those controls
End if
Remember, on each button click or anything that post's back the page, then all your code runs again - including the page load event. So for a "really" first page load, we check IsPostBack as per above.
However, for the most part, when we need repeating data or information, there no need to inject or to try and add controls by code. You can try doing this, but in the VAST majority of cases, you can use one of the built control's that allow you to do this is great ease, and in most cases without looping code either.
So, for some controls - that you want to repeat? Then use what is called a repeater.
For table like data? Then use a gridview, or say listview. And even better is those controls are data bound - and will fill out for you automatic, and do so without again without looping code.
So, say we want some text boxes to show a list of hotels we have. and MORE often then not, such repeating data comes from a database, which is even better yet.
So, we don't even have to know how many labels or text boxes we need ahead of time.
So, now our markup can be this:
And now, our code can be this:
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack == false)
{
LoadGrid();
}
}
public void LoadGrid()
{
using (SqlCommand cmdSQL = new SqlCommand("SELECT * from tblHotels ORDER BY HotelName",
new SqlConnection(Properties.Settings.Default.TEST3)))
{
cmdSQL.Connection.Open();
MyGrid.DataSource = cmdSQL.ExecuteReader();
MyGrid.DataBind();
}
}
And output:
so, you can see how we did not need to inject, or loop to inject or add controls to the web page, but used a repeating control.
But, in your case? I would make sure your code that adds the controls ONLY runs inside of the IsPostBack = false stub.
It seems perhaps that the page already has a control with Label1.
in that case you can use the following statement inside loop.
label.ID = "Label" + i+1.ToString();

How to get primary key values from gridview which is not shown in gridview - c#

I have a gridview looks like below.
Name Attended_Exam
Raj English
Hindi
Das Korea
Rahul Spanish
English
And the query used to bind datatable to this gridview contains a submission_id. Which is unique for each student and his subject.
Each attended exam name is shown as a linkbutton. Now, when clicking on it, I want to get the Submission_id of each subject. What is the best way to achieve this?
<asp:GridView ID="gvSubmissionHeaders" runat="server" AutoGenerateColumns="true"
Width="80%" OnRowDataBound="gvSubmissionHeaders_RowDataBound"
Font-Bold="false" RowStyle-Height="30px" >
</asp:GridView>
protected void gvSubmissionHeaders_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{ //for adding linkbutton to Attended_Exam
//loop through the cell.
for (int j=1;j< e.Row.Cells.Count;j++)
string[] arrLinks =null;
if (!string.IsNullOrEmpty(e.Row.Cells[j].Text.ToString()) && e.Row.Cells[j].Text.ToString()!= " ")
{
arrLinks = e.Row.Cells[j].Text.Split(',');
}
if(arrLinks!=null)
{
for (int i = 0; i < arrLinks.Length; i++)
{
LinkButton btnLink = new LinkButton();
btnLink.ID = "Id" + arrLinks[i] + i;
btnLink.Text = arrLinks[i] + "<br>";
e.Row.Cells[j].Controls.Add(btnLink);
}
}
}
Ok, the detail here is that you could have simply noted that you have cell/colum in the grid, and you might add 1 or maybe 4 link buttons into that cell. So you have "N" buttons that you add, and you need/want particular information from that button.
If the button was static (a single link button), then you can add custom attributes to that button, and even additional columns data (ones not displayed in the grid) like this:
<td align="center" >
<asp:LinkButton ID="pUploadFiles" runat="server"
CommandArgument='<%# Eval("ID")%>' CommandName='cmdView'
Width="120px" align="center"
ContactNameID = '<%# Eval("ContactNameID")%>'
QuoteNum = '<%# Eval("QuoteNum")%>'
ProjectHeaderID = '<%# Eval("ID")%>'
>
</asp:LinkButton>
</td>
So now when you get the sender, or do a findcontrol, you can do this in code:
Dim btn As LinkButton ' we get required data from btn on row.
btn = lvd.FindControl("pUploadFiles")
With btn.Attributes
Session("ContactID") = .Item("ContactNameID")
Session("ContactGeneralID") = .Item("ContactGeneralID")
Session("QuoteNum") = .Item("QuoteNum")
End With
So linkbtn.Attributes.Item("my custom value") will get you any extra values (columns) that you attached to that link button. And with the above eval(), you can even pull any column from the data source as long as those column exist in the datatable/datasource that drives the listview or gridview. (the great part here is that you don't need actual columns in the gridview/listview to try and store and "hide" these values. The extra values are simply part of that given control as custom attributes.
Now you are adding the link btn in code, but you can do the same thing.
eg:
LinkButton btnLink = new LinkButton();
btnLink.ID = "Id" + arrLinks[i] + i;
btnLink.Text = arrLinks[i] + "<br>";
btnLink.Attributes.Add("Submission_id","100");
e.Row.Cells[j].Controls.Add(btnLink);
Now of course you would replace the hard coded "100" in above with the value you are pulling or want to store as a custom attribute. So you can add 1 or "many" custom attributes to that link button. When the click on that link button, then you grab/get the additional attributes that are associated with that link button by using Mybtn.Attributes.Item("Submission_id").
So be it one link button that is part of the grid, you can add those extra attributes (without even extra code), and even rows from the databind that are not in the grid.
So I can have several buttons, and when they click, then additional information such as PK row, or even several other values can be part of (or added) to that one button. And in your case this should work fine if you dynamic adding 1 or 5 buttons as you are. So, those additonal values you want can simply become additonal attributes of that button.
Edit:
Ok, the problem is that controls that require events that are created "after" the page has been rendered cannot really be wired up. You would have to move the code to a earlier event. So you are free to add controls, but they will in "most" cases be rendered TOO LATE to have events attached. Thus when you click on the link button, nothing fires.
So there are two solutions I can think of that will work.
First, set the control to have a a post back URL, and include a parameter on that post back.
eg this:
Dim lnkBtn As New LinkButton
lnkBtn.Text = "<br/>L" & I
lnkBtn.ID = "cL" & I
lnkBtn.PostBackUrl = "~/GridTest.aspx?r=" & bv.RowIndex
If you put a PostbackUrl, then when you click on the button, the page will post back. However, the grid row events such as rowindex change, or row click event etc. will NOT fire. So, if you willing to have a parameter passed back to the same page as per above, then you can pass the 1-3 (or 1-N) values you have for each control.
Of course that means you now have a parameter on the web page URL (and users will see this). You of course simply pick up the parameter value on page load with the standard
Request.QueryString["ID"] or whatever.
However, another way - which I think is better is to simple wire up a OnClickClick() event in js, and thus do this:
I = 1 to N
Dim lnkBtn As New LinkButton
lnkBtn.Text = "<br/>L" & I
lnkBtn.ID = "cL" & I
lnkBtn.OnClientClick = "mycellclick(" & I & ");return false;"
Now in above note how I am passing "I" to the js routine. You would pass your 200, 300 or whatever value you want.
then you script will look like this:
<script>
function mycellclick(e) {
__doPostBack("MySelect", e);
}
</script>
So above simply takes the value passed from the cell click (and linkbutn), and then does the postback with a dopostback. I used "MySelect", and you can give that any name you want.
Now, in the on-load event, you can simply go like this:
If Request("__EVENTTARGET") = "MySelect" Then
Dim mypassvalue As String = Request("__EVENTARGUMENT").ToString
Debug.Print("row sel for MySelect = " & mypassvalue)
End If
So, you are 100% correct - clicking on those controls does NOT fire server side event, and they are wired up too late for this to occur. so you can and often do say add some columns or controls to a gridview, but they are created and rendered TOO LATE for the events to be wired up (and thus they don't fire when clicked on).
But, you can add a postback to the lnkbutton, and you can also add a OnClickClick() event (JavaScript function call) and they will both work. I don't like parameters in the URL appearing when you click, so I think the js script call as per above works rather nice.
So while in the comments I noted (and suggested) that you have to set the CommandName="Select". This suggesting still holds true (without CommandName = select, then the rowindex will not fire. You can't use just ANY name - it MUST be select. However this ONLY works if the control is part of the grid and not added on the fly. As noted, it might be possible to move the grid event to "earlier" event (page initialize) but it going to be a challenge and will require you to re-organize the page. The most clean, and one that does not require parameters in the URL is adding that js OnClientClick() event. You can however set the controls postbackurl and along with a parameter in the URL, and that also can work well if you open to URL with parameters (I don't like them).
First you declare your table column ID on the DataKeyNames on GridView eg:
<asp:GridView DataKeyNames="cTableColumnID" ID="gvSubmissionHeaders" runat="server" ...
Then you can get this ID per Row using this line
gvSubmissionHeaders.DataKeys[CurrectRowNum]["cTableColumnID"]

Find Control in code behind - multiple results controls - nth occurence

I have the following problem.
I have a asp:textbox on the page, runat server with an id of say txt
This text box is in a <div>, nothing special. ie:
<div>
<asp:TextBox id="txt" runat="server"></asp:TextBox>
</div>
The problem is there is some java script which when you push the corresponding button it doubles (copies) the div. This is by design. It is meant to.
When you hit save but at the bottom of the page on a asp:Button, it can't find the value I need because it returns two results.
In the code behind:
(Textbox) blah = (Textbox)senderbutton.FindControl("txt");
string test = blah.text
But the result is essentially--> "The value in the textbox , The value in the textbox"
I.e. it is there twice. I have worked around this by doing the following:
string[] test = blah.text.split(new[] { ',' })
and then only calling the second value in the array or whatever.
BUT, now I have this situation but the problem is that a user can enter a string with a ' , ' in it, hence the splitting goes to crap....
So can I find a control with an id, but only find the nth occurence of it in the code behind?
Seems you need to give different name(like txt0,txt1...) for each copy of the input controls.
You can do this using javascript up on client click(prior to form submission) of your asp button
-- Javascript method
function ModifyName() {
var x = 0;
$("input[name='txt']").each(function () {
$(this).attr("name", $(this).attr("name") + x);
x++;
});
}
-- asp:Button
<asp:Button ID="btnSubmit" runat="server" Text="Submit"
OnClientClick="ModifyName();" onclick="btnSubmit_Click" />
So in code behind you can get the values like this...
protected void btnSubmit_Click(object sender, EventArgs e)
{
var resultArr = Request.Form.AllKeys.Where(x => x.Contains("txt"))
.Select(x => Request.Form[x]).ToArray();
}
Not a very nice solution, but you could examine the Request.Form collection directly upon postback and write some code to process your dynamically added textbox fields.
The best solution would be: avoiding copying the div in js. Since you said "This is by design. It is meant to."(even I really doubt it), there are some alternative solutions:
(1) Don't use the default submit behavior of the form. That is, in the click (js) event of the save button, organize the data in the form and then submit it.
(2) Modify the second(copied) textbox's id so that its id is different from the original one, and then get the data in code behind.
I am not sure why you using FindControl method to find the control when you can directly access the txt control from code behind.
You can get results easily
String test = txt.Text;

Adding dynamic generic html control to page destroys bound dropdownlist

I encountered some weird behaviour today and I was hoping someone could shed some light on it for me because I'm perplexed.
I have a couple of methods I use to interact with the ui for the sole purpose of displaying error/success/warning messages to the user.
Here is one of them
public static void Confirm(string text)
{
var page = (Page)HttpContext.Current.Handler;
var uiConfirm = new HtmlGenericControl("div")
{
ID = "uiNotify",
InnerHtml = text
};
uiConfirm.Attributes.Add("class", "ui-confirm");
page.Master.FindControl("form1").Controls.AddAt(2, uiConfirm);
}
This works perfectly fine except for one nuance I encountered this morning and I was hoping someone could shed some light on it for me.
I am working on your run of the mill profile editing page. In this page, I am binding a couple of dropdownlists (country, province/state) on page load. I have a submit at the bottom and a click event that fires to update the information, then call the method above to notify the user that their information was successfully updated. This works the first time you click the submit button; the page posts back, the information gets updated in the database, the dynamically added div gets popped in, confirm message is displayed and all is good. However, if you then click the submit button again, it fails stating SelectedItem on the dropdowns I'm binding in the page load is null (Object reference not set to an instance of an object). The dropdown is actually wiped for some reason on the second postback, but not the first.
In sheer desperation after trying everything else, I decided to take out the call to the confirm method... and strangely enough the error disappears and I can update the information on the page as many times as I like.
If I add a generic control statically to the page I'm working on, and change my method slightly so that instead of adding a generic control to the form dynamically it just finds the generic control on the page, that does no produce the same error.
The problem also goes away if I remove the two dropdowns from the page or just stop interacting with them.
Why on earth would adding a dynamic control to the form wipe my dropdowns on postback?
I think you should consider using the PlaceHolder class in your MasterPage, the AddAt(2, uiConfirm) is going to bite you and probably is:
Markup:
.......
<asp:PlaceHolder id="PlaceHolder1"
runat="server"/>
......
Code-behind:
public static void Confirm(string text)
{
var page = (Page)HttpContext.Current.Handler;
var uiConfirm = new HtmlGenericControl("div")
{
ID = "uiNotify",
InnerHtml = text
};
uiConfirm.Attributes.Add("class", "ui-confirm");
//may need to change depending on where you put your placeholder
Control placeHolder = page.Master.FindControl("PlaceHolder1");
placeHolder.Controls.Clear();
placeHolder.Controls.Add(uiConfirm);
}

C# .Net variable unexpected re-initialation

So I am experiencing an issue with an .aspx page and some server side code, where I am getting unexpected results.
The goal of this page is simple, there are 5 radio buttons and a button with a server side onclick function. The idea is the user picks 1 of the 5 radio buttons, and then clicks the button. Upon clicking the button I verify (not using form validation, because I wanted a different feel) that a button is checked, and then store the selected option in a database.
Due to the fact that the number of radio buttons may change in the future I decided to try and abstract the number of radio buttons to make it easier on my self to change in the future.
So at the top of my server side code I created a list of possible options.
I then have a registerVote function that takes in a RadioButton object, and a number to grab a setting from the config file. I throw those 2 values into a wrapper class, and then add them to the list of possible options.
Finally when the submit button is pressed, I iterate through all possible options to see which one is checked, and grab its associated value.
public partial class VotePanel : System.Web.UI.Page
{
List<VoteOption> voteOptions = new List<VoteOption>();
public string registerVote(RadioButton newRadioButton, int voteOption)
{
voteOptions.Add(new VoteOption(newRadioButton, voteOption));
return ConfigurationManager.AppSettings["vote_option_" + voteOption];
}
protected void Submit_Click(object sender, EventArgs e)
{
//Check vote
string vote_value = "";
bool someButtonChecked = false;
foreach (VoteOption vo in voteOptions)
{
if (!someButtonChecked && vo.button.Checked)
{
vote_value = vo.movie;
someButtonChecked = true;
}
}
//....
}
}
class VoteOption
{
public RadioButton button;
public int vote_value;
public VoteOption(RadioButton r, int v)
{
button = r;
vote_value= v;
}
}
The code I use in page to add a radio button looks like this
<asp:RadioButton ID="RadioButton1" runat="server" GroupName="Vote" style="position: relative; top: 3px;" /><%=registerMovie(RadioButton1,1)%>
Now for the problem I am experiencing. Whenever the submit button is clicked, the list has a count of zero, and looks like it has been reinitialized. I validated that values are being added, by returning the list count in the registerVote method, and objects are indeed being added, but for some reason are not available to the Submit function.
Now variables on a page like this shouldn't reinitialize right? I also tested a string, and it did not reset and was available to the Submit button. What I did was define a class variable string time = DateTime.Now.Ticks.toString(); and displayed that after the submit button was clicked, and the time was always the same reguardless of how many times I clicked it.
So why would my List reinitialize, but not a string? Any ideas?
Keep in mind that your page class will be constructed and destructed for every request - no state will be maintained between each page load, it is up to you to properly recreate state as needed. In this case it appears that your list voteOptions is not being recreated before Submit_Click is called.
You'll have to register all your voting options regardless of whether the page is in a postback or not inside the Page_Load or OnInit handlers of the page. This will reconstruct voteOptions, which will then be accessed when Submit_Click is called.
Take a look at the ASP.NET Page Life Cycle.
The problem seems to be that you are constructing the List<VoteOption> voteOptions at page render then expecting it to still be there on postback. The Page object does not exist past the point that the page is delivered to the browser, so your list of vote options gets disposed of as well when the browser has received the page.
You'll either need to reconstruct the voteOption list before or during Submit_Click on postback, or give yourself enough information in the value of the radio button that you don't need it.
I don't see in your code any place where the list that you are building is placed in memory. I believe you are rebuilding it on each page reload. P.s. might be my reading but you created a function called registerVote and you are calling a method called registerMovie so that might be your problem.
You could place the list in the session and get it back from session.
Personnally I would change the code to
1) Check if the list is in memory and get it. If not in memory call a method to generate it once and then place it in memory.
2) Use a RadioButtonList on your page that you can then bind to your list as a data source.
asp.net is stateless, so every postback (such as clicking Submit) recreates the server-side class. If you want your list to persist between calls, you should save it in ViewState or a Hidden field. Not sure about the string though; what you're describing doesn't fit the asp.net lifecycle.

Categories

Resources