Showing posts with label Behavior. Show all posts
Showing posts with label Behavior. Show all posts

Saturday, 23 November 2013

RichText in a TextBlock

This project contains examples for Silverlight for providing formatting within a text block, either as a behaviour or attached property.  I would have liked to create custom control that derived from TextBlock but the class is sealed in Silverlight.

Source code:  https://github.com/stevenh77/RichTextBlockExample

image

<UserControl x:Class="RichTextBlockExample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:RichTextBlockExample"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<StackPanel>
<TextBlock x:Name="TextBlockUsingBehaviour">
<i:Interaction.Behaviors>
<local:RichTextBlockBehaviour/>
</i:Interaction.Behaviors>
</TextBlock>

<TextBlock x:Name="TextBlockUsingAttachedProperty" local:SupportRichText.RichText="" />
</StackPanel>
</Grid>
</UserControl>

namespace RichTextBlockExample
{
public partial class MainPage
{
public MainPage()
{
InitializeComponent();

string output = "Testing <bold>formatted</bold> text <underline>with</underline> a <italic>textblock</italic>";
TextBlockUsingBehaviour.Text = output;
TextBlockUsingAttachedProperty.SetValue(SupportRichText.RichTextProperty, output);
}
}
}

Behaviour


using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Windows.Documents;
using System.Windows.Interactivity;

namespace RichTextBlockExample
{
public class RichTextBlockBehaviour : Behavior<TextBlock>
{
private PropertyListener propertyListener;
protected override void OnAttached()
{
base.OnAttached();
propertyListener = new PropertyListener();
propertyListener.ListenForChange("Text", this.AssociatedObject, TextBlock_TextChanged);
}

private void TextBlock_TextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var textBlock = sender as TextBlock;
if (textBlock != null)
{
textBlock.Inlines.Clear();
textBlock.Inlines.Add(Traverse(e.NewValue.ToString()));
}
}

protected override void OnDetaching()
{
base.OnDetaching();
}

private static Inline Traverse(string value)
{
// Get the sections/inlines
string[] sections = SplitIntoSections(value);

// Check for grouping
if (sections.Length.Equals(1))
{
string section = sections[0];
string token; // E.g <Bold>
int tokenStart, tokenEnd; // Where the token/section starts and ends.

// Check for token
if (GetTokenInfo(section, out token, out tokenStart, out tokenEnd))
{
// Get the content to further examination
string content = token.Length.Equals(tokenEnd - tokenStart)
? null
: section.Substring(token.Length, section.Length - 1 - token.Length * 2);

switch (token.ToLower())
{
case "<bold>":
return new Run() { Text = content, FontWeight = FontWeights.Bold };

case "<italic>":
return new Run() { Text = content, FontStyle = FontStyles.Italic };

case "<underline>":
return new Run() { Text = content, TextDecorations = TextDecorations.Underline };

case "<linebreak/>":
return new LineBreak();

default:
return new Run() { Text = content };
}
}
else return new Run() { Text = section };
}
else // Group together
{
Span span = new Span();

foreach (string section in sections) span.Inlines.Add(Traverse(section));

return span;
}
}

/// <summary>
/// Examines the passed string and find the first token, where it begins and where it ends.
/// </summary>
/// <param name="value">The string to examine.</param>
/// <param name="token">The found token.</param>
/// <param name="startIndex">Where the token begins.</param>
/// <param name="endIndex">Where the end-token ends.</param>
/// <returns>True if a token was found.</returns>
private static bool GetTokenInfo(string value, out string token, out int startIndex, out int endIndex)
{
token = null;
endIndex = -1;

startIndex = value.IndexOf("<");
int startTokenEndIndex = value.IndexOf(">");

// No token here
if (startIndex < 0) return false;

// No token here
if (startTokenEndIndex < 0) return false;

token = value.Substring(startIndex, startTokenEndIndex - startIndex + 1);

// Check for closed token. E.g. <LineBreak/>
if (token.EndsWith("/>"))
{
endIndex = startIndex + token.Length;
return true;
}

string endToken = token.Insert(1, "/");

// Detect nesting;
int nesting = 0;
int temp_startTokenIndex = -1;
int temp_endTokenIndex = -1;
int pos = 0;
do
{
temp_startTokenIndex = value.IndexOf(token, pos);
temp_endTokenIndex = value.IndexOf(endToken, pos);

if (temp_startTokenIndex >= 0 && temp_startTokenIndex < temp_endTokenIndex)
{
nesting++;
pos = temp_startTokenIndex + token.Length;
}
else if (temp_endTokenIndex >= 0 && nesting > 0)
{
nesting--;
pos = temp_endTokenIndex + endToken.Length;
}
else // Invalid tokenized string
return false;

}
while (nesting > 0);

endIndex = pos;

return true;
}

/// <summary>
/// Splits the string into sections of tokens and regular text.
/// </summary>
/// <param name="value">The string to split.</param>
/// <returns>An array with the sections.</returns>
private static string[] SplitIntoSections(string value)
{
List<string> sections = new List<string>();

while (!string.IsNullOrEmpty(value))
{
string token;
int tokenStartIndex, tokenEndIndex;

// Check if this is a token section
if (GetTokenInfo(value, out token, out tokenStartIndex, out tokenEndIndex))
{
// Add pretext if the token isn't from the start
if (tokenStartIndex > 0) sections.Add(value.Substring(0, tokenStartIndex));

sections.Add(value.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex));
value = value.Substring(tokenEndIndex); // Trim away
}
else
{
// No tokens, just add the text
sections.Add(value);
value = null;
}
}

return sections.ToArray();
}
}
}

