WPF Toggle-Button hover animation issue - c#

In my WPF application, I've created 3 different custom toggle button styles. I would like to add a colour animation anytime a button is hovered over, so using Event Triggers and Color Animation storyboards, this is what I've done;
BTW, there's an image in the each of the toggle buttons styles.
Toggle Button Style
<Style x:Key="ExeatKey" TargetType="ToggleButton">
<Setter Property="Foreground" Value="#B2B2B2"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Margin" Value="0 0 0 0"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid Margin="0 0 0 0" VerticalAlignment="Center" HorizontalAlignment="Center">
<Image Margin="0 10 0 30">
<!--Image Change Colour Trigger-->
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=takeExeatBtn, Path=IsChecked}" Value="True">
<Setter Property="Source" Value="/Images/Others/Button Icons/ex_blu.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=takeExeatBtn, Path=IsChecked}" Value="False">
<Setter Property="Source" Value="/Images/Others/Button Icons/ex_gray.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="Take An Exeat" TextAlignment="Center" FontSize="20" Margin="0 58 0 0" FontWeight="SemiBold"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<Storyboard>
<ColorAnimation To="#BCD0E8" Duration="0:0:0.5" Storyboard.TargetProperty="Background.Color"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard>
<Storyboard>
<ColorAnimation To="Transparent" Duration="0:0:0.5" Storyboard.TargetProperty="Background.Color"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Cursor" Value="Hand"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Background" Value="#BCD0E8"/>
<Setter Property="Foreground" Value="#5050EA"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Cursor" Value="Arrow"/>
<Setter Property="Background" Value="#BCD0E8"/>
<Setter Property="Foreground" Value="#7878FF"/>
</Trigger>
</Style.Triggers>
</Style>
Now naturally, I thought that this would work, but whenever the mouse enters it doesnt animate, only MouseLeave works.
Is there something that I've missed in the code?

Related

IsMouserOver Interference With IsSelected in WPF

Hey guys I have an issue with triggers for IsMouserOver and IsSelected.
My goal is to create an animation that changes the BorderThickness of my ListViewItems in IsMouserOver. Using EnterActions and ExitActions yields the desired result, however, when I try to also take into account the IsSelected property in another trigger, every property but BorderThickness can be set.
When I remove the whole IsMouseOver trigger, BorderThickness will be set in IsSelected and displayed correctly.
<Style TargetType="{x:Type ListViewItem}" x:Key="SubMenuStyles">
<Setter Property="Height" Value="40"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Left"
VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="Orange"/>
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation
Storyboard.TargetProperty="BorderThickness" From="0,0,0,0" To="10,0,0,0"
Duration="0:0:0.5"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation
Storyboard.TargetProperty="BorderThickness" From="10,0,0,0" To="0,0,0,0"
Duration="0:0:0.5"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<Trigger Property="ListViewItem.IsSelected" Value="True">
<Setter Property="Background" Value="#233E4F"/>
<Setter Property="BorderThickness" Value="50,0,0,0"/>
<Setter Property="BorderBrush" Value="Orange"/>
</Trigger>
</Style.Triggers>
</Style>
Problem
The behavior you encountered before is shown in the picture below:
where the problem is that your orange border is being reset every time the mouse is over the selected item.
What I believe you want to achieve is to keep the fixed 50px border on a selected item, like shown below:
Solution
In order to achieve that, we need to find a way to execute the animation only for items that are not selected (i.e. where IsSelected="False").
This is where MultiTriggers come into play.
MultiTriggers are quite like "normal" Triggers with the important addition that they fire not when only one condition is fulfilled, but when all conditions are fulfilled.
e.g.
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsSelected" Value="False" />
<!-- More conditions, if you want -->
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<!-- Will only be set when ALL conditions are fulfilled. -->
</MultiTrigger.Setters>
<MultiTrigger.EnterActions>
<!-- Will also only be executed when ALL conditions are fulfilled -->
</MultiTrigger.ExitActions>
</MultiTrigger>
Code
So, in your case, adjust your style to look like this:
<Window.Resources>
<Style x:Key="SubMenuStyles" TargetType="{x:Type ListViewItem}">
<Setter Property="Height" Value="40" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="ListViewItem.IsSelected" Value="True">
<Setter Property="Background" Value="#233E4F" />
<Setter Property="Foreground" Value="White" />
<Setter Property="BorderThickness" Value="50,0,0,0" />
<Setter Property="BorderBrush" Value="Orange" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsSelected" Value="False" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="BorderBrush" Value="Orange" />
</MultiTrigger.Setters>
<MultiTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="BorderThickness"
From="0,0,0,0"
To="10,0,0,0"
Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</MultiTrigger.EnterActions>
<MultiTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetProperty="BorderThickness"
From="10,0,0,0"
To="0,0,0,0"
Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</MultiTrigger.ExitActions>
</MultiTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ListView Width="200"
Height="150"
Margin="30">
<ListViewItem Style="{StaticResource SubMenuStyles}">A ListView</ListViewItem>
<ListViewItem IsSelected="True" Style="{StaticResource SubMenuStyles}">with several</ListViewItem>
<ListViewItem Style="{StaticResource SubMenuStyles}">items</ListViewItem>
</ListView>
</Grid>

