“井水不犯河水”常用来形容两个组织之间界限分明、互不相干,LogicTree与控件内部这颗小树之间就保持着这种关系。换句话说,如果UI元素树上有个X:Name=“TextBox1”的控件,某个控件内部也是由Template生成的x:Name="TextBox1"的控件,它们并不冲突,LogicTree不会看到控件内部的细节,控件内部元素也不会去理会控件外面是什么值。你可能会想:“这样一来,万一我想从控件外部访问内部的控件,获取它的属性值,岂不是做不到了。”放心,WPF为我们准备了访问控件内部小世界的入口,现在我们就开始出发寻找那些失落的控件。

由ControlTemplate和DataTemplate生成的控件都是“由Template生成的控件”。ControlTemplate和DataTemplate两个类均派生自FrameWorkTemplate类,这个类有个名为FindName的方法供我们检索其内部控件。也就是说,只要我们能拿到Template,找到其内部控件就不成问题。对于ControlTemplate对象,访问其目标控件的Template属性就可以拿到,但想拿到DataTemplate就要费一番周折了。千万不要以为ListBoxItem或者ComBoxItem容器就是DataTemplate的目标控件哦!因为控件的Template和ContentTemplate完全是两码事。

我们先来寻找由ControlTemplate生成的控件。首先设计一个ControlTemplate并把它应用在一个UserControl控件上。界面上还有一个Button,在它的Click事件处理器中我们检索ControlTemplate生成的代码。

程序的XAML代码如下:

  1. <Window x:Class="WpfApplication11.wnd11431"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="wnd11431" Height="174" Width="300">
  5. <Window.Resources>
  6. <ControlTemplate x:Key="cTemp">
  7. <StackPanel>
  8. <TextBox x:Name="txtBox1" BorderBrush="Black" Margin="5"></TextBox>
  9. <TextBox x:Name="txtBox2" BorderBrush="Black" Margin="5"></TextBox>
  10. <TextBox x:Name="txtBox3" BorderBrush="Black" Margin="5"></TextBox>
  11. </StackPanel>
  12. </ControlTemplate>
  13. </Window.Resources>
  14. <StackPanel>
  15. <UserControl x:Name="uc" Template="{StaticResource cTemp}" Margin="5"></UserControl>
  16. <Button Content="Find By Name" Width="200" Click="Button_Click"></Button>
  17. </StackPanel>
  18. </Window>

Button的事件处理器代码如下:

  1. private void Button_Click(object sender, RoutedEventArgs e)
  2. {
  3. TextBox tb = uc.Template.FindName("txtBox1", uc) as TextBox;
  4. tb.Text = "TextBox1";
  5. StackPanel sp = tb.Parent as StackPanel;
  6. (sp.Children[1] as TextBox).Text = "TextBox2";
  7. (sp.Children[2] as TextBox).Text = "TextBox3";
  8. }

接下来我们来寻找由DataTemplate生成的控件。不过在正式寻找之前,我们先思考一个问题:寻找到一个由DataTemplate生成的控件之后,我们想从中获取哪些数据,如果想单纯获取与用户界面相关的数据(比如控件的高度、宽度等),这么做是正确的。但是如果是想获取与业务逻辑相关的数据,那就要考虑是不是程序的设计出了问题------因为WPF采用的是数据驱动UI逻辑,获取业务逻辑数据在底层就能做到,一般不会跑到表层来找。

DataTemplate最常用的地方就是GridViewColumn的CellTemplate属性。把GridViewColumn放置在一个GridView控件里就可以生成表格了。GridViewColumn的默认CellTemplate是使用TextBlock只读属性显示数据,如果我们想让用户能修改数据或者使用CheckBox显示bool类型的数据的话就需要自定义DataTemplate了。

先定义Student的类:

  1. /// <summary>
  2. /// 数据结构
  3. /// </summary>
  4. public class Student
  5. {
  6. public int Id { get; set; }
  7. public string Name { get; set; }
  8. public string Skill { get; set; }
  9. public bool HasJob { get; set; }
  10. }

