I am building some WPF controls using .Net 4.0. One of these controls, called LoadingPane, is a custom control derived from ContentControl.
The only job of this LoadingPane control is to show a semi-transparent layer over it's contained content when it's IsLoading property is set to true.
I use some animations to do fade-in, fade-out when the IsLoading value changes.
When the overlay is shown an animation rotates a circle of elipses.
So far, so good. This all works very nicely. But here's my problem: when i set the Loading property to true the animation isn't shown directly. It takes about half a second. In this time the fade-in animation has already run, so the opacity effectively goes from 0 to 1 in one step.
Here's my animation code:
<ControlTemplate.Triggers>
<Trigger Property="IsLoading"
Value="True">
<Trigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="EndAnimateLoadingCanvas" />
<BeginStoryboard Name="AnimateLoadingCanvas">
<Storyboard FillBehavior="Stop">
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="MyViewBoxje"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation BeginTime="00:00:00"
Duration="00:00:00.5"
Storyboard.TargetName="MyViewBoxje"
Storyboard.TargetProperty="Opacity"
To="1" />
<DoubleAnimation BeginTime="00:00:00"
Duration="00:00:02"
Storyboard.TargetName="AnimatedRotateTransform"
Storyboard.TargetProperty="Angle"
From="360"
To="0"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="AnimateLoadingCanvas" />
<BeginStoryboard Name="EndAnimateLoadingCanvas">
<Storyboard FillBehavior="Stop">
<DoubleAnimation BeginTime="00:00:00"
Duration="00:00:00.5"
Storyboard.TargetName="MyViewBoxje"
Storyboard.TargetProperty="Opacity"
To="0" />
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00.5"
Storyboard.TargetName="MyViewBoxje"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
The strange thing is, when i use this control in a test window and i click the Loading checkbox repeatedly (and before the animation has finished) then the fade-in/fade-out animation does work as i expect it to work.
Can anyone help? Thanx in advance!
It's hard to see exactly what the problem is without seeing the rest of the code but my guess is that it has to do with the start values of the animated properties.
I implemented a custom control in WPF with a rectangle inside a viewbox and used the triggers + storyboards from the question to see the effect. Indeed, my first attempt did not fade in nor fade out.
What I did to solve it was by specifying From values in the animations so that they work regardless of what the original value of the DP's were:
<DoubleAnimation BeginTime="00:00:00"
Duration="00:00:00.5"
Storyboard.TargetName="MyViewBoxje"
Storyboard.TargetProperty="Opacity"
From="0"
To="1" />
Notice From="0" in the above animation. The end storyboard was modified the same way, to go from 1 to 0.
For completeness I set the opacity to 0 on the definition of the viewbox element inside the ControlTemplate as well.
Here is the complete source code for the relevant parts. The control is a standard WPF custom control inheriting from Control. It has a single dependency property called IsLoading (bool) which defaults to false:
public bool IsLoading
{
get { return (bool)GetValue(IsLoadingProperty); }
set { SetValue(IsLoadingProperty, value); }
}
// Using a DependencyProperty as the backing store for IsLoading. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsLoadingProperty =
DependencyProperty.Register("IsLoading", typeof(bool), typeof(LoadingControl), new UIPropertyMetadata(false));
ControlTemplate - Defined in generic.xaml in the style for {x:Type local:LoadingControl}
<ControlTemplate TargetType="{x:Type local:LoadingControl}">
<ControlTemplate.Triggers>
<Trigger Property="IsLoading" Value="True">
<Trigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="EndAnimateLoadingCanvas" />
<BeginStoryboard Name="AnimateLoadingCanvas">
<Storyboard FillBehavior="Stop">
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="MyViewBoxje"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation BeginTime="00:00:00"
Duration="00:00:00.5"
Storyboard.TargetName="MyViewBoxje"
Storyboard.TargetProperty="Opacity"
From="0"
To="1" />
<DoubleAnimation BeginTime="00:00:00"
Duration="00:00:02"
Storyboard.TargetName="AnimatedRotateTransform"
Storyboard.TargetProperty="Angle"
From="360"
To="0"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="AnimateLoadingCanvas" />
<BeginStoryboard Name="EndAnimateLoadingCanvas">
<Storyboard FillBehavior="Stop">
<DoubleAnimation BeginTime="00:00:00"
Duration="00:00:00.5"
Storyboard.TargetName="MyViewBoxje"
Storyboard.TargetProperty="Opacity"
From="1"
To="0" />
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00.5"
Storyboard.TargetName="MyViewBoxje"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Viewbox x:Name="MyViewBoxje" Opacity="0">
<!-- BG with 0x50 alpha so that it's translucent event at 100% visibility -->
<Grid Width="100" Height="100" Background="#50000000">
<Rectangle Width="70" Height="20" Fill="Green" Stroke="Black" StrokeThickness="2" RenderTransformOrigin="0.5,0.5">
<Rectangle.RenderTransform>
<RotateTransform Angle="360" x:Name="AnimatedRotateTransform" />
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</Viewbox>
</Border>
</ControlTemplate>
I used in my main window like so:
<Grid x:Name="LayoutRoot">
<!-- All other stuff here ... -->
<my:LoadingControl IsLoading="{Binding IsLoading}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
And to test it I had a ViewModel with a property IsLoading that is set as DataContext for the main window. And in the constructor of the ViewModel I set IsLoading to true and then start a timer that toggles the value of the property every 5 seconds:
public MainWindowViewModel()
{
IsLoading = true;
DispatcherTimer t = new DispatcherTimer();
t.Interval = TimeSpan.FromSeconds(5);
t.Tick += (s, e) => IsLoading = !IsLoading;
t.Start();
}
I finally figured it out after reading Isak's answer.
Sorry to say that his answer did not help in my case but he got me going in the right direction.
The reason that the first fade-in did not seem to work was because my containing viewbox had it's visibility set to collapsed the whole time the fade-in animation was performed.
This was caused by the ObjectAnimationUsingKeyFrames:
DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
There was no duration specified and the single key frame specified was animated too late.
Adding
Duration="00:00:00"
solved my problem.
Thanks to you all helping!
Related
I have a Control that can be moved on by dragging. when i drag the control i have a code behind that changes a DependencyProperty that a TranslateTransform is bound to.
now i need to add a button that when is pressed it moves the control, and needs to update the DependencyProperty. I can move the control but can't figure out how to update the DependencyProperty.
code behind:
public partial class AirspeedIndicatorView : UserControl
{
public static readonly DependencyProperty WantedValueProperty =
DependencyProperty.Register("WantedValue", typeof(double), typeof(AirspeedIndicatorView),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, WantedPropertyChanged));
public double WantedValue
{
get { return (double)GetValue(WantedValueProperty); }
set { SetValue(WantedValueProperty, value); }
}
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
WantedValue += e.VerticalChange;
}
}
XAML:
<Thumb Canvas.Top="-6" Height="12" Width="16" DragDelta="Thumb_DragDelta" x:Name="WantedThumb">
<Thumb.RenderTransform>
<TranslateTransform Y="{Binding WantedValue,ElementName=View}" />
</Thumb.RenderTransform>
</Thumb>
<Button Padding="1" Margin="1">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="OverridesDefaultStyle" Value="False" />
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Trigger.EnterActions>
<BeginStoryboard x:Name="MoveWanted">
<Storyboard Target="{x:Reference WantedThumb}" TargetProperty="RenderTransform.Y" AutoReverse="False">
<DoubleAnimation BeginTime="00:00:00" Duration="0:0:0" By="-1" />
<DoubleAnimation BeginTime="00:00:00.5" Duration="0:0:1.5" By="-15" />
<DoubleAnimation BeginTime="00:00:02" Duration="0:0:1" By="-20" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
<Trigger Property="IsPressed" Value="False">
<Trigger.EnterActions>
<StopStoryboard BeginStoryboardName="MoveWanted" />
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
I don't have time to check it now, but you should be able to use the ObjectAnimationUsingKeyFrames class to update your DependencyProperty:
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Saved">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Double>10.0</System:Double>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
Actually, the DoubleAnimationUsingKeyFrames class might be better for you as it is intended to work with doubles, but the only down side is that I don't believe that you can data bind the numerical value, so it would have to be a hard coded value. From the last linked page:
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="AnimatedTranslateTransform"
Storyboard.TargetProperty="X"
Duration="0:0:6"
RepeatBehavior="Forever">
<!-- Using a LinearDoubleKeyFrame, the rectangle moves
steadily from its starting position to 500 over
the first 3 seconds. -->
<LinearDoubleKeyFrame Value="500" KeyTime="0:0:3" />
<!-- Using a DiscreteDoubleKeyFrame, the rectangle suddenly
appears at 400 after the fourth second of the animation. -->
<DiscreteDoubleKeyFrame Value="400" KeyTime="0:0:4" />
<!-- Using a SplineDoubleKeyFrame, the rectangle moves
back to its starting point. The
animation starts out slowly at first and then speeds up.
This KeyFrame ends after the 6th
second. -->
<SplineDoubleKeyFrame KeySpline="0.6,0.0 0.9,0.00" Value="0" KeyTime="0:0:6" />
</DoubleAnimationUsingKeyFrames>
I defined BeginStoryboard object in resources in style.
In same style definition I would like use this BeginStoryboard object in 2 event triggers which fire animation.
It is possible that 2 different event triggers will use same object or I must define 2 different BeginStoryboard objects?
<Style x:Key="SerialPoster" TargetType="Border">
<Style.Resources>
<BeginStoryboard x:Key="SerialPosterBeginStoryBoard">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
From="0"
To="1"
Duration="0:0:2"
AccelerationRatio="1" />
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
From="0"
To="1"
Duration="0:0:2"
AccelerationRatio="1" />
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[1].Angle"
From="70"
To="0"
Duration="0:0:2" />
</Storyboard>
</BeginStoryboard>
</Style.Resources>
<!-- TriggerAction object must be associated with one and only one trigger object. -->
<Style.Triggers>
<EventTrigger RoutedEvent="Border.Loaded">
<EventTrigger.Actions>
<StaticResource ResourceKey="SerialPosterBeginStoryBoard"/>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Border.MouseEnter">
<EventTrigger.Actions>
<StaticResource ResourceKey="SerialPosterBeginStoryBoard" />
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
Yes, it is possible to reference resource and so to reuse the resource instance at different places.
No need to place the Storyboard inside Style.Resource just place it inside Window.
I have a question, how can I make an animation running first before the visibility changed on an XAML element?
The situation should be like this.
<Grid Name=Header Visiblity="visible">
<Grid.Resources>
<Storyboard x:Name="HeaderGridUp">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="Height"
Storyboard.TargetName="HeaderGrid">
<EasingDoubleKeyFrame Value="30" KeyTime="00:00:02">
<EasingDoubleKeyFrame.EasingFunction>
<ExponentialEase EasingMode="EaseOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Name="HeaderGridDown">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="Height"
Storyboard.TargetName="HeaderGrid">
<EasingDoubleKeyFrame Value="30" KeyTime="00:00:02">
<EasingDoubleKeyFrame.EasingFunction>
<ExponentialEase EasingMode="EaseIn"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>
</Grid>
<Grid Name=Items Visiblity="collapsed">
<Grid.Resources>
<Storyboard x:Name="ItemsGridUp">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="Height"
Storyboard.TargetName="ItemsGrid">
<EasingDoubleKeyFrame Value="30" KeyTime="00:00:02">
<EasingDoubleKeyFrame.EasingFunction>
<ExponentialEase EasingMode="EaseOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Name="ItemsGridDown">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="Height"
Storyboard.TargetName="ItemsGrid">
<EasingDoubleKeyFrame Value="30" KeyTime="00:00:02">
<EasingDoubleKeyFrame.EasingFunction>
<ExponentialEase EasingMode="EaseIn"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>
</Grid>
In my case, I would like an animation that move like Fade in and Fade out, that's why I make two storyboard each grid. First thing that I want my app run is.
The current state of HeaderGrid is visible, and when I touch a button, this grid will be going up and the visibility changed to collapsed. After that, the ItemsGrid will be going up change it's visibility and then going up replacing the HeaderGrid original place. Then I do the revert of the exact thing.
Can I possible doing that? and I would like to do it in code behind.
Any answer will be appreciated. :)
Thank You.
Regards,
Budi Prasetyo
where you were supposed to add the visibility code in c#, u start a dispatchertimer with interval equal to your animations length and when the timer ticks change the visibility
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0,0,x); //length of your animation in seconds
in main()
{
timer.Tick += timer_Tick;
}
and the event handler for the tick
void timer_Tick(object sender, EventArgs e)
{
timer.Stop();
element.Visibility = Visibility.Visible; //or Collapsed
}
and where you're supposed to change the visibility of the element, just start the timer
timer.Start();
hope it helps :) and not too late ;)
Once you set the Visibility of any element to Collapsed, you will not be able to see any animation, because the element is already collapsed. I just had this issue myself, wanting to ease out when items disappear from a list. My solution was to add another property to my ViewModel, IsRemoving, then set this to true before removing the element. You can then bind the animation to the changing of IsRemoving property to run the animation before setting Visibility on the element.
You can do the trick like this, using some async/await magic. This will delay for 1 second before actually removing the item. Because the delay is awaited, UI will not hang.
public async void RemoveItem(SomeItem item){
IsRemoving = true;
await Task.Delay(1000);
<Your collection>.Remove(item);
IsRemoving = false;
}
EDIT: IN XAML:
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsRemoving}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="border">
<EasingDoubleKeyFrame KeyTime="0" Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<QuinticEase EasingMode="EaseOut"></QuinticEase>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<QuinticEase EasingMode="EaseOut"></QuinticEase>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
Hope it helps :-)
is it possible to create a c# animation for the AttachedPropertys like Alignment? Maybe 1 Second Move between the Change from HorizontalAlignment.Left to HorizontalAlignment.Right - is it possible?
Thanks a lot.
You can't animate it in the sense of producing a smooth animation where something slides from left to right since they are discrete states. There aren't any in-between values. It is possible to create an "animation" which changes the alignment at some point from left to right, it just won't slide across. You could also do a lot of work and measure all the controls to manually create an animation which moves something from one side of the screen to the other using things like Canvas.Left or margins to position the controls.
The attached property part is not an issue, just use the full name of the attached property in the target property part of your animation.
While it's not possible to directly animate smoothly between two properties like
HorizontalAlignment="Right" VerticalAlignment="Bottom"
to
HorizontalAlignment="Center" VerticalAlignment="Center"
I did come up with a way to do this for an app in a way that I thought might be worth sharing. I simply placed the control in a grid that took up the full pane of the window. I aligned the control to the bottom right of the grid. Then I animated the grid with to transform and scale the corner down that I need to the point I want to align the control too (center in this example. See the complete code below.
<Grid Margin="5,5,14,70" Visibility="{Binding Path=AdminModeIsEnabled, Converter={StaticResource CollapsedVisibilityConverter}, FallbackValue=Visible}">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="LayoutTransform">
<Setter.Value>
<ScaleTransform/>
</Setter.Value>
</Setter>
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding CenterPanel}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleY)" To="2" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleY)" To=".5" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleX)" To="2" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleX)" To=".5" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleY)" To="1" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleY)" To="1" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleX)" To="1" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleX)" To="1" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Image Height="15" Source="\Images\Test.png" HorizontalAlignment="Right" VerticalAlignment="Bottom" />
</Grid>
It is possible, here is an example:
class StoryBoardManager : System.Windows.Media.Animation.Storyboard
{
public void ChangeRectangleAlignment(DependencyObject target, VerticalAlignment verticalAlignment, HorizontalAlignment horizontalAlignment, int BeginTimeMillisecond)
{
ObjectAnimationUsingKeyFrames objectAnimation = new ObjectAnimationUsingKeyFrames()
{
BeginTime = TimeSpan.FromMilliseconds(0)
};
Storyboard.SetTarget(objectAnimation, target);
Storyboard.SetTargetProperty(objectAnimation, new PropertyPath("(FrameworkElement.HorizontalAlignment)"));
DiscreteObjectKeyFrame keyFrame = new DiscreteObjectKeyFrame(horizontalAlignment, TimeSpan.FromMilliseconds(BeginTimeMillisecond));
objectAnimation.KeyFrames.Add(keyFrame);
this.Children.Add(objectAnimation);
}
}
For more information, see this other question.
I would like to have an item's width shrink on a click of a button.
Right now I have two objects basically, when you click the button on objectA, a storyboard starts that rotates it around the x-axis and collapses it. Then it shows objectB by setting it's visibility to visible and rotates it around into view.
All I want to add is setting the width smaller while the storyboard is happening to objectA and objectB and then setting it back to normal at the end of the storyboard.
I tried setting the Thickness but I got a compile-time error complaining that it was readonly.
<ObjectAnimationUsingKeyFrames
BeginTime="00:00:00"
Storyboard.TargetName="objectA"
Storyboard.TargetProperty="(UIElement.Margin)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Thickness Left="10" Right="10"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
I have a simple layout for now...
Here is my UI XAML:
<StackPanel>
<Border x:Name="objectA" BorderBrush="Blue" BorderThickness="1" Height="100" Width="100">
<StackPanel>
<TextBox Margin="10"></TextBox>
<Button Width="50" x:Name="btn1" Content="Flip" Click="btn1_Click"/>
</StackPanel>
<Border.Projection>
<PlaneProjection RotationX="0"></PlaneProjection>
</Border.Projection>
</Border>
<Border Visibility="Collapsed" x:Name="objectB" BorderBrush="Red" BorderThickness="1" Height="100" Width="100">
<StackPanel>
<TextBox Margin="10"></TextBox>
<Button Width="50" x:Name="btn2" Content="Flip" Click="btn2_Click"/>
</StackPanel>
<Border.Projection>
<PlaneProjection RotationX="90"></PlaneProjection>
</Border.Projection>
</Border>
Here is the storyboard...
<Storyboard x:Name="Storyboardtest">
<DoubleAnimation BeginTime="00:00:00"
Storyboard.TargetName="objectA"
Storyboard.TargetProperty="(UIElement.Projection).(RotationX)"
From="0" To="-90">
</DoubleAnimation>
<ObjectAnimationUsingKeyFrames
BeginTime="00:00:01"
Storyboard.TargetName="objectA"
Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
BeginTime="00:00:01"
Storyboard.TargetName="objectB"
Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation BeginTime="00:00:01"
Storyboard.TargetName="objectB"
Storyboard.TargetProperty="(UIElement.Projection).(RotationX)"
From="90" To="0">
</DoubleAnimation>
</Storyboard>
If it is just the visual width you want to affect, add the following to your storyboard. It will give the appearance of the controls moving into the distance and back as it flips:
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="objectA">
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0.5"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="objectB">
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="0.5"/>
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="1"/>
</DoubleAnimationUsingKeyFrames>
you will also need to add the following as I used Expression blend to add the animation and it adds any required elements automatically:
<Border x:Name="objectA" BorderBrush="Blue" BorderThickness="1" Height="100" Width="100" RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<CompositeTransform/>
</Border.RenderTransform>
[Snip]
<Border Visibility="Collapsed" x:Name="objectB" BorderBrush="Red" BorderThickness="1" Height="100" Width="100" RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<CompositeTransform/>
</Border.RenderTransform>
The problem is that both the Width and Margin properties are not DependencyProperties so they can not be animated. On workaround method to accomplish this involves adding some custom DependencyProperties to your user control code-behind which can be hooked up to the storyboard and can in turn manipulate the actual properties of the objects.
For example you could add this DependencyProperty to your UserControl which basically allow the setting of the Width property of object A:
public static readonly DependencyProperty ObjectWidthProperty = DependencyProperty.Register(
"ObjectWidth",
typeof(double),
typeof(MainPage),
new PropertyMetadata(50.0, new PropertyChangedCallback(OnObjectWidthChanged)));
public double ObjectWidth
{
get { return (double)GetValue(ObjectWidthProperty); }
set { SetValue(ObjectWidthProperty, value); }
}
private static void OnObjectWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MainPage)d).OnObjectWidthChanged(e);
}
private void OnObjectWidthChanged(DependencyPropertyChangedEventArgs e)
{
this.objectA.Width = this.ObjectWidth;
}
You could then add the following to your storyboard which would animate the width of objectA from 50 pixels down to 0:
<DoubleAnimation BeginTime="00:00:00"
Storyboard.TargetName="MyControl"
Storyboard.TargetProperty="ObjectWidth"
From="50" To="0"/>
The would also require you to add x:Name="MyControl" to your top-level UserControl. It's a little bit hacky, but it works to animate some of the underlying properties of elements that don't happen to be DependencyPropertys.