Button's nested style trigger not executed after click - wpf

I've tried to recreate a material design kind of button. One of the animations of this button is that upon mouse over is that the color of the button changes to a darker hue of the original color.
In order to realise this for every color, I overlay my button with a rectangle and change it's opacity between 25 % and 0 % depending on mouse over or not. This style is implemented in the style of the rectangle. But it appears that this style is only executed before I click. Once I click the button, this style trigger is not executed anymore.
I've also implemented a animation to change the opacity of the rectangle to 40 % when the button is clicked. this animation however, is still visible after the first click.
I thought that it might be because the rectangle's style is included in the control template. But I don't know how to extract those styles from the control template to the button's style.
<Style x:Key="MaterialFlatButton" TargetType="Button">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Rectangle RadiusX="4" RadiusY="4" Fill="#000000" Panel.ZIndex="1" x:Name="Reccie">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Opacity" Value="0"/>
<!--These styles-->
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.26"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="false">
<Setter Property="Opacity" Value="0"/>
</Trigger>
<EventTrigger RoutedEvent="MouseLeftButtonDown">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.4" Duration="0:0:0.25"/>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.25" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"
Margin="16 0 16 0" TextBlock.FontFamily="{StaticResource LatoMedium}"
TextBlock.FontSize="{StaticResource FontSizeBody}" Typography.Capitals="AllSmallCaps"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<!--Should go here perhaps?-->
<Style.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="#000000"/>
<Setter Property="Opacity" Value="0.26"/>
</Trigger>
</Style.Triggers>
</Style>

edit: does it do what you want if you set FillBehavior="Stop"?
<EventTrigger RoutedEvent="MouseLeftButtonDown">
<BeginStoryboard>
<Storyboard FillBehavior="Stop">
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.26" To="0.4" Duration="0:0:0.25"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>

Related

How to address children of a style ControlTemplate for animation?

I have a custom control extending a toggle button. This custom control has three dependency properties for ImageSources that can be used to customize Images that present a certain visual impression:
<Style TargetType="{x:Type local:SimpleFeedbackToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SimpleFeedbackToggleButton}">
<Grid x:Name="BtnGrid">
<Image x:Name="FeedbackImage" Source="{TemplateBinding FeedbackImageSource}" Visibility="Hidden" />
<Image x:Name="NormalImage" Margin="{TemplateBinding Padding}" Source="{TemplateBinding NormalImageSource}" />
<Image x:Name="DisabledImage" Source="{TemplateBinding DisabledImageSource}" Visibility="Hidden" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Visibility" Value="Visible" TargetName="DisabledImage"/>
<Setter Property="Opacity" Value="1.0" TargetName="BtnGrid"/>
</Trigger>
<Trigger Property="IsChecked" Value="true">
<Setter Property="Visibility" Value="Visible" TargetName="FeedbackImage"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding FeedbackBlink, RelativeSource={RelativeSource Self}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="SB_BlinkFeedback">
<Storyboard Storyboard.TargetProperty="Opacity">
<DoubleAnimation From="1.0" To="0.3" Duration="0:0:1" RepeatBehavior="Forever" AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="SB_BlinkFeedback" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
Another dependency property FeedbackBlink shall be used to start and stop blinking of only one of the images (FeedbackImage). Unfortunately I cannot find out how to address this image from the Storyboard "SB_BlinkFeedback". How could I do that?
Found a working solution shortly after posting the question: I had to move the DataTrigger from <Style.Triggers> to <ControlTemplate.Triggers>. Then the Storyboard declaration can be completed by Storyboard.TargetName="FeedbackImage" without compiler or runtime error.

Strange conflict of XAML IsMouseOver and IsEnabled causing button to appear active when disabled

