Loading Dynamic Images
Let's leverage some of the techniques we've been learning and build a slightly more
advanced Silverlight project, a dynamic photo gallery. There are multiple approaches
to building a similar photo gallery in Flash, all of them well documented online
(See Additional Resources below). Also, we will be introducing some new Silverlight
techniques that don't have an obvious Flash corelation. So we will forgo the Flash
example for this lesson and jump straight into the Silverlight project. But we will
be using Photoshop instead, to keep things interesting!
Here is the Silverlight Project we will be building:
Part 1 - Building the Interface
As you can see, I wanted the project to have some shadowing effects and a more intricate
background. So I created two .PNG images from Photoshop like so:
The first file lesson05_bkg.png will serve as the background image and the
second file lesson05_cover.png will server as our transparent cover that
will give us the desired inner shadow effects.
Create our Silverlight solution in Visual Studio and import these two images into
the project. After we do the typical modifications to Page.xaml we've been
doing for every project (sizing the control, adding a Loaded event handler
to the User Control tag, changing LayoutRoot from Grid to a Canvas), we're ready
to add the images to our stage. We will add lesson05_cover.png by using an
Image tag like the ones we used in Lesson 04. But let's introduce a new technique,
the ImageBrush, to paint the lesson05_bkg.png image directly onto
the background of the LayoutRoot canvas instead. The code for all these modifications
looks like this:
The decision to use an Image tag versus painting the image directly onto
the background of another element using an ImageBrush is a subtle one and
often determined by programmer preference. However, there are differences between
the two and experimenting with both of them is the best way to become familiar with
what each has to offer. You should note that besides ImageBrush, Silverlight
also provides a GradientBrush and a very powerful VideoBrush that
lets you paint a playing movie directly onto other elements as well!
We're going to add three more elements to Page.xaml. An empty Canvas tag
named thumbBar that will hold our collection of thumbnails we create dynamically.
An empty Image tag named photoFrame that will display the full sized image
when we select the thumbnail. And a basic Rectangle named locator that will
slide above our thumbnails to indicate which image is being displayed.
We will also add a Storyboard named sbMoveLocator that will control the sliding
animation for locator. I opened this project in Blend to finesse some nicer
KeySpline values for the Storyboard to make the animation ease locator
to its target more gracefully. We will also introduce another powerful trick for
combining XAML Storyboards and animation code. Notice that we've given a name of
locatorTarget to the SplineDoubleKeyFrame property of our Storyboard below
in Line 11. This will let us programatically change the target value of the Storyboard
in code but still leverage Blend and the ease of markup for all the rest.
This new Storyboard and the three additional elements now make our Page.xaml
look like this:
Next, let's add a new User Control to our solution and name it thumb.xaml.
This will serve as both the User Control that loads its designated photo from the
server and the visual thumbnail for that photo as well. Set the Width to
126 and the Height to 78 on the User Control. We don't need to add a Loaded
event this time. After changing LayoutRoot to a Canvas and removing its background
value, we need to add another Canvas tag within it named bkg with a Width
of 126, Height of 78, and background set to #444444. Add an empty Image tag
named photoThumb of the same dimensions that has its Opacity set to
zero and Stretch set to Fill.
We will adjust the height of bkg progressively as we load each image into
its thumb.xaml container to give us a vertical progress bar. Once each image
is loaded, we will set it as the source for photoThumb to have it appear
on screen. However, let's make it a little more elegant than just popping into view
once loaded by adding a Storyboard to our XAML that will fade up the Opacity of
photoThumb once called. Therefore, our final script for thumb.xaml
looks like this:
The three additions we'll make for now to the code behind file thumb.xaml.cs
are the X and Y public properties we've been using to all our User Controls to make
positioning easier, as well as a public integer called index. We also need
to add some library references we'll be using once we start working with the actual
images:
Although we haven't added any image logic to our thumb.xaml user control,
we have enough to start testing out our positioning logic and indicator animation
back on Page.xaml. So let's go to the code behind file Page.xaml.cs
and start adding these User Controls to the thumbBar canvas once Page.xaml
is loaded.
Here is the updated code:
In Line 12, we added a using reference to the Imaging library since Page.xaml.cs
will also be displaying images as well. At Line 20, we create a reference for whichever
thumb.xaml control will be the currently selected one. The value of this
will be made apparent when we start loading images a little later.
Line 24 is an interesting one and a common gotcha when dealing with transparent
PNG files. Since thumbBar is under our cover Image to take advantage
of the drop shadow effect, we need to make cover invisible for any hit testing
so as not swallow the MouseLeftButtonDown() events we want to add to to the
thumb objects in Line 30. Hopefully, the instantiation and positioning logic of
Line 25 through Line 32 is starting to look familiar.
Line 35 through Line 40 defines the handler for the MouseLeftButtonDown()
event we added in Line 30. The first thing it does is store a reference to the thumb
that was clicked in our thumbToShow variable in Line 37. Remember how we
gave a name of locatorTarget to the target value of our sbMoveLocator
Storyboard back in the Page.xaml? Line 38 and Line 39 lets us take advantage
of this now by quickly changing the target value of the Storyboard before calling
it to slide locator to its new position. This is a nice way to mix programatic
code with XAML Storyboards and doesn't force us to build our own animation engine
for a basic sliding effect with easing we can quickly compose in Expression Blend.
At this point, if you run the code you will see our application UI in place and
hopefully you can click on any of the grey thumb tiles and have locator slide
to the correct position above it. Check your code against the code above and make
sure any bugs are resolved before moving on.
Part 2 - Adding the Imaging Code
The majority of the imaging code needs to get added to the User Control thumb.xaml.cs
and looks like this:
The main function is defined on Lines 38 - 47 and named loadPhoto(). It is called
from Page.xaml.cs after each thumb instance has been positioned and had its public
index variable (Line 34) set to a unique number in our source photos naming sequence.
In the Dragging, Easing & Inertia Lesson, we used a URI and BitmapImage() to create an image source for our
Image object. We could repeat that same process here. However, we want to visually
represent the progress of the images as they each load from the server, so let's
use the more powerful WebClient object (Line 41) instead.
To do this, we attach an OpenReadCompleted event handler (Line 42) and a
DownloadProgressChanged
event handler (Line 44) to our WebClient object named imgLoader. Line 46 has
imgLoader
begin the actual file download request by passing in the photo's location relative to the page as
a URI in the OpenReadAsync() command. (You can get the image for your applicaiton by downloading the images below.)
Starting at Line 50, we have our DownloadProgressChanged() event handler. We only
need one line of code for our loading bar animation, since we are setting the Height
of our bkg canvas to reflect the progress percentage as it changes.
The rest of the work takes place once imgLoader finishes loading the file from the
server in the OpenReadCompleted handler (Line 56). We should check to make sure
we've aquired a valid result by querying the OpenReadCompletedEventArgs variable
we receive, e (Line 58). The actual photo data is in the stream referenced by
e.Result.
That is why we created a public System.IO.Stream variable in Line 36 called
image
to store a copy of this photo stream and make it available to Page.xaml later (Line
63). We want to use this photo stream data to also create the thumbnail, though,
so the rest of the function does that. We turn the e.Result data into a valid
BitmapImage()
and set it as the Source of our photoThumb Image in thumb.xaml. Since we initially
set photoThumb's Opacity to zero, once we have given it a valid Source in Line 65,
we can play the Storyboard sbShowThumb to fade it up.
Now lets add some code to Page.xaml.cs to take advantage of these changes in
thumb.xaml.cs.
Before we do, though, there are two more Storyboards I want to add to Page.xaml
as shown below that will control the transition between images whenever we want
to display a large copy of the photos we just loaded. They are named sbShowPhoto
(Line 8) and sbHidePhoto (Line 14). Together, they control the Opacity of our
photoFrame
image (Line 36). Also note that sbHidePhoto has a Completed event handler which
we will write up next.
Here is the final Page.xaml with the two additional Storyboards added:
And the changes we need to make to Page.xaml.cs are the following:
Line 36 calls the loadPhoto() function we created in thumb.xaml.cs. The only addition
needed in the MouseLeftButtonDown handler for our thumbs is shown on Line 43, where
we start the sbHidePhoto Storyboard to fade out any image currently on screen after
we store a reference of the thumb that was clicked in our variable thumbToShow.
Once this Storyboard is complete and photoFrame is completely transparent, the
Completed
event handler will get fired (Line 48). Now we can use our updated thumbToShow reference
to use the image stream we saved in each thumb and set it as the Source for the
larger photoFrame Image tag in Page.xaml. Once we've updated the photo to display
in photoFrame, the only thing left to do is fade it back up by calling sbShowPhoto.Begin()
in Line 53.
So in this lesson, we combined many of the techniques we've been learning into a
larger Silverlight photo gallery application. We also introduced ImageBrush and
WebClient, two powerful objects we have at our disposal. However, there are still
many more features to explore. In our next lesson, we will compare Flash masking
with Silverlight clipping and OpacityMask features.
Additional Resources
You can find many Flash photo gallery tutorials online. Here is one for Actionscript
1.0:
http://www.kirupa.com/developer/mx2004/xml_flash_photogallery.htm
and one for Actionscript 3.0:
http://www.flashmagazine.com/tutorials/detail/as3_photo_gallery/
Here is a good introduction to VideoBrushes in Silverlight:
http://msdn.microsoft.com/en-us/library/bb404773.aspx
And a nice example of animating video using a Silverlight VideoBrush:
http://dotnetslackers.com/articles/silverlight/Animation...Silverlight20Beta4.aspx