Silverlight 4 How to Command Control

Date February 6, 2010 @ 11:20 am in Silverlight

SilverlightCheckout my latest article on the use of commanding in Silverlight 4. In this article I demonstrate how through the use of commanding and intelligent binding we can virtually eliminate all code behind and implement to a strong MVVM architectural pattern.

Silverlight 4 Webcam Article

Date December 12, 2009 @ 12:08 pm in Silverlight

SilverlightCheckout my latest article on the use of  the Webcam in Silverlight 4. The article demonstrates how to attach to a device apply a PixelShader then store a captured JPEG in Isolated Storage .

Navigation Transitions with WriteableBitmap

Date August 14, 2009 @ 10:16 am in Silverlight

If you have not had a chance to play with Navigation Frames in Silverlight they are very cool, they give developers the ability to navigate between "Page" within a single Silverlight Solution.  The first time I used them I could believe how easy it was to implement.  You simply add a few pages to your solution, drop a Navigation:Frame on to your MainPage.xaml UserControl and define some UriMappers.

One thing that i thought was missing was the ability to  create nice looking transitions between pages as they are loaded into a frame.  Recently I voiced my complaint to a coworker (the infamous Steve Saxon – @xmlguy) and he had the great idea of using a WritableBitmap to capture the current and next page within the Frame, than animating the images.  Here is the solution I arrived at.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
 
namespace NavigationTransition
{
    public partial class MainPage : UserControl
    {
        private HyperlinkButton _lastClicked;
        private bool _isNavigating = false;
        private readonly Image _imgLast = new Image();
        private readonly Image _imgNext = new Image();
        private Storyboard _sb = new Storyboard();
 
        public MainPage()
        {
            InitializeComponent();
        }
 
        void SbCompleted(object sender, EventArgs e)
        {
            Panel panel = ContentFrame.Parent as Panel;
            panel.Children.Remove(_imgLast);
            panel.Children.Remove(_imgNext);
            ContentFrame.Visibility = Visibility.Visible;
            _sb.Stop();
            _isNavigating = false;
        }
 
        private void ContentFrameNavigated(object sender, NavigationEventArgs e)
        {
            ContentFrame.Navigated -= ContentFrameNavigated;
            Panel panel = ContentFrame.Parent as Panel;
            panel.UpdateLayout();
 
            WriteableBitmap bitmapN = new WriteableBitmap(ContentFrame, new TranslateTransform());
            _imgNext.Source = bitmapN;
            TranslateTransform ttNext = new TranslateTransform();
 
            _imgNext.RenderTransform = ttNext;
            panel.Children.Add(_imgNext);
 
            _sb.Children.Add(CreateDoubleAnimation(ttNext, "X", -bitmapN.PixelWidth, 0, true)); 
            ContentFrame.Visibility = Visibility.Collapsed;
 
            _sb.Begin();
 
        }
 
 
        private void Nav_Click(object sender, RoutedEventArgs e)
        {
            _lastClicked = (HyperlinkButton)sender;
            if (ContentFrame.Source.ToString() != _lastClicked.Tag.ToString() && !_isNavigating)
            {
                _sb = new Storyboard();
                _sb.Completed += SbCompleted;
 
                Panel panel = ContentFrame.Parent as Panel;
 
                WriteableBitmap bitmapL = new WriteableBitmap(ContentFrame, new TranslateTransform());
                _imgLast.Source = bitmapL;
                TranslateTransform ttLast = new TranslateTransform();
                _imgLast.RenderTransform = ttLast;
                if (panel != null) panel.Children.Add(_imgLast);
 
                ContentFrame.Navigated += ContentFrameNavigated;
                ContentFrame.Navigate(new Uri(_lastClicked.Tag.ToString(), UriKind.Relative));
 
                _sb.Children.Add(CreateDoubleAnimation(ttLast, "X", 0, bitmapL.PixelWidth, true));
 
                _isNavigating = 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;
        }
        // If an error occurs during navigation, show an error window
        private void ContentFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            e.Handled = true;
            ChildWindow errorWin = new ErrorWindow(e.Uri);
            errorWin.Show();
        }
    }
}

One of the things I found necessary was to delay the navigation so that I could capture a bitmap of the current frame.  The way I intersected the navigation  was to attach a Click event handler to my HyperlinkButton and then store the Uri in the Tag property.  With a little more effort you could make this into a Behavior or even add a few additional types of transitions.

 


Code: NavigationTransition.zip

Control Visibility with triggers

Date August 10, 2009 @ 2:30 pm in Silverlight

