Find control's ancestor by type

2.4k views Asked by At

I have a UserControl 'child' within another UserControl (that's acting as a TabItem in a TabControl). Between the child UserControl and the TabItem ancestor are a number of other controls (eg: Grids, a StackPanel, possibly a ScrollViewer, etc).

I want to access a property of the TabItem UserControl in my child UserControl and customised a commonly suggested recursive function that walks up the Visual tree. However, this always returned true at the first null check until I added a query on the Logical tree.

Code:

public MyTabItem FindParentTabItem(DependencyObject child)
{
  DependencyObject parent = VisualTreeHelper.GetParent(child) ?? LogicalTreeHelper.GetParent(child);

  // are we at the top of the tree
  if (parent == null)
  {
      return null;
  }
  MyTabItem parentTabItem = parent as MyTabItem;
  if (parentTabItem != null)
  {
    return parentTabItem;
  }
  else
  {
    //use recursion until it reaches the control
    return FindParentTabItem(parent);
  }
}

Unfortunately, this too returns null. When stepping through the method, I see it does find the correct UserControl TabItem, but then as it recurses(?) back through the returns, it reverts this back to null which is then returned to the calling method (in the child UserControl's Loaded event):

MyTabItem tab = FindParentTabItem(this);

How do I fix this so my method correctly returns the found MyTabItem?

1

There are 1 answers

2
Blacktempel On

Here's a working Unit-Tested solution.

public static T FindAncestor<T>(DependencyObject obj)
    where T : DependencyObject
{
    if (obj != null)
    {
        var dependObj = obj;
        do
        {
            dependObj = GetParent(dependObj);
            if (dependObj is T)
                return dependObj as T;
        }
        while (dependObj != null);
    }

    return null;
}

public static DependencyObject GetParent(DependencyObject obj)
{
    if (obj == null)
        return null;
    if (obj is ContentElement)
    {
        var parent = ContentOperations.GetParent(obj as ContentElement);
        if (parent != null)
            return parent;
        if (obj is FrameworkContentElement)
            return (obj as FrameworkContentElement).Parent;
        return null;
    }

    return VisualTreeHelper.GetParent(obj);
}

Usage would be

FindAncestor<MyTabItemType>(someChild);

Edit:

Let's assume your xaml looks like what you describe it as:

<UserControl>
    <Grid></Grid>
    <StackPanel></StackPanel>
    <!-- Probably also something around your child -->
    <Grid>
        <UserControl x:Name="child"/>
    </Grid>
</UserControl>

You're currently in your child-xaml.cs

void OnChildUserControlLoaded(object sender, RoutedEventArgs e)
{
    var parent = FindAncestor<ParentUserControlType>(this);
    DoSomething(parent.SomeProperty);
}

Unless you do something you did not describe the code will work as is.
I suggest you provide a MCVE with all necessary information.