Attached Property


using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

namespace RichTextBlockExample
{
public class SupportRichText
{
public static string GetRichText(DependencyObject obj)
{
return (string)obj.GetValue(RichTextProperty);
}

public static void SetRichText(DependencyObject obj, string value)
{
obj.SetValue(RichTextProperty, value);
}

public static readonly DependencyProperty RichTextProperty =
DependencyProperty.RegisterAttached(
"RichText",
typeof(string),
typeof(SupportRichText),
new PropertyMetadata("", RichTextChanged));

private static Inline Traverse(string value)
{
// Get the sections/inlines
string[] sections = SplitIntoSections(value);

// Check for grouping
if (sections.Length.Equals(1))
{
string section = sections[0];
string token; // E.g <Bold>
int tokenStart, tokenEnd; // Where the token/section starts and ends.

// Check for token
if (GetTokenInfo(section, out token, out tokenStart, out tokenEnd))
{
// Get the content to further examination
string content = token.Length.Equals(tokenEnd - tokenStart)
? null
: section.Substring(token.Length, section.Length - 1 - token.Length * 2);

switch (token.ToLower())
{
case "<bold>":
return new Run() { Text = content, FontWeight = FontWeights.Bold };

case "<italic>":
return new Run() { Text = content, FontStyle = FontStyles.Italic };

case "<underline>":
return new Run() { Text = content, TextDecorations = TextDecorations.Underline };

case "<linebreak/>":
return new LineBreak();

default:
return new Run() { Text = content };
}
}
else return new Run() { Text = section };
}
else // Group together
{
Span span = new Span();

foreach (string section in sections) span.Inlines.Add(Traverse(section));

return span;
}
}

/// <summary>
/// Examines the passed string and find the first token, where it begins and where it ends.
/// </summary>
/// <param name="value">The string to examine.</param>
/// <param name="token">The found token.</param>
/// <param name="startIndex">Where the token begins.</param>
/// <param name="endIndex">Where the end-token ends.</param>
/// <returns>True if a token was found.</returns>
private static bool GetTokenInfo(string value, out string token, out int startIndex, out int endIndex)
{
token = null;
endIndex = -1;

startIndex = value.IndexOf("<");
int startTokenEndIndex = value.IndexOf(">");

// No token here
if (startIndex < 0) return false;

// No token here
if (startTokenEndIndex < 0) return false;

token = value.Substring(startIndex, startTokenEndIndex - startIndex + 1);

// Check for closed token. E.g. <LineBreak/>
if (token.EndsWith("/>"))
{
endIndex = startIndex + token.Length;
return true;
}

string endToken = token.Insert(1, "/");

// Detect nesting;
int nesting = 0;
int temp_startTokenIndex = -1;
int temp_endTokenIndex = -1;
int pos = 0;
do
{
temp_startTokenIndex = value.IndexOf(token, pos);
temp_endTokenIndex = value.IndexOf(endToken, pos);

if (temp_startTokenIndex >= 0 && temp_startTokenIndex < temp_endTokenIndex)
{
nesting++;
pos = temp_startTokenIndex + token.Length;
}
else if (temp_endTokenIndex >= 0 && nesting > 0)
{
nesting--;
pos = temp_endTokenIndex + endToken.Length;
}
else // Invalid tokenized string
return false;

}
while (nesting > 0);

endIndex = pos;

return true;
}

/// <summary>
/// Splits the string into sections of tokens and regular text.
/// </summary>
/// <param name="value">The string to split.</param>
/// <returns>An array with the sections.</returns>
private static string[] SplitIntoSections(string value)
{
var sections = new List<string>();
while (!string.IsNullOrEmpty(value))
{
string token;
int tokenStartIndex, tokenEndIndex;

// Check if this is a token section
if (GetTokenInfo(value, out token, out tokenStartIndex, out tokenEndIndex))
{
// Add pretext if the token isn't from the start
if (tokenStartIndex > 0) sections.Add(value.Substring(0, tokenStartIndex));

sections.Add(value.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex));
value = value.Substring(tokenEndIndex); // Trim away
}
else
{
// No tokens, just add the text
sections.Add(value);
value = null;
}
}

return sections.ToArray();
}

private static void RichTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
string value = e.NewValue as string;
TextBlock textBlock = sender as TextBlock;
if (textBlock != null) textBlock.Inlines.Add(Traverse(value));
}
}
}

