We are changing some dimensions (to take care of my BlogEngine restrictions) and put an image (SilverlightLogo.jpg) in the background of our main container (a grid named LayoutRoot as usual). Nothing very special yet.
<UserControl x:Class="Anim1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500" Height="500">
<Grid x:Name="LayoutRoot" >
<Grid.Background>
<ImageBrush ImageSource="SilverlightLogo.jpg" Stretch="Uniform" />
</Grid.Background>
</Grid>
</UserControl>
We insert a transparent canvas in front of our main LayoutRoot container. Since this canvas will be in rotation, we name it RotatingSurface. Inside this canvas, we insert a rail. Images will be fixed on this rail. We also decide to react to the Loaded event, in order to initialize things.
<Grid x:Name="LayoutRoot" >
.....
<Canvas x:Name="RotatingSurface" Width="400" Height="400"
Background="Transparent" Loaded="RotatingSurface_Loaded" >
<Ellipse x:Name="rail" Canvas.Left="50" Canvas.Top="50" Width="300"
Height="300" Stroke="Gray" StrokeThickness="10" />
</Canvas>
</Grid>
We are here adding five images as resource (DaVinci, Newton, Mozart, Shakespeare and Einstein, all of same size, 90x110) but we code our program to be more general (doing so, adding more images will just require a change in the tabNames array).
In Page.xaml.cs, we add (outside of functions) :
string[] tabNames = {"DaVinci", "Shakespeare", "Newton", "Mozart", "Einstein" };
Image[] tabImages;
In the RotateSurface_Loaded function :
- we create an array of images (remember, to keep things simple, these images were inserted in the project and are thus automatically copied into the XAP file),
- place them along the the rail (a bit of trigonometry, with Sin and Cos, is needed but nothing worth a Nobel prize),
- finally add each of these images as children of the canvas.
private void RotatingSurface_Loaded(object sender, RoutedEventArgs e)
{
int N = tabNames.Length; // number of images
// get radius and position of rail
double wRail = rail.ActualWidth / 2; // width of rail
double hRail = rail.ActualHeight / 2; // height of rail
double xRail = (double)rail.GetValue(Canvas.LeftProperty);
double yRail = (double)rail.GetValue(Canvas.TopProperty);
double wImage = 90, hImage = 110; // same size for all images
// create an array of images (N depending on tabNames array)
tabImages = new Image[N];
for (int i = 0; i < N; i++)
{
tabImages[i] = new Image();
tabImages[i].Source=new BitmapImage(new Uri(tabNames[i]+".jpg",
UriKind.Relative));
tabImages[i].Width = wImage;
tabImages[i].Height = hImage;
// put image at the right place
double X = wRail * Math.Cos(i * 2 * 3.14 / N) + xRail + wRail - wImage / 2;
double Y = wRail * Math.Sin(i * 2 * 3.14 / N) + yRail + hRail - hImage / 2;
tabImages[i].SetValue(Canvas.LeftProperty, X);
tabImages[i].SetValue(Canvas.TopProperty, Y);
RotatingSurface.Children.Add(tabImages[i]);
}
}
We need to add
using System.Windows.Media.Imaging;
to make BitmapImage known by Visual Studio.
At this point, images are displayed but the result is still very static (nothing in rotation yet) :
So, let's animate the canvas. We define for the RotatingSurface canvas :
- the rotation point, at the center (coordinates are relative in RenderTransformOrigin),
- a rotation transform named rotCanvas (the storyboard will animate its Angle property),
- a storyboard (named stb) that animates Angle forever from 0 to 360° in 10 seconds :
<Canvas x:Name="RotatingSurface" Width="400" Height="400"
Background="Transparent" Loaded="RotatingSurface_Loaded"
RenderTransformOrigin="0.5, 0.5">
<Canvas.RenderTransform>
<RotateTransform x:Name="rotCanvas" />
</Canvas.RenderTransform>
<Canvas.Resources>
<Storyboard x:Name="stb" RepeatBehavior="Forever">
<DoubleAnimation Storyboard.TargetName="rotCanvas"
Storyboard.TargetProperty="Angle" From="0" To="360"
Duration="0:0:10" />
</Storyboard>
</Canvas.Resources>
<Ellipse x:Name="rail" Canvas.Left="50" Canvas.Top="50"
Width="300" Height="300" Stroke="Gray" StrokeThickness="10" />
</Canvas>
As last instruction in the RotatingSurface_Loaded function, we start the stb storyboard :
stb.Begin();
Here is the result of the current implementation. It doesn't give full satisfaction :
To keep images vertical, we need to add a rotation to each image : forever, from 0 to -360° in 10 seconds. Such a storyboard has to be added dynamically (remember, we don't know the number of images and these images were created dynamically).
To be able, later on, to start / pause these storyboards, we declare and initialize an array of N storyboards. For each image, we create by program :
- a RotateTransform (we'll animate its Angle property),
- a storyboard,
- a DoubleAnimation that will animate Angle and that will pertain to the storyboard just created.
Storyboard[] tabStoryboards; // outside of functions
.....
tabStoryboards = new Storyboard[N];
for (int i = 0; i < N; i++)
{
..... // create an Image in tabImages[i]
RotateTransform rt = new RotateTransform();
tabImages[i].RenderTransform = rt;
tabImages[i].RenderTransformOrigin = new Point(0.5, 0.5);
Storyboard st = new Storyboard();
DoubleAnimation da = new DoubleAnimation();
da.Duration = new Duration(new TimeSpan(0, 0, 10));
da.To = -360;
da.RepeatBehavior = RepeatBehavior.Forever;
Storyboard.SetTargetProperty(da, new PropertyPath("Angle"));
Storyboard.SetTarget(da, rt);
st.Children.Add(da);
tabStoryboards[i] = st;
RotatingSurface.Children.Add(tabImages[i]);
}
And now, to start the animation :
stb.Begin();
for (int i=0; i<N; i++) tabStoryboards[i].Begin();
You know the result, at the beginning of this post.