Find control's ancestor by type
Asked Answered
J

1

1

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?

Jewelfish answered 8/11, 2018 at 5:17 Comment(2)
A UserControl shouldn't directly access a property of one of its parent elements. Why don't you simply use a view model and bind the relevant control properties?Privacy
@Privacy that's what i'm in the process of doing, but time constraints mean I'm doing it bit by bit. After changing another area to use a ViewModel and do mvvm properly, this bit broke. I need a quick (make it work) fix before dealing with this properly.Jewelfish
L
5

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.

Lordship answered 8/11, 2018 at 5:49 Comment(2)
This returns null at first GetParent() callJewelfish
I meant to describe it as: <UserControl><Grid><StackPanel><MyChildControl /></StackPanel></Grid></UserControl> (by 'between' I meant wrapped around), but never mind, I've worked out a different way to get to it by starting at the MainWindow and working down (instead of up from my child control) cheers & thanks.Jewelfish

© 2022 - 2024 — McMap. All rights reserved.