Friday, December 5, 2008

How to Make a Silverlight Image Slide Show

There's a lot of websites out there with some very cool image slideshows. I figured I would take the time to write a short tutorial on how to make a basic image slideshow with fade in/out transitions between images.

First things first... The xaml...

<UserControl x:Class="ImageSlideshow.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<
UserControl.Resources>
<
Storyboard x:Name="FadeOutAnimation">
<
DoubleAnimation Duration="00:00:00.50" From="1" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="theImage" />
</
Storyboard>
<
Storyboard x:Name="FadeInAnimation">
<
DoubleAnimation Duration="00:00:00.50" From="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="theImage" />
</
Storyboard>
</
UserControl.Resources>
<
Grid x:Name="LayoutRoot" Background="Black">
<
Image x:Name="theImage" Stretch="Uniform"/>
</
Grid>
</
UserControl>

This is all the xaml it takes to make a very basic slide show. The application itself consists of a Grid and an image inside the grid. I have set the Stretch property to Uniform so the image will take up the entire screen or as much of it as the aspect ratio of the image will allow. The other important piece here is the Resources section. The FadeInAnimation and FadeOutAnimation modify the image's Opacity property from 0 to 1 or 1 to 0 respectively.


On to the code behind.



Now, create a timer which will be used to transition between images. To initialize the timer, create a new event handler for the tick. Set the value of 'INTERVAL' to the number of seconds you wish to display each image.


timer.Interval = new TimeSpan(0, 0, 0, INTERVAL, 0);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();

Now create an ObservableCollection of strings to hold the names of the images. Also create an integer to indicate which image is currently displayed.


private ObservableCollection<string> _images;
private int _currentIndex = 0;

To initialize the _images collection I created a separate method (good programming practice!)

private void InitializeImages()
{
_images = new ObservableCollection<string>()
{
"Autumn Leaves.jpg",
"Creek.jpg",
"Desert Landscape.jpg",
"Dock.jpg",
"Forest Flowers.jpg",
"Forest.jpg"
};

SetImageSource(_images[_currentIndex]);
}

These strings correspond to the names of the images I placed in a new directory I created in the solution named Images. Next, create a method named SetSource (which is the method I call at the end of the InitializeImages method). I separated this into a separate method because the code inside the method will be run over and over.


private void SetImageSource(string imageName)
{
string name = "Images/" + imageName;
BitmapImage bmi = new BitmapImage(new Uri(name, UriKind.Relative));
theImage.Source = bmi;
}

All this method does is set the source of the image object to the image that has the name that is passed in as the input parameter. You cannot just set the images.Source property to the string passed in, you need to set it to a BitmapImage object which we can create based on the string passed in.

We're getting close! Now implement the timer's tick event handler


void timer_Tick(object sender, EventArgs e)
{
_currentIndex++;

if (_currentIndex == _images.Count)
{
_currentIndex = 0;
}

FadeOutAnimation.Begin();
}

Here, you increment the _currentIndex. If the index reaches the end of the list, simply set it back to zero. Once the index has been updated, fade out the image. Do this by calling the Begin method of the FadeOutAnimation defined in the xaml. The last thing that needs to be done is to define an even handler (in the class constructor) for the Completed event of the FadeOutAnimation. When the animation is finished, set the image source to the next image and fade the image back in by calling the FadeInAnimation's Begin method.

void FadeOutAnimation_Completed(object sender, EventArgs e)
{
SetImageSource(_images[_currentIndex]);
FadeInAnimation.Begin();
}


That's it! Not a lot of code! This could easily be modified to add some play, stop, pause, next, previous controls. It would also not take a lot of work to add different animations to change the transitions between the images. 

Good luck and have fun with this one!

