Wednesday, 26 November 2008

In my previous post, I introduced my thoughts on how animation between positions should be done. Today I will walk you through some of the technical benefits of that approach with a cool animated wrappanel sample. Yes, the silverlight control toolkit includes a wrappanel. Let’s animate it, the easy way!

Sample

I got some feedback that the samples that I inlined were not visible from rss feeds. So, visit this page to see the sample in action.

image

Please note the big black slider on the right.. Drag it to the left to do some layout transitions between items.
Also, to illustrate the flexibility of this approach, there is a textbox on the top that determines the startdelay of the items (Random between 0 and the number you give it). Set it to 700 milliseconds and then quickly drag the slider to the left. You will see some items moving immediately, others moving after some time. This gives it a less ‘plastic-fantastic’ feel.

The items are shown on the page using the xaml below. Notice how I use a regular wrappanel as ItemsPanelTemplate.

        <ItemsControl Grid.Row="1" x:Name="anim" HorizontalAlignment="Left">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <ItemMovementSpike:MoveableItem Margin="10">
                        <Border Background="Yellow" Width="80" Height="80" >
                            <ContentPresenter Content="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center" />
                        </Border>
                    </ItemMovementSpike:MoveableItem>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Controls:WrapPanel/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemsSource>
                <Controls:ObjectCollection>
                    <System:String>1</System:String>
                    <System:String>2</System:String>
                    <System:String>3</System:String>
                    <System:String>4</System:String>
                    <System:String>5</System:String>
                    <System:String>6</System:String>
                    <System:String>7</System:String>
                    <System:String>8</System:String>
                    <System:String>9</System:String>
                    <System:String>10</System:String>
                    <System:String>11</System:String>
                    <System:String>12</System:String>
                    <System:String>13</System:String>
                    <System:String>14</System:String>
                </Controls:ObjectCollection>
            </ItemsControl.ItemsSource>
</ItemsControl>

 

Again: who does the animation? Panel or the item

One of the first samples on layout transitions inside a panel are from Dave Relyea (DevDave) and can be seen here. The big difference between the approaches is that he uses a custom-made wrappanel, which does the layout transition.

As you can remember from my previous post, I believe that a panel should only calculate and determine the actual logical position of an item. The transition between positions is not the concern of the panel and should be handled by some other dedicated object. In this case, you can see that I’ve chosen to wrap my content inside of a ‘MoveableItem’ whose concern it is to transition between positions.

As discussed, it is a pity that we have to work so hard inside of MoveableItem, because panels do not notify their children about a position change. But it can be done fairly efficient.

C’mon, isn’t the panel responsible for layout.. So it is HIS concern!

No sir, I strongly disagree. The panel is responsible for the layout of an element on the screen, sure. But they exist in all kinds of forms (Grid, dockpanel, wrappanel) and the intent is to determine the end position as quickly as possible. I feel it is not okay to start mixing that concern with an animation framework.
As always, a class should have only 1 reason to change. A panel should only be changed to alter the layout logic, not because we decided to add features to our transitioning logic.

What are the benefits of separating these concerns

By letting a panel be a panel, and having another class handle the transition, you get a much cleaner model.
First and foremost, we now do not have to alter panels in order to use animation. Most of the solutions I have seen require you to change the baseclass of your panel. This means you can not easily leverage the panels that someone else created, like the toolkits WrapPanel or DockPanel or the build-in Grid and StackPanel! It would mean creating your own versions of these panels. If you’re on the Silverlight side, you may copy our source and change the baseclass, but that will be pretty hard to do for a Grid in any case.

Second, designtime experience. Although I setup my animation in code at this moment, MoveableItem could also just go to a different visual state: ‘Moved’ and allow you to use Blend to create a transition yourself! This would be very easy to do, because MoveableItem could just expose a double where 1 means: in endposition and 0 means: in beginposition. That double could be easily animated using keysplines to create cool effects. See my expander series for more on that approach. I might follow up with a complete sample.

Stretching it a bit, I feel these come into play as well:

Flexibility. I can think of quite a few interesting properties I might want to set on my ‘MoveableItem’, like Weight, Gravity and so on. We could plugin Penner-equations (like, easeIn, bounce etc.) at the item level in a very clean way (cleaner than attached properties).

