using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Windows; using System.Windows.Media; namespace Phone7.Fx.Preview { public static class VisualTreeHelperExtensions { /// /// Equivalent of FindName, but works on the visual tree to go through templates, etc. /// /// The node to search from /// The name to look for /// The found node, or null if not found public static FrameworkElement FindVisualChild(this FrameworkElement root, string name) { FrameworkElement temp = root.FindName(name) as FrameworkElement; if (temp != null) return temp; foreach (FrameworkElement element in root.GetVisualChildren()) { temp = element.FindName(name) as FrameworkElement; if (temp != null) return temp; } return null; } /// /// Gets the visual parent of the element /// /// The element to check /// The visual parent public static FrameworkElement GetVisualParent(this FrameworkElement node) { return VisualTreeHelper.GetParent(node) as FrameworkElement; } /// /// Gets a visual child of the element /// /// The element to check /// The index of the child /// The found child public static FrameworkElement GetVisualChild(this FrameworkElement node, int index) { return VisualTreeHelper.GetChild(node, index) as FrameworkElement; } /// /// Gets all the visual children of the element /// /// The element to get children of /// An enumerator of the children public static IEnumerable GetVisualChildren(this FrameworkElement root) { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++) yield return VisualTreeHelper.GetChild(root, i) as FrameworkElement; } /// /// Gets the ancestors of the element, up to the root /// /// The element to start from /// An enumerator of the ancestors public static IEnumerable GetVisualAncestors(this FrameworkElement node) { FrameworkElement parent = node.GetVisualParent(); while (parent != null) { yield return parent; parent = parent.GetVisualParent(); } } /// /// Gets the VisualStateGroup with the given name, looking up the visual tree /// /// Element to start from /// Name of the group to look for /// Whether or not to look up the tree /// The group, if found public static VisualStateGroup GetVisualStateGroup(this FrameworkElement root, string groupName, bool searchAncestors) { IList groups = VisualStateManager.GetVisualStateGroups(root); foreach (object o in groups) { VisualStateGroup group = o as VisualStateGroup; if (group != null && group.Name == groupName) return group; } if (searchAncestors) { FrameworkElement parent = root.GetVisualParent(); if (parent != null) return parent.GetVisualStateGroup(groupName, true); } return null; } /// /// Finds the VisualStateGroup with the given name /// /// The root. /// The name. /// public static VisualStateGroup FindVisualState(this FrameworkElement root, string name) { if (root == null) return null; IList groups = VisualStateManager.GetVisualStateGroups(root); return groups.Cast().FirstOrDefault(group => group.Name == name); } /// /// Performs a breadth-first enumeration of all the descendents in the tree /// /// The root node /// An enumerator of all the children public static IEnumerable GetVisualDescendents(this FrameworkElement root) { Queue> toDo = new Queue>(); toDo.Enqueue(root.GetVisualChildren()); while (toDo.Count > 0) { IEnumerable children = toDo.Dequeue(); foreach (FrameworkElement child in children) { yield return child; toDo.Enqueue(child.GetVisualChildren()); } } } /// /// Provides a debug string that represents the visual child tree /// /// The root node /// StringBuilder into which the text is appended /// This method only works in DEBUG mode [Conditional("DEBUG")] public static void GetVisualChildTreeDebugText(this FrameworkElement root, StringBuilder result) { List results = new List(); root.GetChildTree("", " ", results); foreach (string s in results) result.AppendLine(s); } private static void GetChildTree(this FrameworkElement root, string prefix, string addPrefix, List results) { string thisElement = ""; if (String.IsNullOrEmpty(root.Name)) thisElement = "[Anonymous]"; else thisElement = string.Format("[{0}]", root.Name); thisElement += string.Format(" : {0}", root.GetType().Name); results.Add(prefix + thisElement); foreach (FrameworkElement directChild in root.GetVisualChildren()) { directChild.GetChildTree(prefix + addPrefix, addPrefix, results); } } /// /// Provides a debug string that represents the visual child tree /// /// The root node /// StringBuilder into which the text is appended /// This method only works in DEBUG mode [Conditional("DEBUG")] public static void GetAncestorVisualTreeDebugText(this FrameworkElement node, StringBuilder result) { List tree = new List(); node.GetAncestorVisualTree(tree); string prefix = ""; foreach (string s in tree) { result.AppendLine(prefix + s); prefix = prefix + " "; } } private static void GetAncestorVisualTree(this FrameworkElement node, List children) { string name = String.IsNullOrEmpty(node.Name) ? "[Anon]" : node.Name; string thisNode = name + ": " + node.GetType().Name; // Ensure list is in reverse order going up the tree children.Insert(0, thisNode); FrameworkElement parent = node.GetVisualParent(); if (parent != null) GetAncestorVisualTree(parent, children); } /// /// Returns a render transform of the specified type from the element, creating it if necessary /// /// The type of transform (Rotate, Translate, etc) /// The element to check /// The mode to use for creating transforms, if not found /// The specified transform, or null if not found and not created public static TRequestedTransform GetTransform(this UIElement element, TransformCreationMode mode) where TRequestedTransform : Transform, new() { Transform originalTransform = element.RenderTransform; TRequestedTransform requestedTransform = null; MatrixTransform matrixTransform = null; TransformGroup transformGroup = null; // Current transform is null -- create if necessary and return if (originalTransform == null) { if ((mode & TransformCreationMode.Create) == TransformCreationMode.Create) { requestedTransform = new TRequestedTransform(); element.RenderTransform = requestedTransform; return requestedTransform; } return null; } // Transform is exactly what we want -- return it requestedTransform = originalTransform as TRequestedTransform; if (requestedTransform != null) return requestedTransform; // The existing transform is matrix transform - overwrite if necessary and return matrixTransform = originalTransform as MatrixTransform; if (matrixTransform != null) { if (matrixTransform.Matrix.IsIdentity && (mode & TransformCreationMode.Create) == TransformCreationMode.Create && (mode & TransformCreationMode.IgnoreIdentityMatrix) == TransformCreationMode.IgnoreIdentityMatrix) { requestedTransform = new TRequestedTransform(); element.RenderTransform = requestedTransform; return requestedTransform; } return null; } // Transform is actually a group -- check for the requested type transformGroup = originalTransform as TransformGroup; if (transformGroup != null) { foreach (Transform child in transformGroup.Children) { // Child is the right type -- return it if (child is TRequestedTransform) return child as TRequestedTransform; } // Right type was not found, but we are OK to add it if ((mode & TransformCreationMode.AddToGroup) == TransformCreationMode.AddToGroup) { requestedTransform = new TRequestedTransform(); transformGroup.Children.Add(requestedTransform); return requestedTransform; } return null; } // Current ransform is not a group and is not what we want; // create a new group containing the existing transform and the new one if ((mode & TransformCreationMode.CombineIntoGroup) == TransformCreationMode.CombineIntoGroup) { transformGroup = new TransformGroup(); transformGroup.Children.Add(originalTransform); transformGroup.Children.Add(requestedTransform); element.RenderTransform = transformGroup; return requestedTransform; } Debug.Assert(false, "Shouldn't get here"); return null; } /// /// Returns a string representation of a property path needed to update a Storyboard /// /// The element to get the path for /// The property of the transform /// The type of transform to look fo /// A property path public static string GetTransformPropertyPath(this FrameworkElement element, string subProperty) where TRequestedType : Transform { Transform t = element.RenderTransform; if (t is TRequestedType) return String.Format("(RenderTransform).({0}.{1})", typeof(TRequestedType).Name, subProperty); else if (t is TransformGroup) { TransformGroup g = t as TransformGroup; for (int i = 0; i < g.Children.Count; i++) { if (g.Children[i] is TRequestedType) return String.Format("(RenderTransform).(TransformGroup.Children)[" + i + "].({0}.{1})", typeof(TRequestedType).Name, subProperty); } } return ""; } /// /// Returns a plane projection, creating it if necessary /// /// The element /// Whether or not to create the projection if it doesn't already exist /// The plane project, or null if not found / created public static PlaneProjection GetPlaneProjection(this UIElement element, bool create) { Projection originalProjection = element.Projection; PlaneProjection projection = null; // Projection is already a plane projection; return it if (originalProjection is PlaneProjection) return originalProjection as PlaneProjection; // Projection is null; create it if necessary if (originalProjection == null) { if (create) { projection = new PlaneProjection(); element.Projection = projection; } } // Note that if the project is a Matrix projection, it will not be // changed and null will be returned. return projection; } } /// /// Possible modes for creating a transform /// [Flags] public enum TransformCreationMode { /// /// Don't try and create a transform if it doesn't already exist /// None = 0, /// /// Create a transform if none exists /// Create = 1, /// /// Create and add to an existing group /// AddToGroup = 2, /// /// Create a group and combine with existing transform; may break existing animations /// CombineIntoGroup = 4, /// /// Treat identity matrix as if it wasn't there; may break existing animations /// IgnoreIdentityMatrix = 8, /// /// Create a new transform or add to group /// CreateOrAddAndIgnoreMatrix = Create | AddToGroup | IgnoreIdentityMatrix, /// /// Default behaviour, equivalent to CreateOrAddAndIgnoreMatrix /// Default = CreateOrAddAndIgnoreMatrix, } }