准备数据集合,呈现数据的工作全部由XAML代码来完成,为显示姓名的TextBox添加GetFocus事件处理器:

  1. <Window x:Class="WpfApplication11.wnd11432"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:local ="clr-namespace:WpfApplication11"
  5. xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
  6. Title="wnd11432" Height="250" Width="350">
  7. <Window.Resources>
  8. <!--数据集合-->
  9. <c:ArrayList x:Key="stuList">
  10. <local:Student Id="1" Name="Jack" Skill="C++" HasJob="true"></local:Student>
  11. <local:Student Id="2" Name="Tom" Skill="C#/Java" HasJob="true"></local:Student>
  12. <local:Student Id="3" Name="Super" Skill="SQL" HasJob="false"></local:Student>
  13. <local:Student Id="4" Name="Mac" Skill="WPF" HasJob="false"></local:Student>
  14. <local:Student Id="5" Name="Thinker" Skill="Writing" HasJob="true"></local:Student>
  15. <local:Student Id="6" Name="Pad" Skill="ASP.NET" HasJob="true"></local:Student>
  16. </c:ArrayList>
  17. <!--数据模版-->
  18. <DataTemplate x:Key="nameDT">
  19. <TextBox x:Name="txtBoxName" Width="100" Text="{Binding Name}" GotFocus="txtBoxName_GotFocus"></TextBox>
  20. </DataTemplate>
  21. <DataTemplate x:Key="skillDT">
  22. <TextBox x:Name="txtBoxSkill" Width="100" Text="{Binding Skill}"></TextBox>
  23. </DataTemplate>
  24. <DataTemplate x:Key="hasJobDT">
  25. <CheckBox x:Name="chBoxhasJob" Width="30" IsChecked="{Binding HasJob}"></CheckBox>
  26. </DataTemplate>
  27. </Window.Resources>
  28. <StackPanel Margin="5" Background="LightSlateGray">
  29. <ListView x:Name="listView" ItemsSource="{StaticResource stuList}">
  30. <ListView.View>
  31. <GridView>
  32. <GridView.Columns>
  33. <GridViewColumn Header="Id" Width="30" DisplayMemberBinding="{Binding Id}"></GridViewColumn>
  34. <GridViewColumn Header="Name"  CellTemplate="{StaticResource nameDT}"></GridViewColumn>
  35. <GridViewColumn Header="Skill" CellTemplate="{StaticResource skillDT}"></GridViewColumn>
  36. <GridViewColumn Header="HasJob" CellTemplate="{StaticResource hasJobDT}"></GridViewColumn>
  37. </GridView.Columns>
  38. </GridView>
  39. </ListView.View>
  40. </ListView>
  41. </StackPanel>
  42. </Window>

因为我们是在DataTemplate里面添加了事件处理器,所以界面上任何一个由此DataTemplate生成的TextBox都会在获得焦点的时候调用txtBoxName_GotFocus这个事件处理器。txtBoxName_GotFocus的代码如下:

  1. private void txtBoxName_GotFocus(object sender, RoutedEventArgs e)
  2. {
  3. // 业务逻辑数据
  4. TextBox tb = e.OriginalSource as TextBox;
  5. ContentPresenter cp = tb.TemplatedParent as ContentPresenter; // 数据模版目标控件
  6. Student stu = cp.Content as Student;
  7. listView.SelectedItem = stu; // 设置选中项
  8. // 访问界面元素
  9. ListViewItem lvi = listView.ItemContainerGenerator.ContainerFromItem(stu) as ListViewItem; // 条目数据包装
  10. CheckBox chb = FindVisualChild<CheckBox>(lvi);
  11. if(chb != null)
  12. {
  13. MessageBox.Show(string.Format("ChectBox IsChecked: {0}", chb.IsChecked.ToString()));
  14. }
  15. }
  16. private ChildType FindVisualChild<ChildType>(DependencyObject obj)
  17. where ChildType:DependencyObject
  18. {
  19. for(int i=0; i<VisualTreeHelper.GetChildrenCount(obj); i++)
  20. {
  21. DependencyObject child = VisualTreeHelper.GetChild(obj, i);
  22. if(child != null && child is ChildType)
  23. {
  24. return (child as ChildType);
  25. }
  26. else
  27. {
  28. ChildType childOfChild = FindVisualChild<ChildType>(child);
  29. if (childOfChild != null)
  30. return (childOfChild);
  31. }
  32. }
  33. return (null);
  34. }

当使用GridView作为ListView的View属性时,如果某一列使用TextBox作为CellTemplate,那么即使这列中的TextBox被鼠标单击并获得了焦点ListView也不会把此项做为自己的SelectedItem。所以,txtBoxName_GotFocus的前半部分是获得数据的源头(TextBox),然后沿UI元素树上朔到DataTemplate目标控件(ContentPresenter)并获取它的内容,它的内容一定是一个Student实例。
    txtBoxName_GotFocus的后半部分则借助VisualTreeHelper类检索由DataTemplate生成的控件。前面说过,每个ItemsControl的派生类(如ListBox,ComBox,ListView)都具有自己独特的条目容器,本例中是一个包装着Student对象的ListViewItem(注意,此ListViewItem对象的Content也是Student对象)。可以把这个ListViewItem控件视为一颗树的根,使用VisualTreeHelper类就可以遍历它的各个节点。本例中是吧遍历算法分装在了FindVisualChild泛型方法里。
运行程序,并单击某个显示姓名的TextBox,效果如下图所示:

由本例可以看出,无论是从事件源头“自下而上”的找,还是使用ItemContainerGenerator.ContainerFromItem方法找到条目容器再“自上而下”的找,总之,找到业务逻辑数据(Student实例)并不难,而工作中大多是操作业务逻辑数据。如果真的想找由DataTemplate生成的控件,对于结构简单的控件,可以使用DataTemplate对象的FindName方法;对于结构复杂的控件,则需要借助VisualTreeHelper来实现。

可以使用WPF Inspector查看VisualTree或LogicTree细节:

 
 

