Validation adorner does not completely disappear in Animation - wpf

I have a WPF Window that contains a ContentPresenter that has Height and Width set to 0 by default.
When a user clicks a button, I run an animation to transform the ContentPresenter's Height and Width properties to 896,1024 (actually it does 3 rotations whilst its growing, too) and this is all good...
The DataContext for the User control implements IDataErrorInfo and if the user does not click the 'I have read and understand these Health & Safety instructions" checkbox, then a red border is shown around the checkbox...
My problem is that if the user clicks 'Cancel', and I run the animation that shrinks the Height & Width back down to 0,0, then the UserControl shrinks as required, but the red border does not completely disappear - it leaves a single red pixel in the middle of my Window
Anybody any ideas what I'm doing wrong? The 'red-border', I'm assuming is just an Adorner being rendered by WPF for me, so I'm not sure how to change this behaviour...
All help much appreciated!
Update - I tried Abe's excellent suggestion, but unfortunately it didn't work, but it did get me trying other stuff... So now I have (temporarily) commented out the 'shrinking' animations, and simply set the visibility to Collapsed at KeyTime="0:0:0.9"... when I press cancel, just less than a second later, the UserControl disappears but the red adorner stubbornly remains :(
As an extra bit of info (not sure if relevant?) the UserControl being shown in the ContentPresenter also contains a ContentPresenter to render a UserControl, and its the inner content that contains the validation adorner...
code sample:
<Button
Name="signInButton"
Grid.Row="0" Grid.Column="0"
Margin="30"
HorizontalAlignment="Right" VerticalAlignment="Bottom"
Style="{StaticResource LargeButtonStyle}"
Content="Sign In"
Command="{Binding SignInCommand}">
<Button.Triggers>
<EventTrigger
RoutedEvent="Button.Click">
<BeginStoryboard
Storyboard="{DynamicResource openViewAnimation}" />
</EventTrigger>
</Button.Triggers>
</Button>
<ContentPresenter
Name="mainView"
Grid.RowSpan="2" Grid.ColumnSpan="2"
HorizontalAlignment="Center" VerticalAlignment="Center"
Opacity="0.9"
Content="{Binding CurrentContent}">
<ContentPresenter.RenderTransform>
<RotateTransform
Angle="0" />
</ContentPresenter.RenderTransform>
</ContentPresenter>
<Storyboard x:Key="closeViewAnimation">
<DoubleAnimation
Storyboard.TargetName="mainView" Storyboard.TargetProperty="Height"
From="896" To="0" Duration="0:0:0.9"
AutoReverse="False" RepeatBehavior="1x" />
<DoubleAnimation
Storyboard.TargetName="mainView" Storyboard.TargetProperty="Width"
From="1024" To="0" Duration="0:0:0.9"
AutoReverse="False" RepeatBehavior="1x" />
</Storyboard>
Thanks, Ian

If you add an ObjectAnimationUsingKeyFrames that sets the Visibility of the element to Collapsed at the time that the other animations complete, the adorner will go away also.
<Storyboard x:Key="closeViewAnimation">
<DoubleAnimation
Storyboard.TargetName="mainView" Storyboard.TargetProperty="Height"
From="896" To="0" Duration="0:0:0.9"
AutoReverse="False" RepeatBehavior="1x" />
<DoubleAnimation
Storyboard.TargetName="mainView" Storyboard.TargetProperty="Width"
From="1024" To="0" Duration="0:0:0.9"
AutoReverse="False" RepeatBehavior="1x" />
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="mainView"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}"
KeyTime="0:0:0.9" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
Obviously, you would need to do the reverse operation at KeyTime 0 for the openViewAnimation.

Related

Change TabIndex with Storyboard

