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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <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> <local:AutoMarkToLogoPathConverter x:Key="amp"/> <local:NameToPhotoPathConverter x:Key="npp"/> <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> <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>
|
初始化数据
1 2 3 4 5 6 7 8 9 10 11
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <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
1 2
| <TextBox Style="{DynamicResource TextBoxStyle}"/> <TextBox Style="{DynamicResource TextBoxStyle}"/>
|
其实每个控件本身就是一颗UI元素树,WPF可以看做两颗树,LogicalTree和VisualTree树,这两棵树的交点就是ControlTemplate。
ItemsControl的PanelTemplate
ItemsControl具有一个名为ItemsPanel的属性,其数据类型为ItemsPanelTemplate,它的作用是控制ItemsControl的条目容器。比如,ListBox条目都是纵向排列,现在改为横向排列。
1 2 3 4 5 6 7 8 9
| <Grid Margin="2"> <ListBox> <TextBlock Text="A"/> <TextBlock Text="B"/> <TextBlock Text="C"/> <TextBlock Text="D"/> <TextBlock Text="E"/> </ListBox> </Grid>
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <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}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <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标记。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <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>
|
1 2 3 4 5
| public class Unit { public int Price { set; get; } public string Year { set; get; } }
|

XML数据源
DataTemplate可以把XML数据中的元素名作为DataType,元素子节点和Attribute可以使用xpath来访问。
上面的案例改造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <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文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <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数据。
1 2 3
| <StackPanel MenuItem.Click="StackPanel_Checked"> <Menu ItemsSource="{Binding Source={StaticResource ds}}"/> </StackPanel>
|
1 2 3 4 5 6
| 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就可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <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>
|
1 2 3 4 5 6 7 8 9
| private void Button_Click(object sender, RoutedEventArgs e) { 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采用数据驱动的方式,获取数据一般在底层就可以实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <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 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>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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);
}
|

GridView使用Template
DataTemplate的常用之处是GridViewColumn的CellTemplate属性。GridViewColumn默认的CellTemplate属性使用了一个TextBlock,如果想使用CheckBox呢?
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <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 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>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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类可以遍历控件内部的各个节点。