Using Silverlight 2.0 and C#/VB.NET, we will build an image rotator that has a useful set of basic features and is easy to setup and deploy.
- Can link to local images deployed with an application, or to images hosted elsewhere on the Internet.
- Image paths and attributes stored in an XML file for easy changes without the need to rebuild the application.
- Auto play can be toggled on/off.
- Duration that each image is shown is adjustable.
- Image transition effects can be turned on/off.
- Image number, last and next buttons can be hidden/displayed.
- Can pause auto-play when the number button is selected, restarted when another button is selected.
- Image border can be displayed/hidden.
- Image border width and color/opacity can be changed.
- All configuration changes can be made without recompiling the ImageRotator.
Using the code
The main emphasis I put on building this image viewer was to make it easy to implement on a site (HTML or ASP.NET) and yet contain a handful of useful features. I also wanted to make sure images could be changed, removed, and added without having to do a rebuild of the application or stop/start the web server. Since I abstracted a lot of the configuration complexities away, the amount of code got a little lengthy for what I would consider a simple application. For that reason, I will not go into the details of each method that makes up this control. Instead, I will give a brief overview of how it works, and then I will spend more time on the few items that caused me the most problems, along with hopefully good deployment instructions for those who don’t care how it works, but just want to use it.
This application is divided into two projects. The first is the website project (ImageRotator.Web) and the other is the Silverlight project (ImageRotator). The website project has a folder called ClientBin which the Silverlight application has access to at runtime. This is where we will store the actual images and the SlideShowImages.xml file.
The website also contains the two web pages which use the
ImageRotator control. The first is ImageRotatorTestPage.aspx. This page contains not just the control, but also some controls to change the attributes of the
ImageRotator and refresh the control so you can see the changes. Obviously, this is for demo purposes, and most likely, your page would not contain these other controls. The code-behind of this page also shows how you can load the required
InitParams from the web.config's
appSettings or from the web page control values on page refresh and pass them into the
ImageRotator control. The second page containing the Silverlight control is ImageRotatorTestPage.html. This page does not contain all the controls to change
InitParams and refresh the control at runtime. It does, however, show another example of how to load the
InitParams using a
param tag with the name of “
initParams” and a coma delimited list of name/value pairs.
Please note that the
ImageRotator requires that these parameters all be passed in. There are other methods of passing in
InitParams that I haven’t shown such as passing through a URL string. I’ll leave these other methods for you to investigate.
Moving onto the second project, ImageRotator, we have the classes that make up the Silverlight control and gives it the functionality. I’ll give a brief description of each of these files.
- SlideShowImage.cs: This class represents an image and contains the properties related to an individual image.
- PictureAlbum.cs: This class contains a collection
SlideShowImages and those properties common to all the images in the collection. This class also has the asynchronous call which loads the images from the SlideShowImages.xml file into the collection and raises the
pictureAlbumLoadedHandler to notify listeners that the picture album has been loaded.
- PictureAlbumEventArgs.cs: This class contains the support code for custom events which fire when the picture album has finished asynchronously loading the image collection to be used by the
ImageRotator from the SlideShowImages.xml file contained on the website.
- App.xaml.cs: When the application starts, the
Application_Startup event is fired. This is where we verify all the parameters required by
ImageRotator are being passed in. We are not doing any robust checking of the parameters, but simply verifying that the correct number is passed. Should any of the parameters not be correctly formatted, we will throw an exception later.
- Page.xaml: Contains some of the markup for the
ImageRotator control, the rest of which is created dynamically as the control is loaded. Here, however, you can see the image border which wraps the entire image. Also, the
imageStackPanel which will have image controls dynamically added to it during runtime (1 per image). Finally, the
navigationStackPanel can also be seen here which contains polygon (arrow) buttons, and the
numberedItemsStackPanel which is a container for the numbered buttons which are also added at runtime.
<Border x:Name="imageBorder" Cursor="Hand">
<Canvas x:Name="layoutRoot" Background="White" VerticalAlignment="Stretch"
<StackPanel x:Name="imageStackPanel" />
<Border x:Name="navigationBorder" Background="White" Opacity=".5"
CornerRadius="10" BorderBrush="White" BorderThickness="0"
<StackPanel x:Name="navigationStackPanel" Orientation="Horizontal"
VerticalAlignment="Center" Margin="2,2,8,2" />
VerticalAlignment="Center" Margin="2" />
Page.xml.cs: This is where all the events, event handlers, and methods are contained that bring the
ImageRotator and its functionality to life. Again, I won't cover all the methods here, but feel free to download and review this code yourself. I've tried to add good comments to everything; however, if you have any questions, feel free to contact me. I will cover those areas which caused the biggest hurdles during the development of this application.
Changing images without a rebuild
When I initially built the application, I placed the images in an image directory inside the ImageRotator Silverlight library. This worked fine except for one thing. When the application is called, this library is compiled into a .xap file and placed in the website's ClientBin folder. This includes everything in that project, even the images. Through a little research, however, I discovered the true magic of the website's ClientBin folder. The bottom line is, files can be stored in this directory and accessed at runtime by the Silverlight control. So, I created an images subdirectory, and placed a few of the images I wanted to display in this directory. I also placed the SlideShowImages.xml file here. This file contains the images you want as part of your deployment along with the properties of each.
<PictureAlbum ImageWidth="480" ImageHeight="360">
Passing configuration/setup values from the website into the Silverlight application
Step #1: The first step is actually passing the name/value pairs from the website. This can be accomplished in several different ways. The way I passed them from my ImageRotatorTestPage.aspx page is through the code-behind. I built a string of name value pairs (get the initial load values from web.config and refresh the values from the page controls). I then set the
InitParams string into the
protected void refresh_Click(object sender, EventArgs e)
string browserWindowOptions = "resizable=1|scrollbars=1|menubar=1|status=1|"
browserWindowOptions += "toolbar=1|titlebar=1|width=900|height=725|left=5|top=5";
string initParams = "autoPlay=" + autoPlay.Checked.ToString();
initParams += ",autoPlayInterval=" + autoPlayInterval.Text;
initParams += ",numberedNavigation=" + numberedNavigation.Checked.ToString();
initParams += ",arrowNavigation=" + arrowNavigation.Checked.ToString();
initParams += ",stopStartAutoPlay=" + stopStartAutoPlay.Checked.ToString();
initParams += ",animation=" + animation.Checked.ToString();
initParams += ",border=" + border.Checked.ToString();
initParams += ",borderThickness=" + borderThickness.SelectedValue;
initParams += ",argb=" + a.Text + "|" + r.Text +
"|" + g.Text + "|" + b.Text;
initParams += ",imageRotatorDiv=" + imageRotatorDiv;
initParams += ",browserWindowOptions=" + browserWindowOptions;
imageRotator.InitParameters = initParams;
Note that two of
InitParams' name/value pairs contain a pipe delimited list. I had initially used commas to delimit, but this messed up the delimiters of
initParams. To get around this, I encoded the commas. However, I found this hard to read and maintain. In the end, I decided to use a pipe as a delimiter. Since the ImageRotatorTestPage.html has no code-behind, I had to find another method for passing
InitParams from it. As it turns out, this can be accomplished with the
param tag having its
name attribute set to
initParams and its
value attribute set to a comma delimited list of attribute=value pairs.
type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/ImageRotator_CSharp.xap"/>
<param name="onerror" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="2.0.31005.0" />
<param name="autoUpgrade" value="true" />
<a href=http://go.microsoft.com/fwlink/?LinkID=124807 style="text-decoration: none;">
alt="Get Silverlight" style="border-style: none"/>
Step #2: The parameters must be passed into the Silverlight page (Page.xaml). This is not done directly, and it is not setup to pass
InitParams by default. They must be passed into the application class (App.xaml.cs) first, which then passes them on to whichever user control you want (Page.xaml.cs, in my case). More specifically, every time the Silverlight application is loaded (initially, or by page refresh), the App.xaml.cs default constructor (
App()) is called. This wires up the
Application_Startup event which is then called. By default, this method simply calls the default constructor inside Page.xaml.cs. What we need it to do is to call an overloaded constructor (which we will need to create) that takes in the
InitParams as an argument. Here's a look at the updated
Application_Startup method calling the overloaded
Page class constructor.
private void Application_Startup(object sender, StartupEventArgs e)
if (e.InitParams.Count == 11)
this.RootVisual = new Page(e.InitParams);
string exc = "Must pass the required InitParams " +
"to the Page.xaml.cs constructor"
throw new Exception(exc);
Step #3: Here is a look at the overloaded constructor which accepts the
InitParams and places the values into some private member variables for later use.
public Page(IDictionary<string, > parameters)
foreach (var item in parameters)
enableAutoPlay = Convert.ToBoolean(item.Value);
autoPlayIntervalSeconds = Convert.ToInt32(item.Value);
displayNumberedButtons = Convert.ToBoolean(item.Value);
displayArrows = Convert.ToBoolean(item.Value);
buttonsStopStartAutoPlay = Convert.ToBoolean(item.Value);
enableAnimations = Convert.ToBoolean(item.Value);
displayImageBorder = Convert.ToBoolean(item.Value);
imageBorderThickness = Convert.ToInt32(item.Value);
argbBorderColor = item.Value.ToString().Replace("|", ",");
imageRotatorDiv = item.Value.ToString();
browserWindowOptions = item.Value.ToString().Replace("|", ",");
Asynchronously load the XML file from the website into the instance of the PictureAlbum class and notify the custom event listener when completed
There are two important things to note here. First, we want to asynchronously go to the website and get the SlideShowImages.xml file. This XML file contains a
<SlideShowImage> element for each picture. Each
SlideShowImage element contains several elements which identify attributes of that image. I chose to use elements for these instead of attributes because I want this application to be easy to use for non-developers as well, and I feel elements are easier to read than attributes for them (and me for that matter). We need this in order to load up the
PictureAlbum class which contains a collection
SlideShowImages used to drive the
ImageRotator. We also need to let the
ImageRotator know this process is done so that it can then use the
SlideShowImages instance to dynamically create the images and buttons on the control itself.
This was probably were I spent the most of my time. My first thought was to use a wait-handle of some sort to block the calling thread from my Page.xaml.cs class until the XML file was loaded, then signal that it had completed so the image controls could be created. Depending on how I created a new thread, and using wait-handles, I came across different problems. Either the wait-handle would not appear to block, or it would get to the blocking control and spin forever without ever reaching my callback method to signal. Trust me when I say, I tried everything. Then, I did a little research (which I should have done much earlier), and discovered the problem. There is no issue with using
ManualResetEvent's with Silverlight. However, you cannot block the main UI thread in a Silverlight application. My alternative approach, which worked with little effort, was to create a custom event that gets fired when I'm done loading the XML file.
First, in the
PictureAlbum class, I created a delegate for my asynchronous call, and a custom event to be raised when it was completed.
public delegate void PictureAlbumLoaded(object sender, PictureAlbumLoadedEventArgs a);
public event PictureAlbumLoaded pictureAlbumLoaded;
Next, I created a method (
LoadPictureAlbumXMLFile) which gets called by the page class during its construction. This method creates an instance of
WebClient which we will use to get the XML file across the wire via its
DownloadStringAsync method. We will also wire up to the
DownloadStringCompleted event. This method takes a callback method as a parameter which I have given it (
XMLFileLoaded). Once the
WebClient has completed its asynchronous call to get the file, it will fire its
DownloadStringCompleted event. In our case, this will cause
XMLFileLoaded to be called. This method takes the XML file which has been returned as a string, and parses it into an
XDocument. We then use a little LINQ to XML to get the image details out and into the
PictureAlbum's collection of images. Finally, the
pictureAlbumLoaded event is fired to notify any listeners of the success or failure of this method call.
public void LoadPictureAlbumXMLFile()
WebClient xmlClient = new WebClient();
private void XMLFileLoaded(object sender, DownloadStringCompletedEventArgs e)
if (e.Error == null)
Int32 buttonNumber = 0;
slideShowImages = new List<SlideShowImage>();
PictureAlbumLoadedEventArgs pictureAlbumLoadedCompletedSuccessfully = null;
xdoc = XDocument.Parse(e.Result);
imageWidth = Convert.ToDouble(xdoc.Element("PictureAlbum")
imageHeight = Convert.ToDouble(xdoc.Element("PictureAlbum")
foreach (XElement item in xdoc.Elements("PictureAlbum")
if (item.Element("DisplayImage").Value.ToLower() == "true")
SlideShowImage slideShowImage = new SlideShowImage();
new Uri(item.Element("ImageUri").Value, UriKind.Absolute);
new Uri(item.Element("ImageUri").Value, UriKind.Relative);
slideShowImage.RedirectLink = item.Element("RedirectLink").Value;
slideShowImage.Order = Convert.ToInt32(item.Element("Order").Value);
slideShowImage.ButtonNumber = buttonNumber++;
pictureAlbumLoadedCompletedSuccessfully = new PictureAlbumLoadedEventArgs(true);
The method that calls all of this (
LoadPictureAlbum) is in the
Page class, and it also has some asynchronous logic. It first wires up a listener to the
PictureAlbumLoaded event, passing in the
PictureAlbumLoaded method as the callback. It then calls
LoadPictureAlbumXMLFile, which, as we saw previously, will raise the
PictureAlbumLoaded listener. Once this listener is called,
PictureAlbumLoaded is called. It first checks the
PictureAlbumLoadedEventArgs to verify the load was successful, and if not, throws an exception. If the load was successful, it continues on to dynamically generate an Image control for each
image in the collection.
private void LoadPictureAlbum()
private void PictureAlbumLoaded(object sender,
imageWidth = pictureAlbum.ImageWidth;
imageHeight = pictureAlbum.ImageHeight;
pictures = pictureAlbum.SlideShowImages;
imageCount = pictures.Count;
imagesToLoadCount = imageCount - 1;
for (int i = 1; (i < pictures.Count); i++)
string exc = "The SlideShowImages.xml file failed to load the "
exc += "PictureAlbum class successfully."
throw new Exception(exc);
Fading out an old image and fading in a new image
This one wasn't too bad, but it did take a little trial and error. Basically, I created a method with animation (
DoubleAnimation) to fade out the current
Image control. Inside that method, I wired up the storyboard's
Completed event. When the storyboard animation completes, the image will have faded, and the event will be raised. This event is tied to a callback method which then swaps the old
Image control with the new one and then calls
FadeIn is another animation (
DoubleAnimation) which, as its name implies, fades in the new
private void FadeOut()
DoubleAnimation fadeOutDoubleAnimation = new DoubleAnimation();
fadeOutDoubleAnimation.From = 1;
fadeOutDoubleAnimation.To = 0;
fadeOutDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
fadeOutStoryboard = new Storyboard();
fadeOutStoryboard.Completed += new EventHandler(FadeOutStoryboard_Completed);
private void FadeOutStoryboard_Completed(object sender, EventArgs e)
lastImageIndex = nextImageIndex;
if ((enableAutoPlay || buttonsStopStartAutoPlay) && leavePaused == false)
private void FadeIn()
DoubleAnimation fadeInDoubleAnimation = new DoubleAnimation();
fadeInDoubleAnimation.From = 0;
fadeInDoubleAnimation.To = 1;
fadeInDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
fadeInStoryboard = new Storyboard();
- February 23, 2009 - Added source code for VB.NET.