Is there a way to change the TabIndex (in XAML only) when a button is clicked.
Below is my try following this article and one on my own which is commented.
Thanks!
<StackPanel>
<Button x:Name="Button" Content="Go" />
<TabControl x:Name="Tab">
<TabItem Header="First">
<TextBlock Text="First" />
</TabItem>
<TabItem Header="Second">
<TextBlock Text="Second" />
</TabItem>
<TabControl.Triggers>
<EventTrigger RoutedEvent="Button.PreviewMouseLeftButtonDown">
<BeginStoryboard>
<Storyboard>
<Int32AnimationUsingKeyFrames
Duration="0"
Storyboard.TargetName="Tab"
Storyboard.TargetProperty="TabIndex">
<DiscreteInt32KeyFrame
KeyTime="0"
Value="1" />
</Int32AnimationUsingKeyFrames>
<!--<Int32Animation
Duration="0"
Storyboard.TargetName="Tab"
Storyboard.TargetProperty="TabIndex"
FillBehavior="HoldEnd"
By="1"
From="0"
To="1" />-->
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TabControl.Triggers>
</TabControl>
</StackPanel>
Edit: Use SelectedIndex instead of TabIndex to change the current tab.
Your event trigger is misplaced. Instead of placing the trigger on TabControl, place trigger on StackPanel.
<StackPanel.Triggers>
<EventTrigger RoutedEvent="Button.PreviewMouseLeftButtonDown">
<BeginStoryboard>
<Storyboard>
<Int32AnimationUsingKeyFrames Duration="0"
Storyboard.TargetName="Tab"
Storyboard.TargetProperty="TabIndex">
<DiscreteInt32KeyFrame KeyTime="0"
Value="1" />
</Int32AnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</StackPanel.Triggers>
Reason:
Button.PreviewMouseLeftButtonDown is a tunnelling routed attached event i.e. it will start from root element and continue to tunnel until it reaches actual sender. So, it will start from window or UserControl and will continue to tunnel until it reaches Go button. It won't tunnel to TabControl since it is sibling of button and not a parent of TabControl.
But it will work if you place it on StackPanel since StackPanel is parent of TabControl and Button. Before tunnelling to GO Button, it will pass via StackPanel. Hence, your storyBoard will work.

Storyboard execution on Mouse Over

I've developed a WPF application in Expression Blend, a simple button attach with a storyboard with bounce effect.
So far so good, now I want my button to bounce when my mouse enters on it, it does. But it keeps doing that. I move mouse once and after bouncing again falls into the mouse region (I am not moving the mouse, mouse cursor is still) and starts bouncing again.
This is the whole code that I am using
<Window.Resources>
<Storyboard x:Key="Storyboard1">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)" Storyboard.TargetName="button">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)" Storyboard.TargetName="button">
<EasingDoubleKeyFrame KeyTime="0" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<BounceEase EasingMode="EaseIn"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="-29">
<EasingDoubleKeyFrame.EasingFunction>
<BounceEase EasingMode="EaseIn"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource Storyboard1}"/>
</EventTrigger>
</Window.Triggers>
<Grid x:Name="LayoutRoot">
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Height="51" Margin="76,100,0,0" VerticalAlignment="Top" Width="130" RenderTransformOrigin="0.5,0.5">
<Button.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Button.RenderTransform>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ei:ControlStoryboardAction Storyboard="{StaticResource Storyboard1}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
Just copy paste it in your xaml window and you'll good to go.
Keep the mouse on the top corner of mouse. You'll see the problem.
Thanks
The thing that pops out is that you're moving the button by 29px down when it's 51px tall. Chances are the button moves away from the mouse and starts moving back to reverse the animation.
You can work around this by reworking the button and animation:
- place the button in a container (grid, stackpanel - anything that works for you)
- make the container's background "Transparent" (which is NOT the same as "null")
- make the button align to the bottom of that container
- make the animation trigger on the container and change the container's height
The result is that when the button moves away the mouse is still over its container so the animation doesn't stop and reverse.
- control animation behavior with AutoReverse and RepeatBehavior
--
This is obviously not the whole code you're using (where's the "i" namespace declaration?) so I can't see your problem on my end.
Let's take this button animation as example:
<Grid>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="Test ">
<Button.RenderTransform>
<ScaleTransform ScaleX="1.0" ScaleY="1.0" />
</Button.RenderTransform>
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.5" Storyboard.TargetProperty="RenderTransform.ScaleX" To="2.0" />
<DoubleAnimation Duration="0:0:0.5" Storyboard.TargetProperty="RenderTransform.ScaleY" To="2.0" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.5" Storyboard.TargetProperty="RenderTransform.ScaleX" To="1.0" />
<DoubleAnimation Duration="0:0:0.5" Storyboard.TargetProperty="RenderTransform.ScaleY" To="1.0" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
So when your mouse enters the button area, it grows, and when the mouse leaves, it shrinks back.
By default, a StoryBoard's:
AutoReverse = False, if true the animation reverts as soon as it's over.
FillBehavior = HoldEnd, which makes the button's appearence stay as how it looks once the animation is over (i.e: the button will remain 2x bigger as long as you keep your mouse above the button). If set to Stop, it will revert back to its original state as soon as the animation finishes (without animation, which is what is different from AutoReverse).
RepeatBehavior = 1x, you can set this to a TimeSpan, nx where n is the number of times you want the animation repeated, or Forever and it will loop forever.
You should check that these settings haven't been tampered with.
And last but not least, make sure the logic tied to your animation makes sense and doesn't induce a loop (which would, in turn, make the animation loop). For example if you animate based on the mouse being over an element, and the animation moves the element, then your mouse isn't over the element anymore, and thus the animation reverts, bringing back the button under the mouse, etc, forever.
You could try to put the mouse inside a canvas with a transparent (not null) background and animate the button when the mouse gets over the canvas. The button would leave its position but the canvas wouldn't. You could use that solution inside a template control, for example.

