using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Sheva.Windows.Components
{
public delegate void VisualVisitedEventHandler(Object sender, VisualVisitedEventArgs e);
/// <summary>
/// This class represents a wrapper around the arguments passed to the VisualVisited event handler.
/// </summary>
/// <remarks>
/// This class is mutated from John Grossman's VisualTreeWalker implementation.
/// </remarks>
public class VisualVisitedEventArgs : EventArgs
{
private Visual visitedVisual;
private Int32 currentDepth;
public VisualVisitedEventArgs(Visual visitedVisual, Int32 currentDepth)
{
this.visitedVisual = visitedVisual;
this.currentDepth = currentDepth;
}
/// <summary>
/// Get the visual currently visited by the VisualTreeWalker.
/// </summary>
public Visual VisitedVisual
{
get { return visitedVisual; }
}
/// <summary>
/// Get the depth of visual tree that the VisualTreeWalker is currently in.
/// </summary>
public Int32 CurrentDepth
{
get { return currentDepth; }
}
}
/// <summary>
/// Represents a class which can walk through the visual tree.
/// </summary>
public class VisualTreeWalker
{
private Int32 visualCount;
/// <summary>
/// Presents the VisualVisited event.
/// </summary>
public event VisualVisitedEventHandler VisualVisited;
/// <summary>
/// Begin to walk through the visual tree starting from the reference visual.
/// </summary>
/// <param name="reference">The visual whose visual tree will be walked through.</param>
/// <returns>The number of visuals visited by the VisualTreeWalker.</returns>
public Int32 Walk(Visual reference)
{
this.visualCount = 0;
this.TraverseVisuals(reference, 1);
return this.visualCount;
}
private void TraverseVisuals(Visual visual, Int32 currentDepth)
{
this.visualCount++;
this.OnVisualVisited(new VisualVisitedEventArgs(visual, currentDepth));
for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
Visual child = VisualTreeHelper.GetChild(visual, i) as Visual;
this.TraverseVisuals(child, currentDepth++);
}
}
/// <summary>
/// Raise the VisualVisited event.
/// </summary>
/// <param name="e">Arguments passed to the VisualVisited event handler.</param>
protected void OnVisualVisited(VisualVisitedEventArgs e)
{
if (this.VisualVisited != null)
{
this.VisualVisited(this, e);
}
}
}
}
The above code is mutated from John Grossman's code, I just add a VisualVisited event to the class to make it a bit straightforward to work with.
Next, I define a VisualTreeUtility class, and defines two helper methods which will leverage upon the code I pasted above:
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
namespace Sheva.Windows.Components
{
public class VisualTreeUtility
{
/// <summary>
/// Find the child element in the visual tree of reference visual.
/// </summary>
/// <typeparam name="T">The type of element you want to examine.</typeparam>
/// <param name="reference">The visual whose visual tree is traversed.</param>
/// <returns>Return the element being first found in the visual tree</returns>
public static T FindChildVisual<T>(Visual reference) where T : Visual
{
T targetVisual = null;
VisualTreeWalker walker = new VisualTreeWalker();
walker.VisualVisited += delegate(object sender, VisualVisitedEventArgs e)
{
if (e.VisitedVisual is T)
{
targetVisual = e.VisitedVisual as T;
}
};
walker.Walk(reference);
return targetVisual;
}
/// <summary>
/// Find the child elements in the visual tree of reference visual.
/// </summary>
/// <typeparam name="T">The type of element you want to examine.</typeparam>
/// <param name="reference">The visual whose visual tree is traversed.</param>
/// <returns>Return the elements being found in the visual tree</returns>
public static T[] FindChildVisuals<T>(Visual reference) where T : Visual
{
List<T> visuals = new List<T>();
VisualTreeWalker walker = new VisualTreeWalker();
walker.VisualVisited += delegate(object sender, VisualVisitedEventArgs e)
{
if (e.VisitedVisual is T)
{
visuals.Add((T)e.VisitedVisual);
}
};
walker.Walk(reference);
return visuals.ToArray();
}
}
}
Note that I use the generic to void casting Visual type back to the type say ListBox we actually need,and the usage of this helper class is even more straightforward, for instance if I have following xaml definition:
<Window x:Class="VisualTreeWalkerDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="WindowLoaded"
>
<Window.Resources>
<XmlDataProvider x:Key="xmlData">
<x:XData>
<ListBoxItems xmlns="">
<ListBoxItem Name="Item One"/>
<ListBoxItem Name="Item Two"/>
<ListBoxItem Name="Item Three"/>
<ListBoxItem Name="Item Four"/>
</ListBoxItems>
</x:XData>
</XmlDataProvider>
<DataTemplate x:Key="dataTemplate">
<TextBlock Text="{Binding XPath=@Name}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox x:Name="myListBox" Width="300" Height="300"
ItemTemplate="{StaticResource dataTemplate}"
ItemsSource="{Binding Source={StaticResource xmlData}, XPath=ListBoxItems/ListBoxItem}"/>
</Grid>
</Window>
Then I can easily get the TextBlocks inside the ListBox's ItemTemplate:
private void WindowLoaded(Object sender, RoutedEventArgs e)
{
List<TextBlock> items = VisualTreeUtility.FindChildVisuals<TextBlock>(myListBox);
if (items.Count > 0)
{
foreach (TextBlock item in items)
{
item.PreviewMouseLeftButtonUp += item_PreviewMouseLeftButtonUp;
}
}
}
private void item_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("I got clicked");
}