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,
}
}