FindControl and INamingContainer - asp.net

I want to word this question carefully, so helpful people don't jump in and spend their time telling me information that I already know (I don't want to waste their time).
I want to understand how FindControl works in ASP.NET web application projects (the ones where the c# files are referenced as CodeBehind, NOT CodeFile, in the markup).
Code behind have two files which sit between the markup file. E.g. Default.aspx will have Default.aspx.cs and Default.aspx.designer.cs
If you put a button on a page, it is added to the designer file. For example:
protected global::System.Web.UI.WebControls.LinkButton LinkButton1;
If you want to get a reference to that control, it is immediately available as a member of the Default class. E.g. this.LinkButton1.Text = "Click Me";
If you look at a trace for the page, it is given a unique id as per the behaviour for INamingContainers (here, the Page): ctl00$ContentPlaceHolder1$LinkButton1
The thing I don't understand is why a null is returned by the statement:
Control c = Page.FindControl("LinkButton1");
I realise this is unnecessary, as the button is already available to the Default class. And this is because it appears as a member in the Default.aspx.designer.cs file.
The thing I do not understand is why null is returned. Because the Page implements INamingContainer, and the button has an ID which correlates to that expected of a control in an INamingContainer. Isn't this exactly the kind of thing FindControl finds?

This behaviour was new to me, maybe because i wouldn't try to search for a control that is directly accessible anyway. I think this might also be the reason why ASP.NET not even allow this, because it's faster and safer to use an existing reference than to find it (or not).
The FindControl method can be used to access a control whose ID is not
available at design time. The method searches only the page's
immediate, or top-level, container; it does not recursively search for
controls in naming containers contained on the page. To access
controls in a subordinate naming container, call the FindControl
method of that container.
http://msdn.microsoft.com/en-us/library/31hxzsdw.aspx
Edit: After i checked this behaviour, i've noticed that null is only returned if used on a Page with MasterPage, since the only control in the page's ControlCollection is the MasterPage itself.
That makes sense. You cannot guarantee an ID to be unique when the control is on the top level of a page with MasterPage, because other ContentPages might as well have a control with this ID and FindControl could today return another control than tomorrow.
If you look at the NamingContainer of the Control you want to find, you see that in case of a MasterPage it is the ContentPlaceHolder and in case of a "normal" Page it is the Page itself.
So you need to get a reference to the MasterPage's ContentPlaceholder first before you could find the control via FindControl:
Page.Master.FindControl("ContentPlaceHolder1").FindControl("LinkButton1");
http://msdn.microsoft.com/en-us/library/xxwa0ff0.aspx

FindControl is not recursive, and it looks like you have an intermediary ContentPlaceHolder1 control, which is a naming container, so this should work: Page.FindControl("ContentPlaceHolder1").FindControl("LinkButton1")

If you put it in a Panel, you can call
myPanel.FindControl("LinkButton1");

Related

I get a NullReferenceException when I use FindControl to find controls in either master or content pages

I am trying to create reversible themes in ASP.NET. I can successfully change themes using a dropdown list, but I am running into problems changing SkinID's and generic HTML controls (which are all div's except for the body tag) programmatically. I moved all my attribute- and skin-changing code to the PreInit method of my Base Page. Now I get a NullReferenceException when I run the page. I thought this code was supposed to use the existing controls it is supposedly pointing to. What am I missing or doing wrong?
Here is my code:
The PreInit event is probably too early in the page lifecycle to look for controls with FindControl. You're also doing your declarations outside of the event. That may have something to do with your null reference as well. I'd see if you can change it to look later in the lifecycle. This may be helpful if you haven't already seen it: http://msdn.microsoft.com/en-us/library/ms178472.aspx

Finding an ascx control inside aspx

I'm finding a dropdown in an ascxcontrol on my aspx in the following way.
Dim cp As ContentPlaceHolder = DirectCast(Page.Form.FindControl("ContentPlaceHolder1"), ContentPlaceHolder)
Dim ascx As UserControl = DirectCast(cp.FindControl("drpType"), UserControl)
Dim drpType As DropDownList = DirectCast(ascx.FindControl("drpType"), DropDownList)
Is there a faster way without having to acces all the elements on the page?
I wouldn't try and reference a control within a user control this way, the user control should encapsulate these and the page should talk to public properties.
Depends on what you're trying to do.
Without a great deal of context, I can only assume that you are either getting or setting the value of the dropdown.
I wouldn't use the approach that you're going for. It introduces an element of implementation specific coupling.
You would be far better off exposing whatever you need to get/set via a property which you can call from the .aspx page.
However, in answer to your question, if you are going to reference the dropdown from the .aspx page, you will have to use FindControl.
If you know what naming containing the control is in you can go
ucNamingContainerControl.FindControl(controlId)
That will at least limit it to that section of the page.
Otherwise the only other thing I can think of is if you are accessing a predefined set of controls - put them in a Dictionary collection and use the Find method to pick them out. Could be a quicker retrieval but might look a bit clunky on the page.

iterate through all tetxbox controls in a asp.net webpage

i have 6 textboxes which i want to iterate.
they are however in a TD in a TR in a TABLE in a PANEL etc.
the only way i've figured out to iterate them is in this way:
this.Controls[0].Controls[3].Controls[7].Controls
that's not only errorprone, but also hard to come up with.
but this.FindControl (to find one by name) doesn't work either, does findcontrol also only search in the direct child, and not the whole hierarchie?
so basicly what i'm looking for is to iterate ALL controls in the page, no matter in which level of the hierarchie, to check if it's a textbox.
Is there a way to do that?
EDIT: i don't want to find them by their name (they are server controls so i could do that) because i would have to modify that code every time i add a textbox. By iterating the form i would not have to do that.
FindControl searches the hierarchy but it doesn't go into controls that are an INamingContainer
Any control that implements this interface creates a new namespace in which all child control ID attributes are guaranteed to be unique within an entire application. The marker provided by this interface allows unique naming of the dynamically generated server control instances within the Web server controls that support data binding. These controls include the Repeater, DataGrid, DataList, CheckBoxList, ChangePassword, LoginView, Menu, SiteMapNodeItem, and RadioButtonList controls.
Basically it defines a boundary to avoid naming collisions. Consider how hard it'd be if all your control IDs really had to be unique.
Note this information is also in the FindControl remarks. Tip: Always read the remarks.
The FindControl method can be used to access a control whose ID is not available at design time. The method searches only the page's immediate, or top-level, container; it does not recursively search for controls in naming containers contained on the page. To access controls in a subordinate naming container, call the FindControl method of that container.
By doing so you could navigate to the control you want going through only the naming containers & calling FindControl at each level i.e. FindControl("SomeNamingContainer").FindControl("AChildContainer")
That's not necessarily practical, and depending on what you're doing you really just need to get All TextBoxes.
IEnumerable<TextBox> TextBoxes(ControlCollection ctrls)
{
var texts = ctrls.OfType<TextBox>();
var children = ctrls.SelectMany(c => TextBoxes(c.Controls));
return texts.Union(children);
}
Try FindControl on the Page object
Page.FindControl(id)
Are they in a formview or something?
If you don't know the ID of the textboxes as well (i.e. they are dynamic) then a quick recursion code will help. I can post the code here if Page.FindControl does not work. Let me know,.
Here is the code
List<System.Web.UI.WebControls.TextBox> _textBoxes = new List<System.Web.UI.WebControls.TextBox>();
private void FindTextBoxes(ControlCollection cc)
{
foreach (Control c in cc)
{
if (c is System.Web.UI.WebControls.TextBox)
_textBoxes.Add(c as System.Web.UI.WebControls.TextBox);
else if (c.Controls.Count > 0)
FindTextBoxes(c.Controls);
}
}
You can call it as
FindTextBoxes(Page.Controls);
FindTextBoxes(MyTable.Controls);
_textBoxes collection will contain all the textboxes the code finds.
Please click the checkbox next to my answer if it solves your problem!

What's the difference in behavior between adding a control to an ASPX page directly, loading a control programmatically & adding to a placeholder?

