I am making a custom control. When mouse over it, the opacity of content will animate from 0.5 to 0.8. When mouse pressed it, the opacity of content will set to 1 immediately.
Here is my code:
<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>
When the button is pressed, the opacity doesn't change any.
Soon I found this solution: Storyboard animation not completing when triggering property changes
I add the RemoveStoryboard
to the EnterActions and ExitActions, now when the mouse is over, the opacity does not change any.
It seems because it removes the Storyboard immediately but not after it is complete.
How can I solve this?
CodePudding user response:
This is a better job for VisualState
s:
<ControlTemplate TargetType="{x:Type local:OpacityButton}">
<Border x:Name="B"
Background="{TemplateBinding Background}"
Opacity="0.5"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="B"
Storyboard.TargetProperty="Opacity"
To="0.5"
Duration="0:0:0.3" />
</Storyboard>
</VisualState>
<VisualState Name="MouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="B"
Storyboard.TargetProperty="Opacity"
To="0.8"
Duration="0:0:0.3" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="B"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:0.3" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<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>
The problem with your Triggers is that more than one of them is true at the same time - when the button is pressed, the mouse is also over it, so the triggers are competing and the IsMouseOver
one is winning.
The control can only have one VisualState
at a time though, so you can define exactly what happens in each state and only that state.
If you're committed to triggers you could use MultiTrigger
for the mouse over state, making the conditions IsMouseOver = true
and IsPressed = false
. Then when IsPressed
is true, that trigger will run and the other won't.
But assuming you're subclassing Button
here, WPF is already giving you the VisualState
's for free, so you might as well use them.