How to change ZIndex inside a bound ItemsControl?

I simply created 3 borders in a StackPanel like this:
<StackPanel Orientation="Horizontal" >
<Border Width="100" Height="30" Background="Red" BorderBrush="DarkRed" BorderThickness="4" Margin="0" >
<Border.Style>
<Style TargetType="Border">
<Setter Property="Panel.ZIndex" Value="0"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Panel.ZIndex" Value="1"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<Border Width="100" Height="30" Background="Blue" BorderBrush="DarkBlue" BorderThickness="4" Margin="-10,0,0,0" >
<Border.Style>
<Style TargetType="Border">
<Setter Property="Panel.ZIndex" Value="0"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Panel.ZIndex" Value="1"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<Border Width="100" Height="30" Background="Green" BorderBrush="DarkGreen" BorderThickness="4" Margin="-10,0,0,0" >
<Border.Style>
<Style TargetType="Border">
<Setter Property="Panel.ZIndex" Value="0"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Panel.ZIndex" Value="1"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</StackPanel>
the xaml will layout 3 different colors borders:
When i mouse over the blue one, it'll on the top of others:
But when I move all of these into a ItemsControl, mouse on blue border no longer on the top of others, it just stay it's original ZIndex.
<DataTemplate x:Key="BorderTemplate">
<Border Width="100" Height="30" >
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Red"/>
<Setter Property="BorderBrush" Value="DarkRed"/>
<Setter Property="BorderThickness" Value="4"/>
<Setter Property="Panel.ZIndex" Value="0"/>
<Setter Property="Margin" Value="0,0,0,0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="2">
<Setter Property="Background" Value="Blue"/>
<Setter Property="BorderBrush" Value="DarkBlue"/>
<Setter Property="BorderThickness" Value="4"/>
<Setter Property="Panel.ZIndex" Value="0"/>
<Setter Property="Margin" Value="-10,0,0,0"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="3">
<Setter Property="Background" Value="Green"/>
<Setter Property="BorderBrush" Value="DarkGreen"/>
<Setter Property="BorderThickness" Value="4"/>
<Setter Property="Panel.ZIndex" Value="0"/>
<Setter Property="Margin" Value="-10,0,0,0"/>
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Panel.ZIndex" Value="1"/>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</DataTemplate>
<ItemsControl ItemsSource="{Binding Borders}" ItemTemplate="{StaticResource BorderTemplate}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate >
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
So why Panel.ZIndex worked in StackPanel but not work in ItemsControl->StackPanel?
ZIndex is by no means limited to canvas, so let's not push that idea off on other readers Maximus. However, since you're now loading your stuff as an ItemTemplate, using just a ContentPresenter, so you're basically sandboxing your object in a panel not associated to the overall DOM. Try instead to throw your influence at the ContentPresenter as the object being your container instead of disassociated children nested in it individually. Something like this (after you pull the same triggers off each Border of course).
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Panel.ZIndex" Value="100" />
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
Hope this helps. Cheers.

Validation.HasError and custom ToolTip