10 comments:

  1. This raises errors. You should have published the entire code-behind source it looks like you've taken interested persons for granted and have skipped providing some code.

    ReplyDelete
  2. no. this is literally all the code it took. What kind of errors are you running into?

    ReplyDelete
  3. // current code runs which results in all black page but no images displayed:

    public MainPage()
    {
    InitializeComponent();

    timer.Interval = new TimeSpan(0, 0, 0, 4, 0); // 4 seconds per slide
    timer.Tick += new EventHandler(Timer_Tick);
    timer.Start();

    InitializeImages();
    }

    // If InitializeImages() is not called from MainPage() a NullReferenceException is raised in the Timer_Tick method.

    // Intellisense says FadeOutAnimation_Completed is never used.

    ReplyDelete
  4. // the source...

    public partial class MainPage : UserControl
    {

    System.Windows.Threading.DispatcherTimer timer = new DispatcherTimer();

    private ObservableCollection _images;
    private int _currentIndex = 0;

    public MainPage()
    {
    InitializeComponent();

    timer.Interval = new TimeSpan(0, 0, 0, 4, 0); // 4 seconds per slide
    timer.Tick += new EventHandler(Timer_Tick);
    timer.Start();

    InitializeImages();
    }

    private void InitializeImages()
    {
    _images = new ObservableCollection()
    {
    "DigitalSignage_280x140_1.png",
    "DigitalSignage_280x140_2.png",
    "DigitalSignage_280x140_3.png",
    "DigitalSignage_280x140_4.png",
    "DigitalSignage_280x140_5.png",
    "DigitalSignage_280x140_6.png",
    "DigitalSignage_280x140_7.png",
    "DigitalSignage_280x140_8.png",
    "DigitalSignage_280x140_9.png",
    "DigitalSignage_280x140_10.png",
    "DigitalSignage_280x140_11.png",
    "DigitalSignage_280x140_12.png"
    };
    SetImageSource(_images[_currentIndex]);
    }


    private void SetImageSource(string imageName)
    {
    var imagePath = "Images/" + imageName;
    var bmi = new BitmapImage(new Uri(imagePath, UriKind.Relative));
    theImage.Source = bmi;
    }

    void Timer_Tick(object sender, EventArgs e)
    {
    _currentIndex++;
    if (_currentIndex == _images.Count) // NullReferenceException?
    {
    _currentIndex = 0;
    }
    FadeOutAnimation.Begin();}


    void FadeOutAnimation_Completed(object sender, EventArgs e)
    {
    SetImageSource(_images[_currentIndex]);
    FadeInAnimation.Begin();
    }
    }

    ReplyDelete
  5. I believe the probelm is in this section of code:

    public MainPage()
    {
    InitializeComponent();

    timer.Interval = new TimeSpan(0, 0, 0, 4, 0); // 4 seconds per slide
    timer.Tick += new EventHandler(Timer_Tick);
    timer.Start();

    InitializeImages();
    }

    What you want to do is call the InitializeImages method before you start the timer.

    Changing that to this should take care of it:

    public MainPage()
    {
    InitializeComponent();

    InitializeImages();

    timer.Interval = new TimeSpan(0, 0, 0, 4, 0); // 4 seconds per slide
    timer.Tick += new EventHandler(Timer_Tick);
    timer.Start();
    }

    Let me know if that works out for you. Good luck.

    ReplyDelete
  6. I had tried that order of operations but it just loads a black page. I liked your way of thinking so I really want to get this working so I have a base to learn and build on so thanks for hanging in there.

    I've also learned there's three types of timers. Is the DispatchTimer the right type timer object?

    I placed breakpoints throughout and see data where I think it is supposed to be but we're still missing something here: a slide :-)

    Another note: Intellisense greys out FadeOutAnimation_Completed so its not being called or what?

    ReplyDelete
  7. Ok. First, make sure the build action on all your images is Resource. Now, I found the original project and ran it. Everything works just fine, so here is the entire source file. You should just be able to copy and paste, change the image names and run the app. Make sure you are defining the storyboard in xaml too.

    Here is the code.

    using System;
    using System.Windows.Controls;
    using System.Collections.ObjectModel;
    using System.Windows.Media.Imaging;
    using System.Windows.Threading;

    namespace ImageSlideshow
    {
    public partial class Page : UserControl
    {
    private ObservableCollection _images;
    private int _currentIndex = 0;
    private DispatcherTimer timer = new DispatcherTimer();

    private const int INTERVAL = 3;

    public Page()
    {
    InitializeComponent();

    InitializeImages();
    AddEventHandlers();
    InitializeTimer();
    }

    private void AddEventHandlers()
    {
    FadeOutAnimation.Completed += new EventHandler(FadeOutAnimation_Completed);
    }

    private void InitializeTimer()
    {
    timer.Interval = new TimeSpan(0, 0, 0, INTERVAL, 0);
    timer.Tick += new EventHandler(timer_Tick);
    timer.Start();
    }

    private void SetImageSource(string imageName)
    {
    string name = "Images/" + imageName;
    BitmapImage bmi = new BitmapImage(new Uri(name, UriKind.Relative));
    theImage.Source = bmi;
    }

    private void InitializeImages()
    {
    _images = new ObservableCollection()
    {
    "Autumn Leaves.jpg",
    "Creek.jpg",
    "Desert Landscape.jpg",
    "Dock.jpg",
    "Forest Flowers.jpg",
    "Forest.jpg"
    };

    SetImageSource(_images[_currentIndex]);
    }

    void FadeOutAnimation_Completed(object sender, EventArgs e)
    {
    SetImageSource(_images[_currentIndex]);
    FadeInAnimation.Begin();
    }

    void timer_Tick(object sender, EventArgs e)
    {
    _currentIndex++;

    if (_currentIndex == _images.Count)
    {
    _currentIndex = 0;
    }

    FadeOutAnimation.Begin();
    }
    }
    }

    ReplyDelete
  8. About the timer object... yes the dispatcher timer is the object you want to use in your silverlight applications.

    I wrote another post on the timers here:
    http://dotnetgui.blogspot.com/2008/07/what-timer-to-use-for-wpf-and.html

    Good luck.

    ReplyDelete
  9. All fine now thank you. I had to make a new project that did not host in a website. Otherwise was still getting the same results, a black display and no slides.

    ReplyDelete
  10. But what is the issue if it is hosted in the website. I tried the code above and getting a black screen.

    ReplyDelete