MyException - 我的异常网
当前位置:我的异常网» 编程 » WPF Template模版之找寻失落的控件【三】

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

www.MyException.Cn  网友分享于:2015-04-19  浏览:0次
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代码如下:

<Window x:Class="WpfApplication11.wnd11431"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="wnd11431" Height="174" Width="300">
    <Window.Resources>
        <ControlTemplate x:Key="cTemp">
            <StackPanel>
                <TextBox x:Name="txtBox1" BorderBrush="Black" Margin="5"></TextBox>
                <TextBox x:Name="txtBox2" BorderBrush="Black" Margin="5"></TextBox>
                <TextBox x:Name="txtBox3" BorderBrush="Black" Margin="5"></TextBox>
            </StackPanel>
        </ControlTemplate>
    </Window.Resources>
    <StackPanel>
        <UserControl x:Name="uc" Template="{StaticResource cTemp}" Margin="5"></UserControl>
        <Button Content="Find By Name" Width="200" Click="Button_Click"></Button>
    </StackPanel>
</Window>

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

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            TextBox tb = uc.Template.FindName("txtBox1", uc) as TextBox;
            tb.Text = "TextBox1";

            StackPanel sp = tb.Parent as StackPanel;
            (sp.Children[1] as TextBox).Text = "TextBox2";
            (sp.Children[2] as TextBox).Text = "TextBox3";
        }


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

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

先定义Student的类:

/// <summary>
/// 数据结构
/// </summary>
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Skill { get; set; }
    public bool HasJob { get; set; }
}
准备数据集合,呈现数据的工作全部由XAML代码来完成,为显示姓名的TextBox添加GetFocus事件处理器:
<Window x:Class="WpfApplication11.wnd11432"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local ="clr-namespace:WpfApplication11"
        xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
        Title="wnd11432" Height="250" Width="350">
    <Window.Resources>
        <!--数据集合-->
        <c:ArrayList x:Key="stuList">
            <local:Student Id="1" Name="Jack" Skill="C++" HasJob="true"></local:Student>
            <local:Student Id="2" Name="Tom" Skill="C#/Java" HasJob="true"></local:Student>
            <local:Student Id="3" Name="Super" Skill="SQL" HasJob="false"></local:Student>
            <local:Student Id="4" Name="Mac" Skill="WPF" HasJob="false"></local:Student>
            <local:Student Id="5" Name="Thinker" Skill="Writing" HasJob="true"></local:Student>
            <local:Student Id="6" Name="Pad" Skill="ASP.NET" HasJob="true"></local:Student>
        </c:ArrayList>
        <!--数据模版-->
        <DataTemplate x:Key="nameDT">
            <TextBox x:Name="txtBoxName" Width="100" Text="{Binding Name}" GotFocus="txtBoxName_GotFocus"></TextBox>
        </DataTemplate>
        <DataTemplate x:Key="skillDT">
            <TextBox x:Name="txtBoxSkill" Width="100" Text="{Binding Skill}"></TextBox>
        </DataTemplate>
        <DataTemplate x:Key="hasJobDT">
            <CheckBox x:Name="chBoxhasJob" Width="30" IsChecked="{Binding HasJob}"></CheckBox>
        </DataTemplate>
    </Window.Resources>
    <StackPanel Margin="5" Background="LightSlateGray">
        <ListView x:Name="listView" ItemsSource="{StaticResource stuList}">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="Id" Width="30" DisplayMemberBinding="{Binding Id}"></GridViewColumn>
                        <GridViewColumn Header="Name"  CellTemplate="{StaticResource nameDT}"></GridViewColumn>
                        <GridViewColumn Header="Skill" CellTemplate="{StaticResource skillDT}"></GridViewColumn>
                        <GridViewColumn Header="HasJob" CellTemplate="{StaticResource hasJobDT}"></GridViewColumn>
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>
    </StackPanel>
</Window>
    因为我们是在DataTemplate里面添加了事件处理器,所以界面上任何一个由此DataTemplate生成的TextBox都会在获得焦点的时候调用txtBoxName_GotFocus这个事件处理器。txtBoxName_GotFocus的代码如下:

        private void txtBoxName_GotFocus(object sender, RoutedEventArgs e)
        {
            // 业务逻辑数据
            TextBox tb = e.OriginalSource as TextBox;
            ContentPresenter cp = tb.TemplatedParent as ContentPresenter; // 数据模版目标控件
            Student stu = cp.Content as Student;
            listView.SelectedItem = stu; // 设置选中项

            // 访问界面元素
            ListViewItem lvi = listView.ItemContainerGenerator.ContainerFromItem(stu) as ListViewItem; // 条目数据包装
            CheckBox chb = FindVisualChild<CheckBox>(lvi);
            if(chb != null)
            {
                MessageBox.Show(string.Format("ChectBox IsChecked: {0}", chb.IsChecked.ToString()));
            }
        }

        private ChildType FindVisualChild<ChildType>(DependencyObject obj)
                where ChildType:DependencyObject
        {
            for(int i=0; i<VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if(child != null && child is ChildType)
                {
                    return (child as ChildType);
                }
                else
                {
                    ChildType childOfChild = FindVisualChild<ChildType>(child);
                    if (childOfChild != null)
                        return (childOfChild);
                }
            }

            return (null);
        }
    当使用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细节:


文章评论

Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
Java程序员必看电影
Java程序员必看电影
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
程序员都该阅读的书
程序员都该阅读的书
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
如何成为一名黑客
如何成为一名黑客
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
老程序员的下场
老程序员的下场
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
那些争议最大的编程观点
那些争议最大的编程观点
我的丈夫是个程序员
我的丈夫是个程序员
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
 程序员的样子
程序员的样子
程序员应该关注的一些事儿
程序员应该关注的一些事儿
代码女神横空出世
代码女神横空出世
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
旅行,写作,编程
旅行,写作,编程
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
程序员和编码员之间的区别
程序员和编码员之间的区别
10个调试和排错的小建议
10个调试和排错的小建议
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
程序员的鄙视链
程序员的鄙视链
一个程序员的时间管理
一个程序员的时间管理
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
总结2014中国互联网十大段子
总结2014中国互联网十大段子
鲜为人知的编程真相
鲜为人知的编程真相
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
我是如何打败拖延症的
我是如何打败拖延症的
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
为什么程序员都是夜猫子
为什么程序员都是夜猫子
程序员必看的十大电影
程序员必看的十大电影
每天工作4小时的程序员
每天工作4小时的程序员
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有