Welcome to MSDN Blogs Sign in | Join | Help

Ed's Blog

Ed Maia's blog covering Silverlight Animation and Media features.
Animating objects' Visibility in Silverlight

One exciting feature added in Silverlight 2 is called ObjectAnimationUsingKeyFrames.  It allows you to target animations at properties that are not Double, Point, or Color.  One of the most basic scenarios is to animate the Visibility property of a UIElement.  Here is an example:

<Storyboard Duration="0:0:2">

  <ObjectAnimationUsingKeyFrames Storyboard.TargetName="myRect" Storyboard.TargetProperty="(UIElement.Visibility)">

    <ObjectAnimationUsingKeyFrames.KeyFrames>

      <DiscreteObjectKeyFrame KeyTime="00:00:01">

        <DiscreteObjectKeyFrame.Value>

          <Visibility>Collapsed</Visibility>

        </DiscreteObjectKeyFrame.Value>

      </DiscreteObjectKeyFrame>

    </ObjectAnimationUsingKeyFrames.KeyFrames>

  </ObjectAnimationUsingKeyFrames>

</Storyboard>

Please note that in Silverlight, the only way to specify an DiscreteObjectKeyFrame's value is using property syntax (using the DOKF.Value tag).  Silverlight 2 does not support WPF's x:Static markup extension.  In WPF, you could specify the value using:

(note: this is NOT supported in Silverlight)
<
DiscreteObjectKeyFrame KeyTime="00:00:01" Value="{x:Static Visibility.Collapsed}">

Have fun with ObjectAnimationUsingKeyFrames!

Animating Custom Attached Properties in SL2

One feature in SL2 that was added between Beta 2 and RTW is a new constructor to the PropertyPath class that can be used from code. You can now do the following from code:

Storyboard.SetTargetProperty(myAnimation, new PropertyPath(MyCustomClass.MyCustomDP));

This is especially important if you are trying to target an animation at a custom attached property.  For example:

<DoubleAnimation Storyboard.TargetProperty="(custom:MyCustomClass.MyCustomAttachedDP)" .../>

Specifying these types of property path from a string representation presents a number of complications on the SL, and is not currently supported.  This piece of XAML currently raises an InvalidOperationException with message "Cannot resolve TargetProperty (custom:MyCustomClass.MyCustomAttachedDP)".

Please note that this only applies to custom attached properties - attached properties of built-in objects (e.g., Canvas.Top) as well as custom non-attached properties should work just fine.  As reference, an overview of the PropertyPath syntax can be found here.

Hopefully this will unblock you if you run into this limitation!
Ed

Silverlight 2 Shipped!!!!!!!!

Late last night, the final release of Silverlight 2 made its way into the MS servers.  Woot woot!!!!!  You can download the runtime, as well as supported tools here.

ScottGu, the VP responsible for our release, has an excellent blog post outlining what is in SL2, how it fits into the big picture, as well as resources on how you can get started.

 

Enjoy!!!
Ed

Mix '08 Samples Updated

First off, let me just say that I suck at blogging.  If I ever want to blog more often, the first step is to admit I have a problem. :)

In any case, a couple of people have asked me to update my samples from Mix so they will work with Beta 2 and RC0.  Without further ado, here is the latest version of the VideoRSSViewer that works with RC0 bits (I haven't tried it with Beta 2, but the changes should be about the same.  If you need help converting this to Beta 2, you can use the following doc to find the relevant breaking changes between Beta 2 and RC0: http://silverlight.net/GetStarted/sl2rc0.aspx.

Hope this comes in handy!
Ed

 

Mix 08 Demos Posted

I *really* apologize for the delay, but the code for the demos I showed on stage at Mix can be found at:

http://cid-155596ce0aa0cb80.skydrive.live.com/self.aspx/Mix08%20Demos

I have the following zip files there:
1) VideoRSSViewer - Demo.zip - this is the basic demo I created on stage, with bells and whistles, but not a full app.
2) VideoRSSViewer - Full App.zip - this is the full app I showed, where I load videos from msn.com using cross-domain web services in Silverlight.  Users can drag-and-drop entries from the VideoPanel and display mutliple videos at a time, including taking each one to full-screen mode.  Disclaimer: the app is not supposed to be a shipping app, but rather give you an idea of how to accomplish an e2e experience using Silverlight.
3) Web Playlists.zip - app where I showed interaction with Web Playlists in IIS 7 and BitRate Throttling.  I also make an advertisement request where I send as much data about the user as is available.  Also try pointing the MediaElement to a progressively downloaded file (from any web service) and seek to the non-downloaded region.  You will see Byte-Range Requests in action.

