PF的UI框架,用户可以轻松地使用代码控制控件的外观。一个控件在鼠标进入的时候背景变成蓝色,下面这段代码实现:
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
Background = new SolidColorBrush(Colors.Blue);
}
但是这样做代码和UI过于耦合,难以扩展。应该是使用代码告诉ControlTemplate去改变外观,或者控制ControlTemplate中可用的元素进入某个状态。
MyExpander是一个HeaderedContentControl,它包含一个IsExpanded用于指示当前是展开还是折叠。ControlTemplate中包含ExpanderToggleButton及ContentPresenter两个元素。
代码:
public class MyExpander : HeaderedContentControl
{
public MyExpander()
{
DefaultStyleKey = typeof(MyExpander);
}
public bool IsExpanded
{
get => (bool)GetValue(IsExpandedProperty);
set => SetValue(IsExpandedProperty, value);
}
public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.Register(nameof(IsExpanded), typeof(bool), typeof(MyExpander), new PropertyMetadata(default(bool), OnIsExpandedChanged));
protected virtual void OnIsExpandedChanged(bool oldValue, bool newValue)
{
if (newValue)
OnExpanded();
else
OnCollapsed();
}
通常ControlTemplate中元素都通过TemplateBinding获取控件的属性值。但需要双向绑定的话,就是RelativeSource
RelativeSource有几种模式,分别是:
FindAncestor: 引用数据绑定元素的父链中的上级。 这可用于绑定到特定类型的上级或其子类。
PreviousData: 允许在当前显示的数据项列表中绑定上一个数据项(不是包含数据项的控件)。
Self: 引用正在其上设置绑定的元素,并允许你将该元素的一个属性绑定到同一元素的其他属性上。
emplatedParent: 引用应用了模板的元素,其中此模板中存在数据绑定元素。。
ControlTemplate中主要使用RelativeSource Mode=TemplatedParent的Binding,它相当于TemplateBinding的双向绑定版本。,主要是为了可以和控件本身进行双向绑定。ExpanderToggleButton.IsChecked使用这种绑定与Expander的IsExpanded关联,当Expander.IsChecked为True时ExpanderToggleButton处于选中的状态。
代码:
IsChecked="{Binding IsExpanded,RedlativeSource={RelativeSource
Mode= TemplatedParent},Mode=TwoWay}"
可以为ControlTemplate添加Triggers,内容为Trigger或EventTrigger的集合,Triggers通过响应属性值变更或事件更改控件的外观。
代码:
<ControlTemplate TargetType="{x:Type local:ExpanderUsingTrigger}">
<Border Background="{TemplateBinding Background}">
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded"
Value="True">
<Setter Property="Visibility"
TargetName="ContentPresenter"
Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
为了表明控件期待在ControlTemplate存在某个特定部件,防止编辑ControlTemplate的开发人员删除它,控件上会添加添加TemplatePartAttribute协定。
[TemplatePart(Name =ContentPresenterName,Type =typeof(UIElement))]
意思是期待在ControlTemplate中存在名称为 "ContentPresenterName",类型为UIElement的部件。
TemplateVisualStateAttribute协定:
自定义控件可以使用TemplateVisualStateAttribute协定声明它的VisualState,用于通知控件的使用者有这些VisualState可用。
[TemplateVisualState(Name = StateExpanded, GroupName = GroupExpansion)]
[TemplateVisualState(Name = StateCollapsed, GroupName = GroupExpansion)]
TemplateVisualStateAttribute是可选的,而且就算控件声明了这些VisualState,ControlTemplate也可以不包含它们中的任何一个,并且不会引发异常。
需要向控件发出命令的,如响应点击事件,就用TemplatePart。
简单的UI,如隐藏/显示某个元素就用Trigger。
如果要有动画,并且代码量和使用Trigger的话,我会选择用VisualState。