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

WPF自定义控件中IsMouseOver与IsPressed触发Storyboard失效求助

OpacityButton自定义控件透明度问题解决

需求与问题

要实现一个名为OpacityButton的自定义控件,要求:

  • 鼠标悬停时,透明度从0.5平滑过渡到0.8
  • 鼠标按下时,透明度立即变为1

但实际运行时,按下按钮后透明度没有变化;尝试添加RemoveStoryboard后,鼠标悬停的过渡动画又失效了。

原代码

<ControlTemplate TargetType="{x:Type local:OpacityButton}">
    <Border x:Name="B" Background="{TemplateBinding Background}" Opacity="0.5" 
            BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
        <ContentPresenter Margin="{TemplateBinding Padding}" TextBlock.FontFamily="{TemplateBinding FontFamily}" 
                          Content="{TemplateBinding Content}" TextBlock.Foreground="{TemplateBinding Foreground}" 
                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                          TextBlock.FontSize="{TemplateBinding FontSize}">
        </ContentPresenter>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Trigger.EnterActions>
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation
                            Storyboard.TargetName="B"
                            Storyboard.TargetProperty="Opacity"
                            To="0.8" Duration="0:0:0.3" />
                    </Storyboard>
                </BeginStoryboard>
            </Trigger.EnterActions>
            <Trigger.ExitActions>
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation
                            Storyboard.TargetName="B"
                            Storyboard.TargetProperty="Opacity"
                            To="0.5" Duration="0:0:0.3" />
                    </Storyboard>
                </BeginStoryboard>
            </Trigger.ExitActions>
        </Trigger>
        <Trigger Property="IsPressed" Value="True">
            <Setter Property="Opacity" Value="1" TargetName="B"></Setter>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

问题根源

WPF中,Storyboard动画会独占目标属性的控制权,静态Setter的优先级低于动画,所以IsPressed的Setter无法覆盖悬停动画对Opacity的修改。直接添加RemoveStoryboard会立即终止动画,导致悬停过渡还没完成就被取消,自然看不到效果。

解决方案

通过命名Storyboard,在IsPressed触发时先移除悬停相关动画,再设置透明度;松开按钮后根据鼠标是否仍在控件上,恢复对应的过渡状态。

修改后的代码:

<ControlTemplate TargetType="{x:Type local:OpacityButton}">
    <Border x:Name="B" Background="{TemplateBinding Background}" Opacity="0.5" 
            BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
        <ContentPresenter Margin="{TemplateBinding Padding}" TextBlock.FontFamily="{TemplateBinding FontFamily}" 
                          Content="{TemplateBinding Content}" TextBlock.Foreground="{TemplateBinding Foreground}" 
                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                          TextBlock.FontSize="{TemplateBinding FontSize}">
        </ContentPresenter>
    </Border>
    <ControlTemplate.Triggers>
        <!-- 鼠标悬停触发的动画,给Storyboard命名 -->
        <Trigger Property="IsMouseOver" Value="True">
            <Trigger.EnterActions>
                <BeginStoryboard x:Name="MouseOverEnterSb">
                    <Storyboard>
                        <DoubleAnimation
                            Storyboard.TargetName="B"
                            Storyboard.TargetProperty="Opacity"
                            To="0.8" 
                            Duration="0:0:0.3" 
                            FillBehavior="HoldEnd"/>
                    </Storyboard>
                </BeginStoryboard>
            </Trigger.EnterActions>
            <Trigger.ExitActions>
                <BeginStoryboard x:Name="MouseOverExitSb">
                    <Storyboard>
                        <DoubleAnimation
                            Storyboard.TargetName="B"
                            Storyboard.TargetProperty="Opacity"
                            To="0.5" 
                            Duration="0:0:0.3" 
                            FillBehavior="HoldEnd"/>
                    </Storyboard>
                </BeginStoryboard>
            </Trigger.ExitActions>
        </Trigger>

        <!-- 鼠标按下的处理 -->
        <Trigger Property="IsPressed" Value="True">
            <Trigger.EnterActions>
                <!-- 移除悬停的两个动画,释放属性控制权 -->
                <RemoveStoryboard BeginStoryboardName="MouseOverEnterSb"/>
                <RemoveStoryboard BeginStoryboardName="MouseOverExitSb"/>
                <!-- 立即将透明度设为1 -->
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation
                            Storyboard.TargetName="B"
                            Storyboard.TargetProperty="Opacity"
                            To="1" 
                            Duration="0:0:0"/>
                    </Storyboard>
                </BeginStoryboard>
            </Trigger.EnterActions>

            <!-- 松开按钮后恢复状态 -->
            <Trigger.ExitActions>
                <!-- 如果鼠标还在控件上,过渡到0.8 -->
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation
                            Storyboard.TargetName="B"
                            Storyboard.TargetProperty="Opacity"
                            To="0.8" 
                            Duration="0:0:0.3" 
                            FillBehavior="HoldEnd"/>
                    </Storyboard>
                </BeginStoryboard>
            </Trigger.ExitActions>
        </Trigger>

        <!-- 松开按钮且鼠标已离开时,过渡到0.5 -->
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsPressed" Value="False"/>
                <Condition Property="IsMouseOver" Value="False"/>
            </MultiTrigger.Conditions>
            <MultiTrigger.EnterActions>
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation
                            Storyboard.TargetName="B"
                            Storyboard.TargetProperty="Opacity"
                            To="0.5" 
                            Duration="0:0:0.3" 
                            FillBehavior="HoldEnd"/>
                    </Storyboard>
                </BeginStoryboard>
            </MultiTrigger.EnterActions>
        </MultiTrigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

关键修改点

  1. 给悬停的两个Storyboard命名,方便在按下时精准移除
  2. IsPressed触发时先移除悬停动画,解除属性锁定,再用零时长动画将透明度设为1
  3. 拆分状态判断,用MultiTrigger处理"松开按钮且鼠标离开"的场景,保证所有状态切换都有对应的过渡动画

内容的提问来源于stack exchange,提问作者Melon NG

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 19:10:20