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.获得更多的支持)

 到这里本文的讲解就结束了,希望大家多提意见和建议,欢迎多多交流。

posted @ 2013-07-16 17:21  wangyafei_it  阅读(1231)  评论(0编辑  收藏  举报