WPF学习(9)样式和行为
在asp.net世界中,我们的美工人员会为我们准备好静态页面,它注意包括三个部分:html、css和js。而在WPF世界里,也同样有着类似这三个部分的静态页面:Xaml、Style和Behaviors,当然,它们和前面三者的作用并不对等。Style几乎完成了css和js的功能,而Sliverlight 3中引入的Behaviors(封装到Expression Blend 3中和Expression Blend 3 SDK中)只是为了方便代码的复用,我们在后面详细来说。本文主要从Style样式和Behaviors行为两个方面来讲。
1.Style
先来看看Style类的属性:
1.1Setters
Setters是Style默认的内容属性,是Setter对象或者EventSetter对象的集合,所以可以省略Style.Setters而直接使用Setter或EventSetter。
Setter是用于设置属性值的,这个属性还得是依赖属性。EventSetter是自动关联事件处理程序的。举个例子:
xaml代码:
<Window x:Class="StyleDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Style x:Key="labelStyle" TargetType="Label"> <Setter Property="Foreground"> <Setter.Value>Red</Setter.Value> </Setter> <Setter Property="Control.FontSize" Value="28" /> <Setter Property="Slider.FontFamily" Value="宋体" /> <EventSetter Event="MouseMove" Handler="Label_MouseMove" /> </Style> <Style x:Key="buttonStyle"> <Setter Property="Button.FontSize" Value="22" /> <Setter Property="Button.FontFamily" Value="SimSun" /> <Setter Property="Button.FontWeight" Value="Bold" /> <Setter Property="Button.Background" Value="Red" /> </Style> </Window.Resources> <Grid> <StackPanel> <Label Content="Hi,WPF" Style="{StaticResource labelStyle}" /> <TextBlock Text="Sliverlight 5" Style="{StaticResource buttonStyle}"/> <Button Content="Click"> <Button.Style> <Style> <Setter Property="Control.Background"> <Setter.Value> <LinearGradientBrush> <GradientStop Offset="0" Color="Red" /> <GradientStop Offset="0.5" Color="Blue" /> <GradientStop Offset="1" Color="Yellow" /> </LinearGradientBrush> </Setter.Value> </Setter> </Style> </Button.Style> </Button> <Button Content="DoubleClick" Style="{StaticResource buttonStyle}"/> </StackPanel> </Grid> </Window>
cs代码:
private void Label_MouseMove(object sender, MouseEventArgs e) { MessageBox.Show("Mouse Move!!"); }
看下效果:
需要说明一下几点:
1)以资源的形式要比内嵌的形式更具灵活性
2)在内嵌的形式中,设置属性值时,要么指定TargetType,要么使用Class.Property的形式
3)"<Setter Property="Control.Background">...</setter>"与"<Setter Property="Background">...</setter>"的区别在于,前者先去设置Control的Backgroud属性,然后应用该样式的控件继承,而后者直接去设置应用该样式的控件的属性
1.2Triggers
Triggers,即为触发器,使用它可以自动完成简单的样式改变。主要有以下几种触发器:
具个例子,一并说明下这五种触发器:
xaml代码:
<Window x:Class="StyleDemo.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:StyleDemo" Title="Window1" Height="300" Width="300"> <Window.Resources> <!--Trigger属性触发器--> <Style x:Key="triggerKey"> <Style.Triggers> <Trigger Property="Control.IsMouseOver" Value="true"> <Setter Property="Control.Foreground" Value="Red" /> <Setter Property="Control.FontSize" Value="20" /> </Trigger> </Style.Triggers> </Style> <!--MultiTrigger多条件属性触发器--> <Style x:Key="multiTriggerKey"> <Style.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="Control.Foreground" Value="Red"></Condition> <Condition Property="Control.IsMouseOver" Value="true"></Condition> </MultiTrigger.Conditions> <!--<MultiTrigger.EnterActions> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(FrameworkElement.Width)"> <SplineDoubleKeyFrame KeyTime="00:00:00.0020000" Value="0"/> <SplineDoubleKeyFrame KeyTime="00:00:00.3450000" Value="95"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(FrameworkElement.Height)"> <SplineDoubleKeyFrame KeyTime="00:00:00.0020000" Value="0"/> <SplineDoubleKeyFrame KeyTime="00:00:00.3450000" Value="54"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </MultiTrigger.EnterActions>--> <MultiTrigger.Setters> <Setter Property="Control.ToolTip" Value="Background:Red,FontSize" /> </MultiTrigger.Setters> </MultiTrigger> </Style.Triggers> </Style> <!--EventTrigger事件触发器--> <Style x:Key="eventTriggerKey"> <Style.Triggers> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="FontSize" To="22" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="FontSize" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style> <!--DataTrigger数据触发器--> <local:L2BConverter x:Key="l2bCvt" /> <Style x:Key="dataTriggerKey"> <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=Text.Length,Converter={StaticResource l2bCvt}}" Value="false"> <Setter Property="Control.BorderBrush" Value="Red" /> <Setter Property="Control.BorderThickness" Value="1" /> </DataTrigger> </Style.Triggers> </Style> <!--MultiDataTrigger多条件数据触发器--> <local:L2BConverter x:Key="l2bCvt1" /> <local:S2BConverter x:Key="s2bCvt" /> <Style x:Key="multiDataTriggerKey"> <Style.Triggers> <MultiDataTrigger> <MultiDataTrigger.Conditions> <Condition Binding="{Binding RelativeSource={RelativeSource Self},Path=Text.Length,Converter={StaticResource l2bCvt1}}" Value="false" /> <Condition Binding="{Binding RelativeSource={RelativeSource Self},Path=Text,Converter={StaticResource s2bCvt}}" Value="false" /> </MultiDataTrigger.Conditions> <MultiDataTrigger.Setters> <Setter Property="Control.BorderBrush" Value="Red" /> <Setter Property="Control.BorderThickness" Value="1" /> </MultiDataTrigger.Setters> </MultiDataTrigger> </Style.Triggers> </Style> </Window.Resources> <Grid> <StackPanel> <Label Style="{StaticResource triggerKey}" Content="Hi,WPF" /> <TextBlock Style="{StaticResource multiTriggerKey}" Foreground="Red" FontSize="16" Text="RIA World" /> <Button x:Name="button" Style="{StaticResource eventTriggerKey}" Content="MouseEnter" /> <TextBox Style="{StaticResource dataTriggerKey}"/> <TextBox Style="{StaticResource multiDataTriggerKey}"/> </StackPanel> </Grid> </Window>
用到的两个Converter:
字符串长度转布尔类:
public class L2BConverter:IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return (int)value > 6 ? true : false; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return null; } }
字符串转布尔类
class S2BConverter:IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { Regex regex = new Regex(@"^\d*$"); return !regex.IsMatch(value.ToString()); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
效果大家可以copy代码运行看看。
1.3Resources
Style的Resources属性是ResourceDictionary类型,可以放一些在style中需要共享的对象资源。来看个例子:
xaml代码:
<Window x:Class="StyleDemo.Window2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window2" Height="300" Width="300"> <Window.Resources> <Style x:Key="buttonKey"> <Style.Resources> <LinearGradientBrush x:Key="lgbKey"> <GradientStop Offset="0" Color="Red" /> <GradientStop Offset="0.5" Color="Green" /> <GradientStop Offset="1" Color="Blue" /> </LinearGradientBrush> </Style.Resources> <Setter Property="Control.Background" Value="{StaticResource lgbKey}" /> </Style> </Window.Resources> <Grid> <StackPanel> <Button Content="button" Style="{StaticResource buttonKey}" /> </StackPanel> </Grid> </Window>
效果如下:
1.4BaseOn
Style的BaseOn属性,可以实现Style的继承,从而实现多层样式。来看个简单的例子:
xaml代码:
<Window x:Class="StyleDemo.Window2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window2" Height="300" Width="300"> <Window.Resources> <Style x:Key="buttonKey"> <Style.Resources> <LinearGradientBrush x:Key="lgbKey"> <GradientStop Offset="0" Color="Red" /> <GradientStop Offset="0.5" Color="Green" /> <GradientStop Offset="1" Color="Blue" /> </LinearGradientBrush> </Style.Resources> <Setter Property="Control.Background" Value="{StaticResource lgbKey}" /> <Setter Property="Control.FontFamily" Value="Times New Roman" /> <Setter Property="Control.FontWeight" Value="Bold" /> <Setter Property="Control.FontSize" Value="18" /> </Style> <Style x:Key="buttonInheritKey" BasedOn="{StaticResource buttonKey}"> <Setter Property="Control.Foreground" Value="DarkOrange" /> <Setter Property="Control.FontSize" Value="22" /> </Style> </Window.Resources> <Grid> <StackPanel> <Button Content="button" Style="{StaticResource buttonKey}" /> <Button Content="button1" Style="{StaticResource buttonInheritKey}" /> </StackPanel> </Grid> </Window>
效果图如下:
1.5TargetType
这个属性,指定的是应用该Style的控件的类型。我们以一个例子为切入点来说明下:
xaml代码:
<Window x:Class="StyleDemo.Window3" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window3" Height="300" Width="300"> <Window.Resources> <!--TargetType属性--> <Style TargetType="Button"> <Setter Property="Foreground" Value="Yellow" /> <Setter Property="FontSize" Value="28" /> </Style> <Style x:Key="buttonKey"> <Setter Property="Control.FontSize" Value="12" /> </Style> </Window.Resources> <Grid> <StackPanel> <TextBox /> <Button Content="Click" /> <Button Content="Click2" Style="{StaticResource buttonKey}"/> </StackPanel> </Grid> </Window>
效果如下:
首先,你可能会奇怪,指定了TargetType的Style这里没有x:key来标识该资源,这是因为xaml解析器会自动以其对象类型来当作它的key,类似这样:x:key="{x:Type Button}"。
另外,需要说明一下几点:
1)设置了TargetType类型的Style会应用该种类型的所有控件。
2)如果某该类型控件另外还设置了Style,会进行Merge的操作(由StaticResource或DynamicResource和TargetType确定的Style和ThemeStyle的合并)。StaticResource或DynamicResource和TargetType相同的Setter属性值,前者优先级高,不同的Setter属性值均起作用。
2.Behaviors
Style提供了重用一组属性设置的方法,为帮助构建统一良好的界面迈出了重要的一步,但是,还是有很多的限制,比如对于动画的支持不够。我们知道,通常要设计一个动画效果,需要很多的xaml代码。而这样的动画也经常会在其他的地方使用,但是我们却不得不复制那一大块代码,为了DRY,微软在Expression Blend 3推出了Behaviors行为的特性。
首先,我们需要了解Behaviors这样几个关键点:
1)Behaviors可复用代码集合(UI功能),可以被任何对象附加使用
2)设计人员和开发人员只需要将它附加到元素上,而无需写任何的逻辑代码
3)一个Behaviors可以被多个对象元素同时使用
与Behaviors相关的程序集:
System.Windows.Interactivity.dll,该链接库定义了Behaviors(行为)基础类,有了该链接库支持,即可支持Behaviors(行为);
Microsoft.Expression.Interactions.dll,该链接库提供了一些扩展行为类库,以及一些Action和Trigger类,作为演示实例;
Behaviors主要包括Trigger、Action和Behavior三个部分。需要注意的是,这里的Trigger和WPF的Trigger并不完全一样,可以同时使用它们。
IAttachObject接口的定义很简单:
// 摘要: // 供可以附加到另一个对象的对象使用的接口。 public interface IAttachedObject { // 摘要: // 获得关联的对象。 // // 备注: // 代表此实例附加到的对象。 DependencyObject AssociatedObject { get; } // 摘要: // 附加到指定的对象。 // // 参数: // dependencyObject: // 要附加到的对象。 void Attach(DependencyObject dependencyObject); // // 摘要: // 将此实例与其关联的对象分离。 void Detach(); }
AssociatedObject是一个只读的依赖对象属性,它指定行为的应用者。
2.1自定义一个Behavior
下面我们来自己定义一个Behaviors看看,要实现的效果是在Canvas中的控件可任意拖动:
先定义一个继承自Behavior<UIElement>的DragInCanvasBehavior类
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Interactivity; using System.Windows.Media; namespace CustomBehaviorsLib { public class DragInCanvasBehavior:Behavior<UIElement> { protected override void OnAttached() { base.OnAttached(); //Hook up event handlers this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown; this.AssociatedObject.MouseMove += AssociatedObject_MouseMove; this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp; } void AssociatedObject_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { if (isDragging) { AssociatedObject.ReleaseMouseCapture(); isDragging = false; } } void AssociatedObject_MouseMove(object sender, System.Windows.Input.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 Canvas canvas; private bool isDragging = false; private Point mouseOffset; void AssociatedObject_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { if (canvas == null) canvas = (Canvas)VisualTreeHelper.GetParent(this.AssociatedObject); isDragging = true; mouseOffset = e.GetPosition(AssociatedObject); AssociatedObject.CaptureMouse(); } protected override void OnDetaching() { base.OnDetaching(); //detach event handlers this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown; this.AssociatedObject.MouseMove += AssociatedObject_MouseMove; this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp; } } }
然后在window4.xaml中添加对DragInCanvasBehavior类所在类库的引用,同时添加System.Window.Interactivity.dll的引用。
xaml代码如下:
<Window x:Class="StyleDemo.Window4" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:custom="clr-namespace:CustomBehaviorsLib;assembly=CustomBehaviorsLib" Title="Window4" Height="300" Width="300"> <Grid> <Canvas x:Name="p_canvas"> <Canvas x:Name="c_canvas1" Width="100" Height="100" Background="Yellow" /> <Canvas x:Name="c_canvas2" Width="100" Height="100" Canvas.Left="110" Background="Red"> <i:Interaction.Behaviors> <custom:DragInCanvasBehavior /> </i:Interaction.Behaviors> </Canvas> <Canvas x:Name="c_canvas3" Width="100" Height="100" Canvas.Top="110" Background="Blue" /> </Canvas> </Grid> </Window>
通过向BehaviorCollection类型的Interaction.Behaviors附加属性添加DragInCanvasBehavior实例。
效果如下:
这里只是给红色的Canvas添加了DragInCanvasBehavior,所以只有红色的Canvas可以Drag。
Expression Blend 3 以及之后的版本(4和4.5)都有Behaviors提供了很好的支持,内部提供了好多封装好的Behaviors。况且,使用blend可以很方便简单的使用它。
2.2Expression Blend中Behaviors
Expression Blend是和Visual Studio配套使用的,也就是说对应的采用的是相同的解决方案文件格式,Blend 3对应VS2008,Blend 4对应VS2010,Blend 5对应VS2012。
这里提供许多现成的行为,也包含自定义的Behaviors。用法基本类似,这里我们以MouseDragElementBehavior为例,来介绍下在Expression Blend中对Behaviors的使用。
先向界面拖放一个主Canvas,里面放三个子Canvas,背景颜色分别设为Yello、Red和Blue。然后将MouseDragElementBehavior拖放到红色的子Canvas上面,代码便自动完成了,实现了与上面相同的效果,及其方便快捷。
有关Expression Blend的使用技巧,将会单独来讲,在后面模板和动画的学习中还将会提到。