Thursday, November 19, 2009

Silverlight - Get the top-level UserControl root element using two different approaches

I recently found a situation where I needed to get the root element of a Silverlight control. Unfortunately, Silverlight controls only have a Parent member and not a Top or Root member. If your control is embedded in multiple controls, you cannot easily get the root and will probably end up writing some ugly casting code that looks like this.

DO NOT USE THIS CODE...KEEP READING!

private UserControl GetParent()
{
return (UserControl)((Grid)((Border)((StackPanel)((UserControl)((Grid)((StackPanel)this.Parent).Parent).Parent).Parent).Parent).Parent).Parent;
}

The problem with this code (besides being ugly) is that if you ever change the containing hierarchy, you’re going to end up having to update this method. After finding a couple of different posts that didn’t work for me, I finally came up with two possible solutions:

Solution 1 – Implement a recursive lookup that finds the top parent and returns it as a UserControl.

1.) Add a method extensions that returns the parent UserControl.

public static class SilverlightExtensions
{
public static UserControl GetRoot(this FrameworkElement child)
{
var parent = child.Parent as FrameworkElement;
if (parent == null)
if (child is UserControl)
{
return (UserControl)child;
}
else
{
throw new Exception("The root element is an unexpected type. The root element should be a UserControl.");
}
return parent.GetRoot();
}}

2.) Access the parent by using the method extension:

this.GetRoot().Resources["SampleText"] = "x y z";
((TextBlock)this.GetRoot().FindName("TitlePage")).Text = "New Title Page";

Solution 2 – Add a property to the child control that references the parent. (Recommended)

This is less expensive than a recursive loop, but you do have to always remember step 1 or else it will not work.

1.) Add a MyParent property of type UserControl in the child control’s class.

public UserControl MyParent { get; set; }
2.) In the parent control’s constructor, add a line that populates the child control’s MyParent property with itself.

public ParentUserControl()
{
InitializeComponent();

((ChildUserControl)this.FindName("childControl")).MyParent = (UserControl)this;
}
3.) Access the parent root using the MyParent property.

MyParent.Resources["SampleText"] = "x y z";
((TextBlock)MyParent.FindName("TitlePage")).Text = "New Title Page";

Note: You should be able to take a similar approach with WPF. One other alternative is to raise an event to the parent UserControl. Unfortunately, I haven’t seen any easy or clear-cut examples on how to do this.

3 comments:

  1. Both ways will not work if I want to find parent for items in stack panel or other itemsource. When I have DataTemplate for item in StackPanel parent of root grid for datatemplate will be null in runtime :(.

    ReplyDelete
  2. John. Live rock.'ll Tech

    ReplyDelete
  3. Here's a solution that may be even easier. In the child control, set the 'Tag' property equal to the parent.

    1) First, give the parent control a name in XAML. e.g. <UserControl x:Name="MainPageControl"....

    2) Then, in the child control, set Tag="{Binding ElementName=MainPageControl}"

    Then, you can get a reference to the parent just by using the 'Tag' property of the child control.

    ReplyDelete