Thursday, December 07, 2006

WPF Composite Transformations - Order Matters

Today, I got it in my head that I wanted to create some code that could break apart an image and then reconstruct it using animation. I wanted the image to simply “tear” apart, piece by piece, one piece at a time. Using WPF I was able to make this happen.

 

The code to do this is actually very straight forward. In XAML, I simply created a Grid object and gave it a few column and row definitions. The columns and rows will determine how my image gets “sliced”. In the code-behind, I simply grab a source image (defined as a resource in XAML), iterate the rows and columns of the Grid, and place a canvas in each cell using an ImageBrush to paint the canvas background. Using the ImageBrush Viewbox property, you can set the correct area of the image to place into each canvas, essentially recreating the entire image piece by piece.

 

With an image sliced, the final step was simply to create a TranslateTransform and a RotateTransform, and then animate the individual slices. I wanted the image slices to “drop” one at a time, so, in order to stagger the animation for each image slice, I simply set the BeginTime property for the animation to a value which takes into account which slice we’re animating. 

 

Of course, nearing completion, I did stumble on one "gotcha". My transformations just weren't working properly. What I’m doing is translating (moving) each image slice down along the y-axis. At the same time I’m applying a rotation to simply give a nice "falling down" effect. Each transformation is added to a TransformGroup object, and applied to the image slice (a.k.a. the canvas.RenderTransform property). Long story short, it just wasn’t working correctly, and after much frustration, I stumbled on the fact that if I added my RotateTransform to the TransformGroup prior to adding the TranslateTransform, all worked well. Back to the MSDN I went, and found my answer - order matters when creating composite transformations.

 

You can read about it here:

 

http://msdn2.microsoft.com/en-us/library/system.windows.media.transformgroup.aspx

 

The results (click image for larger view):

 

The code to recreate the solution is below. If you swap the order of the RotateTransform and TranslateTransform, you'll see the behavior changes quite a bit.

 

Window1.xaml

 

<Window x:Class="PictureTearDown.Window1"

  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

  Title="Image Effect"

  Width="725" Height="440"

  >

 

  <Window.Resources>

    <Image x:Key="TheImage" Source="Images\Crystal.jpg"/>

  </Window.Resources>

 

  <StackPanel ClipToBounds="True"

    Margin="10" Orientation="Vertical">

 

    <Border CornerRadius="5" Width="{Binding

      ElementName=TheImage,   

      Path=Width}" Height="{Binding ElementName=TheImage,

      Path=Height}"

      BorderBrush="Black" BorderThickness="0.5"  

      Background="Transparent" ClipToBounds="True">

 

      <Grid Name="ImageGrid"

        Width="{Binding ElementName=TheImage, Path=Width}"

        Height="{Binding ElementName=TheImage, Path=Height}"

        Margin="10" HorizontalAlignment="Center"

        VerticalAlignment="Center">

 

        <Grid.ColumnDefinitions>

          <ColumnDefinition Width="*"/>

          <ColumnDefinition Width="*"/>

          <ColumnDefinition Width="*"/>

          <ColumnDefinition Width="*"/>

          <ColumnDefinition Width="*"/>

          <ColumnDefinition Width="*"/>

        </Grid.ColumnDefinitions>

 

        <Grid.RowDefinitions>

          <RowDefinition Height="*"/>

          <RowDefinition Height="*"/>

          <RowDefinition Height="*"/>

          <RowDefinition Height="*"/>

          <RowDefinition Height="*"/>

          <RowDefinition Height="*"/>

          <RowDefinition Height="*"/>

          <RowDefinition Height="*"/>

        </Grid.RowDefinitions>

 

      </Grid>

 

    </Border>

 

    <Button Width="100" Click="Animate">Animate!</Button>

 

  </StackPanel>

 

</Window>

 

Window1.xaml.cs

 

using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Media;

using System.Windows.Media.Animation;

 

namespace PictureTearDown

{

 

  public partial class Window1 : System.Windows.Window

  {

 

    public Window1()

    {

      InitializeComponent();

      SliceAndDice();

    }

 

    private void SliceAndDice()