In my application, I have a custom button style and template that has a couple states based on XAML triggers:
<Style x:Key="UniversalButton" TargetType="{x:Type Button}">
<Setter Property="Cursor" Value="Hand" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Background" Value="#FFD7D7D7" />
<Setter Property="BorderBrush" Value="#FF999999" />
<Setter Property="Foreground" Value="#FF666666" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ControlTemplate.Resources>
<Style x:Key="ButtonBorder" TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="3" />
<Setter Property="BorderThickness" Value="0,0,0,2" />
<Setter Property="SnapsToDevicePixels" Value="True" />
</Style>
<Style x:Key="ButtonContent" TargetType="{x:Type ContentPresenter}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Setter Property="Margin" Value="8,7,8,8" />
<Setter Property="RecognizesAccessKey" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
</Style>
</ControlTemplate.Resources>
<ControlTemplate.Triggers>
<Trigger Property="ContextMenu.IsOpen" Value="True">
<Setter Property="Opacity" Value="0.8" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsEnabled" Value="True" />
</MultiTrigger.Conditions>
<MultiTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.8" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</MultiTrigger.EnterActions>
<MultiTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</MultiTrigger.ExitActions>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.6" />
</Trigger>
</ControlTemplate.Triggers>
<Border Style="{DynamicResource ButtonBorder}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
Padding="{TemplateBinding Padding}"
Opacity="{TemplateBinding Opacity}">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ContentPresenter Style="{DynamicResource ButtonContent}"
TextBlock.FontWeight="{TemplateBinding FontWeight}"
TextBlock.Foreground="{TemplateBinding Foreground}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The specific instance of the button in question has a command with a CanExecute based on the selections of an ItemsControl in the view. When I toggle my selections, including those that are meant to disable the button, things work fine. The button becomes semi-transparent as I have called for in the XAML trigger for IsEnabled.
The problem starts when I mouse over the button, activating the storyboards above. After this happens, changing the selections to a state in which the button is disabled leaves the button fully opaque, regardless of the state of IsEnabled/CanExecute.
Through extensive debugging, I've determined that the issue is not caused by the CanExecute as previously thought, and that it must be linked to the IsMouseOver trigger. I've exhausted all of my XAML knowledge in trying to figure this conflict out and I'd be very grateful for help.
Thanks in advance!
I believe that the issue is caused by Dependency Property value precedence in WPF. In this case the animation (Timeline) has the default FillBehavior of FillBehavior.HoldEnd. After the animation finishes all the attempts to change the value (the Opacity property) should have a higher precedence. From the link you can see only system coercing the value can override the animation-based value (even locally setting such as by assignment directly in codebehind cannot override this).
That's why your template Trigger (whose precedence is 7) cannot override the current animated value. There are some solutions to this but I think you should use all storyboard in your triggers (instead of using a setter). If you don't want any animation, just set Duration to zero:
<Trigger Property="ContextMenu.IsOpen" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
To=".8" Duration="0:0:0"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<!-- similar for ExitActions -->
</Trigger>
<!-- -->
<Trigger Property="IsEnabled" Value="False">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
To=".6" Duration="0:0:0"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<!-- similar for ExitActions -->
</Trigger>
Using Storyboard this way is also good for maintenance. That's when you want to add some animation to the property value change, then you already have the Storyboard there and you can just add more options or animations...
PS: Don't be afraid of long XAML code. It's the nature and a characteristic of XAML code. However as a trade-off, we have more controls over the behaviors and effects.
I just have the same problem.
After several tests, I found the ExitActions of IsEnabled trigger, though finished its execution, would still somehow "stays" there and blocks the upcoming animation.
I haven't figured out the exact reason behind it, yet emprically, removing that storyboard before beginning another seems to solve the problem.
Codes like:
<Trigger Property="ContextMenu.IsOpen" Value="True">
<Trigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="someStoryboard" />
<BeginStoryboard>
<!-- animation here -->
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Trigger.ExitActions>
<BeginStoryboard x:Name="someStoryboard">
</Trigger>

WPF Animation of DataGridRow Background color from transparent to "Current" (current is set with Trigger)

