Silverlight 3 Flip TargetedTriggerAction
August 6, 2009 @ 3:36 pm in Silverlight
Last week I created a Trigger Action which flipped a panel. It was intended to be invoked from clicking on the panel directly. Earlier today I was asked if you could invoke this trigger from multiple places. The answer is no, but it is very easily to rewrite this trigger to achieve this behavior. In this blog post I am going to create a TargetedTriggerAction which allows me to add the trigger to a button or multiple UIElement and “Target” a panel to be flipped. What is really cool about this approach is that each button can contain the logic used to flip the panel (Storyboard direction, duration, etc).
The following is the code I used to create my Flip TargetedTriggerAction.
using System.ComponentModel;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace FlipTargetedTrigger
{
public enum RotationDirection
{
LeftToRight,
RightToLeft,
TopToBottom,
BottomToTop
}
public class Flip : TargetedTriggerAction<FrameworkElement>
{
public static readonly DependencyProperty FrontElementNameProperty =
DependencyProperty.Register("FrontElementName", typeof(string),
typeof(Flip), new PropertyMetadata(null));
[Category("Flip Properties")]
public string FrontElementName
{
get
{
return (string)GetValue(FrontElementNameProperty);
}
set
{
SetValue(FrontElementNameProperty, value);
}
}
public static readonly DependencyProperty BackElementNameProperty =
DependencyProperty.Register("BackElementName", typeof(string),
typeof(Flip), new PropertyMetadata(null));
[Category("Flip Properties")]
public string BackElementName
{
get
{
return (string)GetValue(BackElementNameProperty);
}
set
{
SetValue(BackElementNameProperty, value);
}
}
public static readonly DependencyProperty DurationProperty =
DependencyProperty.Register("Duration", typeof(Duration),
typeof(Flip), new PropertyMetadata(null));
[Category("Animation Properties")]
public Duration Duration
{
get
{
return (Duration)GetValue(DurationProperty);
}
set
{
SetValue(DurationProperty, value);
}
}
public static readonly DependencyProperty RotationProperty =
DependencyProperty.Register("Rotation", typeof(RotationDirection),
typeof(Flip), new PropertyMetadata(RotationDirection.LeftToRight));
[Category("Animation Properties")]
public RotationDirection Rotation
{
get
{
return (RotationDirection)GetValue(RotationProperty);
}
set
{
SetValue(RotationProperty, value);
}
}
public static readonly DependencyProperty FrontStoryboardProperty =
DependencyProperty.Register("FrontStoryboard", typeof(Storyboard), typeof(Flip), null);
public Storyboard FrontStoryBoard
{
get
{
return (Storyboard)GetValue(FrontStoryboardProperty);
}
}
public static readonly DependencyProperty BackStoryboardProperty =
DependencyProperty.Register("BackStoryboard", typeof(Storyboard), typeof(Flip), null);
public Storyboard BackStoryboard
{
get
{
return (Storyboard)GetValue(BackStoryboardProperty);
}
}
private bool _forward = true;
protected override void OnAttached()
{
base.OnAttached();
FrameworkElement element = this.AssociatedObject as FrameworkElement;
if (element != null) element.Loaded += TargetLoaded;
}
void TargetLoaded(object sender, RoutedEventArgs e)
{
PlaneProjection pp = Target.Projection as PlaneProjection;
if (Target.Projection == null)
{
pp = new PlaneProjection { CenterOfRotationY = .51 };
Target.RenderTransformOrigin = new Point(.5, .5);
Target.Projection = pp;
}
Storyboard sbF = new Storyboard();
Storyboard sbB = new Storyboard();
UIElement f = null;
UIElement b = null;
f = Target.FindName(FrontElementName) as UIElement;
if (f != null)
{
PlaneProjection ppFront = new PlaneProjection { CenterOfRotationY = .51 };
f.Projection = ppFront;
f.RenderTransformOrigin = new Point(.5, .5);
}
b = Target.FindName(BackElementName) as UIElement;
if (b != null)
{
PlaneProjection ppBack = new PlaneProjection { CenterOfRotationY = .51, RotationY = 180.0 };
b.Projection = ppBack;
b.RenderTransformOrigin = new Point(.5, .5);
b.Opacity = 0.0;
}
double to = 0.0;
double from = 180.0;
string property = "RotationY";
switch (Rotation)
{
case RotationDirection.RightToLeft:
to = 180.0;
from = 0.0;
break;
case RotationDirection.TopToBottom:
property = "RotationX";
break;
case RotationDirection.BottomToTop:
to = 0.0;
from = 180.0;
property = "RotationX";
break;
}
sbF.Duration = Duration;
sbB.Duration = Duration;
sbF.Children.Add(CreateDoubleAnimation(pp, property, from, to, true));
sbB.Children.Add(CreateDoubleAnimation(pp, property, to, from, true));
sbF.Children.Add(CreateDoubleAnimation(f, "Opacity", 1.0, 0.0, false));
sbB.Children.Add(CreateDoubleAnimation(f, "Opacity", 0.0, 1.0, false));
sbF.Children.Add(CreateDoubleAnimation(b, "Opacity", 0.0, 1.0, false));
sbB.Children.Add(CreateDoubleAnimation(b, "Opacity", 1.0, 0.0, false));
SetValue(FrontStoryboardProperty, sbF);
SetValue(BackStoryboardProperty, sbB);
}
protected override void Invoke(object parameter)
{
Storyboard sbF = GetValue(FrontStoryboardProperty) as Storyboard;
Storyboard sbB = GetValue(BackStoryboardProperty) as Storyboard;
if (_forward)
{
sbF.Begin();
_forward = false;
}
else
{
sbB.Begin();
_forward = true;
}
}
private static DoubleAnimation CreateDoubleAnimation(DependencyObject element, string property, double from,
double to, bool addEasing)
{
DoubleAnimation da = new DoubleAnimation();
da.To = to;
da.From = from;
if (addEasing)
da.EasingFunction = new PowerEase() { EasingMode = EasingMode.EaseOut, Power = 3 };
Storyboard.SetTargetProperty(da, new PropertyPath(property));
Storyboard.SetTarget(da, element);
return da;
}
}
}For the most part the code is very simmiler to the other trigger. When you inherate from TargetTriggerAction you gain access to a new property called “Target” this will contain a reference to the UIELement I plan on flipping. Instead of adding my PlainProjection to the AssociatedObject I instead add it to the target. I made a few other changes like storing my Storyboards as dependency properties which allow me to reuse the same animiation each time I lick the button. These DependencyProperites get stored in the AssociatedObject and not the Target. This allows each item being clicked to have a unique animation. Below is how I implement the TargetTriggerAction.
<StackPanel Orientation="Vertical">
<Grid Margin="10" x:Name="flipMe">
<StackPanel x:Name="back1" Height="200" Width="200" HorizontalAlignment="Center"
VerticalAlignment="Center">
<Rectangle Fill="Green" Height="200" Width="200" StrokeThickness="1" Stroke="Black" />
</StackPanel>
<Rectangle x:Name="front1" Fill="Gold" Height="200" Width="200"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<Button Content="Flip RightToLeft" Width="100">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<targeted:Flip FrontElementName="front1" BackElementName="back1"
TargetName="flipMe" Duration="00:00:1" Rotation="RightToLeft"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Content="Flip TopToBtoom" Width="100">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<targeted:Flip FrontElementName="front1" BackElementName="back1"
TargetName="flipMe" Duration="00:00:1" Rotation="TopToBottom"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
Code: FlipTargetedTrigger.zip
/p>
Tagged as 