Property Listener


using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace RichTextBlockExample
{
public class PropertyListener
{
/// <summary>
/// Listens for changes to dependency properties on a given FrameworkElement calls a callback on property change.
/// Usage example: PropertyListener.ListenForChange("IsBusy", BusyIndicator, (d,e) => DoStuff())
/// (may cause memory leaks)
/// </summary>
public void ListenForChange(string propertyName, FrameworkElement element, PropertyChangedCallback callback)
{
var b = new Binding(propertyName) { Source = element };
var prop = DependencyProperty.RegisterAttached(
"ListenAttached" + propertyName,
typeof(object),
typeof(Control),
new PropertyMetadata(callback));
element.SetBinding(prop, b);
}
}
}

Friday, 15 June 2012

Silverlight Explosions

Here’s an explosive behaviour that was first seen a while ago from Microsoft IT.

Here’s the source:

MainPage.xaml

<UserControl x:Class="Explosions.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Explosions="clr-namespace:Explosions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d" Width="400" Height="500">

<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Margin="20">
<Button x:Name="button" Content="Click me" Margin="20" Height="50" FontSize="29.333">
<i:Interaction.Behaviors>
<Explosions:ExplodeBehavior>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click" SourceObject="{Binding ElementName=button}">
<i:InvokeCommandAction CommandName="Ignite"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Explosions:ExplodeBehavior>
</i:Interaction.Behaviors>
</Button>
<TextBlock x:Name="textBlock" TextWrapping="Wrap" Text="Or click me" HorizontalAlignment="Center" Margin="20" FontSize="29.333" >
<i:Interaction.Behaviors>
<Explosions:ExplodeBehavior>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown" SourceObject="{Binding ElementName=textBlock}">
<i:InvokeCommandAction CommandName="Ignite"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Explosions:ExplodeBehavior>
</i:Interaction.Behaviors>
</TextBlock>
<Image x:Name="image" Height="200" Source="/hoff.PNG" Margin="20">
<i:Interaction.Behaviors>
<Explosions:ExplodeBehavior>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown" SourceObject="{Binding ElementName=image}">
<i:InvokeCommandAction CommandName="Ignite"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Explosions:ExplodeBehavior>
</i:Interaction.Behaviors>
</Image>
</StackPanel>
</Grid>
</UserControl>

