本文简介
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()));
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 = ;
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 = ;
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.获得更多的支持)
到这里本文的讲解就结束了,希望大家多提意见和建议,欢迎多多交流。