August 7th, 2009 at 1:35 pm
very nice, thanks a lot. I’ve been looking for a good example of how to do this and appreciate your contribution.
August 7th, 2009 at 10:14 pm
[...] I forgot to mention that I added a bit of code to this behavior based on a great behavior I saw from Joel Nuebeck. So I want to give him props for giving me the idea of making the 4 directional options so the [...]
August 10th, 2009 at 10:20 am
I have a ListBox where I perform some logic in the MouseButtonUp Event. In this logic there are situations where I want to execute the Flip behavior, how can I achieve this?
August 10th, 2009 at 10:47 am
After some more digging I found out how, from code-behind, to apply the EventTrigger to an object. Below is the code to do this where “LefNav” is a ListBox (replaced “front1″)
Flip flip = new Flip();
flip.TargetName = "flipMe";
flip.FrontElementName = "LeftNav";
flip.BackElementName = "back1";
flip.Duration = new TimeSpan(0, 0, 0, 1);
flip.Rotation = RotationDirection.RightToLeft;
System.Windows.Interactivity.EventTrigger et = new System.Windows.Interactivity.EventTrigger("MouseLeftButtonUp");
et.SourceName = "LeftNav";
et.Actions.Add(flip);
et.Attach(LeftNav);
However this resolves only part of what I’m trying to accomplish here, what I really need is a way to execute the trigger from logic in the code-behind (like inside the MouseLeftButtonUp event there are certain conditions when I want the flip to occur and other times that I do not want it to).
I’ll continue to dig into this and let you know what I find
August 10th, 2009 at 10:54 am
When I have written Triggers to work with a ListBox I have always attached to the “SelectionChanged” event. If you do that than each time a new item in the ListBox is selected the Invoke method will be called. Later today I will post a new Trigger that created that changes an items visibility based on a specific item in a Listbox. This example might help you with tying a listbox to the Flip TargetedTriggerAction
August 11th, 2009 at 2:24 pm
OK, I finally found a way to accomplish a Flip from code behind. It’s not pretty but it works, here are the details:
I added 2 private properties:
At the bottom of the TargetLoaded event I set IsLoaded = true, I then check to see if the property FlipOnTargetLoadComplete is set to true, if it is I call the method ExecuteFlip()
The ExecuteFlip() code is below:
You need to assign the EventTrigger to an object on your XAML page via codebehind which will allow you to have access to the Flip object in order to make the original call to ExecuteFlip(). Here is how to do that:
Now anywhere in my code-behind I can simply call the flip.ExecuteFlip() method and the animation will execute.
I’m sure there are better ways to do this and I would love to see them but for now this gets the job done.
September 1st, 2009 at 1:03 pm
Great stuff here, thanks. Can you think of a reason why text on the “back” panel would look a tad blurry?
September 1st, 2009 at 1:26 pm
The other observation is that the input controls on the back panel are not responding to input, and the procesor of the machine is stressed when looking at that one (as opposed to whatever one is designated “front”). The storyboards and animations seems straight up in the code. Any thoughts?
September 8th, 2009 at 8:02 am
Hey,
Did anyone successfully managed to invoke the trigger from code behind?
I’ve tried mknopf’s approach but i couldn’t get the flip to invoke..
Any help would be cool ;)
September 22nd, 2009 at 6:54 pm
hi
Can you please help me with the blur effect that we get on the back panel of the controls…any help would be appreciated
Thanks
Raja
January 13th, 2010 at 2:57 am
Hi,
Still looking for an explanation on why the back panel is blurry, has anybody found a way to get rid of this?
January 28th, 2010 at 12:26 am
When adding a button to the back side I cant click it. Just like the person above me who cant access textbox.
Does anyone now how to do this?