WPF Template模版之寻找失落的控件【三】的更多相关文章

  1. WPF Template模版之DataTemplate与ControlTemplate【一】

    WPF Template模版之DataTemplate与ControlTemplate[一] 标签: Wpf模版 2015-04-19 11:52 510人阅读 评论(0) 收藏 举报  分类: -- ...

  2. WPF Template模版之DataTemplate与ControlTemplate的关系和应用【二】

    1. DataTemplate和ControlTemplate的关系 学习过DataTemplate和ControlTemplate,你应该已经体会到,控件只是数据的行为和载体,是个抽象的概念,至于它 ...

  3. 【转】WPF Template模版之DataTemplate与ControlTemplate的关系和应用(二)

    1. DataTemplate和ControlTemplate的关系 学习过DataTemplate和ControlTemplate,你应该已经体会到,控件只是数据的行为和载体,是个抽象的概念,至于它 ...

  4. 《Programming WPF》翻译 第5章 7.控件模板

    原文:<Programming WPF>翻译 第5章 7.控件模板 如果仔细的看我们当前的TTT游戏,会发现Button对象并没有完全为我们工作.哪些TTT面板有内圆角? 图5-14 这里 ...

  5. WPF自定义控件(二)の重写原生控件样式模板

    话外篇: 要写一个圆形控件,用Clip,重写模板,去除样式引用圆形图片可以有这三种方式. 开发过程中,我们有时候用WPF原生的控件就能实现自己的需求,但是样式.风格并不能满足我们的需求,那么我们该怎么 ...

  6. WPF 构建无外观(Lookless)控件

    原文:WPF 构建无外观(Lookless)控件 构建一个用户可以使用Template属性设置外观的WPF控件需要以下几步 1.继承自System.Windows.Controls.Control 2 ...

  7. WPF教程002 - 实现Step步骤条控件

    原文:WPF教程002 - 实现Step步骤条控件 在网上看到这么一个效果,刚好在用WPF做控件,就想着用WPF来实现一下 1.实现原理 1.1.该控件分为2个模块,类似ComboBox控件分为Ste ...

  8. WPF中嵌入WinForm中的webbrowser控件

    原文:WPF中嵌入WinForm中的webbrowser控件 使用VS2008创建WPF应用程序,需使用webbrowser.从工具箱中添加WPF组件中的webbrowser发现其中有很多属性事件不能 ...

  9. WPF从我炫系列4---装饰控件的用法

    这一节的讲解中,我将为大家介绍WPF装饰控件的用法,主要为大家讲解一下几个控件的用法. ScrollViewer滚动条控件 Border边框控件 ViewBox自由缩放控件 1. ScrollView ...

随机推荐

  1. c# delegate的invoke和bejinInvoke的区别

    先看下面实实例代码 private delegate void testdg(); private void button1_Click(object sender, EventArgs e)     ...

  2. Java 集合 JDK1.7的LinkedList

    Java 集合 JDK1.7的LinkedList @author ixenos LinkedList LinkedList是List接口的双向链表实现,JDK1.7以前是双向循环链表,以后是双向非循 ...

  3. hdu_5790_Prefix(trie+主席树)

    题目链接:hdu_5790_Prefix 题意: 给你n个字符串,字符串总长度不超过10W,然后给你一个区间,问你这个区间的字符串不相同的前缀有多少个. 题解: 由于z与上一个答案有关,所以强制在线, ...

  4. React - Stores

    Event emmiters that make data available, handle business logic, send events to React, and listen for ...

  5. LeetCode OJ 54. Spiral Matrix

    Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral or ...

  6. Openjudge-计算概论(A)-单词翻转

    描述: 输入一个句子(一行),将句子中的每一个单词翻转后输出. 输入只有一行,为一个字符串,不超过500个字符.单词之间以空格隔开.输出翻转每一个单词后的字符串,单词之间的空格需与原文一致. 样例输入 ...

  7. jq 进度条插件

    /***进度条*/var ProgressScrollBar = function (model) { this.defaults = { isCanMove: true,//是否启用拖动 conta ...

  8. CCleaner(著名清理软件) 5.21.5700 中文免费版(著名清理软件) 5.21.5700 中文免费版

    软件名称: CCleaner(著名清理软件) 5.21.5700 中文免费版著名清理软件(CCleaner)软件语言: 多国语言授权方式: 免费软件运行环境: Win 32位/64位软件大小: 5.6 ...

  9. c语言-何为编程?

    大牛,请绕过. 新手,如果你怕我误人子弟,那也请绕过. 以下纯属个人YY 何为编程?何为程序? 说简单也简单,说复杂也复杂. 我在自学的道路上也有两三年了,也探索了两三年(非连续性),却只停留在入门阶 ...

  10. array_walk与array_map 的不同 array_filter

      array_walk 主要是要对数组内的每个值进行操作,操作结果影响原来的数组 array_map主要是对数组中的值进行操作后返回数组,以得到一个新数组 wallk 可以没有返回值 map要有,因 ...