You can also find a video of the talk at http://sessions.visitmix.com/?selectedSearch=BCT05, in case you couldn't wake up in time. 

For more details on IIS 7, go to http://iis.net. (Web Playlists and BitRate Throttling are explained there too).
For more details on Expression Encoder, go to http://www.microsoft.com/expression/products/overview.aspx?key=encoder.

Hope these help you, and please let me know if you have any questions!

Thanks,
Ed 

ps: I used SkyDrive to post these, which was really great and easy to use.  You should give it a try at http://skydrive.live.com.

Back to blogging!!!

Wow!! It's been a long time since my last blog post!

I am currently at Mix 2008, and will be giving a talk on Silverlight Media tomorrow.  The conference has been great, a lot of really exciting stuff.

Tomorrow, after my talk, I will post code for the demos I will be showing.

State-Based Animations in Dec CTP

I thought I'd start my blog on media and animations in "WPF/E" with a frequently asked question about how to accomplish state-based animations on the Dec CTP bits.  This can be a bit tricky given a couple of the limitations and issues that were not fixed in time for the CTP, so I thought I'd share a way of accomplishing this.

This post assumes you are familiar with WPF/E.  If you are not, you can find samples and documentation in the WPF/E Dec CTP SDK, which can be found at http://microsoft.com/wpfe.  There are other resources at that location as well.

By state-based animations, I mean any set of animations that affect the same set of properties on the same object.  Each set of animations can be grouped together to indicate a transition to a new state, and all animations in a set run in parallel.  The classic example is a button that expands when the user mouses over (expanded state), and shrinks when the user mouses out (shrunk state).  Another example is a photo library app where each image can be minimized to a filmstrip area (thumbnail state), or expanded to fit a viewing area (full-view state), where each image goes into full-view state when the user selects it.

There are basically three guidelines that you should follow to successfully create a state-based animation on the Dec CTP bits.  More details about how these were derived are below, but they are:

  1. Create a different Storyboard for each state of your object, each of which defines a different target value for the animations.
  2. Set BeginTime="1 on each Storyboard, so that they can be started interactively by calling the begin() method.
  3. When switching states, first begin the Storyboard associated with the new state, then stop any other storyboards associated with this state change.