I absolutely love writing triggers for Silverlight 3.  As UX developers we often faced with the challenge of finding ways to fit a ton of information on a single user input screen.  On occasion, some of that information may only be relevant to display if users have made certain choices on the interface: checking a box, selecting an item in a combobox or listbox.  In this post I will include two custom TargetedTriggerAction’s which allow a developer to easily tie a UIElement’s visibility to an action made on another control.

VisibilityIsChecked

This first trigger I very simple.  I wanted the ability to tie a checkbox to the visibility of another UIElement.  Since a checkbox derives from ToggleButton we can get creative and write a single trigger that will work with either a CheckBox, RadioButton or ToggleButton.  Here is how I did it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Interactivity;
 
namespace VisibilityTriggers
{
    public class VisibilityIsChecked : TargetedTriggerAction<frameworkelement>
    {
        public static readonly DependencyProperty VisibleWhenCheckedProperty =
            DependencyProperty.Register(&quot;VisibleWhenChecked&quot;, typeof(bool), 
            typeof(VisibilityIsChecked), new PropertyMetadata(true));
        public bool VisibleWhenChecked
        {
            get
            {
                return (bool)GetValue(VisibleWhenCheckedProperty);
            }
            set
            {
                SetValue(VisibleWhenCheckedProperty, value);
            }
        }
 
        protected override void OnAttached()
        {
            base.OnAttached();
            FrameworkElement element = this.AssociatedObject as FrameworkElement;
            if (element != null) element.Loaded += TargetLoaded;
        }
 
        void TargetLoaded(object sender, RoutedEventArgs e)
        {
            ToggleButton tb = this.AssociatedObject as ToggleButton;
            SetVisibility(tb);
        }
        protected override void Invoke(object parameter)
        {
            RoutedEventArgs args = parameter as RoutedEventArgs;
 
            if (args != null)
            {
                ToggleButton tb = args.OriginalSource as ToggleButton;
                SetVisibility(tb);
            }
        }
 
        private void SetVisibility(ToggleButton tb)
        {
            if (tb != null)
            {
                Target.Visibility = ((bool)tb.IsChecked == VisibleWhenChecked) ? 
                Visibility.Visible : Visibility.Collapsed;
            }
        }
    }
}

To implement this trigger, we simply add an EventTrigger to a checkbox and set the TargetName to the control I want to visualize. Since there are times that we want IsChecked to either show or hide the element I have included a DependencyProperty called VisibileWhenChecked that can alter this behavior.

1
2
3
4
5
6
7
8
<checkbox content="Show?" margin="5">
    <i:interaction.triggers>
        <i:eventtrigger eventname="Click">
            <triggers:visibilityischecked targetname="rectangle" />
        </i:eventtrigger>
    </i:interaction.triggers>
</checkbox>
<rectangle margin="10" x:name="rectangle" width="100" height="100" fill="Yellow" visibility="Collapsed" />

VisibilitySelectedItem

The second TargetedTriggerAction I created achieves a similar result, but instead of targeting a checkbox or radio button, it targets a ComboBox or ListBox. In this trigger I will flip a UIElements visibility based on a specific value being returned from a selection in the the ListBox. Here is how I implemented the trigger.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Interactivity;
 
namespace VisibilityTriggers
{
    public class VisibilitySelectedItem : TargetedTriggerAction<frameworkelement>
    {
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register(&quot;Value&quot;, typeof(object), typeof(VisibilitySelectedItem), null);
 
        public object Value
        {
            get
            {
                return (object)GetValue(ValueProperty);
            }
            set
            {
                SetValue(ValueProperty, value);
            }
        }
 
        public static readonly DependencyProperty MatchMemberPathProperty =
            DependencyProperty.Register(&quot;MatchMemberPath&quot;, typeof(string), typeof(VisibilitySelectedItem), new PropertyMetadata(&quot;Content&quot;));
 
        public string MatchMemberPath
        {
            get
            {
                return (string)GetValue(MatchMemberPathProperty);
            }
            set
            {
                SetValue(MatchMemberPathProperty, value);
            }
        }
 
        protected override void OnAttached()
        {
            base.OnAttached();
            FrameworkElement element = this.AssociatedObject as FrameworkElement;
            if (element != null) element.Loaded += TargetLoaded;
        }
 
