10-1.WPF模板
控件由“算法内容”和“数据内容”决定
- 算法内容:指控件能展示哪些数据、具有哪些方法、能激发什么事件等,简而言之是控件的功能,一组相关逻辑
- 数据内容:控件所展示的具体数据是什么
在WPF中,模板将数据和算法的内容和形式进行了解耦,模板分为两大类:
- ControlTemplate:是算法内容的表现形式,决定了控件长什么样
- DataTemplate:是数据内容的表现形式,也就是数据显示成什么样
DataTemplate
DataTemplate常用的地方有3处:
- ContentControl的ContentTemplate属性,相当于给ContentControl的内容穿上外衣
- ItemsControl的ItemTemplate属性,相当于给ItemTemplate的内容穿上外衣
- GridViewColumn的CellTemplate属性,相当于给GridViewColumn单元格里的数据穿上外衣
传统的事件驱动模式是控件和控件之间沟通,模型如下图
数据驱动则是数据与控件之间沟通,使用DataTemplate可以很方便的将事件驱动模式改为数据驱动模式
案例:有一列汽车的数据显示在ListBox里面,点击某条数据,详细数据显示在窗体左侧细节展示框中。
将ListBox和细节展示框的窗体设计分别放进<DataTemplate>
标签内包装,再放到主窗体的资源词典中。最重要的是为<DataTemplate>
里的每个控件设置Binding,告诉各个控件应该关注数据的哪个属性。除此之外,有些属性不能直接使用,需要自定义Converter,在XAML中有两种方法使用Converter:
- 把Converter以资源的形式放在资源词典里
- 为Converter准备一个静态属性,在XAML中使用{x:Static}标签扩展来访问
定义Converter
//厂商名称转换为Logo路径
public class AutoMarkToLogoPathConverter:IValueConverter
{
/// 正向转
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new BitmapImage(new Uri(string.Format(@"Resource/Image/{0}.png",(string)value),UriKind.Relative));
}
/// 逆向转未用到
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
//汽车名称转换为照片路径
public class NameToPhotoPathConverter:IValueConverter
{
/// 正向转
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new BitmapImage(new Uri(string.Format(@"Resource/Image/{0}.jpg", (string)value), UriKind.Relative));
}
/// 逆向转未用到
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML代码如下:
<Window x:Class="WpfApplication1.Window36"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1.Model"
Title="Window36" Height="350" Width="623">
<Window.Resources>
<!--Converter-->
<local:AutoMarkToLogoPathConverter x:Key="amp"/>
<local:NameToPhotoPathConverter x:Key="npp"/>
<!--DataTemplate For DatialView-->
<DataTemplate x:Key="DatialViewTemplate">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="6">
<StackPanel>
<Image x:Name="imgPhoto" Width="400" Height="250" Source="{Binding AutoMark,Converter={StaticResource npp}}"></Image>
<StackPanel Orientation="Horizontal" Margin="5,0">
<TextBlock Text="Name:" FontSize="20" FontWeight="Bold"></TextBlock>
<TextBlock FontSize="20" Margin="5,0" Text="{Binding Name}"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5,0">
<TextBlock Text="AutoMark:" FontWeight="Bold"></TextBlock>
<TextBlock Margin="5,0" Text="{Binding AutoMark}"></TextBlock>
<TextBlock Text="Year:" FontWeight="Bold">
</TextBlock>
<TextBlock Text="{Binding Year}" Margin="5,0">
</TextBlock>
<TextBlock Text="Top Speed:" FontWeight="Bold">
</TextBlock>
<TextBlock Text="{Binding TopSpeed}" Margin="5,0">
</TextBlock>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
<!--Data Template For ItemView-->
<DataTemplate x:Key="ItemView">
<Grid Margin="2">
<StackPanel Orientation="Horizontal">
<Image x:Name="igLogo" Grid.RowSpan="3" Width="64" Height="64" Source="{Binding Name,Converter={StaticResource amp}}"></Image>
<StackPanel Margin="5,10">
<TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"></TextBlock>
<TextBlock Text="{Binding Year}" FontSize="14"></TextBlock>
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</Window.Resources>
<!--窗体内容-->
<StackPanel Orientation="Horizontal">
<UserControl ContentTemplate="{StaticResource DatialViewTemplate}" Content="{Binding Path=SelectedItem,ElementName=lbInfos}"></UserControl>
<ListBox x:Name="lbInfos" ItemTemplate="{StaticResource ItemView}"></ListBox>
</StackPanel>
</Window>
初始化数据
private void InitialCarList()
{
List<Car> infos = new List<Car>() {
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="200", Year="1990"},
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="250", Year="1998"},
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="300", Year="2002"},
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="350", Year="2011"},
new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="500", Year="2020"}
};
this.lbInfos.ItemsSource = infos;
}
ControlTemplate
ControlTemplae可以控制控件的外表样式,在实际项目中ControlTemplate主要用来:
- 通过更换ControlTemplate来改变控件外观
- 借助ControlTemplate,程序员和UI设计可以并行工作,程序员可以使用WPF标准控件来编程,等设计师工作完成后,只需把新的ControlTemplate应用到程序就可以
在Blend中的Application资源词典中增加一个TextBoxStyle
<Application.Resources>
<Style x:Key="TextBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="10">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
注意点:
- 作为资源不是单纯的ControlTemplate,而是Style。使用style时,如果value值比较简单,则直接使用Attribute就可以,如果比较复杂,则只能使用XAML的属性对象语法,如TextBox的Template属性是一个ControlTemplate对象。
- TemplateBinding,ControlTemplate被应用到一个控件上,我们称之为目标控件,TemplateBinding的意思是将自己的属性关联到目标控件上的某个属性值上,如
Background="{TemplateBinding Background}"
意思是让Border的Background与目标控件保持一致。
TextBox应用上面的style
<TextBox Style="{DynamicResource TextBoxStyle}"/>
<TextBox Style="{DynamicResource TextBoxStyle}"/>
其实每个控件本身就是一颗UI元素树,WPF可以看做两颗树,LogicalTree和VisualTree树,这两棵树的交点就是ControlTemplate。
ItemsControl的PanelTemplate
ItemsControl具有一个名为ItemsPanel的属性,其数据类型为ItemsPanelTemplate,它的作用是控制ItemsControl的条目容器。比如,ListBox条目都是纵向排列,现在改为横向排列。
<Grid Margin="2">
<ListBox>
<TextBlock Text="A"/>
<TextBlock Text="B"/>
<TextBlock Text="C"/>
<TextBlock Text="D"/>
<TextBlock Text="E"/>
</ListBox>
</Grid>
<Grid Margin="2">
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<TextBlock Text="A"/>
<TextBlock Text="B"/>
<TextBlock Text="C"/>
<TextBlock Text="D"/>
<TextBlock Text="E"/>
</ListBox>
</Grid>
DataTemplate和ControlTemplate的关系
控件只是个数据和行为的载体,它的外观由ControlTemplate决定,数据外观由DataTemplate决定,他们正对应着Control类的Template和ContentTemplate两个属性。
凡是Template都要作用在控件上,这个控件叫做Template的目标控件,也叫做模板化控件。DataTemplate同样也是,它施加在数据对象上,但是展示数据对象(一组展示数据的控件)也要有一个载体,该载体一般落实在一个ContentPresenter对象上,该对象只有一个ContentTemplate属性,表明它的功能就是承载由DataTemplate生成的一组控件。因为ContentPresenter是ControlTemplate控件树的一个节点,所以DataTemplate是ControlTemplate一颗子树。
由Template生成的控件树都有根,每个控件都有个TemplateParent属性,如果值不为NULL,说明这个控件是由Template自动生成的,而属性值就是应用了该模板的控件。如果由Template生成的控件使用了TemplateBinding获取属性值,则TemplateBinding的数据源就是应用了这个模板的目标控件。
应用
为Template设置应用目标有两种方法
- 逐个设置控件的Template/ContentTemplate/ItemsTemplate/CellTemplate等属性
- 整体应用,借助Style来实现,但是Style不能标记x:Key,如果不想应用则设置控件的Style为{x:Null}
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp5"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="300">
<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<StackPanel Background="Orange"/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="5"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="Height" Value="25"/>
</Style>
</Window.Resources>
<StackPanel>
<TextBox/>
<TextBox/>
<TextBox Style="{x:Null}" Margin="5"/>
</StackPanel>
</Window>
把DataTemplate应用在某个数据类型上的方法时设置DataTemplate的DataType属性,并且DataTemplate作为资源时不能带x:Key标记。
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApp5"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Unit}">
<Grid>
<StackPanel Orientation="Horizontal">
<Grid>
<Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"/>
<TextBlock Text="{Binding Year}"/>
</Grid>
</StackPanel>
</Grid>
</DataTemplate>
<c:ArrayList x:Key="ds">
<local:Unit Year="2001" Price="100"/>
<local:Unit Year="2002" Price="120"/>
<local:Unit Year="2003" Price="130"/>
<local:Unit Year="2004" Price="150"/>
<local:Unit Year="2005" Price="160"/>
<local:Unit Year="2006" Price="180"/>
<local:Unit Year="2007" Price="190"/>
</c:ArrayList>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{StaticResource ds}"/>
<ComboBox ItemsSource="{StaticResource ds}" Margin="5"/>
</StackPanel>
</Window>
public class Unit
{
public int Price { set; get; }
public string Year { set; get; }
}
XML数据源
DataTemplate可以把XML数据中的元素名作为DataType,元素子节点和Attribute可以使用xpath来访问。
上面的案例改造
<Window.Resources>
<DataTemplate DataType="Unit">
<Grid>
<StackPanel Orientation="Horizontal">
<Grid>
<Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding XPath=@Price}"/>
<TextBlock Text="{Binding XPath=@Year}"/>
</Grid>
</StackPanel>
</Grid>
</DataTemplate>
<XmlDataProvider x:Key="ds" XPath="Units/Unit">
<x:XData>
<Units xmlns="">
<Unit Year ="2001" Price="100"/>
<Unit Year ="2002" Price="110"/>
<Unit Year ="2003" Price="120"/>
<Unit Year ="2004" Price="130"/>
<Unit Year ="2005" Price="140"/>
<Unit Year ="2006" Price="150"/>
</Units>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Source={StaticResource ds}}"/>
<ComboBox ItemsSource="{Binding Source={StaticResource ds}}" Margin="5"/>
</StackPanel>
层级结构
WPF中TreeView和MenuItem控件用来显示层级,能够帮助层级控件显示层级数据的模板是HierarchicalDataTemplate
案例1:Data.xml文件
<?xml version="1.0" encoding="utf-8" ?>
<Data xmlns="">
<Grade Name="一年级">
<Class Name="甲班">
<Group Name="A组"/>
<Group Name="B组"/>
<Group Name="C组"/>
</Class>
<Class Name="乙班">
<Group Name="A组"/>
<Group Name="B组"/>
<Group Name="C组"/>
</Class>
</Grade>
<Grade Name="二年级">
<Class Name="甲班">
<Group Name="A组"/>
<Group Name="B组"/>
<Group Name="C组"/>
</Class>
<Class Name="乙班">
<Group Name="A组"/>
<Group Name="B组"/>
<Group Name="C组"/>
</Class>
</Grade>
</Data>
程序的XAML:
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApp5"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<Window.Resources>
<XmlDataProvider x:Key="ds" Source="Data.xml" XPath="Data/Grade"/>
<!--年级-->
<HierarchicalDataTemplate DataType="Grade" ItemsSource="{Binding XPath=Class}">
<TextBlock Text="{Binding XPath=@Name}"/>
</HierarchicalDataTemplate>
<!--班级-->
<HierarchicalDataTemplate DataType="Class" ItemsSource="{Binding XPath=Group}">
<RadioButton Content="{Binding XPath=@Name}" GroupName="gn"/>
</HierarchicalDataTemplate>
<!--小组-->
<HierarchicalDataTemplate DataType="Group" ItemsSource="{Binding XPath=Student}">
<CheckBox Content="{Binding XPath=@Name}" />
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView Margin="5" ItemsSource="{Binding Source={StaticResource ds}}"/>
</Grid>
</Window>
DataType指定了HierarchicalDataTemplate模板用哪种数据类型
ItemsSource指定下一层显示哪些数据
案例2:同一种数据类型的嵌套,只需设置一个HierarchicalDataTemplate,他会自动迭代
data.xml
<?xml version="1.0" encoding="utf-8" ?>
<Data xmlns="">
<Operation Name="文件" Gesture="F">
<Operation Name="新建" Gesture="N">
<Operation Name="项目" Gesture="N"/>
<Operation Name="网站" Gesture="N"/>
<Operation Name="文档" Gesture="N"/>
</Operation>
<Operation Name="保存" Gesture="N"/>
<Operation Name="打印" Gesture="N"/>
<Operation Name="退出" Gesture="N"/>
</Operation>
<Operation Name="编辑" Gesture="E">
<Operation Name="拷贝" Gesture="N"/>
<Operation Name="剪切" Gesture="N"/>
<Operation Name="粘贴" Gesture="N"/>
</Operation>
</Data>
XAML:
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApp5"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="300">
<Window.Resources>
<XmlDataProvider x:Key="ds" Source="Data1.xml" XPath="Data/Operation"/>
<HierarchicalDataTemplate DataType="Operation" ItemsSource="{Binding XPath=Operation}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding XPath=@Name}" Margin="10,0"/>
<TextBlock Text="{Binding XPath=@Gesture}"/>
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<StackPanel>
<Menu ItemsSource="{Binding Source={StaticResource ds}}"/>
</StackPanel>
</Window>
HierarchicalDataTemplate的作用目标不是MenuItem的内容,而是它的Header。如果对MenuItem的单击事件进行侦听,可以从单击MenuItem的Header中提取XML数据。
<StackPanel MenuItem.Click="StackPanel_Checked">
<Menu ItemsSource="{Binding Source={StaticResource ds}}"/>
</StackPanel>
private void StackPanel_Checked(object sender, RoutedEventArgs e)
{
MenuItem mi = e.OriginalSource as MenuItem;
XmlElement xe = mi.Header as XmlElement;
MessageBox.Show(xe.Attributes["Name"].Value);
}
控件内部的树形结构
如果UI元素树上有个x:Name="txt"的控件,而某个控件内部有个由Template生成的x:Name="txt"的控件,他们不会产生冲突,因为LogicTree不会看到控件内部的细节。由ControlTemplate和DataTemplate有一个FindName的方法供我们检索其中的内部控件,也就是说,只要我们拿到Template就能找到内部的控件。
获取ControlTemplate对象
想拿到ControlTemplate对象,直接访问目标对象的Template就可以。
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp5"
mc:Ignorable="d"
Title="MainWindow" Height="172" Width="300">
<Window.Resources>
<ControlTemplate x:Key="cTmp">
<StackPanel Background="Orange">
<TextBox x:Name="txt1" Margin="6"/>
<TextBox x:Name="txt2" Margin="6"/>
<TextBox x:Name="txt3" Margin="6"/>
</StackPanel>
</ControlTemplate>
</Window.Resources>
<StackPanel Background="Yellow">
<UserControl x:Name="uc" Template="{StaticResource cTmp}" Margin="5"/>
<Button Content="Find by Name" Width="120" Height="30" Click="Button_Click"/>
</StackPanel>
</Window>
private void Button_Click(object sender, RoutedEventArgs e)
{
//通过目标对象的Template就可以获得ControlTemplate
TextBox tb = this.uc.Template.FindName("txt1", this.uc) as TextBox;
tb.Text = "Hello";
StackPanel sp= tb.Parent as StackPanel;
(sp.Children[1] as TextBox).Text = "Hi";
(sp.Children[2] as TextBox).Text = "XXX";
}
获取DataTemplate对象
如果找到DataTemplate生成的控件后,如果想获得与控件相关的数据,如长宽高,这种做法是正确的。而如果想获得数据,那一般是逻辑出现了问题,因为WPF采用数据驱动的方式,获取数据一般在底层就可以实现。
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp5"
mc:Ignorable="d"
Title="MainWindow" Height="175" Width="220">
<Window.Resources>
<!--数据对象-->
<local:Student x:Key="stu" Id="1" Name="Tom" Skill="WPF" HasJob="True"/>
<!--DataTemplate-->
<DataTemplate x:Key="stuDT">
<Border BorderBrush="Orange" BorderThickness="2" CornerRadius="5">
<StackPanel>
<TextBlock Text="{Binding Id}" Margin="5"/>
<TextBlock x:Name="txtName" Text="{Binding Name}" Margin="5"/>
<TextBlock Text="{Binding Skill}" Margin="5"/>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<StackPanel Background="Yellow">
<ContentPresenter x:Name="cp" Content="{StaticResource stu}" ContentTemplate="{StaticResource stuDT}" Margin="5"/>
<Button Content="Find" Margin="5" Click="Button_Click_1"/>
</StackPanel>
</Window>
public class Student
{
public int Id { set; get; }
public string Name { set; get; }
public string Skill { get; set; }
public bool HasJob { set; get; }
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
TextBlock tb = this.cp.ContentTemplate.FindName("txtName", this.cp) as TextBlock;
MessageBox.Show(tb.Text);
//如果只是使用数据,最好这样
//Student st = this.cp.Content as Student;
}
GridView使用Template
DataTemplate的常用之处是GridViewColumn的CellTemplate属性。GridViewColumn默认的CellTemplate属性使用了一个TextBlock,如果想使用CheckBox呢?
案例:
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApp5"
mc:Ignorable="d"
Title="MainWindow" Height="175" Width="220">
<Window.Resources>
<!--数据集合-->
<c:ArrayList x:Key="stuList">
<local:Student Id="1" Name="A" Skill="C" HasJob="True"/>
<local:Student Id="2" Name="B" Skill="C++" HasJob="True"/>
<local:Student Id="3" Name="C" Skill="C#" HasJob="True"/>
<local:Student Id="4" Name="D" Skill="jAVA" HasJob="False"/>
<local:Student Id="5" Name="E" Skill="PYTHON" HasJob="True"/>
<local:Student Id="6" Name="F" Skill="SQL" HasJob="True"/>
</c:ArrayList>
<!--DataTemplate-->
<DataTemplate x:Key="nameDT">
<TextBox x:Name="txtName" Text="{Binding Name}"/>
</DataTemplate>
<DataTemplate x:Key="skillDT">
<TextBox x:Name="txtSkill" GotFocus="TxtSkill_GotFocus" Text="{Binding Skill}"/>
</DataTemplate>
<DataTemplate x:Key="hjDT">
<CheckBox x:Name="chkJob" IsChecked="{Binding HasJob}"/>
</DataTemplate>
</Window.Resources>
<Grid Margin="5">
<ListView x:Name="listView" ItemsSource="{StaticResource stuList}">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" DisplayMemberBinding="{Binding Id}"/>
<GridViewColumn Header="姓名" CellTemplate="{StaticResource nameDT}"/>
<GridViewColumn Header="技术" CellTemplate="{StaticResource skillDT}"/>
<GridViewColumn Header="已工作" CellTemplate="{StaticResource hjDT}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
private void TxtSkill_GotFocus(object sender, RoutedEventArgs e)
{
//访问数据
TextBox tb = e.OriginalSource as TextBox;//事件发起源头
ContentPresenter cp = tb.TemplatedParent as ContentPresenter;//获取模板
Student su = cp.Content as Student;//获取业务数据
this.listView.SelectedItem = su;
//访问界面元素
ListViewItem vi = this.listView.ItemContainerGenerator.ContainerFromItem(su) as ListViewItem;
CheckBox crb = FindVisualChild<CheckBox>(vi);
MessageBox.Show(crb.Name);
}
private T FindVisualChild<T>(DependencyObject obj) where T:DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child!=null && child is T)
{
return child as T;
}
else
{
T childOfChild = FindVisualChild<T>(child);
if (childOfChild !=null)
{
return childOfChild;
}
}
}
return null;
}
-
案例中为模板中显示姓名的TextBox增加了GetFocus事件,界面上任何一个姓名TextBox获得焦点后都会调用该事件。
-
每个ItemsControl派生类(ListBox、ListView)都有自己的条目容器,使用ItemContainerGenerator.ContainerFromItem方法能获得包装着指定条目数据的容器。
-
VisualTreeHelper类可以遍历控件内部的各个节点。