You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 20:25:47