    {

      Image image = (Image)this.Resources["TheImage"];

 

      int rows = this.ImageGrid.RowDefinitions.Count;

      int cols = this.ImageGrid.ColumnDefinitions.Count;

 

      // --- slice image

      double cellH = image.Source.Height / rows;

      double cellW = image.Source.Width / cols;

 

      for (int row = 0; row < rows; row++)

      {

        for (int column = 0; column < cols; column++)

        {

          Canvas canvas = new Canvas();

 

          canvas.Height = cellH;

          canvas.Width = cellW;

 

          canvas.SetValue(Grid.ColumnProperty, column);

          canvas.SetValue(Grid.RowProperty, row);

 

          ImageBrush ib = new ImageBrush(image.Source);

 

          float sizeCols = 1.0f / cols;

          float sizeRows = 1.0f / rows;

          ib.Viewbox = new Rect(column * sizeCols, row * 

            sizeRows,

            sizeCols, sizeRows);

          ib.ViewboxUnits =

            BrushMappingMode.RelativeToBoundingBox;

 

          canvas.Background = ib;

          this.ImageGrid.Children.Add(canvas);

        }

      }

    }

 

    private void Animate(object sender, RoutedEventArgs e)

    {

      Image image = (Image)this.Resources["TheImage"];

 

      int item = 0;

      int zindex = 100;

      double animationSpeed = 1000;

 

      int rows = this.ImageGrid.RowDefinitions.Count;

      int cols = this.ImageGrid.ColumnDefinitions.Count;

 

      double cellH = image.Source.Height / rows;

      double cellW = image.Source.Width / cols;

 

      for (int row = 0; row < rows; row++)

      {

        for (int column = 0; column < cols; column++)

        {

          Canvas canvas = (Canvas)this.ImageGrid.Children

            [item];

          canvas.SetValue(Canvas.ZIndexProperty, zindex);

          canvas.ClipToBounds = false;

 

          // --- the order which transforms are added to a

          // --- transformgroup is important!

          TransformGroup group = new TransformGroup();

          RotateTransform rotate = new RotateTransform();

          TranslateTransform translate = new TranslateTransform

            ();

          group.Children.Add(rotate);

          group.Children.Add(translate);

          canvas.RenderTransform = group;

 

          int delay = 100 * item;

 

          // --- translate cell canvas

          DoubleAnimation translateAnimation = new

            DoubleAnimation(image.Source.Height*2,

              TimeSpan.FromMilliseconds(animationSpeed));

          translateAnimation.BeginTime = new TimeSpan(0, 0, 0,

            0, delay);

          translateAnimation.AutoReverse = true;

          translate.BeginAnimation

            (TranslateTransform.YProperty,

            translateAnimation);

 

          // --- rotate cell.canvas.background.imagebrush

          rotate.CenterX = cellH / 2;

          rotate.CenterY = cellW / 2;

          DoubleAnimation rotAnimation = new DoubleAnimation

            (720,

            TimeSpan.FromMilliseconds(1000));

          rotAnimation.BeginTime = new TimeSpan(0, 0, 0, 0,

            delay);

          rotAnimation.AutoReverse = true;

          rotate.BeginAnimation(RotateTransform.AngleProperty,

            rotAnimation);

 

          item++;

          zindex--;

        }

      }

    }

 

  }

}

 

 

 





Technorati Tags:
, , , ,

Wednesday, November 01, 2006

WPF Routed Event Model - Part 1

WPF introduces a rich new event model for windows development. In windows forms development, an event is directed to a single object, usually the control that initiated the event. In WPF, events are routed through the Visual Tree, offering far greater flexibility in what we can do with the event and which element or elements can respond to it.

 

To understand the WPF Routed Event Model, we first need to understand how WPF views UI elements and the relationships between them. WPF introduces a new composition model in which UI elements contain other elements. WPF also introduces the the Visual Tree, which represents the hierarchy of UI elements which result from the new composition model. Let’s look at a sample element hierarchy:

 

--Window

---- Grid

------ Canvas

-------- TextBlock

 

As shown above, parent-child relationships are formed between the elements. The Window is a top-level element which contains a Grid, which contains a Canvas, which contains a TextBlock.

 

Let’s create a window in XAML with a few elements and wire-up a simple event which will show a MessageBox to the user when the TextBlock is clicked. Note that TextBlock does not have a Click event, so we’ll use a MouseDown event instead. Utilizing our element hierarchy above the simplified XAML and event wire-up looks like this:

 

