VisualTreeHelper

I seem to have gotten away from posting any coding things on this blog.   At first I thought its because I really haven’t done anything exciting; most of the work has been “figure out customer’s need, create something that solves that need”.   Then as I dug deeper, I rediscovered some fun things that I had created along the way:

The Problem

There’s a screen with a list of grids (this is a stock trading app) – when tabbing out of one grid, it needed to get to the next grid.  I also needed to jump left and right from master to detail easily.   (Sorry about all the blurring, but those were real numbers):

image

The screen, in and of itself, was a solution to “how to balance a portfolio to get it back in line with sector totals easily”.   The user could enter a proposed change in a holding, see how the math worked, and then click save positions to generate all the trade(s) necessary to make it happen.

Solution

I needed to write code that would take the grid that was currently focused, figure out what the next grid was, and focus there.    when solved, it looks like this:

image

Oy! What is this?  It’s a helper function that I wrote.   Basically, Starting with a known visual element, I look for “stuff that I’m interested in” (if the lambda returns true, then I’m interested in it):

image

This cuts down the (very) complicated visual control tree (see: http://www.codeproject.com/Articles/21495/Understanding-the-Visual-Tree-and-Logical-Tree-in) down into something much more manageable (this is the output of the Dump() function defined below with some additional comments thrown in)

image

Here’s the source that does it:

        public static VisualTreeInterestingNode GetInterestingNodes(this FrameworkElement topDependencyObject, Func<FrameworkElement, bool> isInteresting)
        {
            var timer = new Stopwatch();
            timer.Start(); 

            var topInterestingNode = new VisualTreeInterestingNode() { FrameworkElement = topDependencyObject, Parent = null };

            var nodesToVisit = new LinkedList<Tuple<DependencyObject, VisualTreeInterestingNode>>();
            nodesToVisit.AddLast(new Tuple<DependencyObject, VisualTreeInterestingNode>(topDependencyObject, topInterestingNode));

            while (nodesToVisit.Any())
            {
                var a = nodesToVisit.First();
                nodesToVisit.RemoveFirst();

                var currentDependencyObject = a.Item1;
                var currentInterestingNode = a.Item2;

                int childCount = VisualTreeHelper.GetChildrenCount(currentDependencyObject);
                for (int i = 0; i < childCount; i++)
                {
                    var childDependencyObject = VisualTreeHelper.GetChild(currentDependencyObject, i);
                    if (childDependencyObject == null) continue;

                    var childFrameworkElement = childDependencyObject as FrameworkElement;

                    if (childFrameworkElement != null)
                    {
                        if (isInteresting(childFrameworkElement))
                        {
                            var b = new VisualTreeInterestingNode() { FrameworkElement = childFrameworkElement, Parent = currentInterestingNode };
                            currentInterestingNode.InterestingChildNodes.Add(b);
                            nodesToVisit.AddLast(new Tuple<DependencyObject, VisualTreeInterestingNode>(childDependencyObject, b));
                        }
                        else
                        {
                            nodesToVisit.AddLast(new Tuple<DependencyObject, VisualTreeInterestingNode>(childDependencyObject, currentInterestingNode));
                        }
                    }
                    else
                    {
                        nodesToVisit.AddLast(new Tuple<DependencyObject, VisualTreeInterestingNode>(childDependencyObject, currentInterestingNode));
                    }
                }
            }

            timer.Stop(); 

            return topInterestingNode;
        }

And the VisualTreeNode class that the result is in:

    public class VisualTreeInterestingNode
    {
        private List<VisualTreeInterestingNode> _all;

        public VisualTreeInterestingNode()
        {
            InterestingChildNodes = new List<VisualTreeInterestingNode>();
        }
        public FrameworkElement FrameworkElement { get; set; }
        public List<VisualTreeInterestingNode> InterestingChildNodes { get; set; }
        public VisualTreeInterestingNode Parent { get; set; }

        public List<VisualTreeInterestingNode> Flatten
        {
            get
            {
                if (_all != null) return _all;

                _all = new List<VisualTreeInterestingNode> { this };
                foreach (var child in InterestingChildNodes)
                    _all.AddRange(child.Flatten);

                return _all;
            }
        }

        public StringBuilder Dump(StringBuilder prefix = null, StringBuilder result = null)
        {
            if (prefix == null) prefix = new StringBuilder();
            if (result == null) result = new StringBuilder();

            result.Append(prefix).Append(FrameworkElement.GetType().Name);
            result.Append(" ").Append(FrameworkElement.Name);
            result.AppendLine();

            var l = prefix.Length;
            prefix.Append("  ");
            foreach (var node in InterestingChildNodes)
            {
                node.Dump(prefix, result);
            }
            prefix.Length = l;
            return result;
        }
    }

Leave a Reply

Your email address will not be published. Required fields are marked *