Is there a difference in behavior between adding a control to the ASPX page directly and loading a control programmatically and adding to a placeholder?
The control inherits from System.Web.UI.WebControls.DataBoundControl.
The reason I ask is that I have a control that works when I add it to the ASPX page like so:
...
<blah:GoogleMap ID="GoogleMap1" runat="server" Width="640px" Height="600px" ... DataSourceID="_odsMarkers" DataAddressField="Address" DataTextField="Description">
</blah:GoogleMap>
...
But not when I use the following in a codebehind page:
GoogleMap map = (GoogleMap)this.LoadControl(typeof(GoogleMap), new object[] { });
//... set properties
this.placeholder1.Controls.Add(map); //add to placeholder
Anyone have any ideas why this might be the case?
The control tree ends up the same if you define in markup or add programmatically. However there is plenty of room for the control implementor to screw up along the way.
You can go look how ASP.NET compiles the aspx:
C:\Windows\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files
The timing when the control is added to the page might be an issue. The usual pattern is add the control in an overload of the CreateChildControls method. If the control needs to resolve viewstate you need to make sure this is called during init, e.g. by calling EnsureChildControls.
Adding to ninja's debbugging hint. Does it make any difference if you add a label the same way. Does it show up?
Is this a user control or server control?
If it's a user control they should be loaded by their path and not their type:
GoogleMap map = (GoogleMap)this.LoadControl("~/Controls/GoogleMap.ascx");
If it's server control then you can just new up an instance:
GoogleMap map = new GoogleMap();
after you have the instance and add it to the control tree (by inserting it into the PlaceHolder) it should perform the same as when it would have been declared in the markup.
If you are setting properties outside of the LoadControl call, why are you making that new empty object array instead of just using the overload that has one parameter?
Also, if you attach a debugger to it and step through, do you notice anything weird about the control before you do your Controls.Add() call? Is there an exception being thrown? if so, which one? if not, what does the markup in the browser look like for where the placeholder is?
"Works" is kind of ambiguous, but if you mean, event handlers are never executed, you need to load it in the page onload event.
If the control requires the use of viewstate you must ensure that it is added to the page BEFORE the Page_Load event, otherwise viewstate will not be populated and most likely events and other items will not function properly.
One important difference is that if you create a control dynamically, you will not get, by default, any values from skins set. You must manually call control.ApplyStyleSheetSkin(page): http://msdn.microsoft.com/en-us/library/system.web.ui.control.applystylesheetskin.aspx

Dynamically added controls in Asp.Net