I'm setting extra ToolTip for a field with errors in ResourceDictionary
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
I also defined special style for this tooltip, which simulate the one on red triangle in right upper corner
<Style TargetType="ToolTip">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="HasDropShadow" Value="True"/>
<Setter Property="Placement" Value="Right"/>
<Setter Property="HorizontalOffset" Value="5"/>
<Setter Property="Foreground" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border Name="Border"
Background="Red"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}">
<ContentPresenter Margin="5 2 5 3"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Opened">
<BeginStoryboard>
<Storyboard TargetProperty="HorizontalOffset">
<DoubleAnimation From="0" To="5" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Everything works, but now ALL ToolTips have this style.
Is it possible to achieve that ToolTip style take his part only if Validation.HasError occurs? I can as x:Key, but how to apply that to Style.Triggers part?
Because I have this ToolTip defined also on other controls I wouldn't like to copy all this code multiple times, but if only that is a solution I will do so :(
After another couple of hours of Googling I try the solution provided here
https://social.msdn.microsoft.com/Forums/vstudio/en-US/10d2ecbf-9e6e-4414-b57e-79dd02e0944e/changing-style-of-tooltip-in-textbox
and it works!
A created ToolTip style
<ToolTip x:Key="ErrorToolTip"
Placement="Right"
Background="Red"
Foreground="White"
BorderThickness="0"
DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<ToolTip.Content>
<Binding Path="(Validation.Errors)[0].ErrorContent"/>
</ToolTip.Content>
<ToolTip.Triggers>
<EventTrigger RoutedEvent="ToolTip.Opened">
<BeginStoryboard>
<Storyboard TargetProperty="HorizontalOffset">
<DoubleAnimation From="0" To="5" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ToolTip.Triggers>
</ToolTip>
and changed Style.Triggers to
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{StaticResource ErrorToolTip}" />
</Trigger>
</Style.Triggers>
Need to do some more styling, but it's OK for now.
You can set the property Validation.ErrorTemplate on the TextBox style:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate"
Value="{StaticResource ValidationTemplate}" />
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
However, this way you can't work with a ToolTip style, but you have to work with a template and a x:Key.
<ControlTemplate x:Key="ValidationTemplate">
<Border Name="Border"
Background="Red"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}">
<ContentPresenter Margin="5 2 5 3"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Opened">
<BeginStoryboard>
<Storyboard TargetProperty="HorizontalOffset">
<DoubleAnimation From="0" To="5" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>

Mouse pressed trigger for control

I have a control which mainly has one image(I will add more later).I have defined the template .
I want to define a trigger for mouse press but I couldn't find any in control class documentation.
Which property should I check for mouse click.
<Grid>
<Control x:key="Mycontrol" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center">
<Control.Style>
<Style TargetType="{x:Type Control}">
<Setter Property="Width" Value="220"/>
<Setter Property="Height" Value="540"/>
<Setter Property="IsHitTestVisible" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate >
<Grid>
<Image Source="Base.png" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="XXXXXXX" Value="True">
<Setter Property="Background" Value="Black"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Control.Style>
</Control>
</Grid>
If you want to "trap" a mouse click trigger in order to change some other properties of your control, you can use an EventTrigger.
<EventTrigger RoutedEvent="Mouse.MouseDown">
<BeginStoryboard>
<Storyboard>
<ColorAnimation ... />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
If your control needs to respond to mouse click, it is always better to use Button for that purpose. IsPressed property is defined in ButtonBase, which will fullfill your need.
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background"
Value="Red" />
</Trigger>
</Style.Triggers>
</Style>

C# WPF Pulsing Button

I need some help figuring out how to make a button pulse its background colour. I use the style below to create the buttons and would like the buttons background colour to pulse (different shades of colour) when in normal state. The purpose is to draw the users attention to the button.
Ideally I would like this to occur after a certain period of inactivity. Is it possible to do this within the style or do I need to create a complete custom UserControl ? If it is possible I would appreciate it if someone could provide sample code illustrating how to achieve this.
I believe it should be possible to do the following:
1. Create a ControlTemplate.Resources section and add a Storyboard which defines the animation to be used.
1.1 What would the storyboard look like and how does it relate to the actual button background colours
1.2 How would one add a delay to the start of the storyboard (so it only gets triggered after a period of inactivity (i.e. no click on the button)
2. Add a trigger somehow to start the storyboard. how to do that for the normal state of the button.
<Style x:Key="ContinueButton" TargetType="{x:Type Button}">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="grid">
<Border x:Name="border" CornerRadius="0" BorderBrush="{StaticResource ThemeSolidColorBrushGreen}" BorderThickness="2" Background="{StaticResource ThemeSolidColorBrushGreen}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" TextElement.Foreground="White"
TextElement.FontSize="{Binding Source={StaticResource settingsProvider}, Path=Default.FontSizeParagraph}" TextElement.FontFamily="{Binding Source={StaticResource settingsProvider}, Path=Default.FontFamily}" TextElement.FontWeight="Bold"></ContentPresenter>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" TargetName="border" Value="{StaticResource ThemeSolidColorBrushGreen}">
</Setter>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" TargetName="grid" Value="0.25"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can use EventTrigger to achieve this. Just replace the ControlTemplate.Trigger section of your code with the following code
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Button.MouseEnter">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="border" AutoReverse="True"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="Blue" RepeatBehavior="Forever" Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" TargetName="border" Value="GreenYellow"/>
</Trigger>
<!--<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" TargetName="border" Value="Gray"/>
</Trigger>-->
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" TargetName="grid" Value="0.25"/>
</Trigger>
</ControlTemplate.Triggers>

Categories