WPF Template
ControlTemplate
在 WPF 中,控件的 ControlTemplate 用于定义控件的外观。 可以通过定义新的 ControlTemplate 并将其分配给控件来更改控件的结构和外观。
控件模板是通过设置 Control.Template 属性来应用的
https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/controls/how-to-create-apply-template
一般在ControlTemplate中,使用的绑定是TemplateBinding,使用此TemplateBinding后,就无需再指定Source,RelatvieSource或者DataContext
TemplateBinding指向的就是ControlTemplate中的TargetType类型的对象。
注意,在ControlTemplate中,最外层的容器,不要使用TemplateBinding来绑定控件的Margin,Width,Height,HorizontalAlignment,VertialAlignment,Focus,IsHitTestVisible等属性,不然会引发一些问题,
可以这么理解,每个控件都有一个默认的透明的矩形区域,而ControlTemplate定义的是矩形区域内部的控件的显式,控件模板默认会填充满整个矩形区域。
如果给ControlTemplate中的最外层容器,设置Margin TemplateBinding,其实是设置控件模板和默认矩形区域的间距,而不是控件(每个默认的透明的矩形区域)之间的间距,并不能起到效果。
<ControlTemplate x:Key="roundbutton" TargetType="Button">
<Grid>
<Ellipse Fill="{TemplateBinding Background}"
Stroke="{TemplateBinding Foreground}" />
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<!-- 在样式中设置ControlTemplate -->
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate../>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Button">
<Setter Property="Template" Value="{StaticResource ...}"/>
</Style>
<StackPanel Margin="10">
<Label>Unstyled Button</Label>
<Button>Button 1</Button>
<Label>Rounded Button</Label>
<Button
Template="{StaticResource roundbutton}">Button 2</Button>
</StackPanel>
但是如果在ControlTemplate中,要绑定到控件本身设置的附加属性上,则使用RelativeSoruce + TemplateBinding。
<ControlTemplate>
<DockPanel
Visibility="{Binding Path=(hc:InfoElement.Title)
,RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource String2VisibilityConverter}}" />
</ControlTemplate>
绑定到附加属性:
DataTemplate
常见的DataTemplate:
ItemControls中的ItemTemplate
HeaderedItemsControl 中的HierarchicalDataTemplate ,如MenuItem中,或者TreeView中
一般在DataTemplate中,使用的绑定就是Binding到具体的Field,当把Template应用到具体控件后,就会复用具体控件树上的DateSource。
ItemTemplate一般只能设置每一行的数据展现形式,但是考虑,如果在DataGrid中,要使用CellTemplateSelector,来设置每一列,甚至每一个Cell的单元格数据模板,则在CellTemplateSelector中只能获取到每一行对应的Model,无法获取到该单元格对应的属性,进而无法根据单元格中的值,来设置CellTemplate。
解决方法:配置使用ContentControl和ContentTemplateSelector来设置。
<DataGrid ItemsSource="{Binding Items}"
<DataGrid.Columns>
<DataGridTemplateColumn IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!--当使用ContentControl的TemplateSelecotr后,可以实现更佳灵活的变化。 -->
<ContentControl Content="{Binding Col1}"
ContentTemplateSelector="{StaticResource CellDataTemplateSelector}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Window x:Class="SDKSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="HierarchicalDataTemplate Sample"
xmlns:src="clr-namespace:SDKSample">
<DockPanel>
<DockPanel.Resources>
<HierarchicalDataTemplate
DataType = "{x:Type src:League}"
ItemsSource = "{Binding Path=Divisions}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType = "{x:Type src:Division}"
ItemsSource = "{Binding Path=Teams}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
</DockPanel.Resources>
<TreeView>
<TreeViewItem
ItemsSource="{Binding Source={StaticResource MyList}}"
Header="My Soccer Leagues" />
</TreeView>
</DockPanel>
</Window>
TemplateSelector
public class Animal
{
public string Name { get; set; }
public string Age { get; set; }
}
public class Cat : Animal
{
public string CatType { get; set; }
}
public class Dog : Animal
{
public string DogType { get; set; }
}
public partial class MainWidnowViewModel:ObservableObject
{
[ObservableProperty]
private ObservableCollection<Animal> selectorItems;
public MainWidnowViewModel()
{
selectorItems = new ObservableCollection<Animal>();
selectorItems.Add(new Dog
{
Age = "1",
DogType = "DogType12",
Name = "Dog12"
});
selectorItems.Add(new Cat
{
Age = "1",
CatType = "CatType13",
Name = "Cat13"
});
}
}
public class CusListViewItemSelector : DataTemplateSelector
{
public DataTemplate DogTemplate { get; set; }
public DataTemplate CatTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is Dog)
{
return DogTemplate;
}
else if (item is Cat)
{
return CatTemplate;
}
return base.SelectTemplate(item, container);
}
}
<Window x:Class="WpfFeature.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:model="clr-namespace:WpfFeature.Models"
xmlns:selector="clr-namespace:WpfFeature.STS"
mc:Ignorable="d"
DataContext="{StaticResource mainViewModel}"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<!--定义DataTemplate -->
<DataTemplate DataType="{x:Type model:Dog}" x:Key="DogTemp">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DogType}" Foreground="Green"/>
<TextBlock Text="{Binding Name}" Foreground="Green"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type model:Cat}" x:Key="CatTemp">
<StackPanel Orientation="Vertical">
<RadioButton Content="{Binding CatType}" Foreground="Orange"/>
<TextBlock Text="{Binding Name}" Foreground="Green"/>
</StackPanel>
</DataTemplate>
<!--定义Selector -->
<selector:CusListViewItemSelector x:Key="cusListViewSelector"
DogTemplate="{StaticResource DogTemp}"
CatTemplate="{StaticResource ResourceKey=CatTemp}"/>
</Window.Resources>
<ListView ItemsSource="{Binding SelectorItems}"
ItemTemplateSelector="{StaticResource cusListViewSelector}">
</ListView>
</Window>
TemplateSelector,DataTrigger,Control中的子部件通过MultiBinding和Converter单独控制状态,
三种方式均能实现,某属性改变后,修改整个自定义控件的外观。
TemplateSelector:只能绑定Content或者Item,无法绑定更多的数据