Animation restarts repeatedly when mouse touches the boundaries - c#

I have a problem with animation on an image Mouse Over Event trigger in WPF. When the mouse touches the boundaries of the Image and stands still, the animation restarts repeatedly forever until moving mouse. What should I do in this case to stop it and the trigger fires just one time?
My code is:
<Image Source="/Project_12;component/Images/cancel-64.png" RenderTransformOrigin=".5,.5" Stretch="Uniform">
<Image.RenderTransform>
<RotateTransform x:Name="AnimatedRotatex" Angle="0" />
</Image.RenderTransform>
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="sb">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.(RotateTransform.Angle)"
By="0"
To="90"
Duration="0:0:.5"
FillBehavior="HoldEnd" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="sb" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>

The basic problem of your animation is, that you use the IsMouseOver property of the animated object. As soon as the image gets rotated, you move the HitTestArea of the image "out of the way" which causes the IsMouseOver property to become false, ending the animation and therefore causing an infinite loop. I added a blue border to show the HitTestArea of the image to highlight the problem.
What you need to do, is create another, static area (with a transparent Background to make sure it has a HitTestArea) and use the IsMouseOver property of that are instead (red border).
That way, even if the image "moves away" from the mouse cursor, the IsMouseOver property of the other element will be unaffected by the moving image.
An alternative approach would be to use a circle as the HitTestArea since it won't be affected by rotation.
Source Code of above example:
<Border Background="Transparent" BorderThickness="1" BorderBrush="Red" Width="40" Height="40">
<Border BorderThickness="1" BorderBrush="Blue" Width="36" Height="36" RenderTransformOrigin=".5,.5" IsHitTestVisible="False">
<Border.RenderTransform>
<RotateTransform x:Name="AnimatedRotatex" Angle="0" />
</Border.RenderTransform>
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=Border}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.(RotateTransform.Angle)"
By="0"
To="90"
Duration="0:0:.2"
FillBehavior="HoldEnd" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.(RotateTransform.Angle)"
By="90"
To="0"
Duration="0:0:.2"
FillBehavior="HoldEnd" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Image VerticalAlignment="Center" HorizontalAlignment="Center" Width="32" Height="32" Stretch="Uniform" Source="cancel.png"/>
</Border>
</Border>

Related

Binding to an ancestor property inside an animation in WPF

I have a WPF page with a TabControl in it, and I wanted to design the tabs to have a nice underline when selected and on mouse hover.
I've managed to do that but each tab has a different length, and it seems that I can't bind a property of the TabItem inside of its animation.
This is what i have done so far:
<TabControl Width="{Binding ActualWidth,
ElementName=cnvsMain}" Height="{Binding
ActualHeight, ElementName=cnvsMain}"
BorderThickness="0" >
<TabControl.Resources>
<Style TargetType="TabItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<StackPanel>
<Border Name="Border"
BorderThickness="1,1,1,0"
BorderBrush="Gainsboro"
CornerRadius="6,6,0,0"
Margin="2,0">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="10,2"/>
</Border>
<Label Name="TabUnderline" Background="LimeGreen"
Margin=" 5, 0" Height="5" Width="0"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="TabUnderline"
Storyboard.TargetProperty="Width"
From="0" To="100"
Duration="0:0:0.1"
AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="TabUnderline"
Storyboard.TargetProperty="Width"
From="100" To="0" Duration="0:0:0.1"
AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Header="Rooms">
<StackPanel>
<Label Content="TODO: List all rooms" />
<Label Content="TODO: Create room button" />
</StackPanel>
</TabItem>
<TabItem Header="Statistics" />
<TabItem Header="Details" />
</TabControl>
It works great but there is one simple flaw:
The under line will always be 100 pixels (Or is it units?) wide, and each tab has its unique and different width. I've tried to replace the hard-coded 100 to {Binding ActualWidth} or using a RelativeSource to get the width of the TabItem, but I keep getting an exception everytime this page is being loaded because of a binding-error.
How can I bind a TabItem's property to a value inside its own animation? Is it even possible?

Using a Storyboard on a ToolTip WPF

