什么是XAML Behaviors(行为)
XAML Behaviors 提供了一种简单易用的方法,能以最少的代码为 Windows UWP/WPF 应用程序添加常用和可重复使用的交互性。
但是Microsoft XAML Behaviors包除了提供常用的XAML Behaviors之外,还提供了一些Trigger(触发器)以及Trigger对应的Action(动作)。
可以通过一个最简单的示例来感受一下
首先我们引用Microsoft.Xaml.Behaviors.Wpf包,然后添加如下的代码。
在下面的代码中,我们放置了一个按钮,然后为按钮添加了XAML Behavior,当按钮触发Click事件时,调用ChangPropertyAction去改变控件的背景颜色
1 <Window x:Class="XamlBehaviorsDemo.MainWindow"2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"6 xmlns:local="clr-namespace:XamlBehaviorsDemo"7 xmlns:i="http://schemas.microsoft.com/xaml/behaviors"8 mc:Ignorable="d"9 Title="MainWindow" Height="450" Width="800"> 10 <Grid> 11 <Button Width="88" Height="28" Content="Click me"> 12 <i:Interaction.Triggers> 13 <i:EventTrigger EventName="Click"> 14 <i:ChangePropertyAction PropertyName="Background"> 15 <i:ChangePropertyAction.Value> 16 <SolidColorBrush Color="Red"/> 17 </i:ChangePropertyAction.Value> 18 </i:ChangePropertyAction> 19 </i:EventTrigger> 20 </i:Interaction.Triggers> 21 </Button> 22 </Grid> 23 </Window>
如果没有使用XAML Behavior,我们需要将控件命名,然后在控件的Click事件处理程序中,设置控件的背景颜色
当然,我们可以通过样式和触发器来实现一样的功能,但这仅限于控件内部,如果要设置其它控件就不行了。而XAML Behavior可以设置目标对象是自身也可以是其它控件。
界面
1 <Button Width="88" Height="28" Content="Click" Margin="10" Name="btn" Click="Button_Click"></Button>
事件处理程序
private void Button_Click(object sender, RoutedEventArgs e){btn.Background = Brushes.Red;}
说明:
XAML Behaviors的功能远不止如此,这里只是简单演示。如果不习惯使用XAML Behaviors,也可以继续使用以前的代码。
Microsoft-XAML-Behaviors版本
在.Net Framework时期,XAML Behaviors包含在Blend SDK中,并且需要手动引用 System.Windows.Interactivity.dll。
我前面的文章中介绍过如何安装Blend SDK
https://www.cnblogs.com/zhaotianff/p/11714279.html
随着.NET Core的出现,System.Windows.Interactivity.dll中的功能开始放到开源社区,并且改名为Microsoft-XAML-Behaviors。通过nuget包安装。
详情可以参考下面的文章:
https://devblogs.microsoft.com/dotnet/open-sourcing-xaml-behaviors-for-wpf/
以前使用的XAML命名空间是
1 http://schemas.microsoft.com/expression/2010/interactivity
开源以后使用的XAML命名空间是
1 http://schemas.microsoft.com/xaml/behaviors
项目地址:
https://github.com/microsoft/XamlBehaviorsWpf
实现原理
对于实现原理我没有去细看,大概看了一下。假设有如下代码:
1 <Button> 2 <Behaviors:Interaction.Triggers> 3 <Behaviors:EventTrigger EventName="Click"> 4 <Behaviors:ControlStoryboardAction Storyboard="{StaticResource StoryboardSample}" ControlStoryboardOption="TogglePlayPause"/> 5 </Behaviors:EventTrigger> 6 </Behaviors:Interaction.Triggers> 7 </Button>
XamlBehaviorsWpf库定义了一个Interaction静态类,在这个类中,定义了两个附加属性,分别 是Triggers和Behaviors。
Triggers是一个TriggerCollection类型,可以存储不同的Trigger。
Behaviors是一个BehaviorCollection类型,可以存储不同的Behavior。
添加Trigger时,Trigger会被添加到TriggerCollection中去。这个Trigger可以是EventTrigger、DataStoreChangedTrigger或DataTrigger等。在Trigger下,实现对应的Action。当Trigger触发时,调用Trigger下的Action。
添加Behavior时,Behavior会被添加到BehaviorCollection中去。XamlBehaviorsWpf提供了DataStateBehavior、FluidMoveBehavior、MouseDragElementBehavior、ConditionBehavior和TranslateZoomRotateBehavior等Behavior。
Trigger和Behavior的区别
触发器
包含一个或多个动作的对象,可根据某些刺激调用这些动作。一种非常常见的触发器是针对事件触发的触发器(EventTrigger)。其他例子可能包括在定时器上触发的触发器,或在抛出未处理异常时触发的触发器。
行为
行为没有调用的概念;它是附加到元素上的东西,用于指定应用程序应在何时做出响应。
常见Trigger的用法
EventTrigger
监听源上指定事件并在事件触发时触发的触发器。
在使用MVVM模式开发时,需要将事件转换为命令绑定,就可以使用EventTrigger。
用法如下:
下面的代码演示了,当SelectionChanged事件触发时,将会执行Action
1 <ListBox> 2 <i:Interaction.Triggers> 3 <i:EventTrigger EventName="SelectionChanged"> 4 ..action... 5 </i:EventTrigger> 6 </i:Interaction.Triggers> 7 </ListBox>
PropertyChangedTrigger
代表当绑定数据发生变化时执行操作的触发器。
用法如下:
下面的代码演示了,当TextBoxText属性发生更改时,将会执行Action
1 <TextBox Text="{Binding TextBoxText,UpdateSourceTrigger=PropertyChanged}"> 2 <i:Interaction.Triggers> 3 <i:PropertyChangedTrigger Binding="{Binding TextBoxText}"> 4 ...action... 5 </i:PropertyChangedTrigger> 6 </i:Interaction.Triggers> 7 </TextBox>
DataTrigger
代表当绑定数据满足指定条件时执行操作的触发器。
用法如下:
下面的代码演示了,当CheckBox选中/未选中时,将会执行对应的Action
1 <CheckBox x:Name="checkBox" >2 <i:Interaction.Triggers>3 <i:DataTrigger Binding="{Binding IsChecked, ElementName=checkBox}" Value="False">4 ...选中 action...5 </i:DataTrigger>6 <i:DataTrigger Binding="{Binding IsChecked, ElementName=checkBox}" Value="True">7 ...未选中 action...8 </i:DataTrigger>9 </i:Interaction.Triggers> 13 </CheckBox>
常见Action的用法
ChangePropertyAction
调用时将指定属性更改为指定值的操作。
用法如下:
首先我们在界面上放置一个Rectangle,然后放置两个按钮,当按钮的Click事件触发时,更改Rectangle的Background属性
1 <Grid>2 <Grid.RowDefinitions>3 <RowDefinition Height="5*"/>4 <RowDefinition Height="*"/>5 </Grid.RowDefinitions>6 <Rectangle x:Name="DataTriggerRectangle" Grid.Row="0" />7 8 <Grid Grid.Row="1">9 <Grid.ColumnDefinitions> 10 <ColumnDefinition Width="*" /> 11 <ColumnDefinition Width="*" /> 12 </Grid.ColumnDefinitions> 13 <Button x:Name="YellowButton" Content="Yellow" Grid.Column="0"> 14 <Behaviors:Interaction.Triggers> 15 <Behaviors:EventTrigger EventName="Click" SourceObject="{Binding ElementName=YellowButton}"> 16 <Behaviors:ChangePropertyAction TargetObject="{Binding ElementName=DataTriggerRectangle}" PropertyName="Fill" Value="LightYellow"/> 17 </Behaviors:EventTrigger> 18 </Behaviors:Interaction.Triggers> 19 </Button> 20 <Button x:Name="PinkButton" Content="Pink" Grid.Column="1"> 21 <Behaviors:Interaction.Triggers> 22 <Behaviors:EventTrigger EventName="Click" SourceObject="{Binding ElementName=PinkButton}"> 23 <Behaviors:ChangePropertyAction TargetObject="{Binding ElementName=DataTriggerRectangle}" PropertyName="Fill" Value="DeepPink"/> 24 </Behaviors:EventTrigger> 25 </Behaviors:Interaction.Triggers> 26 </Button> 27 </Grid> 28 29 </Grid>
ControlStoryboardAction
调用时将改变目标Storyboard状态的操作,也就是可以控制动画状态。
用法如下:
首先我们放置一个Rectangle
1 <Rectangle x:Name="StoryboardRectangle" StrokeThickness="5" Fill="Pink" Width="100" Stroke="LightYellow" RenderTransformOrigin="0.5,0.5" > 2 <Rectangle.RenderTransform> 3 <ScaleTransform /> 4 </Rectangle.RenderTransform> 5 </Rectangle>
然后定义Storyboard,目标对象就是前面定义的Rectangle
1 <Window.Resources>2 <Storyboard x:Key="StoryboardSample" >3 <DoubleAnimation Duration="0:0:5" To="0.35"4 Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)"5 Storyboard.TargetName="StoryboardRectangle" d:IsOptimized="True"/>6 <DoubleAnimation Duration="0:0:5" To="0.35"7 Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"8 Storyboard.TargetName="StoryboardRectangle" d:IsOptimized="True"/>9 </Storyboard> 10 11 </Window.Resources>
再放置一个按钮,使用EventTrigger,当点击时,调用ControlStoryboardAction
1 <Button Content="Start storyboard" HorizontalAlignment="Stretch" Grid.Row="1" VerticalAlignment="Stretch" Margin="0,10,0,10" Width="128" Height="28"> 2 <i:Interaction.Triggers> 3 <i:EventTrigger EventName="Click"> 4 <i:ControlStoryboardAction Storyboard="{StaticResource StoryboardSample}" ControlStoryboardOption="TogglePlayPause"/> 5 </i:EventTrigger> 6 </i:Interaction.Triggers> 7 </Button>
CallMethodAction
调用指定对象的方法。
用法如下:
首先我们看一下调用ViewModel中方法的示例,添加一个按钮,如下所示:
1 <Button x:Name="button2" Content="调用当前DataContext类方法" HorizontalAlignment="Stretch" Margin="20,3" Grid.Column="1"> 2 <i:Interaction.Triggers> 3 <i:EventTrigger EventName="Click" SourceObject="{Binding ElementName=button2}"> 4 <i:CallMethodAction TargetObject="{Binding}" MethodName="CallMe"/> 5 </i:EventTrigger> 6 </i:Interaction.Triggers> 7 </Button>
然后我们在ViewModel中增加一个CallMe函数,为了方便演示,我直接将后台代码类设置为DataContext。
1 public partial class ActionWindow : Window2 {3 public ActionWindow()4 {5 InitializeComponent();6 7 //设置数据上下文(ViewModel)8 //这里只做演示,直接使用后台类作为ViewModel9 this.DataContext = this; 10 } 11 12 public void CallMe() 13 { 14 System.Windows.MessageBox.Show("调用了ActionWindow的方法"); 15 } 16 }
GoToStateAction
调用时将元素切换到指定 VisualState 的动作。
关于Visual State,可以参考我前面的文章:https://www.cnblogs.com/zhaotianff/p/13254430.html
首先我们定义一个Button控件,并增加两个VisualState。启用和禁用状态,启用时,颜色切换为Pink,禁用时切换为Silver
1 <Button x:Name="sampleStateButton" Content="示例按钮" VerticalAlignment="Stretch" Height="28" Width="88" Margin="0,0,0,0">2 <Button.Resources>3 <Style TargetType="Button">4 <Setter Property="Template">5 <Setter.Value>6 <ControlTemplate TargetType="Button">7 <Grid x:Name="BaseGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Pink">8 <Label Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"></Label>9 <VisualStateManager.VisualStateGroups> 10 <VisualStateGroup> 11 <VisualState x:Name="Normal"> 12 <Storyboard> 13 <ColorAnimation Storyboard.TargetName="BaseGrid" Duration="0:0:0.3" Storyboard.TargetProperty="(Grid.Background).(SolidColorBrush.Color)" To="Pink"/> 14 </Storyboard> 15 </VisualState> 16 <VisualState x:Name="Disabled"> 17 <Storyboard> 18 <ColorAnimation Storyboard.TargetName="BaseGrid" Duration="0:0:0.3" Storyboard.TargetProperty="(Grid.Background).(SolidColorBrush.Color)" To="Silver"/> 19 </Storyboard> 20 </VisualState> 21 </VisualStateGroup> 22 </VisualStateManager.VisualStateGroups> 23 </Grid> 24 </ControlTemplate> 25 </Setter.Value> 26 </Setter> 27 </Style> 28 </Button.Resources> 29 </Button>
然后增加一个CheckBox,当选中时,切换到Disabled状态,未选中时,切换到Normal状态
1 <CheckBox x:Name="checkBox" Grid.Row="1" Content="启用/禁用" HorizontalAlignment="Center" Margin="0,5">2 <i:Interaction.Triggers>3 <i:DataTrigger Binding="{Binding IsChecked, ElementName=checkBox}" Value="False">4 <i:GoToStateAction StateName="Normal" TargetObject="{Binding ElementName=sampleStateButton}"/>5 </i:DataTrigger>6 <i:DataTrigger Binding="{Binding IsChecked, ElementName=checkBox}" Value="True">7 <i:GoToStateAction StateName="Disabled" TargetObject="{Binding ElementName=sampleStateButton}"/>8 </i:DataTrigger>9 </i:Interaction.Triggers> 10 </CheckBox>
LaunchUriOrFileAction
用于启动进程打开文件或 Uri 的操作。对于文件,该操作将启动默认程序 程序。Uri 将在网络浏览器中打开。
这里我们直接放置一个按钮,当按钮点击 时,打开bing主页
1 <Button x:Name="buttonlaunch" Content="打开Bing" HorizontalAlignment="Stretch" Width="88" Height="28"> 2 <i:Interaction.Triggers> 3 <i:EventTrigger EventName="Click" SourceObject="{Binding ElementName=buttonlaunch}"> 4 <i:LaunchUriOrFileAction Path="https://www.bing.com" /> 5 </i:EventTrigger> 6 </i:Interaction.Triggers> 7 </Button>
PlaySoundAction
播放音频的动作,此操作适用于不需要停止或控制的简短音效。它不支持暂停/继续等操作,仅适用于单次播放。
这是我们放置一个按钮,并嵌入一个音频文件到程序中,当点击按钮的时候,可以播放音频。
注意:音频没播放完成前,退出主窗口,进程不会退出,需要等到音频播放完
1 <Button x:Name="buttonplay" Content="播放音频" Width="88" Height="28"> 2 <i:Interaction.Triggers> 3 <i:EventTrigger EventName="Click" SourceObject="{Binding ElementName=buttonplay}"> 4 <i:PlaySoundAction Source="../../../Resources/Cheer.mp3" Volume="1.0" /> 5 </i:EventTrigger> 6 </i:Interaction.Triggers> 7 </Button>
InvokeCommandAction
执行一个指定的命令。
这个Action是我们最常用的Action,使用MVVM模式进行开发时,需要将控件的事件绑定到Command上,就可以使用InvokeCommandAction。
这里我们放置一个ListBox,并在ViewModel中处理ListBox选择项切换事件。
使用EventTrigger处理SelectionChanged,然后使用InvokeCommandAction,将SelectionChanged事件绑定到OnListBoxSelectionChangedCommand命令。
1 <ListBox Width="300" Height="200">2 <i:Interaction.Triggers>3 <i:EventTrigger EventName="SelectionChanged">4 <i:InvokeCommandAction Command="{Binding OnListBoxSelectionChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListBox},Path=SelectedItem}"></i:InvokeCommandAction>5 </i:EventTrigger>6 </i:Interaction.Triggers>7 8 <ListBoxItem>1</ListBoxItem>9 <ListBoxItem>2</ListBoxItem> 10 <ListBoxItem>3</ListBoxItem> 11 <ListBoxItem>4</ListBoxItem> 12 </ListBox>
在ViewModel中定义OnListBoxSelectionChangedCommand命令
1 public RelayCommand OnListBoxSelectionChangedCommand { get; private set; }2 3 public ActionWindow()4 {5 OnListBoxSelectionChangedCommand = new RelayCommand(OnListBoxSelectionChanged);6 }7 8 public void OnListBoxSelectionChanged(object listBoxItem)9 { 10 var item = listBoxItem as ListBoxItem; 11 12 if (item != null) 13 { 14 MessageBox.Show(item.Content.ToString()); 15 } 16 } 17 }
这样我们在切换列表项时,就会弹窗显示选中项。
RemoveElementAction
调用时将从树中移除目标元素的操作。
我们放置一个Rectangle和一个Button,当Button按下时,移除这个Rectangle。
1 <StackPanel>2 <Rectangle x:Name="RectangleRemove" Fill="Pink" Width="120" Height="120"/>3 <Button x:Name="RemoveButton" Content="移除Rectangle" Width="88" Height="28">4 <i:Interaction.Triggers>5 <i:EventTrigger EventName="Click">6 <i:RemoveElementAction TargetName="RectangleRemove" />7 </i:EventTrigger>8 </i:Interaction.Triggers>9 </Button> 10 </StackPanel>
介绍完Trigger和Action,接下我们看一下Microsoft.Xaml.Behaviors.Wpf包自带的Behavior
常见Behavior的用法
FluidMoveBehavior
观察元素(或元素集)的布局变化,并在需要时将元素平滑移动到新位置的行为。
此行为不会对元素的大小或可见性进行动画调整,只会对元素在其父容器中的偏移量进行动画调整。
例如我们在一个WrapPanel中放置一些控件,并加上FluidMoveBehavior。
这里有几个属性可以注意下:
AppliesTo:表示该行为仅适用于该元素,还是适用于该元素的所有子元素(如果该元素是面板)。
EaseY:用于移动垂直部分的 缓动动画。
EaseX:用于移动水平部分的 缓动动画。
我们看一下使用方法,首先我们在界面上放置一个WrapPanel,然后增加一些子元素(Border),并增加FluidMoveBehavior。
1 <WrapPanel Width="300" Name="wrap">2 <i:Interaction.Behaviors>3 <i:FluidMoveBehavior Duration="00:00:01" AppliesTo="Children">4 <i:FluidMoveBehavior.EaseX>5 <BounceEase EasingMode="EaseOut" Bounces="2" />6 </i:FluidMoveBehavior.EaseX>7 </i:FluidMoveBehavior>8 </i:Interaction.Behaviors>9 <Border Width="60" Height="60" BorderThickness="1" BorderBrush="Black" CornerRadius="5" Margin="5"></Border> 10 <Border Width="60" Height="60" BorderThickness="1" BorderBrush="Black" CornerRadius="5" Margin="5"></Border> 11 <Border Width="60" Height="60" BorderThickness="1" BorderBrush="Black" CornerRadius="5" Margin="5"></Border> 12 <Border Width="60" Height="60" BorderThickness="1" BorderBrush="Black" CornerRadius="5" Margin="5"></Border> 13 <Border Width="60" Height="60" BorderThickness="1" BorderBrush="Black" CornerRadius="5" Margin="5"></Border> 14 <Border Width="60" Height="60" BorderThickness="1" BorderBrush="Black" CornerRadius="5" Margin="5"></Border> 15 <Border Width="60" Height="60" BorderThickness="1" BorderBrush="Black" CornerRadius="5" Margin="5"></Border> 16 <Border Width="60" Height="60" BorderThickness="1" BorderBrush="Black" CornerRadius="5" Margin="5"></Border> 17 </WrapPanel>
当窗口打开时,可以看到动画效果。然后我们增加一个按钮,点击 一次面板宽度减少50,因为WrapPanel会根据宽度自动排列元素,所以在宽度变化 时,我们也可以看到元素的动画效果。
1 <Button Content="移除元素" Width="88" Height="28" Click="Button_Click"></Button>
1 private void Button_Click(object sender, RoutedEventArgs e) 2 { 3 this.wrap.Width -= 50; 4 }
运行效果如下:
MouseDragElementBehavior
这个行为可以根据鼠标在元素上的拖动手势重新定位所附元素。
属性:
ConstrainToParentBounds:是否限制在父容器内
X:被拖动元素相对于根元素左侧 X 位置。
Y:X:被拖动元素相对于根元素顶部 Y 位置。
例如我们在界面上放置一个Rectangle元素,然后使之可以拖动,使用方法如下:
1 <Rectangle Width="50" Height="50" Fill="LightGreen"> 2 <i:Interaction.Behaviors> 3 <i:MouseDragElementBehavior></i:MouseDragElementBehavior> 4 </i:Interaction.Behaviors> 5 </Rectangle>
效果如下:
TranslateZoomRotateBehavior
这个行为和MouseDragElementBehavior类似,如果在触控设备上,除了支持移动元素外,还支持手指缩放,如果在不支持触控的设备上,和TranslateZoomRotateBehavior功能一致。
DataStateBehavior
根据条件语句在两种状态之间切换。它表示一种状态,可以根据某些条件是否为真来改变对象的状态。
这个行为也是跟VisualState有关的。
这里我们定义两个State,一个Normal,一个Blue,并增加一个文本框和一个按钮,当文本框数据改变时,改变按钮的状态为Blue。
1 <StackPanel>2 <TextBox Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" Margin="5" x:Name="MyText" />3 <Button x:Name="sampleStateButton" Width="88" Height="28">4 <Button.Resources>5 <Style TargetType="Button" >6 <Setter Property="Template">7 <Setter.Value>8 <ControlTemplate TargetType="Button">9 <Grid x:Name="BaseGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Pink"> 10 <VisualStateManager.VisualStateGroups> 11 <VisualStateGroup> 12 <VisualState x:Name="Normal"> 13 <Storyboard> 14 <ColorAnimation Storyboard.TargetName="BaseGrid" Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)" To="Pink" From="Pink" /> 15 </Storyboard> 16 </VisualState> 17 <VisualState x:Name="Blue"> 18 <Storyboard> 19 <ColorAnimation Storyboard.TargetName="BaseGrid" Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)" To="LightBlue" From="LightBlue" Duration="Forever" /> 20 </Storyboard> 21 </VisualState> 22 </VisualStateGroup> 23 </VisualStateManager.VisualStateGroups> 24 <i:Interaction.Behaviors> 25 <i:DataStateBehavior Binding="{Binding Text, ElementName=MyText}" Value="" TrueState="Normal" FalseState="Blue" /> 26 </i:Interaction.Behaviors> 27 </Grid> 28 </ControlTemplate> 29 </Setter.Value> 30 </Setter> 31 </Style> 32 </Button.Resources> 33 </Button> 34 </StackPanel>
运行效果如下:
示例代码
下载
参考资料:
https://stackoverflow.com/questions/34017496/what-is-the-difference-between-behaviors-and-event-triggers
https://github.com/microsoft/XamlBehaviorsWpf