Sunday, March 26, 2017

VisualTreeHelper for Xamarin.Forms

I enjoy working with Xamarin.Forms because it allows me to leverage my XAML background for cross-platform technologies. While Xamarin.Forms has come a long way over the last few years, there are a few gaps between WPF/Modern Apps and the Xamarin implementation of XAML.

One of those gaps is the VisualTreeHelper that ships as part of WPF. The VisualTreeHelper is especially useful when developing custom controls, Attached Properties or Behaviors. For example, an Attached Property like Grid.Row can search up the visual tree to find the parent Grid control.

While there is no implementation within Xamarin.Forms, there are some examples that offer partial solutions.

The class hierarchy for Xamarin.Forms gives us a bit of insight about the visual tree. A controls and visual elements share a common base class, Element.

Xamarin.Forms-ElementHierarchy

Here’s my implementation for GetParent<T> and GetChildren<T>:

public static class VisualTreeHelper
{
    public static T GetParent<T>(this Element element) where T : Element
    {
        if (element is T)
        {
            return element as T;
        }
        else
        {
            if (element.Parent != null)
            {
                return element.Parent.GetParent<T>();
            }

            return default(T);
        }
    }
        
    public static IEnumerable<T> GetChildren<T>(this Element element) where T : Element
    {
        var properties = element.GetType().GetRuntimeProperties();

        // try to parse the Content property
        var contentProperty = properties.FirstOrDefault(w => w.Name == "Content");
        if (contentProperty != null)
        {
            var content = contentProperty.GetValue(element) as Element;
            if (content != null)
            {
                if (content is T)
                {
                    yield return content as T;
                }
                foreach (var child in content.GetChildren<T>())
                {
                    yield return child;
                }
            }
        }
        else
        {
            // try to parse the Children property
            var childrenProperty = properties.FirstOrDefault(w => w.Name == "Children");
            if (childrenProperty != null)
            {
                // loop through children
                IEnumerable children = childrenProperty.GetValue(element) as IEnumerable;
                foreach (var child in children)
                {
                    var childVisualElement = child as Element;
                    if (childVisualElement != null)
                    {
                        // return match
                        if (childVisualElement is T)
                        {
                            yield return childVisualElement as T;
                        }

                        // return recursive results of children
                        foreach (var childVisual in childVisualElement.GetChildren<T>())
                        {
                            yield return childVisual;
                        }
                    }
                }
            } 
        }
    }
}

As the VisualTreeHelper is implemented as a static class, it can be used stand-alone or as extension methods on an object:

// from within user control...

Page page = null;
StackLayout stack = this.FindByName<StackLayout>("myStack");

// utility class
page = VisualTreeHelper.GetParent<Page>(stack);

// using extension method
page = stack.GetParent<Page>();

Well, that's it for now. My next post will show how a practical example.

Happy coding.

0 comments: