Silverlight之Styles和Behaviors
本文简介
1.定义简单的Style
2.应用Style到元素
3.动态加载Style资源文件
4.Style的继承
5.组织内编写Style(在元素内)
6.自动应用Style到指定类型元素
7.Style绑定表达式
8.Behaviors的准备工作
9.创建Actions
10.在元素(Element)上使用Action
11.创建TargetedTriggerAction.
12.创建Behaviors
13.一些微软提供的Actions,Triggers,Behaviors
1.定义简单的Style
<UserControl.Resources> <Style x:Key="redButtonStyle" TargetType="Button"> <Setter Property="Background" Value="Red"></Setter> <Setter Property="FontFamily" Value="宋体"></Setter> <Setter Property="FontSize" Value="14"></Setter> </Style> </UserControl.Resources>
Silverlight和css是完全不同的,通过Style标签定义,一个Style必须指定TargetType(应用的元素的类型,当前是Button按钮),x:Key不是必须,当然如果你想让样式应用到所有指定类型的元素上,可以忽略x:Key,此处我们指定了Key,则要显示的使用。
2.是用Style到元素
<Button Content="14号字体按钮" Style="{StaticResource redButtonStyle}"></Button>
代码同样很简单,只需指定Stlye属性即可,属性值为静态资源中德redButtonStyle,即上述定义的样式。
3.动态加载Style资源文件
右键Silverlight程序添加一个Dictionary文件即可,结构如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> </ResourceDictionary>
只有一个ResourceDictionary节点作为根节点,我们可以在里边添加很多个Style,如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Style x:Key="redButtonStyle" TargetType="Button"> <Setter Property="Background" Value="Red"></Setter> </Style> <Style x:Key="greenButtonStyle" TargetType="Button"> <Setter Property="Background" Value="Red"></Setter> </Style> </ResourceDictionary>
Style的语法和上例子中的一样,不再赘述。定义好了资源文件,我们要进行动态加载,加载的地方就是StartUp,如下:
ResourceDictionary testReourceDictionary = new ResourceDictionary(); testReourceDictionary.Source = new Uri("Dictionary1.xaml",UriKind.Relative); Application.Current.Resources.MergedDictionaries.Add(testReourceDictionary);
定义一个ResourceDictionary对象,设置属性Source为一个Uri,最终添加到Application.Current.Resources.MergedDictionaries,这样就可以在程序中的任意页面访问Key为"redButtonStyle"和"greenButtonStyle"了,使用方法和上述一致。
注意:此处必须注意,如果要动态加载资源文件,那么资源文件的输出类型(Output Type)必须为内容(Content),默认为页面(Page),所以此处要进行修改。
4.Style的继承
有时候我们可能会重复写一些样式,此时可以将公共的部分提取出来作为一个BaseStyle,然后再添加一些继承其的Style。
<Style x:Key="baseButtonStyle" TargetType="Button"> <Setter Property="FontSize" Value="12"></Setter> <Setter Property="Foreground" Value="Yellow"></Setter> </Style> <Style x:Key="redButtonStyle" TargetType="Button" BasedOn="{StaticResource baseButtonStyle}"> <Setter Property="Background" Value="Red"></Setter> </Style> <Style x:Key="greenButtonStyle" TargetType="Button" BasedOn="{StaticResource baseButtonStyle}"> <Setter Property="Background" Value="Red"></Setter> </Style>
注意:如果基样式的属性子样式中不存在,则会出现错误。
5.组织内编写Style(在元素内)
<Button Content="A Customized Button"> <Button.Style> <Style TargetType="Button"> <Setter Property="FontFamily" Value="Georgia" /> <Setter Property="FontSize" Value="40" /> <Setter Property="Foreground" Value="White" /> </Style> </Button.Style> </Button>
之前的例子都是将Style作为资源进行编写和访问,那样便于资源在多个地方进行使用,如果将Style写在元素的Style标签内,则Style只在本元素内有效,同时其优先级也最高。
6.自动应用Style到指定类型元素
我们之前的代码中Style都指定了Key,当我们希望所有的TargetType都应用样式的时候,忽略Key属性即可实现:
<Style TargetType="Button"> <Setter Property="FontSize" Value="14"></Setter> <Setter Property="Foreground" Value="Gray"></Setter> <Setter Property="Margin" Value="10"></Setter> </Style>
上述Style并没有指定Key,同时指定了TargetType为Button,那么所有的Button都将使用样式。
7.Style绑定表达式
首先,可以在静态资源中使用使用表达式引用一个静态资源:
<UserControl.Resources> <FontFamily x:Key="buttonFontFamily">Georgia</FontFamily> <sys:Double x:Key="buttonFontSize">18</sys:Double> <FontWeight x:Key="buttonFontWeight">Bold</FontWeight> <Style x:Key="BigButtonStyle1" TargetType="Button"> <Setter Property="FontFamily" Value="{StaticResource buttonFontFamily}" /> <Setter Property="FontSize" Value="{StaticResource buttonFontSize}" /> <Setter Property="FontWeight" Value="{StaticResource buttonFontWeight}" /> </Style> </UserControl.Resources>
其次,也可以定义为类方式访问,如下:
public class FontInfo { public FontFamily FontFamily { get; set; } public double FontSize { get; set; } public FontWeight FontWeight { get; set; } }
<UserControl.Resources> <local:FontInfo x:Key="buttonFont" FontFamily="Georgia" FontSize="18" FontWeight="Bold"></local:FontInfo> <Style x:Key="BigButtonStyle2" TargetType="Button"> <Setter Property="Padding" Value="20" /> <Setter Property="Margin" Value="10" /> <Setter Property="FontFamily" Value="{Binding Source={StaticResource buttonFont}, Path=FontFamily}" /> <Setter Property="FontSize" Value="{Binding Source={StaticResource buttonFont}, Path=FontSize}" /> <Setter Property="FontWeight" Value="{Binding Source={StaticResource buttonFont}, Path=FontWeight}" /> </Style> </UserControl.Resource
8.Behaviors的准备工作
我们使用Style可以改变UI元素的一些外观,但是往往我们不仅仅如此的进行基本的设置。比如,缩放,拖动等功能,这样的代码复杂度要比业务代码复杂的多,当然它们也是通用的,所以这时候我们可以使用Behavior,将代码进行封装多处使用。
准备前奏,很遗憾的告诉大家Behavior本身是不存在Silverlight SDK中的,所以我们是无法直接创建它的,它的初衷是在Blend中,供设计时候使用,当然不代表我们无法在Silverlight中进行Behavior的开发。
步骤如下:
(1)下载Blend for Silverlight(下载)
(2)安装Blend for Silverlight
(3)早目录"c:\Program Files\Microsoft SDKs\Expression\Blend\Silverlight\v5.0\Libraries"下找到System.Windows.Interactivity.dll(提供Behavior功能的类库),Microsoft.Expression.Interactions.dll(包含了扩展的一些Behaviors)
到这里准备工作完毕。
9.创建Actions
Action继承自TriggerAction<T>,通常情况下T为FrameworkElement(支持VisualTreeHelper操作)。接下来我们实现一个点击按钮播放音频的功能,首先新建一个Action如下:
[DefaultTrigger(typeof(ButtonBase), typeof(i.EventTrigger), new object[] { "Click" })] [DefaultTrigger(typeof(Shape), typeof(i.EventTrigger), new object[] { "MouseEnter" })] [DefaultTrigger(typeof(UIElement), typeof(i.EventTrigger), new object[] { "MouseLeftButtonDown" })] public class PlaySoundAction : TriggerAction<FrameworkElement> { 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); } } protected override void Invoke(object parameter) { Panel container = FindContainer(); if (container != null) { MediaElement media = new MediaElement(); media.Source = this.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() { FrameworkElement element = this.AssociatedObject; while (element != null) { if (element is Panel) return (Panel)element; element = VisualTreeHelper.GetParent(element) as FrameworkElement; } return null; } }
在类中添加了一个Uri类型的属性,用于设置MediaElement(该控件用于播放音频和视频,详情可查看MSDN),同时重写了TriggerAction<T>的Invoke,因为只要触发了Action,则会执行Invoke。在Invoke中我们创建了一个MediaElement并且将其放到页面的元素中,并开始播放。FindContainer方法用于找到当前页面的Panel容器(当前页面的为Grid,因为Grid的基类为Panel),在Invoke中将ModiaElement添加到查找到的Panel中。
注:使用TriggerAction.AssociatedObject来得到整个UIElement。可以看到我们给PlaySoundAction添加了几个DefaultTrigger这样的特性,解释下这几个参数,第一个就是指定的UIElement的类型,第二个当然也就是我们的事件触发器类型,第三个就是默认的事件了,可以看到第三个参数是一个数组的类型,当然也就是说可以给一个UIElement指定多个默认事件。
10.在元素(Element)上使用Action
新建一个UserControl,代码如下,添加Interactivity命名空间引用i,以及当前程序的命名空间引用local
<UserControl x:Class="SilverlightApplication2.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:local="clr-namespace:SilverlightApplication2" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="LayoutRoot" Background="White"> <Button Content="Click to play audio"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <local:PlaySoundAction Source="潘裕文-夏雨诗.mp3"></local:PlaySoundAction> </i:EventTrigger> </i:Interaction.Triggers> </Button> </Grid> </UserControl>
在按钮中添加了Triggers并且指定事件为Click,然后使用了我们的PlaySoundAction,同时指定了Source为一个媒体文件(必须注意,必须将文件的Output Type也就是输出类型选择为Resource资源,这样才可以访问的到)。现在运行程序是不是就可以听到优美的歌声了呢。
11.创建TargetedTriggerAction
在上边的例子中我们使用TriggerAction.AssociatedObject来得到整个UIElement,然后使用VisualTreeHelper得到其父容器。一些Action可以通过检索父容器来得到一些信息,但是有些Action需要获得不同的UIElement(可以通过一个UIElement调用一个Action来影响另一个Element),这时候就没法再使用TriggerAction这个类型,好在Silverlight中提供了TargetedTriggerAction类,这个类提供了Target属性,当然这个属性是需要在XAML中指定的TargetName属性(可以为使用者Element或者其他的UIElement的Name),下面看一个完整的例子,这个例子的功能就是将一个UIElement的Opacity使用动画的方式来修改(目标值为1)。
/// <summary> /// 使Opacity渐变到0 /// </summary> public class FadeOutAction : TargetedTriggerAction<UIElement> { public static readonly DependencyProperty DurationProperty = DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(FadeOutAction), new PropertyMetadata(TimeSpan.FromSeconds(2))); public TimeSpan Duration { get { return (TimeSpan)GetValue(FadeOutAction.DurationProperty); } set { SetValue(FadeOutAction.DurationProperty, value); } } private Storyboard fadeStoryboard = new Storyboard(); private DoubleAnimation fadeAnimation = new DoubleAnimation(); public FadeOutAction() { } protected override void Invoke(object args) { fadeStoryboard.Stop(); Storyboard.SetTarget(fadeAnimation, this.Target); Storyboard.SetTargetProperty(fadeAnimation, new PropertyPath("Opacity")); fadeAnimation.To = 0; fadeAnimation.Duration = Duration; fadeStoryboard.Begin(); } } /// <summary> /// 使Opacity渐变到1 /// </summary> public class FadeInAction : TargetedTriggerAction<UIElement> { // The default fade in is 0.5 seconds. public static readonly DependencyProperty DurationProperty = DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(FadeInAction), new PropertyMetadata(TimeSpan.FromSeconds(0.5))); public TimeSpan Duration { get { return (TimeSpan)GetValue(FadeInAction.DurationProperty); } set { SetValue(FadeInAction.DurationProperty, value); } } private Storyboard fadeStoryboard = new Storyboard(); private DoubleAnimation fadeAnimation = new DoubleAnimation(); public FadeInAction() { fadeStoryboard.Children.Add(fadeAnimation); } protected override void Invoke(object args) { fadeStoryboard.Stop(); Storyboard.SetTarget(fadeAnimation, this.Target); Storyboard.SetTargetProperty(fadeAnimation, new PropertyPath("Opacity")); fadeAnimation.To = 1; fadeAnimation.Duration = Duration; fadeStoryboard.Begin(); } }
首先需要定义一个附加属性DurationProperty表示动画的时间,同时定义一系列动画需要的StoryBoard和Animation;
其次,重写Invoke方法,在这个方法中最重要的一句代码就是 Storyboard.SetTarget(fadeAnimation,this.Target);
其中的this.Target就是TargetedTriggerAction和TriggerAction的最大区别(TargetedTriggerAction继承自TriggerAction类),这个属性就是要进行操作的目标对象。
Xaml代码如下:
<StackPanel> <StackPanel Orientation="Horizontal" Margin="3,15"> <Button Content="Click to Fade the TextBlock" Padding="5"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <local:FadeOutAction TargetName="border" /> </i:EventTrigger> </i:Interaction.Triggers> </Button> <Button Content="Click to Show the TextBlock" Padding="5"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <local:FadeInAction TargetName="border" /> </i:EventTrigger> </i:Interaction.Triggers> </Button> </StackPanel> <Border x:Name="border" Background="Orange" BorderBrush="Black" BorderThickness="1" Margin="3,0" > <TextBlock Margin="5" FontSize="17" TextWrapping="Wrap" Text="I'm the target of the FadeOutAction and FadeInAction."></TextBlock> </Border> </StackPanel>
和上个例子中没有太多变化,唯一区别就是TargetName的设置,通过设置TargetName为Border,这样在Action中的this.Target就可以得到要操作的对象。运行例子,点击
第一个按钮文本则会逐渐消失,点击第二个按钮则会逐渐显示。
注:两个类分别用于Opacity的相反效果。
12.创建Behaviors
大家经常会把Action形容为Behavior,Action单独是无法进行使用的,需要结合Trigger才能使元素进行操作。Behavior的目标是让XAML的代码更加的精简,或者说Behavior 其实是组合了Action和Trigger,XAML无需Code即可实现操作,同时Behavior可以实现代码的通用性。
选择Action还是选择Behavior?如果您试图创建共享数据的操作或交互,或者需要你的行为和具体行为之间的紧密耦合,你应该考虑实现自己的功能一个行为。
创建一个Behavior如下(用于实现拖拽元素到Canvas):
自定义的Behavior的基类为Behaviror<T>,其中需要重写OnAttachmented和OnDetaching,触发前者的时候可以操作应用Behavior的元素并且可以给其添加事件;在后者事件触发的时候可以做清理工作以及对元素进行事件的移除。
public class DragInCanvasBehavior : Behavior<UIElement> { private Canvas canvas; protected override void OnAttached() { base.OnAttached(); this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown; this.AssociatedObject.MouseMove += AssociatedObject_MouseMove; this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp; } protected override void OnDetaching() { base.OnDetaching(); this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown; this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove; this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp; } // 元素是否正在被拖拽 private bool isDragging = false; // 记录下元素的鼠标原始位置 private Point mouseOffset; private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { // 查找到Canvas if (canvas == null) canvas = VisualTreeHelper.GetParent(this.AssociatedObject) as Canvas; // 标识正在拖拽 isDragging = true; // 得到鼠标位置 mouseOffset = e.GetPosition(AssociatedObject); // 设置当前操作的鼠标位置为该元素 AssociatedObject.CaptureMouse(); } private void AssociatedObject_MouseMove(object sender, MouseEventArgs e) { if (isDragging) { // 鼠标移动中,动态得到位置 Point point = e.GetPosition(canvas); // 修改元素的坐标位置 AssociatedObject.SetValue(Canvas.TopProperty, point.Y - mouseOffset.Y); AssociatedObject.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X); } } private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (isDragging) { //释放鼠标 AssociatedObject.ReleaseMouseCapture(); //标识为未拖拽 isDragging = false; } } }
XAML使用如下:
<Canvas> <Rectangle Canvas.Left="10" Canvas.Top="10" Fill="Yellow" Width="40" Height="60"> </Rectangle> <Ellipse Canvas.Left="10" Canvas.Top="70" Fill="Blue" Width="80" Height="60"> <i:Interaction.Behaviors> <local:DragInCanvasBehavior></local:DragInCanvasBehavior> </i:Interaction.Behaviors> </Ellipse> <Ellipse Canvas.Left="80" Canvas.Top="70" Fill="OrangeRed" Width="40" Height="70"> <i:Interaction.Behaviors> <local:DragInCanvasBehavior></local:DragInCanvasBehavior> </i:Interaction.Behaviors> </Ellipse> </Canvas>
一共三个图形,其中Rectangle没有使用Behavior,所以它是不能够拖动的,而另外两个Ellipse指定了Behaviros则可以实现拖动的效果,好了赶紧运行的代码见证神奇的一刻。
13.一些微软提供的Actions,Triggers,Behaviors
上述我们都是自己在做一些Action和Behavior,当然微软已经提供了一些比较实用的类库(他们都藏在Microsoft.Expression.Interactions.dll中),详细如下:
Action:
ChangePropertyAction, GoToStateAction, HyperlinkAction, RemoveElementAction
Triggers:
KeyTrigger,TimerTrigger ,StoryboardCompletedTrigger
Behaviors:
MouseDragElementBehavior,FluidMoveBehavior
具体的使用方法,大家可以参考MSDN解释。(可以访问http://expressionblend.codeplex.com ,http://gallery.expression.microsoft.com.获得更多的支持)
到这里本文的讲解就结束了,希望大家多提意见和建议,欢迎多多交流。