Performance. The effects I am thinking of go beyond mere transitions. For instance, it might take one item some time to ‘settle-in’: slowly rocking for some time before it finally goes silent. Others might have settled earlier. A panel keeps performing arrange overrides to allow for this. This is overkill.

Conclusion

I find this an intriguing topic. I know people have been using transitioning panels for quite some time now and are very comfortable with it. I hope that this post does make you think though, even though you might completely disagree :)
Let me know either way!

Wednesday, 26 November 2008 21:08:01 (Romance Standard Time, UTC+01:00)  #    Comments [8]  |  Trackback
 Sunday, 23 November 2008

In my previous post, we talked about one approach to visualize the change of content. So, we had a control with the content ‘Ruurd’ and when we changed the content to ‘RJ’, we could very easily setup a fade out for ‘Ruurd’ and a fade in for ‘RJ’. Or get more fancy and add a small translate transform.
In this post I want to share some of my thinking on a related subject: how do you visualize the movement of an object? For example: we have a button in a grid on column 0 and we move it to column 1. That move is instant, but in many cases you would like an actual animation between those locations.

There is precedence here, namely in the many tweening libraries for Flash and nowadays for Silverlight. Also, Nikhil has written a lot about this subject with his great attached behavior series.

Sample

I like typing in that textbox a LOT. I’m sure you will too.  :)

Power to the item or does the panel still rule?

It is hard to determine when you should use a panel to do animation and when you should not. I live by this rule:

When the movement itself has meaning, use a panel. If it is an effect, let the item take care of it.

This means: if during the movement you should be able to stop the movement and use the items in that position, than the visual tree should not have any translate transforms going on. The layout system should be managing the location of these items.
If on the other hand the movement is just a transition, the panel should not need to be aware of this transition and just be used to determine the logical positions of an item. That is many times more efficient.

To use examples: a carousel is showing items and we let the user ‘nudge’ the items a few pixels –> the new positions are the real positions and thus a panel should be used.
Items in a dockpanel or a stackpanel that move around within that panel should be managing their transition themselves.

So, I believe effects (visual position) should be managed by the item, logical position should be determined by the panel.

This has the added technical advantage that we do not have to rewrite all panels. This is illustrated by the xaml for this sample:

    <Grid x:Name="LayoutRoot">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <ItemMovementSpike:MoveableItem HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" >
            <Button Click="Button_Click" Content="Move me" />
        </ItemMovementSpike:MoveableItem>
        <ItemMovementSpike:MoveableItem Grid.Row="1" HorizontalContentAlignment="Stretch" >
            <TextBox TextChanged="TextBox_TextChanged" Text="TypeMe" />
        </ItemMovementSpike:MoveableItem>
</Grid>
You can see I just use the regular Grid layout panel, nothing to see there!
Efficient location determination

Because we do not want to change our panels, we will have to determine our position on the screen ourselves. This is a somewhat inefficient process, so it is important to start thinking about where we want to take that hit.

The WPF/Silverlight layout system is very cautious about forcing a layout pass in a panel. It only does so when it is forced, so you can be certain that something has changed. The part of the layout pass that we are interested in is the Arrange part. During the arrange, the panel asks each child to arrange itself given a certain Size and then determines its position. So:

Panel.Arrange
  for each item: Item.Arrange
Panel –> set location and size for item (using a rectangle)

During this time, the most beloved of events is fired continuously on the item: CompositionTarget.Rendering.

The Rending event is called each time before the item is drawn on the screen and therefore comes with great power! If we were to do anything that is using cpu-cycles in this event, we will see some serious performance problems in our application. So, we need to be clever about it.

It is important to know that during the Item.Arrange, our item is still in its old location: The panel has not yet set its position, that will come a few Rendering events later. So, we can use the Item.Arrange to determine the current position with a TransformToVisual call. This is an expensive call, but since we only use it in the Arrange method (which is called only when necessary), it is cheap enough for our purposes.

   78 protected override Size ArrangeOverride(Size finalSize)

   79 {

   80     fromLocation = TransformToVisual(((UIElement) this.Parent)).Transform(new Point(0, 0));

   81     fromLocation.X += tt.X;

   82     moved = true;

   83     return base.ArrangeOverride(finalSize);

   84 }

