WPF自定义控件中IsMouseOver与IsPressed触发Storyboard失效求助
需求与问题
要实现一个名为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>
关键修改点
- 给悬停的两个Storyboard命名,方便在按下时精准移除
- IsPressed触发时先移除悬停动画,解除属性锁定,再用零时长动画将透明度设为1
- 拆分状态判断,用MultiTrigger处理"松开按钮且鼠标离开"的场景,保证所有状态切换都有对应的过渡动画
内容的提问来源于stack exchange,提问作者Melon NG
相关产品推荐
相关产品推荐

