Monday, October 27, 2008

Silverlight and the Logical Tree

Those of you familiar with WPF may experience some disorientation when acclimating to Silverlight.  Although Silverlight has certain new and exciting capabilities currently lacking from WPF there are certain features missing  from Silverlight which you have come to know and love.  One of those features is the logical tree.  The official word from Microsoft is that the logical tree is not necessary in Silverlight because it does not support ElementName bindings.  That said there is a trick you can use if you would like to figure out if an element is the logical ancestor or descendent of another element: confirm that the other object is in the same name scope.  You can always find out if two elements are in the same name scope by calling the FindName method on one and passing in the name of the other object.

element.FindName(otherElement.Name) == otherElement

But what if an object does not have a name?  Well the answer is simple: you give it one.  It is relatively easy to ensure that each object has a unique name by creating a GUID.  Although it is unpleasant to assign names to elements in the visualtree for no other purpose than to determine if they are logically related to other elements it is currently the only way of discerning the information that I'm aware of.  Of course better ideas are always welcome. :-)

Using this trick we can write a logical tree helper class like the one available in WPF.  However instead of using the same API as WPF let's change it a little to make it more Linq friendly.  First we'll write  a GetVisualChildren function that returns the visual children as a sequence.

 

/// <summary> /// Retrieves all the visual children of a framework element. /// </summary> /// <param name="parent">The parent framework element.</param> /// <returns>The visual children of the framework element.</returns> internal static IEnumerable<DependencyObject> GetVisualChildren(this DependencyObject parent) { Debug.Assert(parent != null, "The parent cannot be null."); int childCount = VisualTreeHelper.GetChildrenCount(parent); for (int counter = 0; counter < childCount; counter++) { yield return VisualTreeHelper.GetChild(parent, counter); } }

That was easy.  Now we can run Linq queries on the visual children of an element.  Now let's write a function that recurses down the tree depth-first and grab every immediate descendent that is in the same name scope of the element.  In the interest of speed we'll manage the stack ourselves.

public static class LogicalTreeHelper { /// <summary> /// Retrieves all the logical children of a framework element using a /// depth-first search. A visual element is assumed to be a logical /// child of another visual element if they are in the same namescope. /// For performance reasons this method manually manages the stack /// instead of using recursion. /// </summary> /// <param name="parent">The parent framework element.</param> /// <returns>The logical children of the framework element.</returns> internal static IEnumerable<FrameworkElement> GetLogicalChildren(this FrameworkElement parent) { Debug.Assert(parent != null, "The parent cannot be null."); EnsureName(parent); string parentName = parent.Name; Stack<FrameworkElement> stack = new Stack<FrameworkElement>(parent.GetVisualChildren().OfType<FrameworkElement>()); while (stack.Count > 0) { FrameworkElement element = stack.Pop(); if (element.FindName(parentName) == parent) { yield return element; } else { foreach (FrameworkElement visualChild in element.GetVisualChildren().OfType<FrameworkElement>()) { stack.Push(visualChild); } } } } }

The EnsureName function just checks to see whether an element has a name and names it if it does not. There you have it: a LogicalTreeHelper for Silverlight. 

4 comments:

Anonymous said...

Could you guys please, please, just add a real logical tree? And don't forget Freezables too :)

Anonymous said...

Could you provide a small sample app in Silverlight?

Anonymous said...

Good stuff. One minor thing though; the way the stack is populated causes the logical childs to be returned in reversed order.

Anonymous said...

情趣用品|情趣用品|情趣用品|情趣|情趣用品|情趣

About Me

My photo
I'm a software developer who started programming at age 16 and never saw any reason to stop. I'm working on the Presentation Platform Controls team at Microsoft. My primary interests are functional programming, and Rich Internet Applications.