Welcome to MSDN Blogs Sign in | Join | Help

In my previous post we made a simple Popfly Silverlight display block.  Now we can do something a little more interesting. All of these examples will use the block description from the previous post, so be sure to take a look.

Making a Slideshow

We can make a simple slideshow by fading from one picture to the next. Take a look at the "HelloSilverlight-3" block:

function HelloWorld()
{
  HelloWorld.initializeBase(
this);

  
this.index = -1; // index of image being animated
}

HelloWorld.prototype.getXaml =
function()
{
  
// make a 640x480 canvas
  return '<Canvas Width="640" Height="480"/>';
};

HelloWorld.prototype.addImage =
function(url)
{
  
// start loading the image
  this.addPendingImage(url);
};

HelloWorld.prototype.imageLoaded =
function(image)
{
  
// get our canvas
  this.canvas = this.root.children.getItem(0);

  
// xaml for the image display and animation
  var xaml =
    
'<Image Name="image" Opacity="0" Width="640" Height="480" Stretch="UniformToFill">'
   +'  <Image.Resources>'
   +'    <Storyboard Name="fadeIn" Duration="0:0:5">'
   +'      <DoubleAnimation Storyboard.TargetName="image" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:1"/>'
   +'    </Storyboard>'
   +'    <Storyboard Name="fadeOut" Duration="0:0:1">'
   +'      <DoubleAnimation Storyboard.TargetName="image" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:1"/>'
   +'    </Storyboard>'
   +'  </Image.Resources>'
   +'</Image>';    

  
// create the object and set the image source
  var object = this.content.createFromXaml(xaml, true);
  object.source = image.source;
  
  
// call the onCompleted handler when the image is done animating
  object.findName("fadeIn").addEventListener("completed", delegate(this, this.onCompleted));

  
// add the image to our canvas
  this.canvas.children.add(object);
  
  
// if this is the first image, start the animation
  if(this.index == -1)
  {
    
this.showNextImage();
  }
};

HelloWorld.prototype.showNextImage =
function()
{
  
// get the next image
  this.index++;
  
if(this.index >= this.canvas.children.count)
  {
    
this.index = 0;
  }
  
var image = this.canvas.children.getItem(this.index);
  
  
// zoom the image in
  image.findName("fadeIn").begin();
};

HelloWorld.prototype.onCompleted =
function(sender)
{
  
// start fading the image out
  sender.findName("fadeOut").begin();

  
// show the next image
  this.showNextImage();  
};

HelloWorld.registerClass(
'HelloWorld', Popfly.Blocks.SilverBase);

HelloSilverlight-3 Code

We added a few things here to the previous example here. The getXaml call lets us say how big of a canvas we want to work with because it gets automatically added to the root canvas of our Silverlight control . Note that this will automatically get scaled to fit the user's browser optimally. In the imageLoaded callback we added a bunch of xaml to do the fading in and out. We then create an object using that xaml, get a callback to see when the image has faded in, add the image to the canvas, and show it.

The showNextImage function figures out which image is next and starts fading it in. Once the fadeIn has completed, the onCompleted function starts fading it out and starts fading the next one in. Lather, rinse, repeat! Straightforward enough? Is the xaml a little confusing? Let’s break it down:

<Image Name="image" Opacity="0" Width="640" Height="480" Stretch="UniformToFill">

Here we declare our image in xaml with the name "image" so we can easily reference it in the animations, and set the opacity to 0 so it starts out hidden. We also give it a height and width and ask Silverlight to fill that space with our image. That's what "UniformToFill" basically means.

<Storyboard Name="fadeIn" Duration="0:0:5"
  <DoubleAnimation Storyboard.TargetName="image" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:1"/>
</
Storyboard>

The two storyboards are similar. One fades the image in, the other fades it out. Note the duration of the fadeIn storyboard is 5 seconds but the DoubleAnimation inside it only lasts 1 second. This is so we can actually see the image for a full 4 seconds before the onCompleted function in our code is called, and we start to fade the image back out.

