
El Orfanato《孤儿院》很不错的一部西班牙电影,很牛的一部片子!该片的制片人Guillermo del Toro曾是二年前获奖无数的《潘神的迷宫》的导演.影片很不错,可以当一片悬疑片去解读,也可以当一部温情电影,如果愿花功夫,可以当一部心理片来看,去更深层的解读一下片中的空间与幻想间的关系
Let your soul be your pilot…

El Orfanato《孤儿院》很不错的一部西班牙电影,很牛的一部片子!该片的制片人Guillermo del Toro曾是二年前获奖无数的《潘神的迷宫》的导演.影片很不错,可以当一片悬疑片去解读,也可以当一部温情电影,如果愿花功夫,可以当一部心理片来看,去更深层的解读一下片中的空间与幻想间的关系
在Silverlight做逐帧事件可以用DispatcherTimer计时器,或使用一个空的Storyboard,在每次Completed事件中写你的执行代码然后再启动Storyboard,但是哪一种更好呢?这里有一个解释来自Adam Kinney:
The DispatcherTimer is a lower resolution timer than the timer behind the Storyboard class, which causes loss in fidelity. Additionally, the Storyboard execution is more stable across the different supported OSs and Browsers. I’ll put together a sample to show the comparision.
不管怎样使用空的Storyboard来做逐帧是一个很好的解决方案,为此我设计了一个用于替代DispatcherTimer的StoryboardTimer类,可以在程序中和使用DispatcherTimer一样使用:
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 57 58 59 60 61 | public class StoryboardTimer { private TimeSpan interval; public TimeSpan Interval { get { return interval; } set { interval = value; sbTimer.Duration = new Duration(interval); } } public event RoutedEventHandler Tick; private void RaiseTick() { if (Tick != null) { Tick(this, new RoutedEventArgs()); } } Storyboard sbTimer; private bool startFlag; public static int StoryboardTimerCount = 0; public StoryboardTimer() { sbTimer = new Storyboard(); startFlag = false; sbTimer.Completed += new EventHandler(sbTimer_Completed); (Application.Current).Resources.Add("sbTimer"+StoryboardTimer.StoryboardTimerCount, sbTimer); StoryboardTimer.StoryboardTimerCount++; } void sbTimer_Completed(object sender, EventArgs e) { RaiseTick(); if (startFlag) { sbTimer.Begin(); } } public void Start() { startFlag = true; sbTimer.Begin(); } public void Stop() { startFlag = false; } } |
使用方法
1 2 3 4 5 6 7 8 | StoryboardTimer timer= new StoryboardTimer(); timer.Interval = TimeSpan.FromSeconds(frameRate); timer.Tick += new RoutedEventHandler(timer_Tick); void timer_Tick(object sender, RoutedEventArgs e) { //dosomething } |
[原文地址]
这是有关”部件与状态”模式系列的最后一篇!
今天,我们讨论一些关于如何使用”部件与状态”模式的使用建议.我们还会看看有关VisualStageManager在未来的Silverlight及WPF中的情况.
“部件与状态”模式使用推荐
1.在UserControls用户控件和Custom Control自定义控件中使用”部件与状态”模式
就像我们在第一篇中提到的,”部件与状态”模式只是一个模式.在实际运行中并不是强制的,你可以在创建控件时不采用这种方式.
但是,我们觉得这是一个很好的模式.而且Blend针对自定义控件的的可视化编辑方式只支持这种模式.
虽然我们这一系列一直关注在自定义控件Custom Controls中的VSM,但是你同样可以在用户控件User Control中使用他.
2.自行命名VSM xmlns
因为一个已知的Silverlight 2 Beta 2 Bug,你必须在头部申明VisualStateManager的xmlns.
1 | xmlns:vsm=“clr-namespace:System.Windows;assembly=System.Windows” |
3.命名约定
为了控件的一致性,我们推荐使用如下的一些命名约定.
![]()
4.CommonStates 和 FocusStates 比较特殊
许多控件都使用了这二个状态组
![]()
如果你的控件也有这些状态的特征,为了一致性,我们推荐使用相同的组名和名称
5.能够适应模板中没有Parts & States的情况
有很多原因能说明为什么控件模板中没有给一些部件和状况:设计师可能没有创建等原因.
需要有一些良好的编写习惯去防止由于部件遗失造成的程序崩溃.
注意:VisualStateManager.GoToState()方法已经有这种预防措施,当VisualState没有找到时,会返回false.
6.考虑支持一个”fallback”状态(当状态不存在时,用另一状态替代)
在复杂的控件中,可以提供一个fallback控制当某些状态不存在时提供一种替代方案
1 2 3 4 | if (VisualStateManager.GoToState(this, “FocusContent”, useTransitions) == false) { VisualStateManager.GoToState(this, “Focus", useTransitions); } |
这个处理方式的好处显而易见:当设计师没有提供合适的状态时,控件能够继续进行一些视觉上的正常表现.
但也有一些不好的影响:fallback状态机制并不是完全整合在”部件与状态”模式中,这意味着Blend软件并不能很好的识别出来(在可视化编辑中)
所以,请减少使用fallback状态并只在控件非常复杂时才使用.
同样,如果你认为这种方式对你有用的话,让我们知道!我们乐意接受你的反馈.
7.子类的状态应该添加在新的状态组中(而不是已存在的状态组)
如你所知,每一个状态组是orthogonal(同时使用二个组中的二个状态).这使得子类很容易添加新的状态组.例如,你可以创建一个StackButton从Button继承并添加一个StackState组:
![]()
这个可以正常运作因为StackState状态组的状态是完全独立于Button的CommonStates & FocusStates.
然尔,如果你想把一个新的状态加到一个已经存在的状态组中,这个状态组的逻辑就变得混乱.就无法保证在状态发生时找到正确的状态.
我们这个例子简单一些.BasicControl定义了二个状态在CommonStates中:Normal,MouseOver.创新的逻辑是:
现在,ExtendedControl从BasicControls继承并想加一个Pressed状态,这个逻辑状态的目标可能是:
然尔,并没有好的方式让ExtendedControl去添加有关(而且鼠标按钮没有按下)MouseOver状态的检测,因为这个逻辑存在于Button基类中.
这一切都说明:子类通常只能把状态加到新的状态组中.但我们推荐如果是全新的状态(不破坏原有逻辑的),可以加到状态组中.
注意:为在未来解决这些问题我们有不同的方式(有好的也有坏的).我们现在正在提供一个Triggers-Based基于触发的解决方案.如果想了解更多,继续阅读.
展望未来
VSM & Windows Presentation Foundation
Silverlight的”控件与状态”模式改变了WPF存在的很多现有特性(像ControlTemplates,GetTemplateChild() Hellper,etc).
![]()
然尔,这些相关的特性如VisualStateManager和相关的类并不存在于WPF中.好消息是我们在下一版的WPF中会包含VisualStageManager!
为了让大家能够把Silverlight 2的控件和皮肤移植到WPF,下一版.NET Framework会支持得更好一些.为帮助我们实现,我们会在下一版的完整WPF发售前,就中提供一个包含VisualStateManager的程序集.现然这个计划还为时尚早,未来会提供时间表的细节.
未来的Silverlight特性
经常有人问在Silverlight控件模型中,Triggers触发器在哪里?
有很多原因使得我们不打算在Silverlight 2 的发布中加入触发器.一个主要的技术挑战是我们的属性系统架构不够达到能支持触发器.这在未来的Silverlight会改变,并会加入触发器的支持.
触发器和VSM之间如何协作呢?我们初步的方案是这样构想的:
提供一个GoToState触发器去调用状态变化.
设计师会有一个选项去使用内置的控件状态(不使用视觉状态变化的逻辑去控制).或者,设计师愿意在XAML中使用所有的触发器和VSM状态变化.也可能设计师能够自行添加新的状态到新状态组或存在的状态组中,而不去考虑控件的代码的情况下.
更多激动的特性会到来!
结束
这就是这四篇的有关Silverlight 2 “部件与状态”模式的全部了.如果你有问题,请反馈给我们.
如果这一系列还不够了解VisualStateManager,这有一些其它资源:
做了一些Silverlight开发应该知道,在Loaded事件之后的那个handler之中,画面并没有实际的被layout出来,在Loaded的时候是获得不了ActualWidth或其它相关值的,而只有在Layoutupdated事件中才能获得,但Layoutupdated事件是经常会运行,容易消耗资源,因此有必要找到一个方法,在画面正确渲染出来后,也就是被layouted时,来进行一些操作,之前在WPF中采用过这样一个方法,在这几天也在想办法在Silverlight找寻类似的方法,结论代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //构造中加Loaded事件 Loaded+=new RoutedEventHandler(Page_Loaded); //Loaded Event handler void Page_Loaded(object sender, RoutedEventArgs e) { Debug.WriteLine("Loaded Event:"+this.ActualWidth); Thread thread = new Thread(new ThreadStart(() => { this.Dispatcher.BeginInvoke(new Action(() => { Debug.WriteLine("Thread Start After Loaded Event:" + ActualWidth); })); })); thread.Start(); } |
输出结果:
1 2 | Loaded Event:0 Thread Start After Loaded Event:400 |
在我的项目中已经能很准确的获得ActualWidth,未经更复杂的layout情况下的证实,但我想应该是目前我能用到的唯一的解决方案,在构造中调用线程也是一样的效果,简单写一下,可以像有一个UILoaded事件一样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public partial class Page : UserControl { public Page() { InitializeComponent(); (new Thread(new ThreadStart(() => { Dispatcher.BeginInvoke(UILoaded); }))).Start(); } void UILoaded() { Debug.WriteLine("UILoaded:" + ActualWidth); } } |
如果你不想在new一个DoubleAnimation()后,做大量的From,To,Duration的操作的话,可以试试C#所支持的简写对象的方式,而且在VS的智能感知中就能原生的支持

简写前
1 2 3 4 | DoubleAnimation opacityAnimation = new DoubleAnimation() opacityAnimation.From = 0; opacityAnimation.To = 1; opacityAnimation.Duration = duration; |
简写后
1 | DoubleAnimation opacityAnimation = new DoubleAnimation() { From = 0, To = 1, Duration = duration}; |
[原文地址]
这是系列教程的第三篇.
上一次,你学会了如何对现有的控件使用VisualStateManager来改变皮肤外观.在这一篇中,你会看到如何创建基于”部件”与”状态”的自定义控件.同时我们还会探索创建一些更精密复杂的视觉过渡效果.
视觉状态管理器VisualStageManger
在上一篇中我们曾简述过,但现在我们正式的介绍VisualStageManager视觉状态管理器
![]()
VisualStateManager是一个类,用来管理控件的视觉状态.”Visual”是关键字(用来管理视觉,非逻辑)-控件的逻辑仍然只负责逻辑状态.
VSM暴露PME的二个片段:
上一次,我们专注于XAML中的VisualStageGroups属性.今天,我们深入到控件的代码中的GoToStage()方法.
WeatherControl
我们今天会来看一个简单的自定义控件 WeatherControl. 控件的部分代码可以在下面看到.(注意:为了可读性,我折叠了一些代码片段,你可以从这里找到完整的代码.)
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 | public class WeatherControl : Control { public WeatherControl() { DefaultStyleKey = typeof(WeatherControl); } // OnApplyTemplate() public override void OnApplyTemplate() { base.OnApplyTemplate(); } // Temperature DP public static readonly DependencyProperty TemperatureProperty = DependencyProperty.Register("Condition", typeof(Condition), typeof(WeatherControl), null); public string Temperature { get { /*…*/ } set { /*…*/ } } // Condition DP public static readonly DependencyProperty ConditionProperty = DependencyProperty.Register("Condition", typeof(Condition), typeof(WeatherControl), new PropertyMetadata(new PropertyChangedCallback(WeatherControl.OnConditionPropertyChanged))); public Condition Condition { get { /*…*/ } set { /*…*/ } } // ConditionDescription DP public static readonly DependencyProperty ConditionDescriptionProperty = DependencyProperty.Register("ConditionDescription", typeof(string), typeof(WeatherControl), null); public string ConditionDescription { get { /*…*/ } set { /*…*/} } // Property change notification private static void OnConditionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { WeatherControl weather = d as WeatherControl; //… weather.OnWeatherChange(null); } // OnWeatherChange virtual protected virtual void OnWeatherChange(RoutedEventArgs e) { } } |
你可以看看我们的WeatherControl…
如果让我们的WeatherControl能够使用VSM来换皮肤,我们需要:
Here we go!
定义控件约定
控件的代码负责描述控件的约定.意味着必须声明任意和所有的期望出现的部件(Parts)与状态(Stages).这是使用metadata完成的在类中的声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [TemplatePart(Name="Core", Type=typeof(FrameworkElement))] [TemplateVisualState(Name="Normal", GroupName="CommonStates")] [TemplateVisualState(Name="MouseOver", GroupName="CommonStates")] [TemplateVisualState(Name="Pressed", GroupName="CommonStates")] [TemplateVisualState(Name="Sunny", GroupName="WeatherStates")] [TemplateVisualState(Name="PartlyCloudy", GroupName="WeatherStates")] [TemplateVisualState(Name="Cloudy", GroupName="WeatherStates")] [TemplateVisualState(Name="Rainy", GroupName="WeatherStates")] public class WeatherControl : Control { … } |
在上面的片段中,有二个attribute类:
这些metadata并不是实时调用的.但他能被Expression Blend工具识别(以用于可视化编辑的支持).
这些在WeatherControl上的attributes给了控件下列的内容:
![]()
现在,我们来看看如何编写部件的操作代码.
找到部件
被命名的部件需要在控件代码中手动编写代码来找到相应的部件.这个操作在OnApplyTemplate()这个虚方法中执行当一个template被应用时.
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 | // OnApplyTemplate public override void OnApplyTemplate() { base.OnApplyTemplate(); CorePart = (FrameworkElement)GetTemplateChild("Core"); } // private CorePart property private FrameworkElement CorePart { get { return corePart; } set { FrameworkElement oldCorePart = corePart; if (oldCorePart != null) { oldCorePart.MouseEnter -= new MouseEventHandler(corePart_MouseEnter); oldCorePart.MouseLeave -= new MouseEventHandler(corePart_MouseLeave); oldCorePart.MouseLeftButtonDown -= new MouseButtonEventHandler(corePart_MouseLeftButtonDown); oldCorePart.MouseLeftButtonUp -= new MouseButtonEventHandler(corePart_MouseLeftButtonUp); } corePart = value; if (corePart != null) { corePart.MouseEnter += new MouseEventHandler(corePart_MouseEnter); corePart.MouseLeave += new MouseEventHandler(corePart_MouseLeave); corePart.MouseLeftButtonDown += new MouseButtonEventHandler(corePart_MouseLeftButtonDown); corePart.MouseLeftButtonUp += new MouseButtonEventHandler(corePart_MouseLeftButtonUp); } } } |
你需要使用GetTemplateChild() 这个helper方法来找到模板中的被命名元素.
在上面的例子中,我们找到了”Core”部件,这是一个我们会用于确定当控件引发MouseOver或Pressed状态时的部件.值得注意的是setter访问器的逻辑声明并不是在template中声明的.这很重要,因为控件需要足够健全以便于当某个部件在template中没有时也能够正常工作.
初始化状态改变
控件代码负责向VisualStateManager告之什么时候状态改变.因此,代码必须维护着logical state machine与visual state machine的状态.
所有Silverlight 2的内置控件都创建了简单的helper方法去辅助状态变化.我们推荐你使用这个简单的模式:
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 | // GoToState() helper private void GoToState(bool useTransitions) { // Go to states in NormalStates state group if (isPressed) { VisualStateManager.GoToState(this, "Pressed", useTransitions); } else if (isMouseOver) { VisualStateManager.GoToState(this, "MouseOver", useTransitions); } else { VisualStateManager.GoToState(this, "Normal", useTransitions); } // Go to states in WeatherStates state group if (Condition == Condition.PartlyCloudy) { VisualStateManager.GoToState(this, "PartlyCloudy", useTransitions); } else if (Condition == Condition.Sunny) { VisualStateManager.GoToState(this, "Sunny", useTransitions); } else if (Condition == Condition.Cloudy) { VisualStateManager.GoToState(this, "Cloudy", useTransitions); } else { VisualStateManager.GoToState(this, "Rainy", useTransitions); } } |
GoToStage helper方法包含几个用于确定当前视觉状态的语法.他告诉VisualStateManager去初始化合适的状态变化.然后调用静态方法
public static bool VisualStateManager.GoToState(Control control, string stateName, bool useTransitions)
就像你所看到的,这个方法中…
大部分控件作者会在三个情况下调用GoToStage() helper
在我们的WeatherControl中,我们添加以下调用:
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 | // OnApplyTemplate public override void OnApplyTemplate() { base.OnApplyTemplate(); CorePart = (FrameworkElement)GetTemplateChild("Core"); GoToState(false); } // Property Change Notifications protected virtual void OnWeatherChange(RoutedEventArgs e) { GoToState(true); } // Event Handlers void corePart_MouseEnter(object sender, MouseEventArgs e) { isMouseOver = true; GoToState(true); } void corePart_MouseLeave(object sender, MouseEventArgs e) { isMouseOver = false; GoToState(true); } void corePart_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { isPressed = true; GoToState(true); } void corePart_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { isPressed = false; GoToState(true); } |
我们在下面情况时,需要初始化状态变化:
添加内置Style
现在我们看看我们的控件逻辑!
我使我们的ControlTemplate变得非常有趣,有趣意味着模板会变得冗长..不管怎样,看看编辑后的版本:
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 | <! – VisualStateManager –> <vsm:VisualStateManager.VisualStateGroups> <! – CommonStates StateGroup–> <vsm:VisualStateGroup x:Name="CommonStates"> <! – CommonStates States–> <vsm:VisualState x:Name="Normal"/> <vsm:VisualState x:Name="MouseOver" Storyboard="{StaticResource Glow}"/> <vsm:VisualState x:Name="Pressed" Storyboard="{StaticResource Burn}"/> <! – CommonStates Transitions–> <vsm:VisualStateGroup.Transitions> <vsm:VisualTransition Duration="0:0:.6"/> <vsm:VisualTransition To="Pressed" Duration="0:0:.4"/> <vsm:VisualTransition From="Pressed" Duration="0:0:.4"/> </vsm:VisualStateGroup.Transitions> </vsm:VisualStateGroup> <! – WeatherStates StateGroup–> <vsm:VisualStateGroup x:Name="WeatherStates"> <! – WeatherStates States–> <vsm:VisualState x:Name="Sunny"/> <vsm:VisualState x:Name="PartlyCloudy" Storyboard="{StaticResource PartlyCloudyStoryboard}"/> <vsm:VisualState x:Name="Cloudy" Storyboard="{StaticResource CloudyStoryboard}"/> <vsm:VisualState x:Name="Rainy" Storyboard="{StaticResource RainyStoryboard}"/> <! – WeatherStates Transitions–> <vsm:VisualStateGroup.Transitions>: <vsm:VisualTransition Duration="0:0:.3"/> </vsm:VisualStateGroup.Transitions> </vsm:VisualStateGroup> </vsm:VisualStateManager.VisualStateGroups> |
你可以看到ControlTemplate中,我做了:
我们来运行一下!

添加专门的过渡效果Transitions
默认的过渡效果还可以.但,为了做得更好,我们加一些更多的自定义的视觉过渡效果.
下面是我们在不同weather状态下的不同外观:
![]()
当我们的控件从Sunny变为PartlyCloudy时,我们不想让云层效果慢慢动画过来,替代方法是,让他从左边进来.
![]()
为了让自定义的过渡效果像这个一样,你可以声明一个详细的过渡故事板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <! – WeatherStates Transitions–> <vsm:VisualStateGroup.Transitions> <! – Sunny to PartlyCloudy Transition –> <vsm:VisualTransition From="Sunny" To="PartlyCloudy" Duration="0:0:.5"> <Storyboard Duration="0:0:.5"> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="BottomCloud" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)"> <SplineDoubleKeyFrame KeyTime="00:00:00" Value="-150"/> <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="0" KeySpline="0.173,0.019,1,0.484"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="BottomCloud" Storyboard.TargetProperty="(UIElement.Opacity)"> <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/> <SplineDoubleKeyFrame KeySpline="0.173,0.019,1,1" KeyTime="00:00:00.2000000" Value="0.1"/> <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1" KeySpline="0,0,1,0.484"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </vsm:VisualTransition> <! – WeatherStates Default Transition –> <vsm:VisualTransition Duration="0:0:.3"/> </vsm:VisualStateGroup.Transitions> |
现在,当VisualStateManager为Sunny到PartlyCloudy状态变化生成动画时,不会花很长时间产生BottomCloud的透明度动画.会马上运行我们定义的这个详细的故事板动画.
为了更好的理解生成动画与详细故事板之间的作用关系,我们看下面的示例:
![]()
这里,我们有二个视觉状态:Foo & Bar.每一个动画有一个不同的属性.
这些动画是如何创建的?
返回到WeatherControl,我们同样加了明确的过渡效果为以下几个状态Sunny->PartlyCloudy, Sunny->Cloudy, and PartlyCloudy->Cloudy.

下一次
这就是我们学到的关于自定义控件中如何使用VSM,希望你能获得自定义详细过渡效果的乐趣.
下一次,在这系列教程的最后一篇,我们会给出一些使用”部件”与”状态”模式的推荐方式,你还会了解更我关于此模式在未来Silverlight及WPF中的规划
之前也有很多人问我,我当时也不敢肯定,觉得应该是没有,但结果是, Silverlight中的确支持对Clip的值进行动画,如图:

方法是这样,给对象建立Clip后,在故事板中,可以右键单击这个对象,Release Clip,把Clip对象分离出来, 然后在时间线上给Clip做动画,再在时间线上把Clip对象与原对象组合为带Clip的对象,Over!
需要补充的是,针对Clip Path的动画只能是针对Clip中的各节点进行控制,如果你对一个Path执行RendTranform中的各种变化,那么再把Clip组合回对象的话,就无法记录动画,如果想做位移,就只能调整各节点,或选择所有节点,进行一个运动..使Blend为这个Clip形成一些Segment Path方面的动画
这是四篇关于介绍Silverlight 2 控件的”部件”与”状态”系列中的第二篇.
今天,我们将概念进行实践,实现上次所说的CheckBox的换肤(如果你还没读过上一篇,请先读第一篇).
注意:为了方便阅读我尽可能减短XAML标签.你可以从这里下载完整的代码
CheckBox的控件模板(ControlTemplate)
控件模板定义自定义控件的视觉呈现.CheckBox的控件模板代码我们下面有贴出来.
视觉效果:
![]()
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 | <ControlTemplate TargetType="CheckBox"> <StackPanel x:Name="Root" …> <!– OuterBorder –> <Border Width="20" Height="20" … > <!– InnerBorder –> <Border x:Name="InnerBorder" … > <Grid> <!– Higlight–> <Border x:Name="HighlightBorder" … /> <!– Glow –> <Rectangle x:Name="Glow" Opacity="0" … /> <!– Checkmark Graphic–> <Path x:Name="Checkmark" Opacity="0" … /> <!– Indeterminate Rect–> <Rectangle x:Name="IndeterminateRect" Opacity="0" … /> </Grid> </Border> </Border> <!– ContentPresenter –> <ContentPresenter …/> </StackPanel> </ControlTemplate> |
在上面的控件模板中,你可能发现了有不少元素透明度Opacity为0.因为在控件的最基本的状态中,这些元素是不可见的.然而,在控件模板中并没有实现视觉上的交互,但当你点击CheckBox时,这些元素仍不可见.
我们来解决这个问题!
增加 VisualStates(视觉状态) & VisualStateGroups(视觉状态组)
基于上次的讨论,在”部件”与”状态”模式中我们引入了视觉状态和视觉状态组的概念.
在Silverlight 2,我们希望这些主意能成为基本的类的概念让用户使用时能把这些概念引入他们自己的类中,所以我们建立了VisualState和VisualStateGroup类,并由VisualStateManager来管理.
我们看看如何将VisualStates & VisualStateGroups加到我们自己的CheckBox皮肤中!
增加VisualStateGroups到CheckBox的控件模板(ContrlolTemplate)
CheckBox有二个主要的状态组(其实有3个,但为了简单示例,我们先忽略focsu这个组)
你可以把这些状态组加入控件模板中像下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <ControlTemplate TargetType="CheckBox"> <!– Root Visual –> <StackPanel x:Name="Root" … > <!– VisualStateManager–> <vsm:VisualStateManager.VisualStateGroups> <!– CommonStates StateGroup–> <vsm:VisualStateGroup x:Name="CommonStates"></vsm:VisualStateGroup> <!– CheckStates StateGroup–> <vsm:VisualStateGroup x:Name="CheckStates"></vsm:VisualStateGroup> </vsm:VisualStateManager.VisualStateGroups> <!– Rest of Template –> </StackPanel> </ControlTemplate> |
如XAML所示,使用状态组你需要做如下声明…
现在我们增加了VisualStateGroups,下一步是启用他们.
增加状态VisualStates到状态组VisualStateGroup
CheckBox在二个状态组下共需要七个状态.下图中的蓝色是默认的状态皮肤外观
![]()
让我们开始加四个状态到我们的CommonStates中
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 | <!– CommonStates StateGroup–> <vsm:VisualStateGroup x:Name="CommonStates"> <vsm:VisualStateGroup.Transitions> <vsm:VisualTransition Duration="0:0:.5" /> <vsm:VisualTransition Duration="0:0:0.8" To="MouseOver"/> <vsm:VisualTransition Duration="0:0:0.3" To="Normal"/> <vsm:VisualTransition Duration="0" From="MouseOver" To="Pressed"/> </vsm:VisualStateGroup.Transitions> <!– Normal State –> <vsm:VisualState x:Name="Normal"></vsm:VisualState> <!– MouseOver State –> <vsm:VisualState x:Name="MouseOver"> <Storyboard> <DoubleAnimation Storyboard.TargetName="Glow" Storyboard.TargetProperty="Opacity" Duration="0" To="1"/> </Storyboard> </vsm:VisualState> <!– Pressed State –> <vsm:VisualState x:Name="Pressed"> <Storyboard> <DoubleAnimation Storyboard.TargetName="HighlightBorder" Storyboard.TargetProperty="Opacity" Duration="0" To=".6"/> <ColorAnimation Storyboard.TargetName="InnerBorder" Storyboard.TargetProperty="(Border.BorderBrush).(GradientBrush.GradientStops)[0].(GradientStop.Color)" Duration="0" To="#FF000000"/> <ColorAnimation Storyboard.TargetName="InnerBorder" Storyboard.TargetProperty="(Border.BorderBrush).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Duration="0" To="#FF000000"/> </Storyboard> </vsm:VisualState> <!– Disabled State –> <vsm:VisualState x:Name="Disabled"> <Storyboard> <DoubleAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" Duration="0" To=".7"/> </Storyboard> </vsm:VisualState> </vsm:VisualStateGroup> |
我们来看看上面的VisualState元素…
如果你看了这些我们加入的特别的状态后:
为了清晰一些,我们看下面的一个有关各状态的截屏:

下面再看看CheckStates:
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 | <!– CheckStates StateGroup–> <vsm:VisualStateGroup x:Name="CheckStates"> <!– Unchecked State –> <vsm:VisualState x:Name="Unchecked"/> <!– Checked State –> <vsm:VisualState x:Name="Checked"> <Storyboard> <DoubleAnimation Storyboard.TargetName="Checkmark" Storyboard.TargetProperty="Opacity" Duration="0" To="1"/> </Storyboard> </vsm:VisualState> <!– Indeterminate State –> <vsm:VisualState x:Name="Indeterminate"> <Storyboard> <DoubleAnimation Storyboard.TargetName="IndeterminateRect" Storyboard.TargetProperty="Opacity" Duration="0" To="1"/> </Storyboard> </vsm:VisualState> </vsm:VisualStateGroup> |
我们为CheckStates状态组加了三个视觉状态:
下面是Check States的截屏:

Sweet!
我们创建了所有的视觉状态,接下来做什么呢?
现在,控件的代码使用VisualStateManager来管理各状态的变化.(下次你会学到控件代码到底在做什么)在控件模板中,你不需要为状态做任何检测或使用的操作.
来看看最新的换肤的CheckBox,演示点击这里.

增加视觉过渡VisualTransitions
如果你看了刚才的应用程序示例,你可能注意到各状态间的显示很突然,让整个用户体验很沉闷生硬.如何让各状态间的变化有过渡呢?在Silverlight,你需要在状态组中加入一些视觉过渡VisualTransitions.
为一个状态组中加入一个默认的视觉过渡
我们想让所有的CommonStates中的状态变化有0.5秒的过渡而在CheckStates中有0.2秒的过渡效果.你可以在各组中加入一个默认的VisualTransition视觉过渡.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <!– VisualStateManager–> <vsm:VisualStateManager.VisualStateGroups> <!– CommonStates StateGroup–> <vsm:VisualStateGroup |