DotnetDude
DotnetDude

Reputation: 11817

Accessing parent control from child control - ASP.NET C#

I have a parent user control with a label. On the parent's OnInit, I dynamically load the child control. From the child control, I will need to set the parent's label to something.

Using the Parent property returns the immediate parent which is actually a PlaceHolder in my case. Theoretically, I can recursively loop to get the reference to the Parent User control. Am I heading in the right direction here? Is there a straightforward way of doing this?

Upvotes: 13

Views: 45355

Answers (10)

Mariano Desanze
Mariano Desanze

Reputation: 8161

Similar to teebot's solution, but returning null instead of NullReferenceException, easier to use, and reusable in other contexts:

public static class Extensions
{
    public static IEnumerable<Control> GetAncestors(this Control control)
    {
        if (control == null)
            yield break;
        while ((control = control.Parent) != null)
            yield return control;
    }
}

Sample use cases and comparison:

class Control { public string Name; public Control Parent; }
class Control2 : Control { public string Prop2; }

static class Program
{
    public static Control GetParentOfType(this Control childControl,
                                          Type parentType)
    {
        Control parent = childControl.Parent;
        while(parent.GetType() != parentType) // throws NullReferenceException when "No control of expected type was found" (due to "parent" being null)
        {
            parent = parent.Parent;
        }
        if(parent.GetType() == parentType)
            return parent;

        throw new Exception("No control of expected type was found"); // this line is dead code as null reference throws before this
    }
    public static IEnumerable<Control> GetAncestors(this Control control)
    {
        if (control == null)
            yield break;
        while ((control = control.Parent) != null)
            yield return control;
    }

    static void Main()
    {
        var a = new Control { Name = "A" };
        var b = new Control2 { Name = "B", Parent = a, Prop2 = "B is OK!" };
        var c = new Control { Name = "C", Parent = b };
        var d = new Control { Name = "D",  Parent = c };

        // teebot's 
        var found = d.GetParentOfType(typeof(Control2));
        ((Control2)found).Prop2.Dump(); // properly returns "B is OK!", but needs 2 lines to be clear, and casting to the same type already defined in the line above
        try { b.GetParentOfType(typeof(Control2));
        } catch (Exception e) { e.GetType().Name.Dump(); } // NullReferenceException

        // mine
        d.GetAncestors().OfType<Control2>().First().Prop2.Dump(); // properly returns "B is OK!" (while "yield" and "First()" avoids wasting time looping unneeded ancestors, e.g. "A")
        b.GetAncestors().OfType<Control2>().FirstOrDefault().Dump(); // doesn't throw, just returns null
        d.GetAncestors().Take(2).Select(x => x.Name).ToList().Dump(); // returns a filtered list (instead of a single element) without changing GetAncestors nor wasting performance
    }
}

Code above runs on LinqPad (otherwise replace the .Dump() calls with Console.WriteLine(...))

Upvotes: 0

Art
Art

Reputation: 139

@Rex M has a good and easy solution for this and just to expand on it to show the usage:

This code snippet is used from within the child user control to access parent user control property:

((MyParentUserControlTypeName)NamingContainer).Property1 = "Hello";

Upvotes: 2

Aridane &#193;lamo
Aridane &#193;lamo

Reputation: 334

To me the right way to do this is by exposing an add method in the control. Now if you need to update a label outside it, expose an event, something like OnCollectionChanged(...) and suscribe from the control that will need to show info about the collection.

This way each control does it's part and all stays SOLID

Upvotes: 1

teebot
teebot

Reputation: 1100

Or you could iterate through the parents until you find the desired control, such as with an extension method.

public static Control GetParentOfType(this Control childControl,
                                   Type parentType)
  {
      Control parent = childControl.Parent;
      while(parent.GetType() != parentType)
      {
          parent = parent.Parent;
      }
      if(parent.GetType() == parentType)
            return parent;

     throw new Exception("No control of expected type was found");
  }

More details about this method here: http://www.teebot.be/2009/08/extension-method-to-get-controls-parent.html

Upvotes: 4

andleer
andleer

Reputation: 22578

You best bet is to wait until page_load completes and then recursively search on Page.Controls.

Here are some extension methods that will help you accompish that:

var control = Page.GetControl(MyControlID);    

public static class ControlExtensions
    {
        public static IEnumerable<Control> Flatten(this ControlCollection controls)
        {
            List<Control> list = new List<Control>();
            controls.Traverse(c => list.Add(c));
            return list;
        }

        public static IEnumerable<Control> Flatten(this ControlCollection controls, Func<Control, bool> predicate)
        {
            List<Control> list = new List<Control>();
            controls.Traverse(c => { if (predicate(c)) list.Add(c); });
            return list;
        }

        public static void Traverse(this ControlCollection controls, Action<Control> action)
        {
            foreach (Control control in controls)
            {
                action(control);
                if (control.HasControls())
                {
                    control.Controls.Traverse(action);
                }
            }
        }

        public static Control GetControl(this Control control, string id)
        {
            return control.Controls.Flatten(c => c.ID == id).SingleOrDefault();
        }

        public static IEnumerable<Control> GetControls(this Control control)
        {
            return control.Controls.Flatten();
        }

        public static IEnumerable<Control> GetControls(this Control control, Func<Control, bool> predicate)
        {
            return control.Controls.Flatten(predicate);
        }
    }

Upvotes: 0

bendewey
bendewey

Reputation: 40265

If your creating the UserControl via code, why not pass in the strongly typed parent to the constructor.

public class MyUserControl1 : UserControl
{
  public void Init(...)
  {
    var uc2 = new MyUserControl2(this);
  }
}

public class MyUserControl2 : UserControl
{
  private MyUserControl1 parentUserControl;

  public MyUserControl2(MyUserControl1 parent)
  {
    this.parentUserControl = parent;
  }
}

Now this is tightly coupled and could cause you issues later, but for this scenario it could work.

Upvotes: 0

Jonas
Jonas

Reputation: 4584

There's a few different ways you could go...one would be to add a Parent property to your Child class...and then do:

// in the context of parent's loading of child:
child.ParentObject = self;

I'm sure somebody will come back and say this violates some best practice or another...but shrug. You could use events too, if you wanted to maintain some separation.

Upvotes: 0

Quintin Robinson
Quintin Robinson

Reputation: 82375

You could pass a reference of the parent to the child and expose a method on the parent to set the label, although this would very tightly couple the objects. Otherwise you could expose a property on the child that the parent could then check and set it's own label.

Upvotes: 0

AndreasN
AndreasN

Reputation: 2897

There is a FindControl method, but its not recursive if i remember correctly. Also you're not guarantied that all the control hierarchies exist on page_init, wait til page_load before accessing the controls. Init is for creating them.

Upvotes: 0

Rex M
Rex M

Reputation: 144202

Try getting the child's NamingContainer.

Upvotes: 10

Related Questions