If you are wondering how the fadeIn storyboard actually calls the onCompleted function in the code, the answer is simple. We add a listener for the storyboard's completed event and make it call our function, like so:

// call the onCompleted handler when the image is done animating
object.findName("fadeIn").addEventListener("completed", delegate(this, this.onCompleted));

Animating Slideshow

The Ken Burns Effect might be familiar to you; it's basically zooming in and moving the pictures around to make them seem more alive. Let's extend the previous example to do this. (HelloSilverlight-4)

function HelloWorld()
{
  HelloWorld.initializeBase(
this);

  
this.index = -1; // index of image being animated
}

HelloWorld.prototype.getXaml =
function()
{
  
// make a 400x400 canvas and clip everything to it
  return '<Canvas Width="640" Height="480" Clip="M0,0 H640 V480 H0z"/>';
};

HelloWorld.prototype.addImage =
function(url)
{
  
// start loading the image
  this.addPendingImage(url);
};

HelloWorld.prototype.imageLoaded =
function(image)
{
  
// get our canvas
  this.canvas = this.root.children.getItem(0);

  
// pick a random spot to zoom the image to / from
  var left = Math.random()*-100;
  
var top = Math.random()*-100;
  
var zoomFrom, zoomTo;
  
if(Math.random()>0.5)
  {
    zoomFrom = Math.random()*0.4 + 1;
    zoomTo = 1;
  }
  
else
  {
    zoomFrom = 1;
    zoomTo = Math.random()*0.4 + 1;
  }

  
// xaml for the image display and animation
  var xaml =
    
'<Image Name="image" Opacity="0" Width="740" Height="580" Stretch="UniformToFill">'
   +'  <Image.Resources>'
   +'    <Storyboard Name="fadeIn" Duration="0:0:5">'
   +'      <DoubleAnimation Storyboard.TargetName="image" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:1"/>'
   +'    </Storyboard>'
   +'    <Storyboard Name="zoom">'
   +'      <DoubleAnimation Storyboard.TargetName="translate" Storyboard.TargetProperty="X" From="'+ top +'" To="'+ left +'" Duration="0:0:6"/>'
   +'      <DoubleAnimation Storyboard.TargetName="translate" Storyboard.TargetProperty="Y" From="'+ left +'" To="'+ top +'" Duration="0:0:6"/>'
   +'      <DoubleAnimation Storyboard.TargetName="scale" Storyboard.TargetProperty="ScaleX" From="'+ zoomFrom +'" To="'+ zoomTo +'" Duration="0:0:6"/>'
   +'      <DoubleAnimation Storyboard.TargetName="scale" Storyboard.TargetProperty="ScaleY" From="'+ zoomFrom +'" To="'+ zoomTo +'" Duration="0:0:6"/>'
   +'    </Storyboard>'
   +'    <Storyboard Name="fadeOut" Duration="0:0:1">'
   +'      <DoubleAnimation Storyboard.TargetName="image" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:1"/>'
   +'    </Storyboard>'
   +'  </Image.Resources>'
   +'  <Image.RenderTransform>'
   +'    <TransformGroup>'
   +'      <TranslateTransform Name="translate"/>'
   +'      <ScaleTransform Name="scale"/>'
   +'    </TransformGroup>'
   +'  </Image.RenderTransform>'
   +'</Image>';    

  
// create the object and set the image source
  var object = this.content.createFromXaml(xaml, true);
  object.source = image.source;
  
  
// call the onCompleted handler when the image is done animating
  object.findName("fadeIn").addEventListener("completed", delegate(this, this.onCompleted));

  
// add the image to our canvas
  this.canvas.children.add(object);
  
  
// if this is the first image, start the animation
  if(this.index == -1)
  {
    
this.showNextImage();
  }
};

HelloWorld.prototype.showNextImage =
function()
{
  
// get the next image
  this.index++;
  
if(this.index >= this.canvas.children.count)
  {
    
this.index = 0;
  }
  
var image = this.canvas.children.getItem(this.index);
  
  
// zoom the image in
  image.findName("fadeIn").begin();
  image.findName(
"zoom").begin();
};

