惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
Apple Machine Learning Research
Apple Machine Learning Research
IT之家
IT之家
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog RSS Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
人人都是产品经理
人人都是产品经理
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
V
V2EX
aimingoo的专栏
aimingoo的专栏
爱范儿
爱范儿
博客园 - 聂微东
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Stack Overflow Blog
Stack Overflow Blog
罗磊的独立博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
MongoDB | Blog
MongoDB | Blog
Jina AI
Jina AI
T
The Blog of Author Tim Ferriss
月光博客
月光博客
云风的 BLOG
云风的 BLOG

博客园 - Sheva

Two Xaml Tricks - Sheva I See Cool Stuff Today XmlnsDefinitionAttribute Is Pretty Nifty Application.DoEvents In WPF 征召译者 C# Trivia Test CreateParams Is Pretty Cool! In-Place Editing In WPF SingleLinkedList Implementation Data Binding In WPF C# Application Markup Language Regex 101 Exercise I10 - Extract repeating hex blocks from a string AvalonPaint: Features Added Regex 101 Exercise I9 - Count the number of matches Crossbow: Hosting WPF Controls In Winforms Application Crossbow? WTF? The Architecture Of Avalon Drop Shadow Effect In WPF XamlReader.Load(): Build Up Your Own XamlPad
DataTemplate Is A Bit Naughtier To Play With
Sheva · 2006-09-23 · via 博客园 - Sheva

    Some one pointed out on MSDN WPF forum post that FindName breaks on DataTemplate, when we want to reference the visual elements inside the data template, we cannot use FindName, then what we can use? unfortunately, the only solution currently I can think of is to traverse through the visual tree of the templated element, with some helper classes and methods, we can make this process a bit easier. First I create a VisualTreeWalker class which houses all the logic of walking through the tree.

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");
        }