Find ContentPlaceHolders in Master page - c#

I'm looking for a way to dynamically load a master page in order to get a collection of ContentPlaceHolders within.
I would prefer not to have to load a page object to assign the master page to before I can access it's controls, but if that's the only way I'll be happy to use it. This is the way I was hoping it would work:
Page page = new Page();
page.MasterPageFile = "~/home.master";
foreach (Control control in page.Master.Controls)
{
if (control.GetType() == typeof(ContentPlaceHolder))
{
// add placeholder id to collection
}
}
But page.Master throws a null reference exception. It only seems to load at some point when an actual page has been created in the page lifecycle.
I even thought of dynamically changing the current page's MasterPageFile on Page_Init(), reading all ContentPlaceHolders then assigning the original MasterPageFile back, but that would be horrible!
Is there a way to load a master page into memory independent of an actual page so that I can access properties of it?
My final resort will probably involve parsing the master page contents for ContentPlaceHolders instead, which isn't as elegant but might be a bit faster.
Anyone able to help please? Many thanks.

You should be able to use LoadControl to load the master page an enumerate the Controls collection.
var site1Master = LoadControl("Site1.Master");
To find the Content Controls you will need to recursively search the Controls collection. Here is a quick and dirty example.
static class WebHelper
{
public static IList<T> FindControlsByType<T>(Control root)
where T : Control
{
List<T> controls = new List<T>();
FindControlsByType<T>(root, controls);
return controls;
}
private static void FindControlsByType<T>(Control root, IList<T> controls)
where T : Control
{
foreach (Control control in root.Controls)
{
if (control is T)
{
controls.Add(control as T);
}
if (control.Controls.Count > 0)
{
FindControlsByType<T>(control, controls);
}
}
}
}
The above can be used as follows
// Load the Master Page
var site1Master = LoadControl("Site1.Master");
// Find the list of ContentPlaceHolder controls
var controls = WebHelper.FindControlsByType<ContentPlaceHolder>(site1Master);
// Do something with each control that was found
foreach (var control in controls)
{
Response.Write(control.ClientID);
Response.Write("<br />");
}

Related

How do I access controls from the parent page from within an ASP.NET User Control?

I have an ASP.NET Web Forms page childPage.aspx with masterPage.aspx as the master page. The childPage.aspx has a user control (userControl.ascx) control defined on it. Now, I am trying to access the controls on childPage.aspx from within the user control. I have tried a handful of different approaches:
HtmlContainerControl ProductMenu = (HtmlContainerControl)Page.FindControl("ProductMenu");
HtmlContainerControl ProductMenu = (HtmlContainerControl)this.Page.FindControl("ProductMenu");
HtmlContainerControl ProductMenu = (HtmlContainerControl)Parent.FindControl("ProductMenu");
HtmlContainerControl ProductMenu = (HtmlContainerControl)this.Parent.parent.FindControl("ContaintHolder").FindControl("ProductMenu")
In above code, ProductMenu is the id of the <div runat="server" /> on childPage.aspx. Now, I am trying to access it from within my user control, but that fails to return the div.
Please help me out. How should I do this? Thanks in advance.
The reason this doesn't work is likely because the FindControl() method is not recursive. This is called out in the MSDN documentation:
This method will find a control only if the control is directly contained by the specified container; that is, the method does not search throughout a hierarchy of controls within controls.
So, for instance, Page.FindControls() will only search for controls listed in the Page.Controls collection; it won't search the Controls collection of each of those controls. As such, Page.FindControl() would only work if the ProductMenu were at the top-level of your ASPX page; if it is instead nested within, for instance, a Panel control then this code won't work.
To resolve this, you'll need to write a recursive function to crawl the control tree. For instance:
public Control FindControl(Control parentControl, string controlName) {
foreach (var childControl in parentControl.Controls) {
if (childControl.Id == controlName) return childControl;
var foundControl = FindControl(childControl, controlName);
if (foundControl != null) return childControl;
}
return null;
}
In your case, assuming you'll always be looking for an instance of an HtmlContainerControl, you could even validate the type and return a strongly typed object, should you choose. That said, if you want to keep it strongly typed while still supporting different types, you could instead use a generic:
public T FindControl<T>(Control parentControl, string controlName) where T : Control {
foreach (var childControl in parentControl.Controls) {
if (childControl.Id == controlName) return childControl;
var foundControl = FindControl<T>(childControl, controlName);
if (foundControl != null && foundControl is T) return childControl;
}
return null;
}
In addition, if you'll need to do this repeatedly, you might add this as an extension method to the Page class so it's easily accessible on multiple pages.