        void TargetLoaded(object sender, RoutedEventArgs e)
        {
            FrameworkElement element = this.AssociatedObject as FrameworkElement;
            Selector cb = this.Target as Selector;
            if (cb != null)
            {
                int index = cb.SelectedIndex &gt; -1 ? cb.SelectedIndex : 0;
                object item = (cb.Items.Count &gt; 0) ? cb.Items[index] as object : null;
 
                if (item != null)
                {
                    System.Reflection.PropertyInfo pi = item.GetType().GetProperty(MatchMemberPath);
                    var value = (pi != null) ? pi.GetValue(item, null) as object : item;
                    bool match = Equals(value, Value);
                    if (element != null) element.Visibility = match ? Visibility.Visible : Visibility.Collapsed;
                }
            }
        }
        protected override void Invoke(object parameter)
        {
            FrameworkElement element = this.AssociatedObject as FrameworkElement;
            SelectionChangedEventArgs args = parameter as SelectionChangedEventArgs;
 
            if (args != null)
            {
                IList list = args.AddedItems as IList;
                if (list != null)
                {
                    foreach (object item in list)
                    {
                        System.Reflection.PropertyInfo pi = item.GetType().GetProperty(MatchMemberPath);
                        var value = (pi != null) ? pi.GetValue(item, null) as object : item;
 
                        bool match = Equals(value, Value);
                        if (element != null) element.Visibility = match ? Visibility.Visible : Visibility.Collapsed;
                    }
                }
            }
        }
    }
}

To implement this TargetedTriggerAction you will place the custom EventTrigger on the item you want to visualize and target the SelectionChanged event on the ListBox (or ComboBox). Since we only want to visualize when a specific value is returned, we will also specifiy a "Value" and which property of our ListBox’s ItemSource we are targeting (MatchMemeberPath). The following is one way in which we can implement the trigger. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<listbox x:name="myComboBox2" width="100" itemssource="{Binding Path=Items}" displaymemberpath="Description" />
<rectangle margin="10" width="100" height="100" fill="Silver" visibility="Collapsed">
    <i:interaction.triggers>
        <i:eventtrigger eventname="SelectionChanged" sourcename="myComboBox2">
            <triggers:visibilityselecteditem targetname="myComboBox2" value="I1" matchmemberpath="Code" />
        </i:eventtrigger>
    </i:interaction.triggers>
</rectangle>
<ellipse margin="10" width="100" height="100" fill="SaddleBrown" visibility="Collapsed">
    <i:interaction.triggers>
        <i:eventtrigger eventname="SelectionChanged" sourcename="myComboBox2">
            <triggers:visibilityselecteditem targetname="myComboBox2" value="I2" matchmemberpath="Code" />
        </i:eventtrigger>
    </i:interaction.triggers>
</ellipse>





Code: VisibilityTriggers.zip

Silverlight 3 Flip TargetedTriggerAction

Date 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

Silverlight 3 Flip TriggerAction

Date July 30, 2009 @ 11:26 am in Microsoft, Silverlight

flip1In this blog post I am going to create a TriggerAction which makes it incredibly simple to take two UIElements and compose them into a panel which can be flipped. I have demonstrated the technique many times, but here it is all packaged up in a nice reusable class.

The following is the code I used to create by Flip TriggerAction.

using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Interactivity;
using System.ComponentModel;
 
namespace FlipTriggerAction
{
    public enum RotationDirection
    {
        LeftToRight,
        RightToLeft,
        TopToBottom,
        BottomToTop
    }
 
    public class Flip : TriggerAction<FrameworkElement>
    {
        public static readonly DependencyProperty FrontElementNameProperty =
            DependencyProperty.Register("FrontElementName", typeof(string),
                                        typeof(Flip), new PropertyMetadata(null));
 
        [Category("Flip Properties")]
        public string FrontElementName { get; set; }
 
        public static readonly DependencyProperty BackElementNameProperty =
            DependencyProperty.Register("BackElementName", typeof(string),
                                        typeof(Flip), new PropertyMetadata(null));
 
        [Category("Flip Properties")]
        public string BackElementName { get; set; }
 
        public static readonly DependencyProperty DurationProperty =
            DependencyProperty.Register("Duration", typeof(Duration),
                                        typeof(Flip), new PropertyMetadata(null));
 
        [Category("Animation Properties")]
        public Duration Duration { get; set; }
 
        public static readonly DependencyProperty RotationProperty =
            DependencyProperty.Register("Rotation", typeof(RotationDirection),
                                        typeof(Flip), new PropertyMetadata(RotationDirection.LeftToRight));
 
        [Category("Animation Properties")]
        public RotationDirection Rotation { get; set; }
 
        private readonly Storyboard _sbF = new Storyboard();
        private readonly Storyboard _sbB = new Storyboard();
        private bool _forward = true;
 