I am trying to create a storyboard, so when a user clicks on a textbox, its copies the text to their clipboard and shows a tooltip saying copied, which then fades away.
Here is my attempt:
xaml:
<TextBox Name="PolyValue" Text="{Binding .}" IsReadOnly="True" BorderThickness="0" Background="White"
VerticalAlignment="Center" PreviewMouseDown="CopyTextBox" >
<TextBox.ToolTip>
<ToolTip Style="{StaticResource TooltipPopupFadeAway}" IsOpen="True" Opacity="0" Background="Transparent" BorderThickness="0">
<Border Background="White" BorderBrush="Black" BorderThickness="1" CornerRadius="3" >
<Label Content="Copied" Padding="5, 2" />
</Border>
</ToolTip>
</TextBox.ToolTip>
</TextBox>
Here is the Storyboard:
<Style x:Key="TooltipPopupFadeAway" TargetType="ToolTip">
<Style.Triggers>
<DataTrigger Binding="{Binding Opacity, RelativeSource={RelativeSource Self}}" Value="1">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="ClosePopupStoryBoard">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="00:00:01" From="3" To="0" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
Code behind:
private void CopyTextBox(object sender, MouseButtonEventArgs e)
{
if(sender is TextBox textBox)
{
Clipboard.SetText(textBox.Text);
(textBox.ToolTip as ToolTip).IsOpen = false;
(textBox.ToolTip as ToolTip).IsOpen = true; //this recalculates the position
(textBox.ToolTip as ToolTip).Opacity = 1;
}
}
This works exactly how I want it to, however it only works once, after it has been shown and the user clicks again nothing happens.
After setting a break point in the CopyTextBox method, the of the ToolTip opacity is 0 even after programmatically setting it to 1.
I am not sure what I am doing wrong?
This is the sort of approach I mean.
Just binding the text property means you need no code.
I'm not sure this does exactly what you want because you seem to have previewmousedown showing the tooltip. Which is a bit odd for a tooltip since mouseover shows them.
<Window.Resources>
<ControlTemplate x:Key="TooltipPopupFadeAway" TargetType="ToolTip">
<Border Background="Yellow">
<TextBlock Text="{Binding PlacementTarget.Text, RelativeSource={RelativeSource AncestorType={x:Type ToolTip}}}"
Name="TheText"
/>
</Border>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="ToolTip.Opened">
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation From="1.0" To="0" Duration="0:0:2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<Grid>
<TextBox>
<TextBox.ToolTip>
<ToolTip Template="{StaticResource TooltipPopupFadeAway}"/>
</TextBox.ToolTip>
</TextBox>
</Grid>
And you could set that template and tooltip etc via style if it suited you better. I used a yellow background so I can see it easy. The tooltip probably doesn't exactly match what you had.
You should trigger directly on the IsOpen property. Make sure to set the default Opacity to 0, and do not explicitly set it to 1 afterwards.
<Style x:Key="TooltipPopupFadeAway" TargetType="ToolTip">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Opacity" Value="0"/>
<Setter Property="IsOpen" Value="False"/>
<Style.Triggers>
<Trigger Property="IsOpen" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Duration="0:0:1" From="3" To="0"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
In code behind, do not to set the Opacity:
private void CopyTextBox(object sender, MouseButtonEventArgs e)
{
if (sender is TextBox textBox)
{
Clipboard.SetText(textBox.Text);
((ToolTip)textBox.ToolTip).IsOpen = false;
((ToolTip)textBox.ToolTip).IsOpen = true;
}
}

Stop wpf Storyboard and hold actual value

I have following wpf code
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<Image x:Key="MyImage" Source="../Assets/icon.ico"/>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
<Storyboard x:Key="AkomiLogoAnimation">
<DoubleAnimation
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
By="360"
SpeedRatio="0.5"
RepeatBehavior="Forever"
FillBehavior="Stop"
/>
</Storyboard>
</ResourceDictionary>
</Window.Resources>
<TextBox x:Name="txtAuto"/>
<Image Source="../Assets/icon.ico" Width="20" Height="20" RenderTransformOrigin=".5,.5">
<Image.Resources>
<converter:IsNullConverter x:Key="IsNullConverter"/>
</Image.Resources>
<Image.RenderTransform>
<RotateTransform Angle="0" />
</Image.RenderTransform>
<Image.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=txtAuto, Path=Text, Converter={StaticResource IsNullConverter}, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard Name="AkomiRotation" Storyboard="{StaticResource AkomiLogoAnimation}">
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=txtAuto, Path=Text, Converter={StaticResource IsNullConverter}, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Value="True">
<DataTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="AkomiRotation" >
</StopStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
The goal is to rotate the image as soon as some text has been entered. But stopping the storyboard lets the image angle jump into it's initial value.
I tried to add FillBehavior="HoldEnd" to the Storyboard definition, but that doesn't work.
Any suggestions? Maybe I can access the angle-value at the end of the animation anyhow and set this value in RotateTransform
Have you tried PauseStoryboard, instead of stop? Also, you should change fillbehavior on the animation

