Use code to make a slideshow in C# and WPF

This is example shows how to make a slideshow in WPF. Unlike all of the examples I’ve seen on the internet, however, this one uses C# code rather than XAML. It’s actually fairly simple, it just took a long time to figure out how to do it.

The main takeaway from this example is a better understanding off how WPF storyboards work.

The Code

The following XAML code shows how the program builds its single Image control.

<Grid>
    <Image Margin="5,5,5,5" Name="imgPicture" Stretch="Uniform" />
</Grid>

This code simply makes an Image control that fills the program’s main Grid control minus a five-pixel margin.

The program executes the following code when it starts.

private List<BitmapImage> Images = new List<BitmapImage>();
private int ImageNumber = 0;
private DispatcherTimer PictureTimer = new DispatcherTimer();

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    DirectoryInfo dir_info = new DirectoryInfo(Environment.CurrentDirectory);
    foreach (FileInfo file_info in dir_info.GetFiles())
    {
        if ((file_info.Extension.ToLower() == ".jpg") ||
            (file_info.Extension.ToLower() == ".png"))
        {
            Images.Add(new BitmapImage(new Uri(file_info.FullName)));
        }
    }

    // Display the first image.
    imgPicture.Source = Images[0];

    // Install a timer to show each image.
    PictureTimer.Interval = TimeSpan.FromSeconds(3);
    PictureTimer.Tick += Tick;
    PictureTimer.Start();
}

This code starts by creating a list of BitmapImage objects to hold the images that the slideshow will display.

Alternatively you could store the names or URIs of the files instead of pre-loading the images themselves. If the slideshow will display a large number of big images, then you may be unable to load all of the images into memory when the program starts, so it may be better to store only their names or URIs.

However, there is sometimes a delay when loading an image file. If the slideshow loads each image as it is needed, there may be a delay that will mess up the show. To avoid that, you might want the program to pre-load the next image after it displays the current one. That way the program will have time to finish loading the next image before it is needed.

After it creates the list of images, the code defines variable ImageNumber to keep track of the image in the list that is currently being displayed. It also creates a DisplatcherTimer (from the System.Windows.Threading namespace) that will display the images.

When the program’s window loads, the program creates a DirectoryInfo object and loops through the files returned by its GetFiles method. For this example, I added several small images to the project, set their Build Action properties to Content, and set their Copy to Output Directory properties to Copy if Newer. When Visual Studio builds the program starts, it automatically copies the files into the executable directory so the program can find them easily. If you want the program to look for the files in some other directory, pass that directory’s path into the DirectoryInfo constructor.

When it loops through the returned files, if the file’s extension is .png or .jpg, the program uses its name to create a Uri object representing the file’s location, uses that to create a BitmapImage, and adds the image to the Images list.

Next the displays the first image in the imgPicture Image control.

The code then sets the timer’s interval to three seconds so it will display images three seconds apart. In a real slideshow, you might want to make this longer, but I wanted it easy to see in the example program. The code assigns the Tick event handler to handle the timer’s Tick event and then starts the timer.

When the timer’s Tick event occurs, the following code executes.

// Display the next image.
private void Tick(object sender, System.EventArgs e)
{
    ImageNumber = (ImageNumber + 1) % Images.Count;
    ShowNextImage(imgPicture);
}

private void ShowNextImage(Image img)
{
    const double transition_time = 0.9;
    Storyboard sb = new Storyboard();

    // ***************************
    // Animate Opacity 1.0 --> 0.0
    // ***************************
    DoubleAnimation fade_out = new DoubleAnimation(1.0, 0.0,
        TimeSpan.FromSeconds(transition_time));
    fade_out.BeginTime = TimeSpan.FromSeconds(0);

    // Use the Storyboard to set the target property.
    Storyboard.SetTarget(fade_out, img);
    Storyboard.SetTargetProperty(fade_out,
        new PropertyPath(Image.OpacityProperty));

    // Add the animation to the StoryBoard.
    sb.Children.Add(fade_out);


    // *********************************
    // Animate displaying the new image.
    // *********************************
    ObjectAnimationUsingKeyFrames new_image_animation =
        new ObjectAnimationUsingKeyFrames();
    // Start after the first animation has finisheed.
    new_image_animation.BeginTime = TimeSpan.FromSeconds(transition_time);

    // Add a key frame to the animation.
    // It should be at time 0 after the animation begins.
    DiscreteObjectKeyFrame new_image_frame =
        new DiscreteObjectKeyFrame(Images[ImageNumber], TimeSpan.Zero);
    new_image_animation.KeyFrames.Add(new_image_frame);

    // Use the Storyboard to set the target property.
    Storyboard.SetTarget(new_image_animation, img);
    Storyboard.SetTargetProperty(new_image_animation,
        new PropertyPath(Image.SourceProperty));

    // Add the animation to the StoryBoard.
    sb.Children.Add(new_image_animation);


    // ***************************
    // Animate Opacity 0.0 --> 1.0
    // ***************************
    // Start when the first animation ends.
    DoubleAnimation fade_in = new DoubleAnimation(0.0, 1.0,
        TimeSpan.FromSeconds(transition_time));
    fade_in.BeginTime = TimeSpan.FromSeconds(transition_time);

    // Use the Storyboard to set the target property.
    Storyboard.SetTarget(fade_in, img);
    Storyboard.SetTargetProperty(fade_in,
        new PropertyPath(Image.OpacityProperty));

    // Add the animation to the StoryBoard.
    sb.Children.Add(fade_in);

    // Start the storyboard on the img control.
    sb.Begin(img);
}

