使用templatepart实现上篇文章的两个需求(header为空时隐藏headercontentpresenter,鼠标没有放在控件上时headercontentpresent半透明),虽然功能已经实现,但这种实现方式几乎无法进行扩展。例如,开发者无法通过继承或修改controltemplate来实现以下功能:
当然,这些需求也可以通过代码实现,但会变得更加复杂。大多数开发者熟悉C#,对XAML不太了解,容易选择用C#实现所有功能,将所有功能集中在一个地方并使用熟悉的语言处理。这种方法虽然有其优点,但既然在使用XAML平台,就应该尽可能利用XAML平台UI和代码分离的优点。
本文将使用ContentView2示例讲解VisualState如何实现上述需求,ContentView2和上篇文章的ContentView一样继承自HeaderedContentControl。
VisualState指定控件处于特定状态时的外观。控件的代码指定控件处于何种状态,控件的ControlTemplate中根节点包含VisualStateManager.VisualStateGroups附加属性,并在其中确定各个VisualState的外观。
以CheckBox为例,CheckBox基本上包含Unchecked、Checked、Indeterminate三种状态,它通过IsChecked的值在这三种状态中转换。
这三种状态的外观如下所示:
3. 确定VisualState要使用VisualState,首先要明确控件中包含哪些VisualState
在ContentView2中有两组VisualState:
其中“CommonStates”、“HeaderStates”称为VisualStateGroup,“Normal”、“PointerOver”等称为VisualState。在同一个VisualStateGroup中的VisualState是互斥的,控件始终只能处于每组状态中的一种。例如,控件只能处于NoHeader状态,或者HasHeader状态。
模板化控件可以使用TemplateVisualStateAttribute协定声明它的VisualState,用于通知控件的使用者有这些VisualState可用。TemplateVisualStateAttribute是可选的,而且就算控件声明了这些VisualState,ControlTemplate也可以不包含它们中的任何一个,并且不会引发异常。
ContentView2的TemplateVisualStateAttribute如下:
[TemplateVisualState(Name = NormalState, GroupName = CommonStates)]
[TemplateVisualState(Name = PointerOverState,GroupName =CommonStates)]
[TemplateVisualState(Name = NoHeaderState, GroupName = HeaderStates)]
[TemplateVisualState(Name = HasHeaderState, GroupName = HeaderStates)]
public class ContentView2 : HeaderedContentControl{
public const string CommonStates = "CommonStates";
public const string NormalState = "Normal";
public const string PointerOverState = "PointerOver";
public const string HeaderStates = "HeaderStates";
public const string NoHeaderState = "NoHeader";
public const string HasHeaderState = "HasHeader";
}public ContentView2(){
this.DefaultStyleKey = typeof(ContentView2);
}
private bool _isPointerEntered;
protected override void OnApplyTemplate(){
base.OnApplyTemplate();
UpdateVisualState(false);
}
protected override void OnPointerEntered(PointerRoutedEventArgs e){
base.OnPointerEntered(e);
_isPointerEntered = true;
UpdateVisualState();
}
protected override void OnPointerExited(PointerRoutedEventArgs e){
base.OnPointerExited(e);
_isPointerEntered = false;
UpdateVisualState();
}
protected override void OnHeaderChanged(object oldValue, object newValue){
base.OnHeaderChanged(oldValue, newValue);
UpdateVisualState();
}
internal virtual void UpdateVisualState(bool useTransitions = true){
if (_isPointerEntered)
VisualStateManager.GoToState(this, PointerOverState, useTransitions);
else
VisualStateManager.GoToState(this, NormalState, useTransitions);
if (Header == null)
VisualStateManager.GoToState(this, NoHeaderState, useTransitions);
else
VisualStateManager.GoToState(this, HasHeaderState, useTransitions);
}ContentView2的其它代码如上所示,在OnApplyTemplate、OnHeaderChanged及鼠标进入离开时使用VisualStateManager.GoToState(Control control, string stateName,bool useTransitions)更新VisualState。useTransitions这个参数指示是否使用 VisualTransition 进行状态过渡,简单来说即是VisualState之间切换时用不用VisualTransition里面定义的动画。
注意OnApplyTemplate中的这句代码:UpdateVisualState(false)。控件在加载ControlTemplate时就需要确定它的状态,一般这时候都不会使用过渡动画。
VisualStateManager.GoToState不会使控件重复进入某个状态,譬如如果控件已处于PointerOverState,再次调用VisualStateManager.GoToState(this, PointerOverState, useTransitions)不会触发任何操作,也不会打断正在执行的过渡动画或重复触发动画。
到这里为止ContentView2.cs的工作已经完成,接下来就是XAML的责任了。
编辑后结果如下:
<visualstatemanager.visualstategroups>
<visualstategroup x:name="CommonStates">
<visualstategroup.transitions>
<visualtransition generatedduration="0:0:0.5">
<visualtransition.generatedeasingfunction>
<cubicease easingmode="EaseInOut"></cubicease>
</visualtransition.generatedeasingfunction>
</visualtransition>
</visualstategroup.transitions>
<visualstate x:name="Normal">
<visualstate.setters>
<setter target="HeaderContentPresenter.(UIElement.Opacity)" value="0.5"></setter>
</visualstate.setters>
</visualstate>
<visualstate x:name="PointerOver">
<visualstate.setters>
<setter target="HeaderContentPresenter.(UIElement.Opacity)" value="1"></setter>
</visualstate.setters>
</visualstate>
</visualstategroup>
<visualstategroup x:name="HeaderStates">
<visualstate x:name="NoHeader">
<visualstate.setters>
<setter target="HeaderContentPresenter.(UIElement.Visibility)" value="Collapsed"></setter>
</visualstate.setters>
</visualstate>
<visualstate x:name="HasHeader">
</visualstate>
</visualstategroup>
</visualstatemanager.visualstategroups>
从XAML中可以看出VisualState子节点的Setter是关键所在,如PointerOver的VisualState通过Setter将HeaderContentPresenter的Opacity更改为1,满足了“当鼠标移动到控件控件上时,设置Header的Opacity=1”这个需求。
另外,VisualStateGroup.Transitions 节点定义了CommonStates在各个状态之间切换时的过渡动画。VisualStateManager.GoToState(this, PointerOverState, useTransitions) 中的参数useTransitions即是控制是否使用过渡动画。示例中使用的过渡动画为CubicEase,过渡时间为0.5秒。
需要注意的是不同VisualStateGroup之间尽量不要对同一个UI元素的同一个属性进行操作,否则会引起冲突。
这个主题不会详细讲解使用Blend修改VisualState,因为那会占用很多篇幅。幸好Blend在这方面做得很容易上手,而且多年来基本操作都没有变过,可以在网上找到很多这方面的文章。
以上就是[UWP 自定义控件]了解模板化控件(5):VisualState的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号