Wpf button with both result datatrigger storyboard and mouseover background

I'm looking to implement this functionality: A button, bound to a command, that can signal back wether the command was successful in form of a red or green flash. At the same time, I want the button to look like other buttons.
This is the code I have now:
<Button IsEnabled="{Binding EmptyingAllowed}" Content="Töm lista" Foreground="#555555" FontWeight="SemiBold" Height="20" Width="65" Command="{Binding EmptyListCommand}" >
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border BorderBrush="#BBBBBB" Padding="1" BorderThickness="0,0,1,1" Background="#DDDDDD">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ClearedOk}" Value="Ok">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation AccelerationRatio="0.2" DecelerationRatio="0.01" AutoReverse="True" Duration="0:0:1" To="LawnGreen" Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding ClearedOk}" Value="Error">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation AccelerationRatio="0.2" DecelerationRatio="0.01" AutoReverse="True" Duration="0:0:1" To="Red" Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
When the command is executed, ClearedOk is set to "Ok" or "Error" and then to "Empty", this causes the flash I'm looking for, unfortunately, the usual mouseOver-effect is lost for some reason, and if I add a mouseOver-trigger, it always takes precedence over the flashing effect, meaning the flash is only visible of the button isn't mouseOver:ed.
Is there a solution to this?
Try to make datatemplate and model with properties of state and then binding in template to this properties.
<DataTemplate x:Key="DataTemplate">
<Grid >
<Ellipse Stroke="White"
StrokeThickness="5"
Fill="{Binding State, Converter={StaticResource CheckToColorConverter}}"/>
</Grid>
</DataTemplate>

Reset the animation of user control when mouse leave

I want to display a user control on a rectangle with some animation when mouse move over the rectangle, reset the animation and collapse the user control when mouse leave.
My question is how should I "reset" the animation as my current approached is just collapses the user control when mouse leave. Below is my demo code.
EDITTED:
ControlLibrary.xaml
<UserControl.Style>
<Style>
<Style.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Name="opacityStoryBoard">
<Storyboard >
<DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="00:00:10" From="0" To="2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="FrameworkElement.MouseLeave">
<SeekStoryboard BeginStoryboardName="opacityStoryBoard" Offset="00:00:00"> </SeekStoryboard>
</EventTrigger>
<Trigger Property="FrameworkElement.Visibility" Value="Collapsed">
<Setter Property="FrameworkElement.Opacity" Value="0"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Style>
<StackPanel Orientation="Vertical">
<Label Content="Welcome" HorizontalAlignment="Stretch" VerticalAlignment="Top"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
Foreground="White" Background="Transparent" FontSize="25" Height="300" Margin="10,0" />
<!--<Image Height="300" x:Name="qr" Margin="10,0" />-->
</StackPanel>
mainWindow.xaml
<myToolTip:UserControl1 Visibility="Collapsed"
x:Name="customToolTip" />
<Rectangle Fill="Transparent" HorizontalAlignment="Left" Height="322" Stroke="Black"
VerticalAlignment="Top" Width="518" MouseMove="Rectangle_MouseMove_1" MouseLeave="Rectangle_MouseLeave_1">
</Rectangle>
mainWindow.cs
private void Rectangle_MouseMove_1(object sender, MouseEventArgs e)
{
customToolTip.Visibility = System.Windows.Visibility.Visible;
}
private void Rectangle_MouseLeave_1(object sender, MouseEventArgs e)
{
customToolTip.Visibility = System.Windows.Visibility.Collapsed;
}
Please guide me..Thank you.
Use the SeekStoryBoard class to reset the animation time when the mouse leaves the rectangle.
For example:
<Style.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Name="opacityStoryBoard">
<Storyboard >
<DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="00:00:10" From="0" To="2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<Trigger Property="FrameworkElement.Visibility" Value="Collapsed">
<Setter Property="FrameworkElement.Opacity" Value="0"/>
<Trigger.EnterActions>
<SeekStoryboard BeginStoryboardName="opacityStoryBoard" Offset="00:00:00"></SeekStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>

Categories

Resources