I determine the current location on line 80 as it relates to its parent (the panel). You could do a quick visual tree walk to find our plugin root, which would enable some cool scenarios!!

Line 81 corrects for our current translatetransform (tt) and then we set a boolean ‘moved’ to true, indicating that something has happened.

The important stuff happens in our Rendering event:

   55 void CompositionTarget_Rendering(object sender, EventArgs e)

   56 {

   57     if(moved)

   58     {

   59         moved = false;

   60         toLocation = TransformToVisual(((UIElement) this.Parent)).Transform(new Point(0, 0));

   61         startmove = DateTime.Now;

   62         moving = true;

   63     }

   64 

   65     if(moving)

   66     {

   67         double fraction = DateTime.Now.Subtract(startmove).TotalMilliseconds/200d;

   68         tt.X = (fromLocation.X - toLocation.X)*(1 - fraction);

   69 

   70         if(fraction >= 1)

   71         {

   72             moving = false;

   73             tt.X = 0;

   74         }

   75     }

   76 }

It happens to be that in the first rendering event that is called, the move has occured. I’m not sure if that is a general rule, or something that just happens consistently on my machine. I haven’t looked into that yet.

We do one more inefficient TransformToVisual to find out where we currently are. Then we need the current time (startMove) and we indicate that we are moving. In the moving code we set the appropriate translateTransform (tt) and make sure that we stop moving when time is up.
If you’re into penner equations, you would plug them in after line 68.

No source for this one though, it’s just a spike. There are many things yet to be considered. But do let me know your thoughts.

Sunday, 23 November 2008 01:58:52 (Romance Standard Time, UTC+01:00)  #    Comments [15]  |  Trackback
 Thursday, 20 November 2008

This post will show you how to write a very simple contentControl, that allows you to transition your old content with your new content. It is a little prototype I put together after wanting to ‘spice up’ another control I was working on!

Show me!

What?

A contentControl is a control that only has one reason in life: to show your content. You will use it in places where you have an arbitrary object that you wish to display. For instance, a ‘Person’ business object, or a ‘string’. Its two most useful properties are ‘Content’ and ‘ContentTemplate’. The latter will let you define how to show your person.

By bundling two contentControls, we can move the new content to the upper contentControl and show the old content in the lower contentControl. Then we can use blend to think up some great transition between those visuals.

Why?

The current contentControl does not allow you to do a transition effect because it has no concept of ‘old content’. This means the change can not be visualized in a rich manner. The only thing you could do is transition in the new content, but the old content will be gone instantly.

Why not?

So, the Silverlight team could have just build such an approach into the framework, why didn’t they? Well, I have not checked officially, but can come up with a bunch of reasons. Most of all, this will keep a reference to old content (for the time the storyboard runs) that you might not expect. Not a big deal on a string, but possibly a very big deal on a large business object. Also, using this approach will result in a much larger object graph inside of the visual tree than just a contentControl: we’re adding a second contentControl, a layout panel, two visualstates and a few storyboards. Finally, creation of a template is costly and now happens twice when the content gets changed.
So, it is not a good idea to use this control all over the place. Please use sparingly, with caution and very lightweight templates.

How?

