Hard to believe that it's been about a year and a half since I wrote the Photo Gallery Wall application in Silverlight 1.0. I've gotten quite a few emails requesting a C# version of the application, and while I did give permission for it to get translated into C#/Silverlight 2 (an effort that was completed just a few weeks ago), Silverlight 3 is the release I was waiting on to do an update.
Why wait? Silverlight 3 saved me a lot of trouble - the bitmap effects make perfect drop shadows with nice, soft edges, no imperfectly-shaped paths or duplicate text fields. I also get perspective transforms, so now I can build out the panels along a flat plane, and rotate the entire panel into 3D. One more new feature I used was the ImageCompleted event. No more image "popping" when a storyboard starts before an image is fully loaded.
The default view of the application is a gallery with 16 images in it:
If no action is taken, the gallery will cycle through the images. If you select a thumbnail on the left, the image is displayed on the panel on the right for 20 seconds before the slideshow is resumed. If you would like a closer look at an image while the slideshow is running, you can click the image on the right and the panel will animate to an alternate view as shown following:
If you have Silverlight 3 installed, the completed app can be seen here. Since the code is available at the end of this post, I figured I'd hit on a few points from the application, and let you dig up the rest if you're so inclined.
Reflections
My first approach with the application was to insert the images as object instances, so I could use just one object. However, since I had the element binding available to me, I decided to go the long way and actually create each image element to take some of the new features for a spin. This was done by creating a Canvas, and then placing an Image and Rectangle element inside. The Image element is used to hold the thumbnail, and the Rectangle element is used for a stroke highlight when the mouse is over the selected thumbnail. Notice that the image has a DropShadow effect on it.
<Canvas x:Name="canvas" Grid.Row="0" Grid.Column="0" Opacity="0"> <Image x:Name="ImageTile_00" Source="images/msh640x480.jpg" Opacity="1" Width="120" Height="93" Margin="5,5,0,0" Stretch="Fill" Cursor="Hand"> <Image.Effect> <DropShadowEffect Opacity="0.6" ShadowDepth="3"/> </Image.Effect> </Image> <Rectangle x:Name="StrokeHighlight_00" Width="120" Height="93" Stroke="#FFFFBA00" Opacity="0" Margin="5,5,0,0" StrokeThickness="2"/> </Canvas>
Each of the thumbnail images is duplicated in the reflections container, and bound to its counterpart using the element to element binding of Silverlight 3. The code for the reflections container that matches the image container shown above looks like this:
<Canvas x:Name="Reflection" Grid.Row="0" Grid.Column="0" Opacity="{Binding ElementName=canvas, Mode=TwoWay, Path=Opacity}"> <Image x:Name="ReflectionTile_00" Source="{Binding ElementName=ImageTile_00, Mode=TwoWay, Path=Source}" Opacity="{Binding ElementName=ImageTile_00, Mode=TwoWay, Path=Opacity}" Width="120" Height="93" Margin="5,5,0,0" Stretch="Fill" Cursor="Hand"> <Image.Effect> <DropShadowEffect Opacity="0.6" ShadowDepth="3"/> </Image.Effect> </Image> <Rectangle x:Name="ReflectionStroke_00" Width="120" Height="93" Stroke="#FFFFBA00" Opacity="{Binding ElementName=StrokeHighlight_00, Mode=TwoWay, Path=Opacity}" Margin="5,5,0,0" StrokeThickness="2"/> </Canvas>
Notice that I've bound multiple properties - for the Image, both Source and Opacity are bound. This means if I change a thumbnail image, the reflection image updates automatically. If I animate the Opacity property of the thumbnail image, the reflection image opacity also animates. The text caption on the right side panel was done the same way.
ImageOpened Event
There are two sets of storyboards in the project. One that handles the slideshow functionality, and one that handles the selected image functionality. Both work in essentially the same manner. Fade the image being displayed, update the image to the next one to display, and then show the image. I handled the last step in an ImageOpened event handler to ensure that the target image was available before begining the "show" storyboard.
For the change that occurs when the main image fades out, that event listener looks like this:
SelectedImage.ImageOpened += new EventHandler<RoutedEventArgs>(SelectedImage_ImageOpened);
My event handler handles both the case of a new image being loaded for the slideshow, or a new image being loaded by user selection. I flip a flag called StopSlideshow when a thumbnail is selected, and then test against that to shift to the appropriate storyboard set. The handler looks like this:
private void SelectedImage_ImageOpened(object sender, RoutedEventArgs e) { if (!StopSlideshow) { ShowMainImage.Begin(); } else { ShowForSelected.Begin(); } }
All of the images for the application are configured via an XML file called imagefile.xml, which is stored in the ClientBin folder. The format is fairly self-explanatory, but looks like this:
<image thumb="Images/adamsSunrise640x480_thumb.jpg"
url="Images/adamsSunrise640x480.jpg"
caption="Sunrise over Mt. Adams (from Mt. St. Helens)"/>
There were two things I did not code into this version of the application, which I will likely go back and add later. The first is the "spotlight" effect seen in the original application, which was done with an opacity mask. The reason why I left that off is that it doesn't work right in the current rev of Silverlight 3. I get an unmasked edge along the left of the panel. I haven't decided yet if I like the cleaner, brighter look of the updated app more.
The second is a simple test to check and see if the selected image is the same as the one being displayed. If it is, no action should be taken, but as it stands, the app still accepts input, and cycles out of and then in to the same image, which is not an ideal experience, but it's getting late....
Now, as promised, the code for this project can be found here.


Entries (RSS)