ExplosionBehavior.cs

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Explosions
{
public class ExplodeBehavior : Behavior<FrameworkElement>
{

public static readonly DependencyProperty PowerProperty = DependencyProperty.Register("Power", typeof (double),
typeof (ExplodeBehavior),
new PropertyMetadata(50.0d));

public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof (double),
typeof (ExplodeBehavior),
new PropertyMetadata(
20.0d));

public static readonly DependencyProperty DepthProperty = DependencyProperty.Register("Depth", typeof (double),
typeof (ExplodeBehavior),
new PropertyMetadata(
-20.0d));

public static readonly DependencyProperty ShrapnelAmountProperty = DependencyProperty.Register(
"ShrapnelAmount", typeof (double), typeof (ExplodeBehavior), new PropertyMetadata(500.0d));

public static readonly DependencyProperty EpicenterProperty = DependencyProperty.Register("Epicenter",
typeof (Point),
typeof (
ExplodeBehavior),
new PropertyMetadata(
new Point(.5, .5)));

public static readonly DependencyProperty ReverseDurationProperty =
DependencyProperty.Register("ReverseDuration", typeof (TimeSpan), typeof (ExplodeBehavior),
new PropertyMetadata(TimeSpan.FromSeconds(1)));

private readonly List<Shard> shards = new List<Shard>();
private Point? lastMousePoint;
private Popup popup;

public ExplodeBehavior()
{
}

protected override void OnAttached()
{
base.OnAttached();

this.AssociatedObject.MouseMove += this.HandleMouseMove;
}

protected override void OnDetaching()
{
base.OnDetaching();

this.AssociatedObject.MouseMove -= this.HandleMouseMove;
}

public Point Epicenter
{
get { return (Point) this.GetValue(ExplodeBehavior.EpicenterProperty); }
set { this.SetValue(ExplodeBehavior.EpicenterProperty, value); }
}


public double ShrapnelAmount
{
get { return (double) this.GetValue(ExplodeBehavior.ShrapnelAmountProperty); }
set { this.SetValue(ExplodeBehavior.ShrapnelAmountProperty, value); }
}


public double Depth
{
get { return (double) this.GetValue(ExplodeBehavior.DepthProperty); }
set { this.SetValue(ExplodeBehavior.DepthProperty, value); }
}


public double Radius
{
get { return (double) this.GetValue(ExplodeBehavior.RadiusProperty); }
set { this.SetValue(ExplodeBehavior.RadiusProperty, value); }
}


public double Power
{
get { return (double) this.GetValue(ExplodeBehavior.PowerProperty); }
set { this.SetValue(ExplodeBehavior.PowerProperty, value); }
}

public TimeSpan ReverseDuration
{
get { return (TimeSpan) this.GetValue(ExplodeBehavior.ReverseDurationProperty); }
set { this.SetValue(ExplodeBehavior.ReverseDurationProperty, value); }
}

[DefaultTrigger(typeof (ButtonBase), typeof (System.Windows.Interactivity.EventTrigger), "Click"),
DefaultTrigger(typeof (UIElement), typeof (System.Windows.Interactivity.EventTrigger), "MouseLeftButtonDown")]
public ICommand Ignite
{
get
{
return new DelegateCommand(delegate(object args)
{
this.StartExplode();
});
}
}

public ICommand Implode
{
get
{
return new DelegateCommand(delegate(object args)
{
this.StartImplode();
});
}
}


private void HandleMouseMove(object sender, MouseEventArgs e)
{
this.lastMousePoint = e.GetPosition(this.AssociatedObject);
}

private void StartExplode()
{
if (this.AssociatedObject == null)
return;

Vector3D position;
if (this.ReadLocalValue(ExplodeBehavior.EpicenterProperty) == DependencyProperty.UnsetValue &&
this.lastMousePoint.HasValue)
position = new Vector3D(this.lastMousePoint.Value.X, this.lastMousePoint.Value.Y, this.Depth);
else
position = new Vector3D(this.AssociatedObject.ActualWidth*this.Epicenter.X,
this.AssociatedObject.ActualHeight*this.Epicenter.Y, this.Depth);

this.StartExplode(position);
}

public void StartExplode(double x, double y, double z)
{
this.StartExplode(new Vector3D(x, y, z));
}


private void StartExplode(Vector3D point)
{

if (this.shards.Count == 0)
this.PrepareShards(point);

this.ApplyForce(point);
}

private void StartImplode()
{
Storyboard sb = new Storyboard();
sb.Duration = new Duration(this.ReverseDuration);
foreach (Shard shard in this.shards)
shard.Reverse(this.ReverseDuration, sb);

sb.Completed += delegate
{
if (this.popup != null)
{
this.shards.Clear();
this.AssociatedObject.Opacity = 1;
this.popup.IsOpen = false;
this.popup = null;
}
;
};

sb.Begin();
}

private void PrepareShards(Vector3D point)
{

FrameworkElement content = this.AssociatedObject;
if (content == null)
return;

double width = content.ActualWidth;
double height = content.ActualHeight;

Grid container = new Grid();
this.popup = new Popup();
popup.Child = container;


Point popupOffset = content.TransformToVisual(Application.Current.RootVisual).Transform(new Point(0, 0));

popup.HorizontalOffset = popupOffset.X;
popup.VerticalOffset = popupOffset.Y;
popup.IsHitTestVisible = false;
container.IsHitTestVisible = false;

container.Width = width;
container.Height = height;

double rows = Math.Sqrt(this.ShrapnelAmount*height/width);
double columns = this.ShrapnelAmount/rows;

int rowCount = (int) Math.Round(rows);
int columnCount = (int) Math.Round(columns);

for (int x = 0; x < columnCount; ++x)
container.ColumnDefinitions.Add(new ColumnDefinition()
{
Width = new GridLength(1, GridUnitType.Star),
});

for (int y = 0; y < rowCount; ++y)
container.RowDefinitions.Add(new RowDefinition()
{
Height = new GridLength(1, GridUnitType.Star),
});

WriteableBitmap bitmap = new WriteableBitmap(content, null);
for (int x = 0; x < columnCount; ++x)
{
for (int y = 0; y < rowCount; ++y)
{
Rectangle element = new Rectangle();

ImageBrush brush = new ImageBrush()
{
ImageSource = bitmap,
};

ScaleTransform scale = new ScaleTransform()
{
ScaleX = columnCount,
ScaleY = rowCount,
};

TranslateTransform translation = new TranslateTransform()
{
X = -x/(double) columnCount,
Y = -y/(double) rowCount,
};

TransformGroup transform = new TransformGroup();
transform.Children.Add(translation);
transform.Children.Add(scale);


brush.RelativeTransform = transform;

element.Fill = brush;

Grid.SetColumn(element, x);
Grid.SetRow(element, y);

container.Children.Add(element);

Shard shard = new Shard(element,
new Point(width*(x + .5)/columnCount,
height*(y + .5)/rowCount));

this.shards.Add(shard);
}
}

content.Opacity = 0;
popup.IsOpen = true;
}

private void ApplyForce(Vector3D point)
{
Storyboard sb = new Storyboard()
{
Duration = new Duration(TimeSpan.FromSeconds(100)),
};

foreach (Shard shard in this.shards)
{
Vector3D position = shard.Position;

Vector3D delta = position - point;

double magnitude = -Math.Pow(delta.Length, 2)/(2*this.Radius*this.Radius);
double power = this.Power*Math.Pow(Math.E, magnitude);


delta.Normalize();
delta = delta*power*10;

shard.TranslationVelocity += delta;

Vector3D offset = point - position;

Vector3D rotation = new Vector3D(-offset.Y, -offset.X, offset.Z);

rotation.Normalize();

rotation = rotation*power*10;
rotation.Z = 0;

shard.RotationalVelocity += rotation;

shard.StartAnims(sb);

}
sb.Begin();
}

private class Shard
{

private readonly PlaneProjection projection = new PlaneProjection();
private Point initialPosition;

public Shard(FrameworkElement target, Point initialPosition)
{
this.Target = target;
this.initialPosition = initialPosition;
target.Projection = this.projection;
}

public FrameworkElement Target { get; private set; }

public Vector3D RotationalVelocity { get; set; }
public Vector3D TranslationVelocity { get; set; }

public void Update()
{
this.projection.RotationX += this.RotationalVelocity.X;
this.projection.RotationY += this.RotationalVelocity.Y;
this.projection.RotationZ += this.RotationalVelocity.Z;

this.projection.GlobalOffsetX += this.TranslationVelocity.X;
this.projection.GlobalOffsetY += this.TranslationVelocity.Y;
this.projection.GlobalOffsetZ += this.TranslationVelocity.Z;
}

public Vector3D Position
{
get
{
return new Vector3D(this.initialPosition.X + this.projection.GlobalOffsetX,
this.initialPosition.Y + this.projection.GlobalOffsetY,
this.projection.GlobalOffsetZ);
}
}

public void StartAnims(Storyboard sb)
{
TimeSpan duration = TimeSpan.FromSeconds(100);
sb.Duration = new Duration(duration);

DoubleAnimation tx = new DoubleAnimation()
{
To = this.TranslationVelocity.X*duration.TotalSeconds,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(tx, new PropertyPath(PlaneProjection.GlobalOffsetXProperty));
Storyboard.SetTarget(tx, this.projection);
sb.Children.Add(tx);


DoubleAnimation ty = new DoubleAnimation()
{
To = this.TranslationVelocity.Y*duration.TotalSeconds,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(ty, new PropertyPath(PlaneProjection.GlobalOffsetYProperty));
Storyboard.SetTarget(ty, this.projection);
sb.Children.Add(ty);

DoubleAnimation tz = new DoubleAnimation()
{
To = this.TranslationVelocity.Z*duration.TotalSeconds,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(tz, new PropertyPath(PlaneProjection.GlobalOffsetZProperty));
Storyboard.SetTarget(tz, this.projection);
sb.Children.Add(tz);

DoubleAnimation rx = new DoubleAnimation()
{
To = this.RotationalVelocity.X*duration.TotalSeconds,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(rx, new PropertyPath(PlaneProjection.RotationXProperty));
Storyboard.SetTarget(rx, this.projection);
sb.Children.Add(rx);

DoubleAnimation ry = new DoubleAnimation()
{
To = this.RotationalVelocity.Y*duration.TotalSeconds,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(ry, new PropertyPath(PlaneProjection.RotationYProperty));
Storyboard.SetTarget(ry, this.projection);
sb.Children.Add(ry);

DoubleAnimation rz = new DoubleAnimation()
{
To = this.RotationalVelocity.Z*duration.TotalSeconds,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(rz, new PropertyPath(PlaneProjection.RotationZProperty));
Storyboard.SetTarget(rz, this.projection);
sb.Children.Add(rz);

Storyboard.SetTarget(sb, this.Target);
}

public void Reverse(TimeSpan duration, Storyboard sb)
{
sb.Duration = new Duration(duration);

DoubleAnimation tx = new DoubleAnimation()
{
To = 0,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(tx, new PropertyPath(PlaneProjection.GlobalOffsetXProperty));
Storyboard.SetTarget(tx, this.projection);
sb.Children.Add(tx);


DoubleAnimation ty = new DoubleAnimation()
{
To = 0,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(ty, new PropertyPath(PlaneProjection.GlobalOffsetYProperty));
Storyboard.SetTarget(ty, this.projection);
sb.Children.Add(ty);

DoubleAnimation tz = new DoubleAnimation()
{
To = 0,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(tz, new PropertyPath(PlaneProjection.GlobalOffsetZProperty));
Storyboard.SetTarget(tz, this.projection);
sb.Children.Add(tz);

DoubleAnimation rx = new DoubleAnimation()
{
To = 0,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(rx, new PropertyPath(PlaneProjection.RotationXProperty));
Storyboard.SetTarget(rx, this.projection);
sb.Children.Add(rx);

DoubleAnimation ry = new DoubleAnimation()
{
To = 0,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(ry, new PropertyPath(PlaneProjection.RotationYProperty));
Storyboard.SetTarget(ry, this.projection);
sb.Children.Add(ry);

DoubleAnimation rz = new DoubleAnimation()
{
To = 0,
Duration = new Duration(duration),
};
Storyboard.SetTargetProperty(rz, new PropertyPath(PlaneProjection.RotationZProperty));
Storyboard.SetTarget(rz, this.projection);
sb.Children.Add(rz);

Storyboard.SetTarget(sb, this.Target);
}
}

private class DelegateCommand : ICommand
{

public delegate void Handler(object args);

private Handler handler;

public DelegateCommand(Handler handler)
{
this.handler = handler;
}

public bool CanExecute(object parameter)
{
return true;
}

public event EventHandler CanExecuteChanged;

public void Execute(object parameter)
{
this.handler(parameter);
}

protected virtual void OnCanExecuteChanged()
{
if (this.CanExecuteChanged != null)
this.CanExecuteChanged(this, EventArgs.Empty);
}

}

private struct Vector3D
{

public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }

public Vector3D(double x, double y, double z)
: this()
{
this.X = x;
this.Y = y;
this.Z = z;
}

public static Vector3D operator -(Vector3D a, Vector3D b)
{
return new Vector3D(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
}

public static Vector3D operator +(Vector3D a, Vector3D b)
{
return new Vector3D(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
}

public static Vector3D operator *(Vector3D vector, double scalar)
{
return new Vector3D(vector.X*scalar, vector.Y*scalar, vector.Z*scalar);
}

public static Vector3D operator /(Vector3D vector, double scalar)
{
return (Vector3D) (vector*(1.0/scalar));
}

public double Length
{
get { return Math.Sqrt(this.X*this.X + this.Y*this.Y + this.Z*this.Z); }
}

public void Normalize()
{
double x = Math.Abs(this.X);
double y = Math.Abs(this.Y);
double z = Math.Abs(this.Z);
if (z > x)
x = z;
if (y > x)
x = y;

this.X /= x;
this.Y /= x;
this.Z /= x;
double length = Math.Sqrt(this.X*this.X + this.Y*this.Y + this.Z*this.Z);
this = (Vector3D) (this/length);
}

public Vector3D Cross(Vector3D other)
{
Vector3D result = new Vector3D();

result.X = this.Y*other.Z - this.Z*other.Y;
result.Y = this.Z*other.X - this.X*other.Z;
result.Z = this.X*other.Y - this.Y*other.X;

return result;
}

public override string ToString()
{
return this.X + "," + this.Y + "," + this.Z;
}
}
}
}

Live demo:  http://stevenhollidge.com/blog-source-code/explosions


Source:  https://github.com/stevenh77/Explosions

Saturday, 26 May 2012

Silverlight Fluid Move Behavior

Check out this excellent tutorial on http://xaml.tv where Victor Guadioso shows how to use the Fluid Move Behavior in Expression Blend:

What you will learn:

  1. How to create sample data (text and images);
  2. How to create a ListBox and bind the ItemsSource to the sample data;
  3. How to create a Details Grid that reflects the selected item of the ListBox;
  4. How to use the Fluid Move and Fluid Set Tag Behaviors to make the selected item animate from the ListBox to the Master Details Grid;

For more Xaml tips check out http://xaml.tv and follow Victor on twitter:  https://twitter.com/victorgaudioso