It’s a pretty straightforward control. (I’ll not do the pictures thing this time.. let’s see how copying regular code feels!)

    9     [TemplateVisualState(Name = "Transitioning", GroupName = "PresentationStates")]

   10     [TemplateVisualState(Name = "Normal", GroupName = "PresentationStates")]

   11     [TemplatePart(Name = "PreviousContentPresentationSite", Type = typeof(ContentControl))]

   12     public class TransitioningControl : ContentControl

   13     {

   14         private ContentControl PreviousPresentationSite { get; set; }

   15 

   16         public TransitioningControl()

   17         {

   18             DefaultStyleKey = typeof(TransitioningControl);

   19         }

Like I said, we’ll need a second contentControl to host our old content. This is represented by line 11.

Pretty interesting is the onApplyTemplate:

   21         public override void OnApplyTemplate()

   22         {

   23             base.OnApplyTemplate();

   24 

   25             PreviousPresentationSite = GetTemplateChild("PreviousContentPresentationSite") as ContentControl;

   26 

   27             Panel root = GetTemplateChild("LayoutRoot") as Panel;

   28 

   29             Storyboard sb= null;

   30 

   31             if (root != null)

   32             {

   33                 sb =

   34                     (from stategroup in (VisualStateManager.GetVisualStateGroups(root) as Collection<VisualStateGroup>)

   35                      where stategroup.Name == "PresentationStates"

   36                      from state in (stategroup.States as Collection<VisualState>)

   37                      where state.Name == "Transitioning"

   38                      select state.Storyboard).FirstOrDefault();

   39             }

   40 

   41             if (sb != null)

   42             {

   43                 sb.Completed += ((sender, e) =>

   44                                      {

   45                                          // go to normal state and release our hold on the old content.

   46                                          VisualStateManager.GoToState(this, "Normal", false);

   47                                          if (PreviousPresentationSite != null)

   48                                          {

   49                                              PreviousPresentationSite.Content = null;

   50                                          }

   51                                      });

   52             }

   53 

   54             VisualStateManager.GoToState(this, "Normal", false);

   55         }

I’m looking for the Transitioning State and take its StoryBoard. Now I can register some code to execute when the storyboard finishes. This represents our ‘cleanup’.
Obviously, this is prototyping code, so do not just copy this and use in a production system! At the very least you would need to take care of unhooking the event, etc. etc.

   57         protected override void OnContentChanged(object oldContent, object newContent)

   58         {

   59             if (PreviousPresentationSite != null)

   60             {

   61                 PreviousPresentationSite.Content = oldContent;

   62 

   63                 base.OnContentChanged(oldContent, newContent);

   64 

   65                 // if busy with transitioning, let's skip to normal

   66                 VisualStateManager.GoToState(this, "Normal", false);

   67                 // and start our state

   68                 VisualStateManager.GoToState(this, "Transitioning", true);

   69             }

   70         }

So, when our content changes, we get a very convenient ‘old content’ object.

I just quickly change to Normal state, which probably already happened (line 46), but if the content changes again before the storyboard finishes, it looks better to start afresh.

This is my attempt at a transition:

                        <vsm:VisualStateManager.VisualStateGroups>
                            <vsm:VisualStateGroup x:Name="PresentationStates">
                                <vsm:VisualState x:Name="Transitioning">
                                    <Storyboard>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CurrentContentPresentationSite" 
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
                                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="-40"/>
                                            <SplineDoubleKeyFrame KeyTime="00:00:00.800" Value="0"/>
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CurrentContentPresentationSite" 
Storyboard.TargetProperty="(UIElement.Opacity)">
                                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                                            <SplineDoubleKeyFrame KeyTime="00:00:00.800" Value="1"/>
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PreviousContentPresentationSite" 
Storyboard.TargetProperty="(UIElement.Opacity)">
                                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
                                            <SplineDoubleKeyFrame KeyTime="00:00:00.800" Value="0"/>
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PreviousContentPresentationSite" 
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
                                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                                            <SplineDoubleKeyFrame KeyTime="00:00:00.800" Value="40"/>
                                        </DoubleAnimationUsingKeyFrames>
                                    </Storyboard>
                                </vsm:VisualState>
                                <vsm:VisualState x:Name="Normal">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" 
Storyboard.TargetName="PreviousContentPresentationSite" Storyboard.TargetProperty="(UIElement.Visibility)">
                                            <DiscreteObjectKeyFrame KeyTime="00:00:00">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Collapsed</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </vsm:VisualState>
                            </vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>

 

This is how the sample is setup:

        <local:TransitioningControl x:Name="contentChanger" 
                                    HorizontalAlignment="Center" 
                                    VerticalAlignment="Center" 
                                    Content="{Binding Path=CurrentTime}">
            <local:TransitioningControl.ContentTemplate>
                <DataTemplate>
                    <Border BorderBrush="Blue" BorderThickness="2">
                        <ContentPresenter Content="{Binding}" />
                    </Border>
                </DataTemplate>
            </local:TransitioningControl.ContentTemplate>
</local:TransitioningControl>

CurrentTime is a string that changes each second. The datatemplate is not necessary at all, just showing that it works.

I’d appreciate some feedback on this. So let me know if you would find such a control useful.

Source

Thursday, 20 November 2008 01:40:00 (Romance Standard Time, UTC+01:00)  #    Comments [42]  |  Trackback
 Monday, 17 November 2008

In my previous post, I showed an approach to getting the expander to ‘reveal’ its content. Although it worked well, it did so by programmatically creating a storyboard and targeting the height/width. That did not sit well with me, and in this post I’ll show another approach.

Before I had settled on that approach, I had created a dependency property on Expander ‘ExpandAmount’ but quickly realized I could not target it from within the template. Since you also can not target custom attached properties in a storyboard, I was stuck. However, the solution is as clear as it is simple: Just create a custom ContentControl that has our beloved ‘ExpandAmount’ property. We will be able to target that property for sure!

The template becomes:

image

Note that I set the Height and Width to 0, because the expander always begins in ‘collapsed’ state. Visibility is not set to collapsed.
We can target it in our –now Blend friendly- VSM:

image

The relevant code on our ExpandableContentControl is shown here:

image

Since content can be either a UIElement or a regular object (like String), we can not use it to determine the size we would like to be. However, we can take a look at the contentpresenter that is presenting our content and ask it. By doing this in the OnApplyTemplate, we will not be able to respond to changes inside the contenttemplate. However, for our demonstration, that is not a big issue.

One issue I ran into was the fact that this control has no nice way to get to the expander and find out how it should do calculate its Size. Based on the ExpandDirection, we might want to change the width or the height. I chose to have the Expander tell its content when it starts to expand or collapse.

We end up with a fully customizable expander. Go have some fun in Blend. Below is a sample where I adjusted the speed to slow down at the end. Also included is the source.

 

Source

Monday, 17 November 2008 20:42:24 (Romance Standard Time, UTC+01:00)  #    Comments [23]  |  Trackback
 Friday, 14 November 2008

A few weeks ago, we released the Silverlight control toolkit. There are some nice controls in there, including the expander.
In this post I will take a look at the expander and alter is so it’s content transitions in/out. Yes, my peeps: we are extending the expander!

In a simple scenario, the expander is used as follows:

image

I have used a border to show off its content. When you run this program, and click on the expander, this is what you will see:

image

Now, when you click the expander button (helpfully pointed out by my big red arrow!), the expander will immediately remove our content. There is no nice effect or transition going on.
This is consistent with WPF behavior.

But it is not cool, is it?

So, let’s take a better look, and open up the expander.xaml inside of our toolkit source. The relevant part is here:

image

and in the visual states:

image

So, the ‘ExpandSite’ is an element that has a visibility of Collapsed. When the control is ‘expanded’ the expandsite will be set to visible. Effective, but no fun.

We will manually transition the height of the content. I will leave it up to you to create a switch on the expanddirection and animate the width instead.

In order to hook everything up, I will create a new class, called: SmoothExpander.

image

The code should point you to the next change: I have added a scrollviewer inside the new template. So, I copied the expander template and put it inside themes\generic.xaml. The relevant parts are:

image

and

image

First off, I removed the Storyboard in the VisualState, since I will be creating on in code. Also, I have put a scrollviewer around my contentcontrol. Let’s take a look at that in more detail:

  • The scrollviewer will host the contentcontrol
  • Its height is set to 0, so it will not show up
  • The visibility of the contentcontrol is set to Visible

This gives us the option of using the extentHeight property of the Scrollviewer to figure out how large our contentcontrol wants to be!

So, lets implement our behavior:

image

I have been wanting to show how to use a storyboard in code for some time.
In both cases a storyboard is created with a DoubleAnimation that animates from the current Height to either 0 or the intended height. We couple this animation to our scrollViewer and off we go.

Since we do not collapse or expand straight away, we should change our code to run those methods on the storyboard finish:

image

 

Running sample is below. The second expander is of type ‘SmoothExpander’.

Source for this sample

I kept the sample as straightforward as possible. Extra work that should be done though:

  1. Create a templatepart attribute so that Blend knows about this scrollviewer
  2. Do not rely on the existence of the scrollviewer.
  3. Use expanddirection to toggle behavior.
  4. Possibly allow for a maximum to be set on the scrollviewer.
Friday, 14 November 2008 02:15:12 (Romance Standard Time, UTC+01:00)  #    Comments [9]  |  Trackback