<Window x:Class="RoutedEvents.SimpleRoutedEvents"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Simple Routed Events" Height="300" Width="300"

       Name="window1"

> 

 <Grid Name="grid1">

  <Canvas Name="canvas1" Background="Khaki" Width="100" Height="100">

   <TextBlock Name="textblock1" Foreground="Red" Canvas.Top="40"   Canvas.Left="25" MouseDown="textblock1_MouseDown">Press Me!</TextBlock>

  </Canvas>

 </Grid>

</Window>

 

In our code behind we’d place the following event handler:

 

private void textblock1_MouseDown(object sender, RoutedEventArgs e)

{

    MessageBox.Show("TextBlock Clicked");

}

 

On the surface, this is very similar to wiring an event to a Windows Forms control. In our XAML code we have declared a MouseDown event attribute for our TextBlock element and in our code-behind we’ve defined the MouseDown event handler. When we run the example, the MessageBox is displayed as expected when we mouse down on the TextBlock. Not much to it and not very exciting so far but it gets better. Now that we understand how WPF views content (as a hierarchical element tree) and how we can wire an event to an element, we can talk about how WPF works with routed events.

 

The WPF routed event model allows us to take advantage of the fact that our window elements participate in a hierarchy or parent-child relationships. Since WPF knows the relationships, it can ‘route’ the event message up and down the Visual Tree, and all of our elements have an opportunity to handle the message. To clarify a bit, by “all elements”, I mean all elements from the top-level container element (usually Window or Page) down to the element that generated the event.

 

The WPF Event Model defines three types of event routing strategies:

 

Bubbling

The event message traverses the Visual Tree upwards from the element that originated the event to the top-level element (Window or Page)

Tunneling

The event message traverses the Visual Tree downwards from the top-level container (Window or Page) to the target element, which is the element that originated the event.

Direct

Only the element that raised the event can handle the event. Similar to Windows Forms event handling.

 

There is a naming convention for Bubbling and Tunneling event names defined in the WPF framework. Tunneling events are prefixed with ‘Preview”, so using MouseDown as an example, the Bubble event would be named MouseDown, and the tunneling event would be named PreviewMouseDown. Bubble and Tunnel event strategies usually are provided in pairs, when one exists you will usually find the other.

 

Let’s modify our code so we can see the event routing strategies in action. First the XAML:

 

<Window x:Class="RoutedEvents.SimpleRoutedEvents"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Simple Routed Events" Height="300" Width="300"

       Name="window1" PreviewMouseDown="GenericRoutedEventHandler" MouseDown="GenericRoutedEventHandler"

    >

       <Grid Name="grid1" PreviewMouseDown="GenericRoutedEventHandler" MouseDown="GenericRoutedEventHandler">

              <Canvas Name="canvas1" Background="Khaki" Width="100" Height="100" PreviewMouseDown="GenericRoutedEventHandler" MouseDown="GenericRoutedEventHandler">

                     <TextBlock Name="textblock1" Foreground="Red" Canvas.Top="40" Canvas.Left="25" PreviewMouseDown="GenericRoutedEventHandler" MouseDown="GenericRoutedEventHandler">Press Me!</TextBlock>

              </Canvas>

    </Grid>

</Window>

 

We’ve simply added PreviewMouseDown and MouseDown event declarations to each of our elements, and pointed them all at a generic event handler so we can watch what happens. The code for our generic event handler follows:

 

private void GenericRoutedEventHandler(object sender, RoutedEventArgs e)

{

string name = ((FrameworkElement)sender).Name.ToString();

Console.WriteLine(name + " " + e.RoutedEvent.Name.ToString() + " " + e.RoutedEvent.RoutingStrategy.ToString());

}

 

Our generic event handler simply writes to the console the name of the element, the name of the event, and the event strategy. Running the example yields the following results:

 

window1 PreviewMouseDown Tunnel

grid1 PreviewMouseDown Tunnel

canvas1 PreviewMouseDown Tunnel

textblock1 PreviewMouseDown Tunnel

textblock1 MouseDown Bubble

canvas1 MouseDown Bubble

grid1 MouseDown Bubble

window1 MouseDown Bubble

 