HelloWorld.prototype.onCompleted =
function(sender)
{
  
// start fading the image out
  sender.findName("fadeOut").begin();

  
// show the next image
  this.showNextImage();  
};

HelloWorld.registerClass(
'HelloWorld', Popfly.Blocks.SilverBase);

HelloSilverlight-4 Code

There's only a few additional lines in this example. We pick a random spot, figure out whether to zoom in or out, create the animation and transforms for it, and then run!

Where Can You Go From Here?

Here's some things you could add to the block:

  • More animation effects (rotating, squashing, coloring)
  • Captions and descriptions
  • Sound effects
  • Show multiple pictures side-by-side
  • Frames
  • Mouse interactivity

Check out some of the other display blocks, such as "photoPile" for more ideas...

Popfly and Silverlight make it easy to show your pictures from many places in slick little gadgets. It's easy, and fun to make these "display blocks". I wanted to do a little walkthrough of creating a display block. The examples herein are actual Popfly mashups. They do use Silverlight, so make sure you get that installed.

This first blog entry will go over a couple of simple examples. But the next entry will show how to build a moving slideshow like this:

Getting Started

To follow along, you'll need:

Hello, World!

Let's look at this lame Hello World example to get some boilerplate out of the way. Here's the entire JavaScript code for it:

function HelloWorld()
{
  HelloWorld.initializeBase(
this);
}

HelloWorld.prototype.getXaml =
function()
{
    
return "<TextBlock>Hello World!</TextBlock>";
};

HelloWorld.prototype.Hello =
function()
{
    
// Nothing to do!
};

HelloWorld.registerClass(
'HelloWorld', Popfly.Blocks.SilverBase);

Figure 1 - HelloSilverlight-1 Code

Pretty easy! The initializeBase and registerClass functions are important, they say that you are writing a display block and want to use “SilverBase”. SilverBase takes care of creating the Silverlight control, centering, stretching, loading images, and a lot more so you can just do the good stuff!

The only other thing needed is the block description. For Hello World, it looks like this:

<?xml version="1.0"?>
<
block xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://www.popfly.com/schemas/blockschema.xsd"
  class="HelloWorld" hasInitialize="true" type="display">
  <
operations>
    <
operation name="Hello">
      <
description>Display Hello World Text</description>
    </
operation>
  </
operations>
  <
objects/>
</
block>

Figure 2 - HelloSilverlight-1 Description

Images

Showing an image:

The entire code for this simple display is as follows:

function HelloWorld()
{
  HelloWorld.initializeBase(
this);
}

HelloWorld.prototype.addImage =
function(url)
{
  
this.addPendingImage(url);
};

HelloWorld.prototype.imageLoaded =
function(image)
{
  
this.root.children.add(image);
};

HelloWorld.registerClass(
'HelloWorld', Popfly.Blocks.SilverBase);

Figure 3 - HelloSilverlight-2 Code

We added an operation, "addImage". It takes an image URL as a parameter and adds that as a "pending image" to SilverBase. When the image has been completely loaded, SilverBase calls your function, “imageLoaded” with the actual loaded image. So then we add it as a child to the root canvas. Done!

Run this and you’ll see a picture in the upper left. You can easily move it around by setting image["Canvas.Left"] and image["Canvas.Top"]. This and both of tomorrow's examples will use the same block description xml as show below.

<?xml version="1.0"?>
<
block xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://www.popfly.com/schemas/blockschema.xsd"
  class="HelloWorld" hasInitialize="true" type="display">
  <
operations>
    <
operation name="addImage">
      <
description>Add an image</description>
      <
inputs>
        <
input name="url" required="true" type="imageUrl">
          <
description>A URL pointing to an image</description>
          <
defaultValue>http://www.popfly.ms/Images/community_globe.jpg</defaultValue>
        </
input>
      </
inputs>
    </
operation>
  </
operations>
  <
objects/>
</
block>

Figure 4 - HelloSilverlight-2..4 Description

So far, so good? In my next blog entry we'll see how to make simple slideshows. You can get a preview of these by editing the mashup at the top of this entry.
 
Page view tracker