[C# Helper]
Index Books FAQ Contact About Rod
[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

[C# 24-Hour Trainer]

[Beginning Software Engineering]

[C# 5.0 Programmer's Reference]

[MCSD Certification Toolkit (Exam 70-483): Programming in C#]

Title: Use code to make a slideshow in C# and WPF

[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 the example to experiment with it and to see additional details.

© 2009-2022 Rocky Mountain Computer Consulting, Inc. All rights reserved.