In our results we can see that although we clicked the TextBlock, event handlers fired for all of our elements. We ‘tunneled’ from our top level element (Window) down to our target element (TextBlock) and then ‘bubbled’ back up to our top level element again. What’s interesting about this is that all elements are notified of the event occurance and all elements have a chance to handle the event. This is an important concept in WPF because events are not tied to individual controls as in Windows Forms programming, but rather there is an event notification system that alerts all elements in a container that an event occurred, and any element may participate in handling that event.

 

How can we use this? Let’s assume we want our Window container element to show the MessageBox on behalf of any of its contained elements. The message box will show the name of the element that originated the event. (This is just a generic example, in real life we might create a control that contains multiple visual elements, like a border, text, and rectangle, but we want a click event for the entire control.) We can simply add a handler for the window elements MouseDown event and show the message box. The modified XAML follows:

 

<Window x:Class="RoutedEvents.SimpleRoutedEvents"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Simple Routed Events" Height="300" Width="300"

       Name="window1" MouseDown="window1_MouseDown"

    >

       <Grid Name="grid1">

              <Canvas Name="canvas1" Background="Khaki" Width="100" Height="100">

                     <TextBlock Name="textblock1" Foreground="Red" Canvas.Top="40" Canvas.Left="25">Press Me!</TextBlock>

              </Canvas>

    </Grid>

</Window>

 

And our modified code-behind looks like so:

 

private void window1_MouseDown(object sender, RoutedEventArgs e)

{

  string name = ((FrameworkElement)e.OriginalSource).Name.ToString();

  MessageBox.Show(name);

}

 

And there you have it, a quick intro to WPF event routing. In the interest of keeping this post simple, I purposely avoided the use of a Button element as there are more details to the routing model base classes that have an effect on the Button element. I will address that soon in part 2.

 

You can read more about events on the  MSDN

 

 

 

 

 

 




Technorati Tags:
, , , , ,

Sunday, October 22, 2006

What is XAML?

I've been digging in deep to Windows Presentation Framework for a while now. It is an incredible programming model, so I am going to be posting about it whenever I can.

So, what is XAML? XAML is the new XML-based declarative language and part of .Net 3.0. If you haven't already read about it, it is used to declare WPF objects, properties, and event handlers using XML elements and attributes. The objects and properties we define in our XAML map directly to WPF objects and properties. Since XAML is XML, and since the nature of XML is hierarchical (only allows 1 top element) XAML is ideal for defining UI and layout. When a WPF application is compiled, the XAML is parsed and an object tree is created and initialized for each window (ig: form, page, etc) that is XAML based. We can then program against it just as we did in Windows Forms development.

The XAML model seperates UI design from programming logic, so when developing applications, the responsibilities of UI design can be delegated to Designers, and programming left to developers. New tools like Microsoft Expression Interactive Designer can be used to create UI in a tool familiar to developers, the output of which is not a graphic, but XAML. That XAML can be imported into our Visual Studio application, and we can wire-up our event handling and business logic just as we always have.

XAML is not part of WPF, rather it is utilized by WPF for defining UI. You can create XAML and run it as a stand alone application if you like. XAML offers a lot of capabilites, and I think this new programming model will prove to be a powerful one. I'll try to post more about it when I can, exploring more of the features of XAML in detail. In the meantime, the Microsoft Windows SDK is a good place to start learning.

Monday, October 16, 2006

Microsoft Offers Free .Net 3.0 eClinics

Microsoft is offering three free 2-hour eClinics on the following .Net 3.0 subjects:

WPF - Windows Presentation Foundation
WCF - Windows Communication Foundation
WF - Windows Workflow Foundation

These are listed on the MicrosoftLearning website as:
"Developing Rich Experiences with Microsoft® .NET Framework 3.0 and Visual Studio® 2005"

You can go here to check it out.

Sunday, October 15, 2006

I'm Mike, This is my Blog

Hi and welcome to my blog-space on the web!

I am a software consultant working with Microsoft technologies - primarily .Net - and living in 'the OC'.

I am a technology enthusiast as much as I am a developer and I plan to write as much as time allows about software, code, and .Net - especially topics relating to .Net 3.0

I look forward to feedback as I hope to use this as a learning tool and knowledge base where I can write about the technology I'm using and record those 'eureka' moments we're lucky enough to have ever so often.

When I'm not coding or writing, I'm riding my mountain bike anywhere and everywhere I can in So Cal - so I may blog about the occasional "great ride" as well.