In this post, I go over the creation of a mini-panel that has three states.  Each state represents a different size and position on the screen, and the transition between states is done using Storyboards.  This could be used to provide a richer user experience when navigating between different elements on a page (e.g., user selects a an image out a number of thumbnails).  A snapshot of the sample can be found below, and the code for it can be found at the end of the post (I haven't figured out a good way to post a sample).

blogPost01Main

In the above image, the rust-colored rectangle is the mini-panel that moves around the WPF/E control.  The mini-panel has three rectangles inside of it, indicating the different sizes and positions it can assume: top left, bottom left and right.  The mini-panel is at the top left position in the image above.  There are three other rectangles indicating, in the WPF/E control, the location of the mini-panel when it's in each of the three possible states.

The idea is that the panel should move between states based on some user input.  This could be a MouseEnter event in the case of a growing/shrinking button, or a mouse click selecting a different image to display.  In this sample, a state transition occurs whenever the user clicks on one of the rectangles inside of the panel.  Each state is achieved in this example by modifying two transforms on the panel:

<TransformGroup>
  <
ScaleTransform x:Name="myScale" ScaleX="3.2" ScaleY="1.7"/>
  <
TranslateTransform x:Name="myTranslate" X="20" Y="20"/>
</
TransformGroup>

The properties on these two transforms fully define each state.  In this case, state 1 shown in the picture requires a scaling factor of 3.2 horizontally, a scaling factor of 1.7 in the vertical direction, and a translation of 20 in both X and Y directions.

In order to create animations between transitions, a Storyboard is needed to animate the properties above to their appropriate values.  For example, state 1 could be represented by the Storyboard below.  Instead of directly setting these properties, this Storyboard tells the panel to animate to the appropriate values in a certain amount of time.

<Storyboard x:Name="state1SB">
  <
DoubleAnimation To="20" Duration="0:0:1" Storyboard.TargetName="myTranslate" Storyboard.TargetProperty="(TranslateTransform.X)/>"/>
  <
DoubleAnimation To="20" Duration="0:0:1" Storyboard.TargetName="myTranslate" Storyboard.TargetProperty="(TranslateTransform.Y)/>"/>
  <
DoubleAnimation To="3.2" Duration="0:0:1" Storyboard.TargetName="myScale" Storyboard.TargetProperty="(ScaleTransform.ScaleX)/>"/>
  <
DoubleAnimation To="1.7" Duration="0:0:1" Storyboard.TargetName="myScale" Storyboard.TargetProperty="(ScaleTransform.ScaleY)/>"/>
</
Storyboard>

Note that in the animations above, a "From" value is not defined.  This means that the animation will begin from the property value at the instant this Storyboard is begun.  Each state also has a different Storyboard associated with it ("state2SB" and "state3SB", which are identical to "state1SB" except for the "To" values in their animations).

Note also that the animations above are interactive (i.e., they start only in response to user input).  By default on the Dec CTP, however, all animations begin when the element containing the Storyboard is Loaded, i.e., shown on the screen.  We need a way of specifying that the Storyboard should not begin at that point but rather when the user does something.  On Dec CTP bits, this can be accomplished by setting a large BeginTime on the storyboard, such as 1 day.  This way, the Storyboard object only kicks off when its begin() method is called.  Please note this is a limitation in the Dec CTP bits, and will be more easily done in future CTPs.

<Storyboard x:Name="state1SB" BeginTime="1"> ... </Storyboard>
<Storyboard x:Name="state2SB" BeginTime="1"> ... </Storyboard>
<Storyboard x:Name="state3SB" BeginTime="1"> ... </Storyboard>

Now that the Storyboards will not start immediately, we need a way of specifying when they should begin. In this sample, the panel changes state whenever the different rectangles inside of it are clicked, so we listen for the MouseLeftButtonUp events on them:

<Rectangle Canvas.Left="4" Canvas.Top="4" Height="34" Width="64" Fill="#FFBBBBBB" MouseLeftButtonUp="javascript:onState1Selected"/>
<Rectangle Canvas.Left="72" Canvas.Top="4" Height="92" Width="24" Fill="#FFBBBBBB" MouseLeftButtonUp="javascript:onState2Selected"/>
<
Rectangle Canvas.Left="4" Canvas.Top="42" Height="54" Width="64" Fill="#FFBBBBBB" MouseLeftButtonUp="javascript:onState3Selected"/>

Inside of each event handler, we need to do two things (the order is crucial):

  1. Begin the storyboard associated with the newly selected state.
  2. Stop any other storyboards that are associated with the properties being animated.  In this case, we need to stop the Storyboards associated with the other states.

  function onState1Selected(sender, mouseEventArgs)
  {
    state1SB.begin();
    state2SB.stop();
    state3SB.stop();

  }

This is another workaround that should only be needed for the Dec CTP of WPF/E.  The reason why you need to call stop on the other Storyboards is that we can only have a single animation on any given property at a time.  When another animation is started on a property that already has an animation applied to it, the latest one should remove the previous animation and replace it.  This is not working in every case on Dec CTP bits, so you need to call stop() on the previous ones.  Also, you need to begin the new Storyboard _before_ stopping the other ones to ensure that the current property value is correctly snapshoted (keep in mind the animations specified above have a default From value, so they snapshot the current property values when they begin).

By following these three simple (though not very discoverable) guidelines, you too can accomplish state-based animations in the Dec CTP of WPF/E:

  • Define a different Storyboard for each state by using implicit "From" values.  This allows the runtime to use the current property value as the initial value for your animation.  It also allows the user to start a new state transitions while another one is taking place.
  • Delay the starting time of your Storyboards by setting BeginTime="1" (this means 1 day).  This allows you to start your animations later in response to some user input by calling the begin() method on your Storyboard.
  • To enter a state, first begin the Storyboard corresponding to that state, then stop all of the remaining Storyboards.  This will ensure that you only have a single Storyboard running on the same property at any given time.

This enables a multitude of rich user experiences without requiring a lot of code on your part!

XAML for the full Demo:

<Canvas xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Loaded="javascript:onLoaded">
  <Rectangle Height="500" Width="500">
    <
Rectangle.Fill>
      <
LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
        <
GradientStop Offset="0" Color="DarkBlue"/>
        <
GradientStop Offset="1" Color="Black"/>
      </LinearGradientBrush>
    </
Rectangle.Fill>
  </
Rectangle>
  <
Rectangle Canvas.Left="15" Canvas.Top="15" Height="180" Width="330" Fill="#90EEEEEE"/>
  <
Rectangle Canvas.Left="15" Canvas.Top="205" Height="280" Width="330" Fill="#90EEEEEE"/>
  <
Rectangle Canvas.Left="355" Canvas.Top="15" Height="470" Width="130" Fill="#90EEEEEE"/>

  <Canvas>
    <
Canvas.RenderTransform>
      <
TransformGroup>
        <
ScaleTransform x:Name="myScale" ScaleX="3.2" ScaleY="1.7"/>
        <
TranslateTransform x:Name="myTranslate" X="20" Y="20"/>
      </
TransformGroup>
    </
Canvas.RenderTransform>
    <
Canvas.Triggers>
      <
EventTrigger RoutedEvent="Canvas.Loaded">
        <
EventTrigger.Actions>
          <
BeginStoryboard>
            <
Storyboard BeginTime="1" x:Name="state1SB">
              <
DoubleAnimationUsingKeyFrames Storyboard.TargetName="myTranslate" Storyboard.TargetProperty="(TranslateTransform.X)">
                <
SplineDoubleKeyFrame Value="20" KeyTime="0:0:1" KeySpline="0,1 0,1"/>
              </DoubleAnimationUsingKeyFrames>
              <
DoubleAnimationUsingKeyFrames Storyboard.TargetName="myTranslate" Storyboard.TargetProperty="(TranslateTransform.Y)">
                <
SplineDoubleKeyFrame Value="20" KeyTime="0:0:1" KeySpline="0,1 0,1"/>
              </
DoubleAnimationUsingKeyFrames>
              <
DoubleAnimationUsingKeyFrames Storyboard.TargetName="myScale" Storyboard.TargetProperty="(ScaleTransform.ScaleX)">
                <
SplineDoubleKeyFrame Value="3.2" KeyTime="0:0:1" KeySpline="0,1 0,1"/>
              </
DoubleAnimationUsingKeyFrames>
              <
DoubleAnimationUsingKeyFrames Storyboard.TargetName="myScale" Storyboard.TargetProperty="(ScaleTransform.ScaleY)">
                <
SplineDoubleKeyFrame Value="1.7" KeyTime="0:0:1" KeySpline="0,1 0,1"/>
              </
DoubleAnimationUsingKeyFrames>
            </
Storyboard>
          </
BeginStoryboard>
          <
BeginStoryboard>
            <
Storyboard BeginTime="1" x:Name="state2SB">
              <
DoubleAnimationUsingKeyFrames Storyboard.TargetName="myTranslate" Storyboard.TargetProperty="(TranslateTransform.X)">
                <
SplineDoubleKeyFrame Value="360" KeyTime="0:0:1" KeySpline="0,1 0,1"/>
              </
DoubleAnimationUsingKeyFrames>
              <
DoubleAnimationUsingKeyFrames Storyboard.TargetName="myTranslate" Storyboard.TargetProperty="(TranslateTransform.Y)">
                <
SplineDoubleKeyFrame Value="20" KeyTime="0:0:1" KeySpline="0,1 0,1"/>
              </
DoubleAnimationUsingKeyFrames>
              <
DoubleAnimationUsingKeyFrames Storyboard.TargetName="myScale" Storyboard.TargetProperty="(ScaleTransform.ScaleX)">
                <
SplineDoubleKeyFrame Value="1.2" KeyTime="0:0:1" KeySpline="0,1 0,1"/>
              </
DoubleAnimationUsingKeyFrames>
              <
DoubleAnimationUsingKeyFrames Storyboard.TargetName="myScale" Storyboard.TargetProperty="(ScaleTransform.ScaleY)">
                <
SplineDoubleKeyFrame Value="4.6" KeyTime="0:0:1" KeySpline="0,1 0,1"/>
              </
DoubleAnimationUsingKeyFrames>
            </
Storyboard>
          </
BeginStoryboard>
          <
BeginStoryboard>
            <
Storyboard BeginTime="1" x:Name="state3SB">
              <
DoubleAnimationUsingKeyFrames Storyboard.TargetName="myTranslate" Storyboard.TargetProperty="(TranslateTransform.X)">
                <
SplineDoubleKeyFrame Value="20" KeyTime="0:0:1" KeySpline="0,1 0,1"/>
              </DoubleAnimationUsingKeyFrames>
              <
DoubleAnimationUsingKeyFrames Storyboard.TargetName="myTranslate" Storyboard.TargetProperty="(TranslateTransform.Y)">
                <
SplineDoubleKeyFrame Value="210" KeyTime="0:0:1" KeySpline="0,1 0,1"/>
              </
DoubleAnimationUsingKeyFrames>
              <
DoubleAnimationUsingKeyFrames Storyboard.TargetName="myScale" Storyboard.TargetProperty="(ScaleTransform.ScaleX)">
                <
SplineDoubleKeyFrame Value="3.2" KeyTime="0:0:1" KeySpline="0,1 0,1"/>
              </
DoubleAnimationUsingKeyFrames>
              <
DoubleAnimationUsingKeyFrames Storyboard.TargetName="myScale" Storyboard.TargetProperty="(ScaleTransform.ScaleY)">
                <
SplineDoubleKeyFrame Value="2.7" KeyTime="0:0:1" KeySpline="0,1 0,1"/>
              </
DoubleAnimationUsingKeyFrames>
            </Storyboard>
          </
BeginStoryboard>
        </
EventTrigger.Actions>
      </
EventTrigger>
    </
Canvas.Triggers>
    <
Rectangle Height="100" Width="100" Fill="#FF886666"/>
    <
Rectangle Canvas.Left="4" Canvas.Top="4" Height="34" Width="64" Fill="#FFBBBBBB" MouseLeftButtonUp="javascript:onState1Selected"/>
    <Rectangle Canvas.Left="72" Canvas.Top="4" Height="92" Width="24" Fill="#FFBBBBBB" MouseLeftButtonUp="javascript:onState2Selected"/>
    <
Rectangle Canvas.Left="4" Canvas.Top="42" Height="54" Width="64" Fill="#FFBBBBBB" MouseLeftButtonUp="javascript:onState3Selected"/>
  </
Canvas>
</
Canvas>

 JavaScript:

var wpfe;
var state1SB = null;

var state2SB = null;
var state3SB = null;

function onLoaded (s,e)
{
  wpfe = document.getElementById(
"WpfeControl1"); // assumes control is 500x500
  state1SB = wpfe.findName("state1SB");
  state2SB = wpfe.findName("state2SB");
  state3SB = wpfe.findName("state3SB");

}

function onState1Selected(sender, mouseEventArgs)
{
  state1SB.begin();
  state2SB.
stop();
  state3SB.
stop();
}

function onState2Selected(sender, mouseEventArgs)
{
  state2SB.
begin();
  state1SB.
stop();
  state3SB.
stop();
}

function onState3Selected(sender, mouseEventArgs)
{
  state3SB.
begin();
  state1SB.
stop();
  state2SB.
stop();
}

Page view tracker