Looping through controls on a page using a masterpage - c#

I'm trying to perform an operation on every control on a page that is inherited from a masterpage. I don't know how to access the child pages controls. I have tried recursively getting to my controls like this:
private void checkControls(ControlCollection controlcollection)
{
foreach (Control control in controlcollection)
{
if (control.Controls.Count > 0)
{
Debug.WriteLine(control.GetType().ToString());
checkControls(control.Controls);
}
else
{
Debug.WriteLine(control.GetType().ToString());
}
}
The method is called like this:
protected void resettodefault()
{
checkControls(this.Page.Controls);
}
However, the only controls that are printed from this execution are:
ASP.site_master
System.Web.UI.LiteralControl
I would prefer to access my controls directly (without recursion). Otherwise, how can I modify my recursion to get to the desired page's controls?

I would suggest using a base page instead of a master page, this way your logic for iterating over controls (and whatever you will do with that afterwards) is not tied to which master page a page is using.
As far as getting all the controls on the page, because the controls are hierarchical, as is the HTML they represent, so iterating over them recursively makes sense. However if you are dead set on not recursively getting controls something like this should work:
IEnumerable<Control> GetAllControls()
{
var allControls = new List<Control>();
var currentControls = new Queue<Control>();
currentControls.Enqueue(this.Page);
while (currentControls.Count >0)
{
var c = currentControls.Dequeue();
if (!allControls.Contains(c))
{
allControls.Add(c);
if (c.Controls != null && c.Controls.Count > 0)
{
foreach (Control e in c.Controls)
{
currentControls.Enqueue(e);
}
}
}
}
return allControls;
}
The last thing to consider is the lifecycle of the page, and when you iterate over the controls. If you try to walk to control tree too early not all controls may exist.
EDIT: Updated code.
Update
For validation purposes I would highly recommend using the built in validation controls of asp.net. You can read more about them here. This has the added benefit of providing validation on the client, providing faster UI responses and easing the load off the servers.
For resetting all the textboxes. I would recommend creating a new class for this purpose, then calling upon that class when needed instead of messing with the master page:
public class UIControlsHelper
{
public static void ClearTextboxes(Page page)
{
GetAllControls(page)
.Where(x => typeof(TextBox).IsAssignableFrom(x.GetType())
.ToList()
.ForEach(x => (TextBox)x.Text = string.Empty);
}
IEnumerable<Control> GetAllControls(Page page)
// Same as above, but with the page parameter replaced.
}
}
And in any of your pages:
UIControlsHelper.ClearTextboxes(this);

To access the controls in your child page do the following steps:
1-declare a variable of the type you want to access. For example if you want to access a Label in your child page use:
Label lbl_child=this.ContentPlaceHolder1.findcontrol("your label id in child page") as Label;
Now you have your label and you are free to make changes on it. Every change on this control will be reflected on the child control.
ContentPlaceHolder1 is your contentplace holder id so change it with your content id.

public void ClearTextboxes(Page page) {
GetAllControls(page)
.Where(x => typeof(TextBox).IsAssignableFrom(x.GetType()))
.ToList()
.ForEach(x => ((TextBox)x).Enabled=false);
}

Related

How to find a panel and add control to it from it's code behind

In a master page I got a panel which I want to add controls to it from the master page's code behind as follow:
var cphRegionName = this.Page.FindControl("pnlLeft") as Panel;
cphRegionName.Controls.Add(uc);
but I get this error:
Object reference not set to an instance of an object at cphRegionName.Controls.Add(uc);
I have tried all possible other ways, but get the same error.
The reason I user FindControl to access the PANEL is the panel's name is dynamic ("pnlLeft"), reading from Database.
The FindControl method doesn't work recursively. This means that unless your control was added directly to the page, it would not find it.
If you know the container control, use FindControl on that and not on the Page.
If you don't, you could use a a function like this to solve the problem
private Control FindControlRecursive(Control root, string id)
{
if (root.ID == id)
{
return root;
}
foreach (Control c in root.Controls)
{
Control t = FindControlRecursive(c, id);
if (t != null)
{
return t;
}
}
return null;
}
FindControl is not recursive, so you have to make sure you're calling it on the correct container. It doesn't look like the panel is defined at the root based on the null reference.
Try calling FindControl on the parent of the panel

runat="server" div causes System.Web.HttpException when accessed from control collection

I have a div that is runat="server". The div is contained in a panel, and within the div are some controls. All of the controls outside of the div (but within the panel) are cleared when I run my "ClearControlsInPanel()" method, which looks like this:
public static void ClearControlsInPanel(Panel paneltoclear, string[] ignorelist)
{
foreach (Control c1 in paneltoclear.Controls)
{
if (c1 is TextBox)
{
if (!ignorelist.Contains(c1.ID.ToString()))
{
((TextBox)c1).Text = "";
}
}
if (c1 is DropDownList)
{
if (!ignorelist.Contains(c1.ID.ToString()))
{
((DropDownList)c1).SelectedIndex = 0;
}
}
//etc.
}
}
Once the div is reached, I cannot see the controls in it, and thus none of those controls get cleared. Ironically I found a guy who made a post about the exact same thing, Why adding runat=server to a div tag throws an exception of type 'System.Web.HttpException in controls collection? But the thread ends, with no real solution or explanation.
The full exception is:
base {System.Web.UI.HtmlControls.HtmlContainerControl} = {InnerText =
'((System.Web.UI.HtmlControls.HtmlContainerControl)
(((System.Web.UI.HtmlControls.HtmlGenericControl)(paneltoclear.Controls._controls
[165])))).InnerText' threw an exception of type 'System.Web.HttpException'}
Please assume that this must remain a runat server div, and cannot be changed to a panel. (I'm almost certain changing it to a panel will solve it, but we have other requirements that need this to be a runat server div (long story)).
A panel is rendered into a div anyway, so why the div? I see no requirement for it and it solves this issue rather quickly :)
Easier if you post the actual code that accesses the div. i.e. which branch of the if statement actually deals with the generic control case.
I'm guessing the problem is that you are trying to set the innertext property of the div (as the exception suggests).
The trick might be that if the div control has children, then the innertext property doesn't exist, and any text is represented as a literal in the controls array of the div control?
So to clear the child collection try something like...
Also probs better if you do else if vs if
public static void ClearControlsInPanel(ControlCollection controls, string[] ignorelist)
{
foreach (Control c1 in controls)
{
if (c1 is TextBox)
{
if (!ignorelist.Contains(c1.ID.ToString()))
{
((TextBox)c1).Text = "";
}
}
else if (c1 is DropDownList)
{
if (!ignorelist.Contains(c1.ID.ToString()))
{
((DropDownList)c1).SelectedIndex = 0;
}
}
else
{
if (c1.HasControls())
{
ClearControlsInPanel(c1.Controls, ignorelist);
}
}
}
}

ASP.NET How to access a deeply nested user control on the parent page

I have a login control and at is nested 2 deep in a header control
i.e Page --> Header Control --> Login Control. I cannot get a reference to the control on the page using FindControl. I want to be able to set the visible property of the control like
if (_loginControl != null)
_loginControl.Visible = false;
I ended up using a recursive FindControl method to find the nested control.
public static Control FindControlRecursive(Control root, string id)
{
if (root.ID == id)
{
return root;
}
foreach (Control c in root.Controls)
{
Control t = FindControlRecursive(c, id);
if (t != null)
{
return t;
}
}
return null;
}
Are you needing to disable/hide the User Control from the ASP.NET page it resides on (or does the User Control exist on a master page, say)? If it's in the same page, then in your ASP.NET page's code-behind you'd do:
MyUserControlsID.Visible = false
Where MyUserControl is the ID of your User Control. To determine the ID of your User Control look at the markup of your .aspx page and you will see something like this:
<uc1:UserControlName ID="MyUserControlsID" runat="server" ... />
Happy Programming!
A good way would be to use:
Page.FindControl()
if that yields null, the control is not there.
Try calling this.FindControl("_loginControl") or this.Page.FindControl("_loginControl").
See MSDN for method details:
http://msdn.microsoft.com/en-us/library/system.web.ui.control.findcontrol.aspx
The login control, if it's registered in the markup, will also be an instance member of your codebehind page; you can refer to it from the codebehind class as if it were a normal member, using the same name you provided as the ID (I do recommend using codebehinds for most logic, instead of inlining code in the markup, BTW).
You can also use the FindControl() method of your page, which will search its control subtree for a control with a given ID. That takes longer, so I would recommend the first option unless the logic control is added dynamically and you don't always know it's there.
private List<Control> GetAllNestedUserControl(Control ph)
{
List<Control> Get = new List<Control>();
foreach (var control in ph.Controls)
{
if (control is UserControl)
{
UserControl uc = control as UserControl;
if (uc.HasControls())
{
Get = GetAllNestedUserControl(uc);
}
}
else
{
Control c = (Control)control;
if (!(control is LiteralControl))
{
Get.Add(c);
}
}
}
return Get;
}
just call this code from you any parent page and then get any control by the following code
List<Control> Get = GetAllNestedUserControl(ph);
Label l = (Label)Get.Find(o => o.ID == "lblusername");
l.Text = "changed from master";

How to Dynamically get All controls (and it's IDs) in an aspx Page?

HEllo, I need to dynamically activate fields in a page according to the service that is going to be executed...
Let me explain:
There's a page with all the possible fields and a ListBox with all the selected services to be executed, then when the user selects which service to execute (change a car plate, for example), then I need to activate only the field(s) that the service require... (The realationship between Services and Fields are stored in a database).
public void CheckAll(int pService_Id, Control pPage)
{
foreach (Control control in pPage.Controls)
{
busExecutaServico vExecuta = new busExecutaServico();
if (vExecuta.EnableField(control.ID.ToString(), Convert.ToInt32(listBoxServices.SelectedValue)))
{
switch (control.GetType().ToString())
{
case "TextBox":
TextBox controleText = (TextBox)Page.FindControl(control.ID.ToString());
controleText.Enabled = true;
break;
Note that busExecutaServico is the class which contains the method (EnableField) for checking if the selected item matches any field on the database..
I can't seem to get the control.ID.ToString() to work properly (the ID always comes as NULL)
If anyone can help me solve this, or if there's another way (even if it's completely different from what i'm trying), it would be of great help. thanks
I like to use a recursive function for locating controls by either type or ID.
public Control FindControlRecursive(Control rootControl, string controlId)
{
if (rootControl.ID == controlId)
return rootControl;
foreach (Control control in rootControl.Controls)
{
Control foundControl = FindControlRecursive(control, controlId);
if (foundControl != null)
{
return foundControl;
}
}
return null;
}
public Control FindControlRecursive(Control rootControl, Type type)
{
if (rootControl.GetType().Equals(type))
return rootControl;
foreach (Control control in rootControl.Controls)
{
Control foundControl = FindControlRecursive(control, type);
if (foundControl != null)
{
return foundControl;
}
}
return null;
}
You can adapt these to first return a collection of controls, then process them later. Might be easier to keep track of what's happening.
I learned this technique here: http://www.west-wind.com/Weblog/posts/5127.aspx
Be aware that FindControl only searches the current naming container so Page.FindControl will only find controls that are added directly to Page. For example, if you had a repeater control that had the controls you were looking for and it was added to Page, you could find your repeater control via Page.FindControl but it wouldn't find child controls within your repeater, you'd have to recursively perform the FindControl on all container controls in the page.
This might seem a bit strange but it allows you to have controls with the same ID existing on the same page. For example, if you had 10 instances of a user control with textboxes within them called "MyName", you'd really want them to not being over-writing each other's 'MyName' fields!
Your code will come across a null for an ID unless every control has been given an ID.
Also why use:-
TextBox controleText = (TextBox)Page.FindControl(control.ID.ToString());
at all instead of:-
TextBox controleText = (TextBox)control;
and indeed since you only want to change the Enabled property consider:-
((WebControl)control).Enabled = False;
That I suspect will eliminate many case statements.
In your code you don't need to search any control - you already have it in 'control' variable. You even don't need to cast it to TextBox, just to a WebControl, just do this:
...
if (vExecuta.EnableField(control.ID.ToString(), Convert.ToInt32(listBoxServices.SelectedValue)))
((WebControl)control).Enabled = true;
P.S. control.ID is already string, so you should remove any ID.ToString() also.

Finding controls inside nested master pages

I have a master page which is nested 2 levels. It has a master page, and that master page has a master page.
When I stick controls in a ContentPlaceHolder with the name "bcr" - I have to find the controls like so:
Label lblName =(Label)Master.Master.FindControl("bcr").FindControl("bcr").FindControl("Conditional1").FindControl("ctl03").FindControl("lblName");
Am I totally lost? Or is this how it needs to be done?
I am about to work with a MultiView, which is inside of a conditional content control. So if I want to change the view I have to get a reference to that control right? Getting that reference is going to be even nastier! Is there a better way?
Thanks
Finding controls is a pain, and I've been using this method which I got from the CodingHorror blog quite a while ago, with a single modification that returns null if an empty id is passed in.
/// <summary>
/// Recursive FindControl method, to search a control and all child
/// controls for a control with the specified ID.
/// </summary>
/// <returns>Control if found or null</returns>
public static Control FindControlRecursive(Control root, string id)
{
if (id == string.Empty)
return null;
if (root.ID == id)
return root;
foreach (Control c in root.Controls)
{
Control t = FindControlRecursive(c, id);
if (t != null)
{
return t;
}
}
return null;
}
In your case, I think you'd need the following:
Label lblName = (Label) FindControlRecursive(Page, "lblName");
Using this method is generally much more convenient, as you don't need to know exactly where the control resides to find it (assuming you know the ID, of course), though if you have nested controls with the same name, you'll probably get some strange behavior, so that might be something to watch out for.
Firstly, you should know that MasterPages actually sit inside Pages. So much so that a MasterPage's Load event is actually called after your ASPX's Load event.
This means, the Page object is actually the highest control in the control hierarchy.
So, knowing this, the best way to find any control in such a nested environment, is to write a recursive function that loops through every control and child controls until it finds the one you're looking for. in this case, your MasterPages are actually child controls of the main Page control.
You get to the main Page object from inside any control like this:
C#:
this.Page;
VB.NET
Me.Page
I find that usually, the Control's class FindControl() method is pretty useless, as the enviroment is always nested.
Because if this, I've decided to use .NET's 3.5 new Extension features to extend the Control class.
By using the code below (VB.NET), in say, your AppCode folder, all your controls will now peform a recursive find by calling FindByControlID()
Public Module ControlExtensions
<System.Runtime.CompilerServices.Extension()> _
Public Function FindControlByID(ByRef SourceControl As Control, ByRef ControlID As String) As Control
If Not String.IsNullOrEmpty(ControlID) Then
Return FindControlHelper(Of Control)(SourceControl.Controls, ControlID)
Else
Return Nothing
End If
End Function
Private Function FindControlHelper(Of GenericControlType)(ByVal ConCol As ControlCollection, ByRef ControlID As String) As Control
Dim RetControl As Control
For Each Con As Control In ConCol
If ControlID IsNot Nothing Then
If Con.ID = ControlID Then
Return Con
End If
Else
If TypeOf Con Is GenericControlType Then
Return Con
End If
End If
If Con.HasControls Then
If ControlID IsNot Nothing Then
RetControl = FindControlByID(Con, ControlID)
Else
RetControl = FindControlByType(Of GenericControlType)(Con)
End If
If RetControl IsNot Nothing Then
Return RetControl
End If
End If
Next
Return Nothing
End Function
End Module
Although I love recursion, and agree with andy and Mun, one other approach you may want to consider is to have a strongly typed Master page. All you have to do is add one directive in your aspx page.
Instead of accessing a page's control from your master page, consider accessing a control in your master page from the page itself. This approach makes a lot of sense when you have a header label on your master page, and want to set its value from each page that uses the master.
I'm not 100% sure, but I think this would be simpler technique with nested master pages, as you would just point the VirtualPath to the master containing the control you wish to access. It might prove to be tricky though if you want to access two controls, one in each respective master page.
Here is a code that is more generic and works with a custom condition (that can be a lambda expression!)
Call:
Control founded = parent.FindControl(c => c.ID == "youdId", true);
Control extension
public static class ControlExtensions
{
public static Control FindControl(this Control parent, Func<Control, bool> condition, bool recurse)
{
Control founded = null;
Func<Control, bool> search = null;
search = c => c != parent && condition(c) ? (founded = c) != null :
recurse ? c.Controls.FirstOrDefault(search) != null :
(founded = c.Controls.FirstOrDefault(condition)) != null;
search(parent);
return founded;
}
}
I have used the <%# MasterType VirtualPath="~/MyMaster.master" %> method. I have a property in the main master page then in the detail master page other property with the same name calling the main master property and it works fine.
I have this in the main master page
public string MensajeErrorString
{
set
{
if (value != string.Empty)
{
MensajeError.Visible = true;
MensajeError.InnerHtml = value;
}
else
MensajeError.Visible = false;
}
}
this is just a div element that have to show an error message. I would like to use this same property in the pages with the detail master page(this is nested with the main master).
Then in the detail master I have this
public string MensajeErrorString
{
set
{
Master.MensajeErrorString = value;
}
}
Im calling the main master property from the detail master to create the same behavior.
I just got it working perfectly.
In contentpage.aspx, I wrote the following:
If Master.Master.connectsession.IsConnected Then
my coded comes in here
End If

Categories

Resources