• WPF Template模版之寻找失落的控件【三】


    “井水不犯河水”常用来形容两个组织之间界限分明、互不相干,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代码如下:

    [html] view plain copy
     
     print?
    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的事件处理器代码如下:

    [csharp] view plain copy
     
     print?
    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.   
    6.     StackPanel sp = tb.Parent as StackPanel;  
    7.     (sp.Children[1] as TextBox).Text = "TextBox2";  
    8.     (sp.Children[2] as TextBox).Text = "TextBox3";  
    9. }  


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

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

    先定义Student的类:

    [csharp] view plain copy
     
     print?
    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事件处理器:
    [html] view plain copy
     
     print?
    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的代码如下:
    [csharp] view plain copy
     
     print?
    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.     // 访问界面元素  
    10.     ListViewItem lvi = listView.ItemContainerGenerator.ContainerFromItem(stu) as ListViewItem; // 条目数据包装  
    11.     CheckBox chb = FindVisualChild<CheckBox>(lvi);  
    12.     if(chb != null)  
    13.     {  
    14.         MessageBox.Show(string.Format("ChectBox IsChecked: {0}", chb.IsChecked.ToString()));  
    15.     }  
    16. }  
    17.   
    18. private ChildType FindVisualChild<ChildType>(DependencyObject obj)  
    19.         where ChildType:DependencyObject  
    20. {  
    21.     for(int i=0; i<VisualTreeHelper.GetChildrenCount(obj); i++)  
    22.     {  
    23.         DependencyObject child = VisualTreeHelper.GetChild(obj, i);  
    24.         if(child != null && child is ChildType)  
    25.         {  
    26.             return (child as ChildType);  
    27.         }  
    28.         else  
    29.         {  
    30.             ChildType childOfChild = FindVisualChild<ChildType>(child);  
    31.             if (childOfChild != null)  
    32.                 return (childOfChild);  
    33.         }  
    34.     }  
    35.   
    36.     return (null);  
    37. }  
        当使用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细节:

     
    https://www.cnblogs.com/lizhenlin/p/5906737.html
     
    @David: The TemplatedParent for an item in an ItemsControl is neither of the ones you think. I believe it's a ContentPresenter instance (a separate one for each item in your ItemsSource).
  • 相关阅读:
    CSS
    前端初识
    JQuery实现前端增删上下移文字计数
    jq实现去底部去顶部功能
    JQuery选择器,一篇博客就够(非原创)
    input,textarea,select设置默认字体样式
    图片-定义select向下箭头样式
    自定义单选框,复选框样式
    HTML(多行)文本超过部分隐藏,末尾显示(...)
    ZooKeeper可以用来做什么
  • 原文地址:https://www.cnblogs.com/sjqq/p/8367451.html
Copyright © 2020-2023  润新知