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();
Related
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"]
I have a repeater in one of the sublayouts in Sitecore 6.6. rev 120918. Inside this repeater, I have a placeholder. On ItemDataBound, I assign a key with a GUID to it, and using Nick Wesselman's GetAllowedRendering pipeline, I am able to render the insert options for this.
The repeater code is very simple:
<asp:Repeater runat="server" ID="rptrTimelineItemsEdit" OnItemDataBound="rptrTimelineItemsEdit_OnItemDataBound">
<ItemTemplate>
<sc:Placeholder ID="scTimelineItemPlaceholder" Key="timelineitemcontent" runat="server" />
</ItemTemplate>
</asp:Repeater>
The Item Data bound is also pretty simple:
protected void rptrTimelineItemsEdit_OnItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
Item item = (Item)e.Item.DataItem;
Placeholder scTimelineItemPlaceholder = (Placeholder)e.Item.FindControl("scTimelineItemPlaceholder");
scTimelineItemPlaceholder.Key = "timelineitemcontent" + item.ID.ToString().ToLower();
}
}
Everything works up to this point. After I choose the sublayout I want to insert in the placeholder, it throws me an in the JS error popup. After inspecting in the chrome inspector, I see the error is:
Could not find the rendering in the HTML loaded from server PlaceholderChromeType.js:601
Sitecore.PageModes.ChromeTypes.Placeholder.Sitecore.PageModes.ChromeTypes.ChromeType.extend._frameLoaded
So - it would seem like that in the page reload, the sublayout renderings are happening before the repeater bindings, so it can't find the placeholder key.
I did some google, and found that there is a way to change the layout engine sequence using the layoutpageevent setting:
However, this didn't solve my problem. I'm out of ideas. Help!
Hi there Ive found a solution to this issue, it had me stumped as well.
It only occurs to placeholders that sit under a repeater.
Overwrite PlaceholderChromeType.js in Website\sitecore\shell\Applications\Page Modes\ChromeTypes\
With the one ive dropboxed (also available on the credit link below)
https://www.dropbox.com/s/7m99b8jgdz3cgl2/PlaceholderChromeType.
Your keys then have to have dynamic in them for it to work.
Example code:
protected void R1_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
var p = e.Item.FindControl("andysplaceholder") as Placeholder;
p.ID = "Andy" + (e.Item.ItemIndex + 1);
p.Key = "dynamic" + p.ID;
}
<asp:repeater id="MyRepeater" runat="server" OnItemDataBound="R1_ItemDataBound">
<ItemTemplate>
<sc:placeholder runat="server" id="andysplaceholder" key="dynamicandysplaceholder" ></sc:placeholder>
</ItemTemplate>
</asp:repeater>
On top of this i do my repeater binding in the oninit method
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
and i set the web.config setting from preInit to load
<setting name="LayoutPageEvent" value="load" />
Although that might be overkill, I will revert today to page_load and preinit today and see if it still works.
Credit to Sitecore support and Mickey Rahman for this http://mickeyrahman.wordpress.com/2014/05/05/an-ode-to-sitecore-support/#respond
This should also work with the dynamic key apprach from Nick, you'll just have to make sure the keys generated by his code have dynamic in them
You have not implemented it in the way it's explained by Nick.
What you need to do is create a WebControl class for your Dynamic Placeholder.
That will ensure that things are processed in the same order as it would have with a 'real' Placeholder.
So the first thing I would do is make sure you do it in the same way as it's been described by Nick.
Then verify that it works with just 1 dynamic placeholder on the page.
Secondly, you should take a look at this blog post: Dynamic Placeholders and IExpandable
It explains how to deal with adding multiple dynamic placeholders in repeaters (and other controls).
He also thought that it had something to do with the change in the LayoutPageEvent, but it did not turn out to be that.
EDIT:
I've reproduced the issue and I highly doubt that you're going to solve it with this approach.
The placeholders are not loaded yet when the Page Editor is requesting their renderings after trying to add controls to them.
Even when you create the Placeholders in the OnInit (which is the earliest), it doesn't work.
If I get more time I'll try to look into it further.
Ruud referenced my blog post Dynamic Placeholders and IExpandable in his answer, and I think that he's definitely on the right track. I know that you haven't used dynamic placeholders in the way that I describe, however you are still creating placeholders dynamically.
I personally have never had any success with the LayoutPageEvent; which is why I decided to post about the use of IExpandable as it isn't something that's been highlighted that much.
You ought to create your placeholders in the Expand method and also expand them after you add them to the page. I would personally advise against using a Repeater to do so - add them to the controls collection or maybe to a normal ASP.Net PlaceHolder control.
public class YourSublayout : UserControl, IExpandable
{
public void Expand()
{
var myListOfItems = GetItems()
foreach (var item in myListOfItems)
{
var placeholder = new Placeholder { Key = "key" + item.ID.ToString() };
container.Controls.Add(placeholder); // add to a control container
placeholder.Expand();
}
}
}
Hopefully that should get you a bit further (if you haven't solved it already). Just keep in mind the other things I mention on my post, namely that the Expand method can be called before the control gets added to the page, and so getting your DataSource may break if you rely on getting it from the parent control.
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",
});
}
}
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;
I have a repeater that creates n-number of panels. I am trying to dynamically add different controls to each of the panels. I may very well be going about this the wrong way.
My code is more or less:
.aspx:
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<% Response.Write("<asp:panel runat=\"server\" id=\"uxPanel_"); %>
<%# DataBinder.Eval(Container.DataItem, "TableId")%><% Response.Write("\"></asp:panel>"); %>
</ItemTemplate>
</asp:Repeater>
.cs:
public partial class class1: System.Web.UI.Page
{
DataSet ds= null;
protected void Page_Load(object sender, EventArgs e)
{
GetRecords(1,1);
}
protected void GetRecords()
{
ds= dal.LoadRecords();
this.Repeater1.DataSource = ds.Tables[0];
this.Repeater1.DataBind();
Literal lit = new Literal();
lit.Text = "Some text";
this.FindControl("uxPanel_1").Controls.Add(lit);
}
}
Just to be clear in this example "dal.LoadRecords" is simply call to a method that retrieves some records from a DB.
I think my problem is how I am adding my panels in the first place, but this seemed like an easy way to have them uniquely named.
Any Pointers?
Thanks
As the previous answerer has said, this is fraught with peril!
If you must: I should ditch the repeater approach altogether.
Create a container panel or placeholder and in the code behind dynamically add your panels to that using ContainerPanel.Controls.Add(newPanel);
Your child "panel" could be a UserControl if needs be too.
Be aware that you'll have to regenerate your dynamic controls on postback.
Pointers? Yes : Don't dynamically add controls!
Dynamically adding controls adds a lot of overhead and you run into issues with viewstates. Usually , it's not worth the time and headaches.
Panels, as well as Literals, PlaceHolders, and every other ASP.NET control have a Visible property that you can toggle on and off, like so:
myPanel.Visible=true;
myOtherPanel.Visible = false;
myTogglePanel.Visible = ! myTogglePanel.Visible; //toggle it's visibility
This approach is much, much easier.