Finding control on pages that inherits from base page

In my project all of the .aspx pages inherit from a custom base class page which in turn inherits from System.Web.UI.Page.
I want to find the controls in current page. for that one I am using
foreach loop
foreach(control c in page.controls)
for this I am unable to cast my current page to system.web.ui.page.
how to cast the current page to system system.web.ui.page?
Please note that you are dealing with a control-tree, so you might want to have a recursion here. Following method should help (Linq with a recursive call):
private static IEnumerable<Control> FlattenControlTree<TFilterType>(Control control)
{
if (control is TFilterType)
{
yield return control;
}
foreach (Control contr in control.Controls.Cast<Control>().SelectMany((c) => FlattenControlTree<TFilterType>(c)))
{
if (contr is TFilterType)
{
yield return contr;
}
}
}
By the end of the day, you only need to call:
var controls = FlattenControlTree<YourBaseType>(this.Page);
Please note that this kind of recursion is not very effective when it comes to big trees.
You may try this;
foreach(Control c in ((System.Web.UI.Page)this.Page).Controls)

Problem looping through web controls

I've got a web page where I am dynamically creating controls during Page_Load event (this is done so because I do not know how many controls I will need until session is active and certain variables are accessible)
I need to be able to loop through these controls to find Checkbox when a button click is processed. Looping through the Form.Controls does not appear to be sufficient. I would think that Request.Form might work but it does not appear to be accessible in my C# block?
What should code for Request.Form look like? OR
Has anyone done this before with dynamically created controls?
Any insight is appreciated.
Simplified Example from MSDN:
var myControl = FindControl("NameOfControl");
if(myControl != null)
{
//do something
}
else
{
//control not found
}
Hope this helps! ;)
Your controls will be accessible trough the Controls collection of their immediate parent. Unless you add them like Page.Form.Controls.Add (myControl);, you won't find it in Page.Form.Conttrols. If you add them to a place holder, you must find them in thePlaceHolder.Controls.
LinkButton myDynamicLinkButton = new myDynamicLinkButton ();
myDynamicLinkButton.ID = "lnkButton";
myPlaceHolder.Controls.Add (myDynamicLinkButton);
//........
LinkButton otherReferenceToMyLinkButton = myPlaceHolder.FindControl ("lnkButton");
As #David said in his comment, you should probably think about using a Repeater instead. It would probably simplify your case a lot.
Since the controls might be nested in other controls, you need to search recursively. You can use this method to find the control:
public 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;
}
And you can implement it this way:
CheckBox check = FindControlRecursive(Page.Form, "CheckBox1");
You should have access to Request["xyz"] anywhere in your aspx.cs code. You can either find control as described above and read it's value or do so directly from Request using the Control.UniqueID property. For example if it's a checkbox that's within the repeater then the UniqueID would look like dtgData$ctl02$txtAmount
Thanks for the insight guys. I kind of took the discussion and ran with it and found my solution that worked best for me.
foreach(String chk in Request.Form)
{
if (chk.Contains("chkRemove"))
{
int idxFormat = chk.LastIndexOf("chkRemove");
objectname = chk.Substring(idxFormat);
}
}
Turned out really all I needed was the name. The string contained a number at the end which was needed to determine a position of datatable items. Thanks for the advice!

Actual Element That Lost Focus

