Silverlight之Action和Behavior
首先看下创建Action和Behavior的首要条件,由于Action和Behavior原本是Blend特有的,不过没关系,可以在Blend的目录中找到我们需要的dll文件
打开
c:\Program Files\MicrosoftSDKs\Expression\Blend\Silverlight\v4.0\Librariest目录找到System.Windows.Interactivity.dll,这个就是需要的核心文件
Behavior包含三个元素,trigger,action,behavior
这三个东西是需要合作完成任务的,当触发一些事件或者调用一个Action则触发一个Trigger;Trigger和Action组成一个简单的Behavior。(Trigger是用来监听,Action用来响应)
创建Action,通常来说我们会创建一个新的Silverlight 类库来存放这些东西.创建好Silverlight类库之后开始创建真正的Action类
public class PlaySoundAction : TriggerAction<FrameworkElement>
所有的Action都继承自TriggerAction类,因为一个Action是通过Trigger来触发的。通常的话这个TriggerAction的泛型为FrameworkElement或者UIElement.
当一个Trigger被触发了,就会调用Action的Invoke方法,所以,我们需要重写Invoke方法来实现自定义的功能。
下面来一个完整的例子,例子的要求是,当点击一个按钮播放一个音频声音,当然这个还是使用MiediaElement来播放,只是说通过Action的方式来实现。
首先创建一个Action类,名字为PlaySoundAction:
public class PlaySoundAction : TriggerAction<FrameworkElement>
每一个Action都应该有自己的属性(依赖属性)的,这样才能通用的来达到通用的效果,在这个例子中定义一个Source属性,表示音频的地址,
依赖属性的类型就是
DependencyProperty ,通过DependencyProperty.Register创建一个依赖属性,参数一就是 暴漏给调用者的属性名字,即代码中的Source属性,参数二为当前依赖属性的类型,
由于是表示音频的地址,所以应该是Uri类型,参数三为当前依赖属性的所属类,当前的类为PlaySoundAction,所以这个参数值就是这个,第四个参数为默认值,在此指定的为null,
当然也可以自己进行指定。
在属性Source的get访问器中通过GetValue方法得到依赖属性的值,通过SetValue方法对依赖属性赋值。
//定义依赖属性
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(Uri),
typeof(PlaySoundAction), new PropertyMetadata(null));
public Uri Source
{
get { return (Uri)GetValue(PlaySoundAction.SourceProperty); }
set { SetValue(PlaySoundAction.SourceProperty, value); }
}
接下来开始重写Invoke方法,其实这个方法很简单,就是创建一个MediaElement,然后指定Source属性,然后添加到调用方的容器中,就可以实现播放。
其实真正的代码在FindContainer方法,该方法找到当前调用者(UIElement)所属的父容器,得到父容器,然后将MediaElement添加到该父容器即可,
在这里有一个关键点就是VisualTreeHelper.GetParent,通过该方法得到传递UIElement的父容器(在此使用的是Panel,当然可以自行扩展),然后进行转换类型,至于VisualTreeHelper的具体用法,可以参见
MSDN。
得到了父容器之后,开始创建MediaElement,然后指定Source为我们定义的Source属性(通过在XAML中调用进行赋值得到指定的音频地址)的值,同时处理End事件和
Failed事件,指定AutoPlay为自动播放,最后将MediaElement添加到父容器中,至此Action的工作已经告一段了,接下来看看如何在XMAL中具体的使用。
protected override void Invoke(object parameter)
{
Panel container = FindContainer();
if (container!=null)
{
MediaElement media = new MediaElement();
media.Source = Source;
media.MediaEnded += delegate
{
container.Children.Remove(media);
};
media.MediaFailed += delegate
{
container.Children.Remove(media);
};
media.AutoPlay = true;
container.Children.Add(media);
}
}
private Panel FindContainer()
{
//得到注册Action的Element
FrameworkElement element = this.AssociatedObject;
while (element != null)
{
if (element is Panel) return (Panel)element;
element = VisualTreeHelper.GetParent(element) as FrameworkElement;
}
return (element != null) ? (Panel)element : null;
}
在Xaml中使用Action
首先在Silverlight Application中对我们的Silverlight 类库进行dll的引用,这个不在赘述,然后在Xaml中对PlaySoundAction的namespace进行引用,同时还要对
System.Windows.Interactivity.dll进行引用,引用如下:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:custom="clr-namespace:CustomerBehaviorLibrary;assembly=CustomerBehaviorLibrary"
ok,引用工作也做完了,下面开始真正的调用,
可以看到在此使用的是System.Windows.Interactivity下的Triggers,而并非Button.Triggers,然后还是创建一个EventTrigger,指定EventName为Click,最关键的异步,在EventTrigger中使用我们的PlaySoundAction,同时给Source属性赋值,至此所有代码已经完成,运行示例,点击按钮则会播放指定的音频文件,当然整个过程是没有在xaml.cs中写任何代码,同时这种方式还是通用的,可以在多个Xaml也买那使用这个Action,同时可以指定不同的Source。
<StackPanel>
<Button Height="25" Width="200" Content="Test SoundAction">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<custom:PlaySoundAction Source="/Baby.wma"></custom:PlaySoundAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
至此,一个简单的Action和Element的交互的工作已经完毕。
上述的代码可以看到我们指定了当点击Button的Click时候触发这个Action,当然也可以指定其他的事件来触发这个Action,甚至我们可以在Action中指定哪个事件(每个UIElement的事件会有不同)来触发,看下边代码:
可以看到我们给PlaySoundAction添加了几个DefaultTrigger这样的特性,解释下这几个参数,第一个就是指定的UIElement的类型,第二个当然也就是我们的事件触发器
类型,第三个就是默认的事件了,可以看到第三个参数是一个数组的类型,当然也就是说可以给一个UIElement指定多个默认事件。
有个亮点,由于Ellipse这样的Shape是没有Click事件的,所以我们需要指定其他的Mouse事件来替代,这就告诉我们要看具体的UIElement拥有的事件再来确定其默认事件。
[DefaultTrigger(typeof(Button),typeof(System.Windows.EventTrigger),new object[]{"Click"}) ]
[DefaultTrigger(typeof(Shape), typeof(System.Windows.EventTrigger), new object[] { "MouseEnter" })]
[DefaultTrigger(typeof(UIElement), typeof(System.Windows.EventTrigger), new object[] { "MouseLeftButtonDown" })]
public class PlaySoundAction : TriggerAction<FrameworkElement>
{
创建定向的Trigger(Targeted Trigger)
在上边的例子中我们使用TriggerAction.AssociatedObject来得到整个UIElement,然后使用VisualTreeHelper得到其父容器。一些Action可以通过检索父容器来得到一些
信息,但是有些Action需要获得不同的UIElement(可以通过一个UIElement调用一个Action来影响另一个Element),这时候就没法再使用TriggerAction这个类型,好在Silverlight中提供了TargetedTriggerAction类,这个类提供了Target属性,当然这个属性是需要在XAML中指定的TargetName属性(可以为使用者Element或者其他的UIElement的Name),下面看一个完整的例子,这个例子的功能就是将一个UIElement的Opacity使用动画的方式来修改(目标值为1)。
首先需要定义一个附加属性DurationProperty表示动画的时间,同时定义一系列动画需要的StoryBoard和Animation;
其次,重写Invoke方法,在这个方法中最重要的一句代码就是 Storyboard.SetTarget(fadeAnimation,this.Target);其中的this.Target就是TargetedTriggerAction和
TriggerAction的最大区别(TargetedTriggerAction继承自TriggerAction类),这个属性就是要进行操作的目标对象。
public class FadeInAction : TargetedTriggerAction<UIElement>
{
//定义一个附加属性,表示动画的时间长度
private static readonly DependencyProperty DurationProperty =
DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(FadeInAction), new PropertyMetadata(TimeSpan.FromSeconds(0.2)));
public TimeSpan Duration
{
get { return (TimeSpan)GetValue(DurationProperty); }
set { SetValue(DurationProperty, value); }
}
//定义一个动画面板
private Storyboard fadeStoryBoard = new Storyboard();
//定义一个Double动画
private DoubleAnimation fadeAnimation = new DoubleAnimation();
public FadeInAction()
{
//将动画添加到动画面板中
fadeStoryBoard.Children.Add(fadeAnimation);
}
///<summary>
/// 重写Invoke方法
///</summary>
///<param name="parameter"></param>
protected override void Invoke(object parameter)
{
fadeStoryBoard.Stop();
//设置动画Target为TargetedTriggerAction.Target属性
Storyboard.SetTarget(fadeAnimation,this.Target);
//设置动画的Property为Opacity
Storyboard.SetTargetProperty(fadeAnimation,new PropertyPath("Opacity"));
fadeAnimation.To = 1;
fadeAnimation.Duration = Duration;
fadeStoryBoard.Begin();
}
}
看看在XAML中的调用方法:
其实基本代码没有变化,只是在EventTrigger中指定的的Action指定了一个TargetName属性这个属性可以看到是另一个Board的Name属性,没错这个就是在Action中
通过this.Target得到指定的UIElement的整个过程。
<Button Height="25" Width="150" Content="Show Button By Action" HorizontalAlignment="Left">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<custom:FadeInAction TargetName="myBoard"></custom:FadeInAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Border x:Name="myBoard" BorderBrush='Gray' Opacity="0.1" BorderThickness="2" Background="Green">
<TextBlock FontSize="17" TextWrapping="Wrap" Text="I'm the target of the FadeOutAction and FadeInAction"></TextBlock>
</Border>
运行程序,当点击按钮Border的Opacity就会从初始值(0.1)变化到到1这个动画过程。