How to zoom in on a image when Image.MouseEnter fires?

I would like to zoom in on a Image when Image.MouseEnter fires and then zoom out when Image.MouseLeave fires.
I thought of creating a Trigger, but no luck.
This is what i tried so far:
<Image Name="logo" Source="{Binding Path=ImagePath}"
Width="50" Height="50">
<Image.RenderTransform>
<ScaleTransform x:Name="TransRotate" />
</Image.RenderTransform>
<Image.Triggers>
<EventTrigger RoutedEvent="Image.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="TransRotate"
Storyboard.TargetProperty="ScaleX"
From="0" To="100"
BeginTime="0:0:0"
Duration="0:0:10"
AutoReverse="False"/>
<DoubleAnimation Storyboard.TargetName="TransRotate"
Storyboard.TargetProperty="ScaleY"
From="0" To="100"
BeginTime="0:0:0"
Duration="0:0:10"
AutoReverse="False"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
Is this the correct way, or is there a better way?
I would remove the From property, this makes the animation jump, apart from that you just seem to be lacking the reverse animations on MouseLeave. Also to center the zoom you can set the RenderTransformOrigin of the Image to 0.5,0.5.
Instead of using those two events i usually prefer a trigger on IsMouseOver with Enter and ExitActions.
If you want to retain the space the image takes you can place it in a container with fixed size and set ClipToBounds to true.

wpf animation events overlapping

I've got a canvas control that grows in height when you move the mouse over it and shrinks back on mouse leave.
<Canvas x:Name="infoBar" Width="720" Height="39" Background="Red">
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="infoBar"
Storyboard.TargetProperty="Height"
From="39" To="255" Duration="0:0:0.5"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Canvas.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="infoBar"
Storyboard.TargetProperty="Height"
From="255" To="39" Duration="0:0:0.5"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
<StackPanel>
<TextBlock/>
<TextBlock/>
</StackPanel>
</Canvas>
This works fine. However if two quick consecutive events take place (mouseleave before mouse enter animation finishes) it goes nuts.
Is there anyway i can tell it to cancel out any other events that happen before an animation finishes?
Using your event triggers you can perform pause, stop, resume, etc. commands on named storyboards.
This article should answer your questions.

WPF Animation - Why won't my image spin?

I've created a simple user control in WPF and added an image to it:
<Image x:Name="logo" Source="/View/Images/Logo.png" Width="100" Height="100">
<Image.RenderTransform>
<RotateTransform Angle="0" CenterX="50" CenterY="50" />
</Image.RenderTransform>
</Image>
I want that image to spin constantly and to fade in and out. This is to be used as a busy indicator. So I created this animation storyboard:
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation From="0" To="360" Duration="00:00:05" Storyboard.TargetName="logo" Storyboard.TargetProperty="(RotateTransform.Angle)" />
<DoubleAnimation From="0.1" To="1" AutoReverse="True" Duration="00:00:02" Storyboard.TargetName="logo" Storyboard.TargetProperty="Opacity" />
</Storyboard>
However, when I view a window with the usercontrol in it, the fading works but the spiinning doesn't.
Your StoryBoard.TargetProperty is wrong, it should be:
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"

Resources