I am working on an application that has a GridView item on an ASP.net page which is dynamically generated and does a partial post-back as items are updated within the grid-view. This partial post-back is causing the tab indices to be lost or at the very least ignored as the tab order appears to restart. The grid view itself already has the pre-render that is being caught to calculate the new values from the modified items in the grid-view. Is there a way to get what element had the focus of the page prior to the pre-render call? The sender object is the grid-view itself.
You can try using this function, which will return the control that caused the postback. With this, you should be able to reselect it, or find the next tab index.
private Control GetControlThatCausedPostBack(Page page)
{
//initialize a control and set it to null
Control ctrl = null;
//get the event target name and find the control
string ctrlName = Page.Request.Params.Get("__EVENTTARGET");
if (!String.IsNullOrEmpty(ctrlName))
ctrl = page.FindControl(ctrlName);
//return the control to the calling method
return ctrl;
}
Here's an instance where I had dynamically generated inputs that updated totals via AJAX on change. I used this code to determine the next tab index, based on the tab index of the control that caused the postback. Obviously, this code is tailored to my usage, but with some adjustments I think it could work for you as well.
int currentTabIndex = 1;
WebControl postBackCtrl = (WebControl)GetControlThatCausedPostBack(Page);
foreach (PlaceHolder plcHolderCtrl in pnlWorkOrderActuals.Controls.OfType<PlaceHolder>())
{
foreach (GuardActualHours entryCtrl in plcHolderCtrl.Controls.OfType<GuardActualHours>())
{
foreach (Control childCtrl in entryCtrl.Controls.OfType<Panel>())
{
if (childCtrl.Visible)
{
foreach (RadDateInput dateInput in childCtrl.Controls.OfType<RadDateInput>())
{
dateInput.TabIndex = (short)currentTabIndex;
if (postBackCtrl != null)
{
if (dateInput.TabIndex == postBackCtrl.TabIndex + 1)
dateInput.Focus();
}
currentTabIndex++;
}
}
}
}
}

Trouble with FindControl and dynamicly created controls

Example code:
var div = new HtmlGenericControl("div");
div.Controls.Add(new Literal() { ID = "litSomeLit" });
var lit = (Literal)div.FindControl("litSomeLit");
Assert.IsNotNull(lit);
This code fails the assert, because lit is null. Debugging shows that div.Controls definitely contains a literal with ID of "litSomeLit." My questions are "Why?" and "Is there any way to get a control of a specific ID without doing a recursive search of div.Controls[] by hand one element at a time?"
The reason I'm doing things this way is that my actual application is not so straightforward- a method I'm writing is given a complex control with several subcontrols in a number of possible configurations. I need to access a specific control several layers down (eg, the control with ID "txtSpecificControl" might be at StartingControl.Controls[0].Controls[2].Controls[1].Controls[3]). Normally I could just do FindControl("txtSpecificControl"), but that does not seem to work when the controls were just dynamically created (as in the above example code).
Near as I can tell, there is no way to do what I'm trying to accomplish without adding the control to the page. If I had to guess, I'd say that FindControl uses the UniqueID property of the control, which generally contains the IDs of all the controls above the current one (eg OuterControlID$LowerControlId$TargetControlID). That would only get generated when the control actually gets added to the page.
Anyway, here's an implementation of recursive depth-first-search FindControl that'll work when the control is not attached to the page yet:
public static Control FindControl(Control parent, string id)
{
foreach (Control control in parent.Controls)
{
if (control.ID == id)
{
return control;
}
var childResult = FindControl(control, id);
if (childResult != null)
{
return childResult;
}
}
return null;
}
Change your code to
var div = new HtmlGenericControl("div");
Page.Controls.Add(div);
div.Controls.Add(new Literal() { ID = "litSomeLit" });
var lit = (Literal)div.FindControl("litSomeLit");
As far as i know FindControl only works when the control is in the visual tree of the page.
When you confirmed that the control was in the Controls collection, did you do that by inspecting the collection directly? FindControl() may not work in this context.
When you debug the test, is the var lit null? If so, you may have to access the member by item index instead of using the FindControl() method.

Categories