使用属性控制状态,并创建一个方法帮助状态间的转换。如上面的UpdateVisualStates(bool useTransitions)。当属性值改变或其它有可能影响VisualState的事件发生都可以调用这个方法,由它统一管理控件的VisualState。注意一个控件应该最多只有几种VisualStateGroup,有限的状态才容易管理。
TemplateVisualStateAttribute协定自定义控件可以使用TemplateVisualStateAttribute协定声明它的VisualState,用于通知控件的使用者有这些VisualState可用。这很好用,尤其是对于复杂的控件来说。上面代码也包含了这个协定:
[TemplateVisualState(Name = StateExpanded, GroupName = GroupExpansion)] [TemplateVisualState(Name = StateCollapsed, GroupName = GroupExpansion)]TemplateVisualStateAttribute是可选的,而且就算控件声明了这些VisualState,ControlTemplate也可以不包含它们中的任何一个,并且不会引发异常。
7. Trigger、TemplatePart及VisualState之间的选择正如Expander所示,Trigger、TemplatePart及VisualState都可以实现类似的功能,像这种三种方式都可以实现同一个功能的情况很常见。
在过去版本的Blend中,编辑ControlTemplate可以看到“状态(States)”、“触发器(Triggers)”、“部件(Parts)”三个面板,现在“部件”面板已经消失了,而“触发器”从Silverlight开始就不再支持,以后也应该不会回归(xaml standard在github上有这方面的讨论(Add Triggers, DataTrigger, EventTrigger,___) [and-or] VisualState · Issue #195 · Microsoft-xaml-standard · GitHub[https://github.com/Microsoft/xaml-standard/issues/195])。现在看起来是VisualState的胜利,其实在Silverlight和UWP中TemplatePart仍是个十分常用的技术,而在WPF中Trigger也工作得很出色。
如果某个功能三种方案都可以实现,我的选择原则是这样:
需要向控件发出命令的,如响应点击事件,就用TemplatePart;
简单的UI,如隐藏/显示某个元素就用Trigger;
如果要有动画,并且代码量和使用Trigger的话,我会选择用VisualState;
几乎所有WPF的原生控件都提供了VisualState支持,例如Button虽然使用ButtonChrome实现外观,但同时也可以使用VisualState定义外观。有时做自定义控件的时候要考虑为常用的VisualState提供支持。
8. 结语VisualState是个比较复杂的话题,可以通过我的另一篇文章理解ControlTemplate中的VisualTransition更深入地理解它的用法(虽然是UWP的内容,但对WPF也同样适用)。
即使不自定义控件,学会使用ControlTemplate也是一件好事,下面给出一些有用的参考链接。
9. 参考创建具有可自定义外观的控件 Microsoft Docs
通过创建 ControlTemplate 自定义现有控件的外观 Microsoft Docs
Control Customization Microsoft Docs
ControlTemplate Class (System_Windows_Controls) Microsoft Docs