The Tick event handler increments ImageNumber and then calls the ShowNextImage method. This version takes ImageNumber modulo the number of images, so it repeatedly displays the images forever. If you want the slideshow to run through the image only once, you could disable the timer when ImageNumber equals the number of images. Or you could randomize the list and let ImageNumber wrap around again to zero to display the images again in a new order.

The ShowNextImage method creates a storyboard that makes the slideshow’s current image fade out and then makes a new image fade in. A storyboard is basically a collection that holds timelines such as animations. The storyboard also provides targeting information for those timelines. When the timelines are property animations, as they are in this example, the storyboard tells those animations what properties they will animate.

The ShowNextImage method first creates its storyboard. It then makes a DoubleAnimation object to animate the Image control’s Opacity property. the animation will make that property go from value 1.0 (fully opaque) to 0.0 (fully transparent) over the time span given by the constant transition_time. In this example, I set transition_time to 0.9, so it takes the image 0.9 seconds to fade out.

The code sets the animation’s BeginTime to zero, so the animation starts as soon as the storyboard starts.

Having initialized the animation object, the code uses the Storyboard class’s static SetTarget method to tell the animation that it will be targeting the Image control named img, which is passed into the method as a parameter. It then uses the SetTargetProperty method to tell the animation that it will be targeting the Image control’s Opacity property. You can define the property path in several ways including by using a string. This version uses the dependency property Image.OpacityProperty. (That seems easier in this example because you don’t need to know the path format and you can’t mistype it.)

At this point, the animation knows all it needs to do its job. The program adds it to the storyboard’s Children collection.

Next the program creates a second animation. This time it makes an ObjectAnimationUsingKeyFrames object. This type of animation lets you change a property abruptly from one value to another. The values that this animation uses are stored in DiscreteObjectKeyFrame objects. This is the only kind of animation that makes sense when you are animating a property that is an object. in this example, the animation will swap the Image control’s image. While you can make Opacity range from 1.0 to 0.0, you can’t really make an image slowly transition from one object to another. (well, you could, of course, but the WPF classes aren’t smart enough to know how to transition smoothly between objects in general.)

For this example, the ObjectAnimationUsingKeyFrames object starts running transition_time seconds after the storyboard starts. That makes it start after the first animation finishes.

The program then adds a DiscreteObjectKeyFrame to the animation. The constructor initializes that object’s value to the next image that the slideshow should display. It sets the object’s key time to zero so the object switches the property as soon as the animation starts. In general you could make the ObjectAnimationUsingKeyFrames include several ObjectAnimationUsingKeyFrames objects and they could make the property switch from one value to another at different times during the animation.

After it finishes initializing the ObjectAnimationUsingKeyFrames object, the code adds it to the animation’s KeyFrames collection. It then uses the storyboard’s SetTarget and SetTargetProperty methods to tell the animation that it will be acting on the img control’s Source property. It then adds the new animation to the storyboard.

At this point the program has created two animations, one to fade the image’s Opacity property from 1.0 to 0.0 and a second to switch the control’s image. Next the program creates a new DoubleAnimation to fade the Opacity back from 0.0 to 1.0 so the new image will appear. This is similar to the previous DoubleAnimation except this one starts after the previous animations end.

After it has finished creating the three animations, the program calls the storyboard’s Begin method to start ths storyboard running. That call is asynchronous so the ShowNextImage method returns immediately and the animation continues running on its own schedule.

Conclusion

You may not need a slideshow program, but there’s a good chance that you’ll eventually need to use storyboards in your WPF applications. This example shows how you can use a storyboard to play multiple animations in the same timeline.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in animation, wpf and tagged , , , , , , . Bookmark the permalink.

2 Responses to Use code to make a slideshow in C# and WPF

  1. Sinan Basalan says:

    Mr. Stephens, please do not give more trouble us to get such error messages. If you a good stuff then why not use our era’s framework. Please change your mindset to provide project with our era’s framework.
    https://pasteboard.co/J5os8qP.jpg

    • RodStephens says:

      Sorry but I have no idea what version of Visual Studio anyone might be running or what service packs you may have installed. I use an older version because Visual Studio can update it but it cannot downgrade newer programs to an older version if you only have the older version. I don’t know why Visual Studio didn’t just upgrade that for you. (IMHO Visual Studio 2008 is also a better version than the newest version for most purposes. It’s smaller, faster, and easier to use.)

      In any case, you can copy and paste the XAML and C# code into your version of Visual Studio if you have to.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.