        protected override void Invoke(object parameter)
        {
            if (AssociatedObject.Projection == null)
            {
                FrameworkElement parent = AssociatedObject as FrameworkElement;
                UIElement f = null;
                UIElement b = null;
 
                if (parent != null)
                {
                    f = parent.FindName(FrontElementName) as UIElement;
                    if (f != null)
                    {
                        PlaneProjection ppFront = new PlaneProjection { CenterOfRotationY = .51 };
                        f.Projection = ppFront;
                        f.RenderTransformOrigin = new Point(.5, .5);
                    }
                    b = parent.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;
                    }
                }
 
                PlaneProjection pp = new PlaneProjection { CenterOfRotationY = .51 };
 
                AssociatedObject.RenderTransformOrigin = new Point(.5, .5);
                AssociatedObject.Projection = pp;
 
                double to = 180.0;
                double from = 0.0;
                string property = "RotationY";
 
                switch (Rotation)
                {
                    case RotationDirection.RightToLeft:
                        to = 0.0;
                        from = 180.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));
            }
            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;
        }
    }
}

Once you have the class constructed you can add the trigger to your UIElement in Blend. Clicking on the trigger in the Object & Timeline tab will allow you to configure each of the the properties you have defined in your class.
flip2


Code: FlipTriggerAction.zip

Silverlight 3 Drag Behavior

Date July 30, 2009 @ 10:35 am in Microsoft, Silverlight

By now most Silverlight developers should have stumbled on a great new feature of Silverlight 3 and Expression Blends, custom Triggers and Behaviors. I seriously don’t know how I lived without them!

drag_1In this blog post I am going to demonstrate a simple, yet useful, example of a behavior which allows a UIElement to become drag able. Figure 1 show the view of my XAML from within Blend. For this example I will create an image with a rounded border which has been placed within a Grid. In the center of my grid I have a Red Rectangle.

To create my Behavior I will jump in to VS2008 and create a new class that will reside within my solution. If my goal was to create a collection of generic behaviors and triggers I would likely create a Silverlight class assembly and deploy it to the library folder within Blend. This would allow my custom behaviors to be available to all Silverlight or WPF project. The following demonstrates the code I used to create by Drag behavior.

using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Interactivity;
using System.Windows.Media.Imaging;
 
namespace DragBehaviors
{
    public class Drag : Behavior
    {
        public static readonly DependencyProperty IsMovableProperty =
            DependencyProperty.Register("IsMovable", typeof(bool),
                                        typeof(Drag), new PropertyMetadata(null));
 
        [Category("Target Properties")]
        public bool IsMovable { get; set; }
 
        private bool _isDragging = false;
        private Point _offset;
        private readonly TranslateTransform _elementTranslate = new TranslateTransform();
        private TranslateTransform _imgTranslate = new TranslateTransform();
        private Image _img = new Image();
 
        protected override void OnAttached()
        {
            base.OnAttached();
 
            AssociatedObject.Loaded += AssociatedObjectLoaded;
            AssociatedObject.HorizontalAlignment = HorizontalAlignment.Left;
            AssociatedObject.VerticalAlignment = VerticalAlignment.Top;
            AssociatedObject.MouseLeftButtonDown += AssociatedObjectMouseLeftButtonDown;
        }
 
        void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
        {
            AssociatedObject.RenderTransform = _elementTranslate;
        }
 
        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.MouseLeftButtonDown -= AssociatedObjectMouseLeftButtonDown;
        }
 
        private void AssociatedObjectMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (!_isDragging) return;
            FrameworkElement parent = _img.Parent as FrameworkElement;
            Point newPosition = e.GetPosition(parent);
 
            // Move the dragon via the new position less the offset
            _imgTranslate.X = (newPosition.X - _offset.X);
            _imgTranslate.Y = (newPosition.Y - _offset.Y);
        }
 
        private void AssociatedObjectMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (!_isDragging) return;
            Panel panel = AssociatedObject.Parent as Panel;
 
            // turn off drag
            _isDragging = false;
 
            // Free the Mouse
            _img.ReleaseMouseCapture();
            _img.MouseMove -= AssociatedObjectMouseMove;
            _img.MouseLeftButtonUp -= AssociatedObjectMouseLeftButtonUp;
 
            if (IsMovable)
            {
                _elementTranslate.X = _imgTranslate.X;
                _elementTranslate.Y = _imgTranslate.Y;
            }
            _imgTranslate = new TranslateTransform();
            _offset = new Point(0, 0);
            AssociatedObject.Opacity = 1;
            if (panel != null) panel.Children.Remove(_img);
            _img = new Image();
        }
 
        private void AssociatedObjectMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            _isDragging = true;
 
            AssociatedObject.Opacity = .35;
 
            WriteableBitmap bitmap = new WriteableBitmap(AssociatedObject, new TranslateTransform());
            if (_img == null) return;
 
            _img.Source = bitmap;
            _img.HorizontalAlignment = HorizontalAlignment.Left;
            _img.VerticalAlignment = VerticalAlignment.Top;
            _img.Stretch = Stretch.None;
            _img.Width = bitmap.PixelWidth;
            _img.Height = bitmap.PixelHeight;
 
            _imgTranslate.X = _elementTranslate.X;
            _imgTranslate.Y = _elementTranslate.Y;
 
            _img.RenderTransform = _imgTranslate;
            _img.MouseMove += AssociatedObjectMouseMove;
            _img.MouseLeftButtonUp += AssociatedObjectMouseLeftButtonUp;
 
            Panel panel = AssociatedObject.Parent as Panel;
 
            if (panel != null) panel.Children.Add(_img);
 
            _offset = e.GetPosition(_img);
            _img.CaptureMouse();
        }
    }
}