I'm setting DataGridRow BackgroundColor to Green or to Red with trigger based on LogError value.
I want to animate newly added rows with transparency.
This works fine:
From="Transparent" To="Red"
But what I want color to goes to is current color set with Style. Its not always Red it could be Green as well.
This does not work:
From="Transparent" To="{TemplateBinding DataGridRow.Background}"
or
From="Transparent" To="{Binding RelativeSource={RelativeSource Self}, Path=Backgound}"
Code:
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property = "Background" Value="LimeGreen"/>
<Style.Triggers>
<DataTrigger Binding="{Binding LogMessage}" Value="Exception has occured">
<Setter Property = "Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="(DataGridRow.Background).(SolidColorBrush.Color)"
Duration="00:00:03"
From="Transparent"
To="{TemplateBinding DataGridRow.Background}"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
Error message: Cannot freeze this Storyboard timeline tree for use across threads.
There are few problems in your XAML code.
First, you have specified default style under resources section of DataGrid and later provide your own style which will override default style.
Either you should define new style and set its BasedOn DP to refer to default style. But in your case i don't see any use of defining separate style just for trigger.
Second, you want your animation to go from Transparent to colour selected in style which can either by LimeGreen or Red depending on trigger. So, you should not set To value in your animation since it will automatically picked up.
This will work as you desire -
<DataGrid>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property ="Background" Value="LimeGreen"/>
<Style.Triggers>
<DataTrigger Binding="{Binding LogMessage}"
Value="Exception has occured">
<Setter Property = "Background" Value="Red"/>
</DataTrigger>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty=
"(DataGridRow.Background).(SolidColorBrush.Color)"
Duration="00:00:03"
From="Transparent"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
#Den
You have to determinate a standart Color, before you animate. This is my Style for animating a row and cell.
<Style TargetType="{x:Type DataGridRow}">
<Style.Setters>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Background" Value="Transparent"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="#FFF37C21"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(DataGridRow.Background).(SolidColorBrush.Color)" Duration="00:00:0.2" To="#FFF37C21"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(DataGridRow.Background).(SolidColorBrush.Color)" Duration="00:00:0.2" To="Transparent"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type DataGridCell}">
<Style.Setters>
<Setter Property="FontWeight" Value="Light"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="IsTabStop" Value="False" />
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid x:Name="gridCell" Background="#00FFFFFF">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ContentPresenter
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Black"/>
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(TextElement.FontWeight)">
<DiscreteObjectKeyFrame KeyTime="00:00:0.2">
<DiscreteObjectKeyFrame.Value>
<FontWeight>Bold</FontWeight>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(TextElement.FontWeight)">
<DiscreteObjectKeyFrame KeyTime="00:00:0.2">
<DiscreteObjectKeyFrame.Value>
<FontWeight>Light</FontWeight>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>

Setting a button's selected color and focus in WPF

I have a button that has a transparent background and a thick white border. When the button detects a mouse enter I see that the background went from the set transparent background to it's default focused color so for that I just set the opacity to 0.2 so that it shows some feed back that it has a focus.
Now here is my dilemma. When I leave the button's bounds there is a small animation that goes from my .2 opacity to 1 before changing the background to the original transparent. I would like to know how to replace this with either a smoother animation so that I don't see the opaque background before it changing to transparent or just have a way to bypass the animation completely and just have it set my values. I see the similar thing happen when the button is focused. It will animate between 0.2 opacity and 1.0 with the default background color. Any ideas would be greatly appreciated.
<Button BorderBrush="White" BorderThickness="1" Width="45" Height="45" >
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.2" />
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Opacity" Value="1.0" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Here is a XAML only solution, I think this does what you want...
<Button BorderBrush="White" BorderThickness="1" Width="45" Height="45" Content="1234" >
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Opacity" Value="1.0" />
</Trigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.2" To="1"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.2" To="0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>

'Setter' object cannot be added to 'EventTrigger'?

I get the following error on the WPF code bellow:
'Setter' object cannot be added to 'EventTrigger'. The given object must be an instance of TriggerAction or a derived type.
<Style x:Key="LinkLabel" TargetType="{x:Type Label}">
<Setter Property="FontFamily" Value="Tahoma"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Foreground" Value="DarkBlue"/>
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter" >
<Setter Property="Cursor" Value="Hand"/>
</EventTrigger>
<EventTrigger RoutedEvent="MouseDown" >
<Setter Property="Foreground" Value="Red"/>
</EventTrigger>
<EventTrigger RoutedEvent="MouseUp" >
<Setter Property="Foreground" Value="DarkBlue"/>
</EventTrigger>
</Style.Triggers>
</Style>
Can anyone explain and point me in the right direction ?
MadSeb
Option 1 - Use BeginStoryboard like ChrisF said
Option 2 - Don't use EventTrigger - for example, for a label that changes background color when you mouse over it use:
<Style TargetType="Label">
<Setter Property="Background" Value="Blue"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true" >
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
Unfortunately there is no property IsMouseDown you can use.
About your example:
You don't have to set the cursor on MouseEnter, just set the Cursor property and it will only affect the mouse cursor when the mouse is over the control.
If you want to create an hyperlink control don't use a label, eitehr use a Button with a custom template (this will give you the Click event and IsPressed property) or, even better - use the Hyperlink class like this:
<TextBlock><Hyperlink>This is a link</Hyperlink></TextBlock>
This will have all the styling you wanted.
You must use a TriggerAction to change the values as in this example from the EventTriggers MSDN page:
<Style TargetType="Rectangle">
<Setter Property="Width" Value="50" />
<Setter Property="Height" Value="50" />
<Setter Property="Margin" Value="20" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="300" Duration="0:0:1.5"
AccelerationRatio="0.10" DecelerationRatio="0.25"
Storyboard.TargetProperty="(Canvas.Width)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1.5"
AccelerationRatio="0.10" DecelerationRatio="0.25"
Storyboard.TargetProperty="(Canvas.Width)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>

Resources