I'm trying to wrap my head around asp.net. I have a background as a long time php developer, but I'm now facing the task of learning asp.net and I'm having some trouble with it. It might very well be because I'm trying to force the framework into something it is not intended for - so I'd like to learn how to do it "the right way". :-)
My problem is how to add controls to a page programmatically at runtime. As far as I can figure out you need to create the controls at page_init as they otherwise disappears at the next PostBack. But many times I'm facing the problem that I don't know which controls to add in page_init as it is dependent on values from at previous PostBack.
A simple scenario could be a form with a dropdown control added in the designer. The dropdown is set to AutoPostBack. When the PostBack occur I need to render one or more controls denepending on the selected value from the dropdown control and preferably have those controls act as if they had been added by the design (as in "when posted back, behave "properly").
Am I going down the wrong path here?
I agree with the other points made here "If you can get out of creating controls dynamically, then do so..." (by #Jesper Blad Jenson aka) but here is a trick I worked out with dynamically created controls in the past.
The problem becomes chicken and the egg. You need your ViewState to create the control tree and you need your control tree created to get at your ViewState. Well, that's almost correct. There is a way to get at your ViewState values just before the rest of the tree is populated. That is by overriding LoadViewState(...) and SaveViewState(...).
In SaveViewState store the control you wish to create:
protected override object SaveViewState()
{
object[] myState = new object[2];
myState[0] = base.SaveViewState();
myState[1] = controlPickerDropDown.SelectedValue;
return myState
}
When the framework calls your "LoadViewState" override you'll get back the exact object you returned from "SaveViewState":
protected override void LoadViewState(object savedState)
{
object[] myState = (object[])savedState;
// Here is the trick, use the value you saved here to create your control tree.
CreateControlBasedOnDropDownValue(myState[1]);
// Call the base method to ensure everything works correctly.
base.LoadViewState(myState[0]);
}
I've used this successfully to create ASP.Net pages where a DataSet was serialised to the ViewState to store changes to an entire grid of data allowing the user to make multiple edits with PostBacks and finally commit all their changes in a single "Save" operation.
You must add your control inside OnInit event and viewstate will be preserved. Don't use if(ispostback), because controls must be added every time, event in postback!
(De)Serialization of viewstate happens after OnInit and before OnLoad, so your viewstate persistence provider will see dynamically added controls if they are added in OnInit.
But in scenario you're describing, probably multiview or simple hide/show (visible property) will be better solution.
It's because in OnInit event, when you must read dropdown and add new controls, viewstate isn't read (deserialized) yet and you don't know what did user choose! (you can do request.form(), but that feels kinda wrong)
After having wrestled with this problem for at while I have come up with these groundrules which seems to work, but YMMV.
Use declarative controls whenever possible
Use databinding where possible
Understand how ViewState works
The Visibilty property can go a long way
If you must use add controls in an event handler use Aydsman's tip and recreate the controls in an overridden LoadViewState.
TRULY Understanding ViewState is a must-read.
Understanding Dynamic Controls By Example shows some techniques on how to use databinding instead of dynamic controls.
TRULY Understanding Dynamic Controls also clarifies techniques which can be used to avoid dynamic controls.
Hope this helps others with same problems.
If you truly need to use dynamic controls, the following should work:
In OnInit, recreate the exact same control hierarchy that was on the page when the previous request was fulfilled. (If this isn't the initial request, of course)
After OnInit, the framework will load the viewstate from the previous request and all your controls should be in a stable state now.
In OnLoad, remove the controls that are not required and add the necessary ones. You will also have to somehow save the current control tree at this point, to be used in the first step during the following request. You could use a session variable that dictates how the dynamic control tree was created. I even stored the whole Controls collection in the session once (put aside your pitchforks, it was just for a demo).
Re-adding the "stale" controls that you will not need and will be removed at OnLoad anyway seems a bit quirky, but Asp.Net was not really designed with dynamic control creation in mind. If the exact same control hierarchy is not preserved during viewstate loading, all kinds of hard-to find bugs begin lurking in the page, because states of older controls are loaded into newly added ones.
Read up on Asp.Net page life cycle and especially on how the viewstate works and it will become clear.
Edit: This is a very good article about how viewstate behaves and what you should consider while dealing with dynamic controls: <Link>
Well. If you can get out of creating controls dynamicly, then do so - otherwise, what i whould do is to use Page_Load instead of Page_Init, but instead of placing stuff inside the If Not IsPostBack, then set i just directly in the method.
Ah, that's the problem with the leaky abstraction of ASP.NET web forms.
Maybe you'll be interested to look at ASP.NET MVC, which was used for the creation of this stackoverflow.com web site? That should be an easier fit for you, coming from a PHP (thus, pedal-to-the-metal when it comes to HTML and Javascript) background.
I think the answer here is in the MultiView control, so that for example the dropdown switches between different views in the multi-view.
You can probably even data-bind the current view property of the multiview to the value of the dropdown!
The only correct answer was given by Aydsman. LoadViewState is the only place to add dynamic controls where their viewstate values will be restored when recreated and you can access the viewstate in order to determine which controls to add.
I ran across this in the book "Pro ASP.NET 3.5 in C# 2008" under the section Dynamic Control Creation:
If you need to re-create a control multiple times, you should perform the control creation in the Page.Load event handler. This has the additional benefit of allowing you to use view state with your dynamic control. Even though view state is normally restored before the Page.Load event, if you create a control in the handler for the Page.Load event, ASP.NET will apply any view state information that it has after the Page.Load event handler ends. This process is automatic.
I have not tested this, but you might look into it.

Resources