经典论坛's Archiver

Iran 发表于 2008-6-26 08:39

[翻译]Part&States Model With VisualStateManager

转自[url=http://allan.flashempire.net/blog/?p=543]Allan.Blog()[/url]

一篇好文章,简单翻译出来与大家共享

VisualStateManager视觉管理器下的”Part(部件)与States(状态)”模式 [url=http://scorbs.com/2008/06/11/parts-states-model-with-visualstatemanager-part-1-of/]原文地址[/url]

在Silverlight 2 Beta 2中,我们为管理控件中的状态和过渡效果增加了重要的支持.为了帮助理解Parts & States Model(”部件与状态”模式),我会为此写一个4篇的系列文章去显示如何做到以下几点:

    * 使用”部件与状态”模式来创建控件的约定
    * 连接控件的逻辑到”部件与状态”
    * 使用状态和部件来设计控件模板

VisualStateManager(视觉状态管理器)是这一切得以实现的原因所在,我们喜欢简称为VSM,VSM可以使用在UserControl(用户控件)和Custom Control(自定义控件)中.

今天的内容介绍部件与状态模型的概念

(注意:文章假设你已经基本了解用户控件,自定义控件及控件模板,如果你还没了解,可以看看我在[url=http://scorbs.com/2008/03/09/mix08-creating-rich-dynamic-user-interfaces-with-silverlight-2-controls/]Mix08上的有关课程或[/url][url=http://scorbs.com/2008/06/10/teched-samples/]我在TechED上的课程[/url])

Let’s get started!

[b]“部件与状态”模式的产生原因[/b]

自定义控件是Silverlight控件中的一种,用于严格分离控件逻辑与控件的视觉呈现.这对于你想在自定义视觉呈现时不影响程序逻辑来说是非常有用的,或修改逻辑时不影响视觉呈现

尽管这种严格的分离有很多好处,但很难让设计师去理解在控件的模板中到底需要什么元素.这主要由于约定不够清晰
[img]http://scorbs.com/wp-content/uploads/2008/06/definingthecontrolcontract2-thumb.jpg[/img]
如果控件开发者提供了明确的控件约定,设计师就能为控件模板提供相应的素材.这样会使得控件的皮肤化变得容易(而”部件与状态”模式就是一种提供约定的方式)

[b]概念:”部件与状态”模式Parts & States Model[/b]

“部件与状态”模式是提供控件接口约定的一种方法

这是一种推荐的实现Silverlight 2 控件结构的方法.但这个方式在实际工作时不是强制的,你可以选择直接开发控件而不使用这种方式

由上所述,不但我们认为部件与状态模型是一个好的模式-Expression Blend也会支持这个模式(在IDE下直接可视化修改你的自定义控件的外观).因此,如
果你想让你的控件在Blend中被很好的支持(Skinnable可换肤),你应该使用”部件与状态”模式

深层次说,对于”部件与状态”模式有四个概念

    * 部件Parts
    * 状态States
    * 过度效果(状态之间的)Transitions
    * 状态组State Groups

[b]部件Parts[/b]

部件是控件模板中的被命名元素.控件的程序逻辑期望控件在模板中被定义(一种约定,定好名称,程序逻辑会按定好的名称去找这个控件,并操作他),因为程序逻辑会操作这些部件
[img]http://scorbs.com/wp-content/uploads/2008/06/parts-thumb.jpg[/img]
在上面的Slider滑条例子中,有四个部件,每一个都都能被控件的代码访问到.当UpRepeatButton被按下,控件代码会控制Thumb 在Track的范围内向右移动.当DownRepeatButton被按下,就相反.(程序逻辑之所以能完成这些操作,是因为这些控件的名称被约定好了, 我们只要遵守名称,放相应的对象在上面,并按这个名称命名,程序逻辑就知道这个部件是做什么用的,并操作他完成相应的功能)

[b]状态States[/b]
[img]http://scorbs.com/wp-content/uploads/2008/06/states-thumb.jpg[/img]
视觉状态(Visual states)为不同的逻辑状态(logical state)提供提供不同的控件显示.


For instance, the Button above has a light background when in the MouseOver state, and a

例如,在MouseOver状态,按钮显示高亮的背景色,在按下时显示一个较暗的背景.

[b]过渡效果Transitions[/b]

视觉过渡效果为控件几个视觉状态间转换提供一种过度的动画效果
[img]http://scorbs.com/wp-content/uploads/2008/06/transitions-thumb.jpg[/img]
如上,按钮背景从亮色变向淡色当由MouseOver转向Pressed状态时.(实际上中间有一个颜色过渡动画过程,并不是直接切到下一个状态的)

[b]状态组StateGroups[/b]

一个状态组由多个状态组成.可以有多个状态组,可在一个状态下你可以使用二个状态组下的不同的二个状态
[img]http://scorbs.com/wp-content/uploads/2008/06/stategroups-thumb.jpg[/img]
如上的CheckBox例子,有二个状态组:CommonStates和CheckStates.CheckBox可以同时有MouseOver和 Indeterminate状态因为这二个状态分属不同的状态组.另一方面,CheckBox不可能同时有Normal和MouseOver状态因为这二个在同一个状态组.

状态组是一个在Beta2中提出的新概念.帮助我们解决在Beta1中出现的状态过多的问题.CheckBox在Beta2中有7个状态(加上二个focus焦点状态).在Beta1中,则有12个状态(focus是部件而非状态).

[b]开始状态改变[/b]

当控件检测到逻辑上的状态改变时,会让他的视觉状态改变,引发视觉过渡动画效果来显示相应的视觉状态
[img]http://scorbs.com/wp-content/uploads/2008/06/logicdiagram-thumb.jpg[/img]
在上面的例子中,一个控件检测到MouseEnter事件.就引发视觉状态改变.控件的呈现会调用相应的过度效果然后再停止到MouseOver视觉状态

下篇内容

了解了部件和状态模式的概念,在下一部分,我们开始了解如何基于部件和状态模式来给CheckBox换肤

[[i] 本帖最后由 Iran 于 2008-6-26 08:57 编辑 [/i]]

Iran 发表于 2008-6-26 08:52

转自[url=http://allan.flashempire.net/blog/?p=544]Allan.Blog()[/url]
[url=http://scorbs.com/2008/06/18/parts-states-model-with-visualstatemanager-part-2-of-4/]原文地址[/url]
这是四篇关于介绍Silverlight 2 控件的”部件”与”状态”系列中的第二篇.

今天,我们将概念进行实践,实现上次所说的CheckBox的换肤(如果你还没读过上一篇,[url=http://allan.flashempire.net/blog/?p=543]请先读第一篇[/url]).

注意:为了方便阅读我尽可能减短XAML标签.你可以从这里[url=http://scorbs.com/silverlight/checkboxskin/final/CheckBoxSkin.zip]下载完整的代码[/url]

[b]CheckBox的控件模板(ControlTemplate)[/b]

控件模板定义自定义控件的视觉呈现.CheckBox的控件模板代码我们下面有贴出来.

视觉效果:
[img]http://scorbs.com/wp-content/uploads/2008/06/base-checkbox-thumb.jpg[/img]
XAML:
[code]<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>[/code]
在上面的控件模板中,你可能发现了有不少元素透明度Opacity为0.因为在控件的最基本的状态中,这些元素是不可见的.然而,在控件模板中并没有实现视觉上的交互,但当你点击CheckBox时,这些元素仍不可见.

我们来解决这个问题!

[b]增加 VisualStates(视觉状态) & VisualStateGroups(视觉状态组)[/b]

基于上次的讨论,在”部件”与”状态”模式中我们引入了视觉状态和视觉状态组的概念.

在Silverlight 2,我们希望这些主意能成为基本的类的概念让用户使用时能把这些概念引入他们自己的类中,所以我们建立了VisualState和VisualStateGroup类,并由VisualStateManager来管理.

我们看看如何将VisualStates & VisualStateGroups加到我们自己的CheckBox皮肤中!

[b]增加VisualStateGroups到CheckBox的控件模板(ContrlolTemplate)
[/b]
CheckBox有二个主要的状态组(其实有3个,但为了简单示例,我们先忽略focsu这个组)

    * CommonStates state group
    * CheckStates state group

你可以把这些状态组加入控件模板中像下面这样:
[code]<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>[/code]
如XAML所示,使用状态组你需要做如下声明…

    * 使用vsm的xmlns前缀
          o This is required in Silverlight 2 Beta 2 because of known bug.
            这是一个已知bug我们必须使用vsm的声明
          o xmlns:vsm=”clr-namespace:System.Windows;assembly=System.Windows”
    * 增加VisualStateManager.VisualStateManager属性
          o This attached dependency property should be on the ControlTemplate’s root visual.
            这是一个依赖属性应该在控件模板的root visual根视觉上.
          o 然后在下面定义另一个不同的状态组
    * 包含适当的状态组
          o 每个状态组是命名的并包含多个独立的状态.

现在我们增加了VisualStateGroups,下一步是启用他们.

[b]增加状态VisualStates到状态组VisualStateGroup[/b]

CheckBox在二个状态组下共需要七个状态.下图中的蓝色是默认的状态皮肤外观
[img]http://scorbs.com/wp-content/uploads/2008/06/stategroups-thumb1.jpg[/img]
[code]<!– 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>[/code]
我们来看看上面的VisualState元素…

    * 被命名的
          o 被命名,使得VisualStateManager可以在模板中找得到你的状态
    * 包含故事板
          o 故事板用于让状态改变时如何呈现视觉
          o 可以是一个”静态”故事板,意味着可以是0时长的故事板让状态变化时直接变化而不需要动画
          o 也可以是steady state animation持续不停的故事板.换句话说,一个非0时长的故事板有永远循环的行为.使得像是一个不停的动画循环效果.

如果你看了这些我们加入的特别的状态后:

    * Normal状态不包含故事板,因为Normal状态和控件的基础状态一样.
    * MouseOver状态变化Glow元素的透明度,让鼠标移上时有些发光效果.
    * Pressed状态变化CheckBox边框的颜色,也调整高光边框的透明度.
    * Disable状态让整个控件变灰.

为了清晰一些,我们看下面的一个有关各状态的截屏:
[img]http://scorbs.com/wp-content/uploads/2008/06/commonstates.jpg[/img]
下面再看看CheckStates:
[code]<!– 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>[/code]
我们为CheckStates状态组加了三个视觉状态:

    * UnCheck状态,和Normal状态一样,没有定义故事板,和控件基础状态一样.
    * Checked状态调整CheckMark图形的透明度.
    * Indeterminate状态调整Indeterminate矩形的透明度.

下面是Check States的截屏:
[img]http://scorbs.com/wp-content/uploads/2008/06/checkstates.jpg[/img]
Sweet!

我们创建了所有的视觉状态,接下来做什么呢?

现在,控件的代码使用VisualStateManager来管理各状态的变化.(下次你会学到控件代码到底在做什么)在控件模板中,你不需要为状态做任何检测或使用的操作.

来看看最新的换肤的CheckBox,[url=http://scorbs.com/silverlight/checkboxskin/notransitions/]演示点击这里.[/url]
[img]http://scorbs.com/wp-content/uploads/2008/06/app2.jpg[/img]

[b]增加视觉过渡VisualTransitions[/b]

如果你看了刚才的应用程序示例,你可能注意到各状态间的显示很突然,让整个用户体验很沉闷生硬.如何让各状态间的变化有过渡呢?在Silverlight,你需要在状态组中加入一些视觉过渡VisualTransitions.

[b]为一个状态组中加入一个默认的视觉过渡[/b]

我们想让所有的CommonStates中的状态变化有0.5秒的过渡而在CheckStates中有0.2秒的过渡效果.你可以在各组中加入一个默认的VisualTransition视觉过渡.

[code]<!– VisualStateManager–>
<vsm:VisualStateManager.VisualStateGroups>

    <!– CommonStates StateGroup–>
    <vsm:VisualStateGroup x:Name="CommonStates">
        <!– CommonStates Transitions–>
        <vsm:VisualStateGroup.Transitions>
        <vsm:VisualTransition Duration="0:0:.5" />
        </vsm:VisualStateGroup.Transitions>
    </vsm:VisualStateGroup>

    <!– CheckStates StateGroup–>
    <vsm:VisualStateGroup x:Name="CheckStates">
        <!– CheckStates Transitions–>
        <vsm:VisualStateGroup.Transitions>
        <vsm:VisualTransition Duration="0:0:.2" />
        </vsm:VisualStateGroup.Transitions>
    </vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>[/code]
在上面的XAML中,你创建了一个默认的VisualTransition并加到了VisualStateGroups.Transitions属性中,来看一下…

    * 有一个Duration属性
          o 这个告诉VisualStateManager你的过渡效果要持续多长时间

VisualStateManger依次会执行并创建过渡动画:

这说明了些什么,我们看看一个例子.

在Normal 到MouseOver间发生了什么?VisualStateManger检测到了MouseOver状态的故事板对Glow元素的透明度发生了动作,但 Normal状态下却没有.他会自动为Glow元素建立0-1的动画.其中0是在Normal状态下定义的值,而1是MouseOver状态下定义的值.

VisualStateManger在状态之间做如下事情:检查在二个状态间的属性并在二个值之间创建合适的过渡效果.结果是控件看起来状态切换得很平换而不需要写太多的XAML.

可以再看看现在的[url=http://scorbs.com/silverlight/checkboxskin/basictransitions/]运行示例.[/url]
[img]http://scorbs.com/wp-content/uploads/2008/06/app2.jpg[/img]
[b]为特别的状态变化创建VisualTransitions[/b]

一个默认的VisualTransition已经使CheckBox改变很多.但是这样的过渡在某些状态变化中看来还是有些笨拙.

例如,你想在MouseOver到Pressed的时候,让视觉突然变化,而不是缓慢过渡.这会使Click的感觉显得更即时一些.你可以按下面的这个方法来指定From和To属性:
[code]<!– CommonStates Transitions–>
<vsm:VisualStateGroup.Transitions>
    <vsm:VisualTransition Duration="0:0:.5" />
    <vsm:VisualTransition Duration="0:0:0.8" To="MouseOver"/>
    <vsm:VisualTransition Duration="0:0:0.2" From="Pressed"/>
    <vsm:VisualTransition Duration="0" From="MouseOver" To="Pressed"/>
</vsm:VisualStateGroup.Transitions>[/code]
你看到,VisualTransition同样有…

    * From 和 To 属性.
          o 这些属性可以使用状态的名称
          o 帮助VSM根据不同的二个状态变化来选择合适的过渡方式

通过检查这些属性,VisualStateManager为当前状态改变选择最明确的过渡方式.
[img]http://scorbs.com/wp-content/uploads/2008/06/types-of-transitions-thumb.jpg[/img]
当我们的CheckBox从MouseOver到Pressed时发生了什么?VisualStateManager找第一个From=” MouseOver”/To=”Pressed”的过渡,如果不存在,VSM就搜索单一个 To=”Pressed”的过渡,然后如果必要,找找一个From=”MouseOver”的过渡.最后.如果都没找到,就用默认的过渡来完成这个状态过渡.如果没有默认的过渡,就会使用0时长的过渡,也就会立刻跳到下一个状态.

通过给了更多的设定,我们的CheckBox现在看起来非常好!

运行最终的程序[url=http://scorbs.com/silverlight/checkboxskin/final]点这里[/url],你可以获得[url=http://scorbs.com/silverlight/checkboxskin/final/checkboxskin.txt]完整的XAML代码[/url]及[url=http://scorbs.com/silverlight/checkboxskin/final/CheckBoxSkin.zip]完整的程序代码[/url]

[img]http://scorbs.com/wp-content/uploads/2008/06/app2.jpg[/img]

下一篇

我们了解了VisualStagemanager的基本使用.

下一次,你会看如何从0开始搭建自定义的控件并使用”部件”及”状态”模式.你还会学到如何在模板中使用部件并做更复杂的过渡效果

Iran 发表于 2008-6-26 09:06

转自[url=http://allan.flashempire.net/blog/?p=547]Allan.Blog()[/url]
[url=http://scorbs.com/2008/06/23/parts-states-model-with-visualstatemanager-part-3-of-4]原文地址[/url]
这是系列教程的第三篇.

上一次,你学会了如何对现有的控件使用VisualStateManager来改变皮肤外观.在这一篇中,你会看到如何创建基于”部件”与”状态”的自定义控件.同时我们还会探索创建一些更精密复杂的视觉过渡效果.

[b]视觉状态管理器VisualStageManger[/b]

在上一篇中我们曾简述过,但现在我们正式的介绍VisualStageManager视觉状态管理器
[img]http://scorbs.com/wp-content/uploads/2008/06/vsm-thumb.jpg[/img]
VisualStateManager是一个类,用来管理控件的视觉状态.”Visual”是关键字(用来管理视觉,非逻辑)-控件的逻辑仍然只负责逻辑状态.

VSM暴露PME的二个片段:

    * 一个VisualStageGroups attached property(附加属性)
          o 这个属性放在控件模板的RootVisual并包含所有与外观有关的视觉状态与过渡效果
    * 一个静态GoToStage()方法
          o 这个方法的产生原因是因为VisualStageManager需要来控制控件的视觉由一个视觉状态过渡到其它状态

上一次,我们专注于XAML中的VisualStageGroups属性.今天,我们深入到控件的代码中的GoToStage()方法.

[b]WeatherControl[/b]

我们今天会来看一个简单的自定义控件 WeatherControl. 控件的部分代码可以在下面看到.(注意:为了可读性,我折叠了一些代码片段,你可以从这里找到[url=http://scorbs.com/silverlight/weathercontrolpost/complextransitions/WeatherApp.zip]完整的代码[/url].)
[code]    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)
        { }

    }[/code]你可以看看我们的WeatherControl…

    * 是一个自定义控件,继承自Control.
    * 定义内置Style,并由DefaultStyleKey来标识.
    * 有三个公共的依赖属性dependency properties:
          o Temperature
          o Condition
          o ConditionDescription

如果让我们的WeatherControl能够使用VSM来换皮肤,我们需要:

    * 定义一个控件约定
    * 找到和操作parts部件
    * 使用VisualStageManager来接管合适的状态

Here we go!

[b]定义控件约定[/b]

控件的代码负责描述控件的约定.意味着必须声明任意和所有的期望出现的部件(Parts)与状态(Stages).这是使用metadata完成的在类中的声明:

[code][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
{
     …  
}[/code]
在上面的片段中,有二个attribute类:

    * TemplatePartAttribute
          o 指定部件名称与期望的类型
    * TemplateVisualStateAttribute
          o 指定状态和他所属的状态组的名称

这些metadata并不是实时调用的.但他能被Expression Blend工具识别(以用于可视化编辑的支持).

这些在WeatherControl上的attributes给了控件下列的内容:
[img]http://scorbs.com/wp-content/uploads/2008/06/contract-thumb.jpg[/img]
现在,我们来看看如何编写部件的操作代码.

[b]找到部件[/b]

被命名的部件需要在控件代码中手动编写代码来找到相应的部件.这个操作在OnApplyTemplate()这个虚方法中执行当一个template被应用时.

[code]      // 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);
                }
            }  
        }[/code]
你需要使用GetTemplateChild() 这个helper方法来找到模板中的被命名元素.

在上面的例子中,我们找到了”Core”部件,这是一个我们会用于确定当控件引发MouseOver或Pressed状态时的部件.值得注意的是 setter访问器的逻辑声明并不是在template中声明的.这很重要,因为控件需要足够健全以便于当某个部件在template中没有时也能够正常工作.

[b]初始化状态改变[/b]

控件代码负责向VisualStateManager告之什么时候状态改变.因此,代码必须维护着logical state machine与visual state machine的状态.

所有Silverlight 2的内置控件都创建了简单的helper方法去辅助状态变化.我们推荐你使用这个简单的模式:

[code] // 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);
            }  
        }[/code]
GoToStage helper方法包含几个用于确定当前视觉状态的语法.他告诉VisualStateManager去初始化合适的状态变化.然后调用静态方法

public static bool VisualStateManager.GoToState(Control control, string stateName, bool useTransitions)

就像你所看到的,这个方法中…

    * 有三个参数:
          o control: 控件的实例
          o stateName: 要去的视觉状态的名称
          o usetTransitions: 确定正在进行过渡效果的一个标记
    * 返回一个 bool 型
          o 如果状态名找到就返回true,返之则false.
    * 无法满足if条件的情况…
          o 控件在处于发生过的visual state下
          o visual state无法找到

大部分控件作者会在三个情况下调用GoToStage() helper

    * OnApplyTemplate() 没有任何过渡效果
          o 当控件第一次呈现,我们会呈现在合适的状态下,并没有任何过渡效果加于上面.
    * 在确定的属性通知Handler中
    * 在确定的事件Handler中

在我们的WeatherControl中,我们添加以下调用:
[code]// 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);
        }[/code]
我们在下面情况时,需要初始化状态变化:

    * 模板被初次应用
    * 条件属性变化
    * 鼠标事件在由Core部件激发

[b]添加内置Style[/b]

现在我们看看我们的控件逻辑!

我使我们的ControlTemplate变得非常有趣,有趣意味着模板会变得冗长..不管怎样,看看编辑后的版本:
[code]
<! – 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>[/code]
你可以看到ControlTemplate中,我做了:

    * 定义了7个VisualStates:
    * 定义了storyboard资源来声明状态的storyboard
    * 提供一个默认的VisualTransition,用于CommonStages和WeatherStates
    * 指定了VisualTransitions,用于CommonStages的certain stage changes

我们来[url=http://scorbs.com/silverlight/weathercontrolpost/basictransitions/]运行一下![/url]
[img]http://scorbs.com/wp-content/uploads/2008/06/basicapp.jpg[/img]
[b]添加专门的过渡效果Transitions[/b]

默认的过渡效果还可以.但,为了做得更好,我们加一些更多的自定义的视觉过渡效果.

下面是我们在不同weather状态下的不同外观:
[img]http://scorbs.com/wp-content/uploads/2008/06/weatherstates-thumb.jpg[/img]
当我们的控件从Sunny变为PartlyCloudy时,我们不想让云层效果慢慢动画过来,替代方法是,让他从左边进来.
[img]http://scorbs.com/wp-content/uploads/2008/06/transitions-thumb1.jpg[/img]
为了让自定义的过渡效果像这个一样,你可以声明一个详细的过渡故事板:
[code]<! – 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>[/code]
现在,当VisualStateManager为Sunny到PartlyCloudy状态变化生成动画时,不会花很长时间产生BottomCloud的透明度动画.会马上运行我们定义的这个详细的故事板动画.

为了更好的理解生成动画与详细故事板之间的作用关系,我们看下面的示例:
[img]http://scorbs.com/wp-content/uploads/2008/06/transitionexample-thumb.jpg[/img]
这里,我们有二个视觉状态:Foo & Bar.每一个动画有一个不同的属性.

这些动画是如何创建的?

    * VSM会生成属性A,C和D之间的过渡动画
          o A,C和D会在一个或二个状态间发生动画,并且不会执行VisualTransition.Storyboard下定义的详细故事板
    * VSM会根据详细故事板运行B,E和G之间的动画
          o B,E和G会根据VisualTransition.Storyboard来动画.VSM不会自动为这些属性生成动画.
    * VSM不会为属性F生成动画.
          o F会由Foo & Bar状态中的ObjectAnimation来引发动画.他不会被VSM去生成程序化的动画.因此,属性F会简单的根据Bar的值来发生动画

返回到WeatherControl,我们同样加了明确的过渡效果为以下几个状态Sunny->PartlyCloudy, Sunny->Cloudy, and PartlyCloudy->Cloudy.

[url=http://scorbs.com/silverlight/weathercontrolpost/complextransitions/]运行一下[/url]看看程序的完成效果!你可以从这里[url=http://scorbs.com/silverlight/weathercontrolpost/complextransitions/WeatherApp.zip]下载源码.[/url]
[img]http://scorbs.com/wp-content/uploads/2008/06/basicapp1.jpg[/img]
下一次

这就是我们学到的关于自定义控件中如何使用VSM,希望你能获得自定义详细过渡效果的乐趣.

下一次,在这系列教程的最后一篇,我们会给出一些使用”部件”与”状态”模式的推荐方式,你还会了解更我关于此模式在未来Silverlight及WPF中的规划

nasawz 发表于 2008-6-27 18:22

不错的文档.   辛苦了

Iran 发表于 2008-6-27 21:54

转自[url=http://allan.flashempire.net/blog/?p=550]Allan.Blog()[/url]
[url=http://scorbs.com/2008/06/25/parts-states-model-with-visualstatemanager-part-4-of-4/]原文地址[/url]
这是有关”部件与状态”模式系列的最后一篇!

今天,我们讨论一些关于如何使用”部件与状态”模式的使用建议.我们还会看看有关VisualStageManager在未来的Silverlight及WPF中的情况.

系列链接([url=http://allan.flashempire.net/blog/?p=543]第一篇[/url],[url=http://allan.flashempire.net/blog/?p=544]第二篇[/url],[url=http://allan.flashempire.net/blog/?p=547]第三篇[/url],第四篇)

[b]“部件与状态”模式使用推荐[/b]

1.在UserControls用户控件和Custom Control自定义控件中使用”部件与状态”模式

就像我们在第一篇中提到的,”部件与状态”模式只是一个模式.在实际运行中并不是强制的,你可以在创建控件时不采用这种方式.

但是,我们觉得这是一个很好的模式.而且Blend针对自定义控件的的可视化编辑方式只支持这种模式.

虽然我们这一系列一直关注在自定义控件Custom Controls中的VSM,但是你同样可以在用户控件User Control中使用他.

2.自行命名VSM xmlns

因为一个已知的Silverlight 2 Beta 2 Bug,你必须在头部申明VisualStateManager的xmlns.
[code]xmlns:vsm=“clr-namespace:System.Windows;assembly=System.Windows”[/code]
3.命名约定

为了控件的一致性,我们推荐使用如下的一些命名约定.

[img]http://scorbs.com/wp-content/uploads/2008/06/naming-thumb.jpg[/img]

4.CommonStates 和 FocusStates 比较特殊

许多控件都使用了这二个状态组

[img]http://scorbs.com/wp-content/uploads/2008/06/specialstates-thumb.jpg[/img]

如果你的控件也有这些状态的特征,为了一致性,我们推荐使用相同的组名和名称

5.能够适应模板中没有Parts & States的情况

有很多原因能说明为什么控件模板中没有给一些部件和状况:设计师可能没有创建等原因.

需要有一些良好的编写习惯去防止由于部件遗失造成的程序崩溃.

注意:VisualStateManager.GoToState()方法已经有这种预防措施,当VisualState没有找到时,会返回false.

6.考虑支持一个”fallback”状态(当状态不存在时,用另一状态替代)

在复杂的控件中,可以提供一个fallback控制当某些状态不存在时提供一种替代方案
[code]if (VisualStateManager.GoToState(this, “FocusContent”, useTransitions) == false)
{
VisualStateManager.GoToState(this, “Focus", useTransitions);
}[/code]
这个处理方式的好处显而易见:当设计师没有提供合适的状态时,控件能够继续进行一些视觉上的正常表现.

但也有一些不好的影响:fallback状态机制并不是完全整合在”部件与状态”模式中,这意味着Blend软件并不能很好的识别出来(在可视化编辑中)

所以,请减少使用fallback状态并只在控件非常复杂时才使用.

同样,如果你认为这种方式对你有用的话,让我们知道!我们乐意接受你的反馈.

7.子类的状态应该添加在新的状态组中(而不是已存在的状态组)

如你所知,每一个状态组是orthogonal(同时使用二个组中的二个状态).这使得子类很容易添加新的状态组.例如,你可以创建一个StackButton从Button继承并添加一个StackState组:
[img]http://scorbs.com/wp-content/uploads/2008/06/newstategroup-thumb.jpg[/img]
这个可以正常运作因为StackState状态组的状态是完全独立于Button的CommonStates & FocusStates.

然尔,如果你想把一个新的状态加到一个已经存在的状态组中,这个状态组的逻辑就变得混乱.就无法保证在状态发生时找到正确的状态.

我们这个例子简单一些.BasicControl定义了二个状态在CommonStates中:Normal,MouseOver.创新的逻辑是:
*if(鼠标没有移上来) goto Normal
*if(鼠标移上来) goto MouseOver

现在,ExtendedControl从BasicControls继承并想加一个Pressed状态,这个逻辑状态的目标可能是:
*if(鼠标没有移上来) goto Normal
*if(鼠标移上来而且鼠标按钮没有按下) goto MouseOver
*if(鼠标按下) goto Pressed

然尔,并没有好的方式让ExtendedControl去添加有关(而且鼠标按钮没有按下)MouseOver状态的检测,因为这个逻辑存在于Button基类中.

这一切都说明:子类通常只能把状态加到新的状态组中.但我们推荐如果是全新的状态(不破坏原有逻辑的),可以加到状态组中.

注意:为在未来解决这些问题我们有不同的方式(有好的也有坏的).我们现在正在提供一个Triggers-Based基于触发的解决方案.如果想了解更多,继续阅读.
展望未来

VSM & Windows Presentation Foundation

Silverlight的”控件与状态”模式改变了WPF存在的很多现有特性(像ControlTemplates,GetTemplateChild() Hellper,etc).

[img]http://scorbs.com/wp-content/uploads/2008/06/wpf-thumb.jpg[/img]

然尔,这些相关的特性如VisualStateManager和相关的类并不存在于WPF中.好消息是我们在下一版的WPF中会包含VisualStageManager!

为了让大家能够把Silverlight 2的控件和皮肤移植到WPF,下一版.NET Framework会支持得更好一些.为帮助我们实现,我们会在下一版的完整WPF发售前,就中提供一个包含VisualStateManager的程序集.现然这个计划还为时尚早,未来会提供时间表的细节.

[b]未来的Silverlight特性[/b]

经常有人问在Silverlight控件模型中,Triggers触发器在哪里?

有很多原因使得我们不打算在Silverlight 2 的发布中加入触发器.一个主要的技术挑战是我们的属性系统架构不够达到能支持触发器.这在未来的Silverlight会改变,并会加入触发器的支持.

触发器和VSM之间如何协作呢?我们初步的方案是这样构想的:

提供一个GoToState触发器去调用状态变化.

设计师会有一个选项去使用内置的控件状态(不使用视觉状态变化的逻辑去控制).或者,设计师愿意在XAML中使用所有的触发器和VSM状态变化.也可能设计师能够自行添加新的状态到新状态组或存在的状态组中,而不去考虑控件的代码的情况下.

更多激动的特性会到来!

[b]结束[/b]

这就是这四篇的有关Silverlight 2 “部件与状态”模式的全部了.如果你有问题,请反馈给我们.

如果这一系列还不够了解VisualStateManager,这有一些其它资源:
[url=http://electricbeach.org/]Christian Schormann[/url]’s Blog (Group Program Manager for Expression Blend)
*[url=http://electricbeach.org/?p=98]使用Visual State Manager创建控件皮肤-入门[/url]
*[url=http://electricbeach.org/?p=100]Visual State Manager: 目标[/url]
*[url=http://electricbeach.org/?p=107]Visual State Manager for User Controls: 一个简单的 Chord Finder 示例[/url]
Steve White的视频教程(Program Manager for Expression Blend)
*[url=http://expression.microsoft.com/en-us/cc643423.aspx]Expression 教程[/url]
Celso Gomes的教程(Designer for Expression Blend)
*http://www.nibblestutorials.net/
[url=http://www.timheuer.com/blog]Tim Heuer’s Blog[/url] (Senior Program Manager on the .NET Developer Platform)
*[url=http://www.timheuer.com/blog/archive/2008/06/04/skinning-silverlight-controls-made-easier.aspx]Skinning Silverlight controls just got easier[/url]
*[url=http://www.timheuer.com/blog/archive/2008/06/04/silverlight-introduces-visual-state-manager-vsm.aspx]Silverlight 和 VisualStateManager[/url]
[url=http://weblogs.asp.net/scottgu]Scott Guthrie’s Blog [/url](Corporate Vice President of the Developer Division)
*[url=http://weblogs.asp.net/scottgu/archive/2008/06/06/silverlight-2-beta2-released.aspx]Silverlight 2 Beta2 Released[/url]

tmoonlight 发表于 2008-7-16 23:42

好帖!!

页: [1]



Powered by Discuz! Archiver 6.1.0  © 2001-2007 Comsenz Inc.