For the most part the code should be self explanatory. One cool thing I did was that when I first begin dragging the UIElement, I convert it to a Bitmap and drag an image. The idea is that it will be more efficient to move a image around the screen then the actual collection of UIElements. Once I drop the image than I physically move the UIElement. Note that in its current state this only works correctly if the UIElement I am dragging is Left and Top aligned. This ensures that it is sitting at X=0, Y=0 in the panel it is residing. My guess is that it could be rewritten to support other alignments.

Once the Behavior class is constructed we simply add it to the control we want to drag.

<Border BorderThickness="1" BorderBrush="Black" CornerRadius="6" 
    HorizontalAlignment="Center" VerticalAlignment="Center">
    <i:Interaction.Behaviors>
        <behaviors:Drag IsMovable="True"/>
    </i:Interaction.Behaviors>
    <Grid>
        <Image Source="4.jpg" Width="100" Height="100" Stretch="UniformToFill"/>
        <TextBlock Text="Image" FontWeight="Bold" FontSize="20" 
            Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</Border>


Code: DragBehavior.zip

2009 Microsoft MVP Award

Date July 1, 2009 @ 9:02 am in Microsoft, Silverlight

Microsoft Most Valuable ProfessionalsThis morning I received an email that Microsoft has chosen to renew me as a MVP. I am thrilled to continue to have this opportunity. This years has been crazy as I continue to write my book, articles for Microsoft and learn what its like to be a consultant with Neudesic. I am very excited for what this next year will bring and am reenergizes to be even more committed to my involved in the Microsoft community. I look forward to being an active part of the growth of Silverlight and WPF.

A fond farewell and new begining

Date May 26, 2009 @ 10:25 am in Neudesic

neudesic logoToday is a very exciting day for me; tomorrow I begin a new chapter in career as I join Neudesic as a Principal Consultant II.    For years I have listened to the community talked about the amazing talent Neudesic has attracted, and I am so very excited to be a part of that team.  I started my career as a consultant, and am honored to have n an opportunity to return to that type of development.  I have never been as passionate about a technology as I am about Silverlight.  I am excited to be a part of an organization that sees the great opportunity that lies within that technology.

Last week marked the end of a long and exciting adventure.  For those of you who know me, you know that more than three years ago I was asked to join Terralever to help them grow into the company they have become today.  During those crazy three years, I have had the honor to lead some amazing people.  Terralever’s development team has some of the most bright, passionate, devoted developers I have every worked with.  Their ability to be agile, yet innovative, never ceased to amaze me. 

All that made a hard decision even harder.  Leaving Terralever was bitter sweet.  On one hand I am excited to join an organization focused on development .  On the other hand, I will certainly miss the people and the amazing project I have had the opportunity to be a part of.  I owe my new found love of Silverlight to Terralever, and will forever be grateful.  I wish all the best to Terralever and their great people.

Developing a casual game with Silverlight – Module 5

Date May 22, 2009 @ 10:18 am in Microsoft, Silverlight

This week Microsoft published the latest issue of the Expression Newsletter. If you have not seen the newsletter its a great series of articles focused on using Silverlight and the Expression tools to create amazing rich interactive solutions. In this latest issue I was asked to contribute the fifth of a six part series of articles on exploring the process of designing and building a casual online game in Silverlight .. Here is what the series will include.

  • Module 1: Getting Started – Architecture / framework
  • Module 2: Movement and collision detection
  • Module 3: Design – Sprites, boards and dialogs
  • Module 4: Animations and sound
  • Module 5: Initialization and Deployment
  • Module 6: Advanced concepts (Physics, Multiplayer, Optimization)

Make sure to check out the article at http://www.microsoft.com/expression/news-press/newsletter/

Code: Game.zip