如何在WPF的XAML中为任意控件的事件执行Storyboard动画
问题描述
我在使用C# WPF开发,原本为Grid内部的Border控件创建了绑定MouseEnter事件的动画样式:
<Style x:Key="Atash" TargetType="Border">...</Style>
现在希望无需后台代码,仅通过XAML实现:将该样式应用到任意指定控件(比如Grid)时,该控件的指定事件(比如MouseEnter)能触发Border的动画。但将样式应用到Grid后,鼠标进入Grid时抛出异常:
System.InvalidOperationException:“'BorderBrush' 属性在路径 '(0).(1)' 中未指向DependencyObject。”
解决方案
这个错误的核心原因很直接:你把原本针对Border的样式直接套到了Grid上,但Grid本身没有BorderBrush属性——动画试图修改一个不存在的属性,自然就报错了。
下面是几种纯XAML的解决思路,按需选择:
思路1:让Border监听父Grid的鼠标状态
保留原有的Border样式,在样式里通过RelativeSource监听父Grid的IsMouseOver属性,以此触发动画。这样Grid不需要额外设置样式,只需要内部的Border应用样式即可:
<Style x:Key="Atash" TargetType="Border"> <Setter Property="BorderBrush" Value="Gray"/> <Setter Property="BorderThickness" Value="2"/> <Style.Triggers> <!-- 监听父级Grid的鼠标进入状态 --> <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=IsMouseOver}" Value="True"> <DataTrigger.EnterActions> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetProperty="BorderBrush.Color" To="Blue" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </DataTrigger.EnterActions> <DataTrigger.ExitActions> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetProperty="BorderBrush.Color" To="Gray" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </DataTrigger.ExitActions> </DataTrigger> </Style.Triggers> </Style>
使用示例:
<Grid Width="200" Height="100"> <Border Style="{StaticResource Atash}"> <!-- Border内部内容 --> </Border> </Grid>
思路2:为Grid创建带内置Border的样式
如果一定要把样式直接应用到Grid上,可以重新写一个针对Grid的样式,在Grid的模板里内置Border,然后通过Grid自身的IsMouseOver触发器控制Border的动画:
<Style x:Key="GridWithAnimatedBorder" TargetType="Grid"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Grid"> <Border x:Name="InnerBorder" BorderBrush="Gray" BorderThickness="2"> <!-- 保留Grid原有的内容展示 --> <ContentPresenter/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Trigger.EnterActions> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="InnerBorder" Storyboard.TargetProperty="BorderBrush.Color" To="Blue" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="InnerBorder" Storyboard.TargetProperty="BorderBrush.Color" To="Gray" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </Trigger.ExitActions> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
使用示例:
<Grid Style="{StaticResource GridWithAnimatedBorder}" Width="200" Height="100"> <!-- Grid内部的内容 --> </Grid>
思路3:用附加属性实现通用触发(需少量后台代码定义属性)
如果需要更灵活的方案——比如让任意控件都能触发任意Border的动画,可以定义一个附加属性,绑定触发控件的鼠标事件到Border的动画状态。这部分需要一点后台代码,但只是定义属性,不需要写业务逻辑:
第一步:定义附加属性类
public static class MouseTriggerHelper { public static readonly DependencyProperty TriggerElementProperty = DependencyProperty.RegisterAttached( "TriggerElement", typeof(UIElement), typeof(MouseTriggerHelper), new PropertyMetadata(null, OnTriggerElementChanged)); public static UIElement GetTriggerElement(DependencyObject obj) { return (UIElement)obj.GetValue(TriggerElementProperty); } public static void SetTriggerElement(DependencyObject obj, UIElement value) { obj.SetValue(TriggerElementProperty, value); } public static readonly DependencyProperty IsTriggerActiveProperty = DependencyProperty.RegisterAttached( "IsTriggerActive", typeof(bool), typeof(MouseTriggerHelper), new PropertyMetadata(false)); public static bool GetIsTriggerActive(DependencyObject obj) { return (bool)obj.GetValue(IsTriggerActiveProperty); } public static void SetIsTriggerActive(DependencyObject obj, bool value) { obj.SetValue(IsTriggerActiveProperty, value); } private static void OnTriggerElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (e.OldValue is UIElement oldEle) { oldEle.MouseEnter -= OnMouseEnter; oldEle.MouseLeave -= OnMouseLeave; } if (e.NewValue is UIElement newEle) { newEle.MouseEnter += OnMouseEnter; newEle.MouseLeave += OnMouseLeave; } void OnMouseEnter(object sender, MouseEventArgs args) => SetIsTriggerActive(d, true); void OnMouseLeave(object sender, MouseEventArgs args) => SetIsTriggerActive(d, false); } }
第二步:在XAML中使用
<Window.Resources> <Style x:Key="AnimatedBorder" TargetType="Border"> <Setter Property="BorderBrush" Value="Gray"/> <Setter Property="BorderThickness" Value="2"/> <Style.Triggers> <DataTrigger Binding="{Binding Path=(local:MouseTriggerHelper.IsTriggerActive), RelativeSource={RelativeSource Self}}" Value="True"> <DataTrigger.EnterActions> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetProperty="BorderBrush.Color" To="Blue" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </DataTrigger.EnterActions> <DataTrigger.ExitActions> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetProperty="BorderBrush.Color" To="Gray" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </DataTrigger.ExitActions> </DataTrigger> </Style.Triggers> </Style> </Window.Resources> <!-- 页面内容 --> <Grid x:Name="MyGrid" Width="200" Height="100"> <Border Style="{StaticResource AnimatedBorder}" local:MouseTriggerHelper.TriggerElement="{Binding ElementName=MyGrid}"/> </Grid>
内容的提问来源于stack exchange,提问作者Mojtabah
相关产品推荐
相关产品推荐

