IN THIS CHAPTER
.ZIP
FileSilverlight includes a special downloader object that can fetch all sorts of content from your web server, such as XAML files, JavaScript files, font files, images, videos, or even all these inside a .ZIP
file. This enables you to easily delay the retrieval of content until it is needed, which helps you create a more responsive user experience. Or, if nothing else, you can provide a sexy progress indicator while all the content downloads rather than relying on the default browser experience.
The downloader issues HTTP GET
requests that don’t refresh the current web page, much like the XmlHttpRequest
object that has become the foundation for Asynchronous JavaScript and XML (AJAX). If you’ve used the XmlHttpRequest
object that browsers provide to JavaScript, using the Silverlight downloader will be a familiar experience because it was modeled after the object.
To initiate a download, you must create the downloader object with a call to the Silverlight control’s CreateObject
function, optionally attach some event handlers, and call the downloader’s Open
and Send
functions. The following JavaScript demonstrates how to initiate a download to a secondary XAML file (MoreXaml.xaml
) inside the onLoad
event handler for the primary XAML content:
For Silverlight 1.0, "downloader"
(case-insensitive) is the only valid parameter for CreateObject
. Future versions of Silverlight might include additional objects you can create with this mechanism. The downloader defines a Completed
event that will be raised when the download successfully completes. This code uses the AddEventListener
function discussed in the preceding chapter to attach an onCompleted
handler defined in the next section.
The downloader’s Open
and Send
functions are simplified versions of XmlHttpRequest
’s open
and send
functions. The first parameter must always be set to the string "GET"
, referring to the HTTP verb GET. The second parameter is the URL of the file you want to download, relative to the HTML page hosting the JavaScript. The Send
function sends the HTTP GET
request, which is always performed asynchronously. That is why you should listen for the Completed
event if you want to perform an action as soon as the file download is complete. The downloader also has an Abort
function for stopping the download, so you can support a user interface with a cancel button.
Warning
The URL passed to the downloader’s Open
function has several limitations!
The URL passed to Open
must be a relative URL, although you could always start it with a forward slash if you want to reference something relative to the domain root. The result of this limitation is that you can only use the Silverlight downloader to download content from the same domain (and same protocol) that served the current web page. This is consistent with the policy that browsers enforce with the XmlHttpRequest
object and Silverlight enforces for its XAML source
, although these other cases do support absolute URLs as long as the domain matches.
Also, a web page sitting on your local hard drive can’t use the downloader to retrieve content via normal file system paths. Instead, you must host the content on a web server (even if it is just localhost
).
When the download is complete, your Completed
event handler is called (if you attached one), and the downloader itself is passed as the first parameter. The downloader has a property called ResponseText
that contains the actual downloaded content represented as a string. (In the preceding example, that would be the content of MoreXaml.xaml
.)
If the control’s current XAML contained a Canvas
as the root element, and if you wanted to add the entire downloaded XAML content as a new child to this Canvas
, you could use the following implementation of a Completed
event handler:
The downloader object has a Status
property (and corresponding StatusText
property) that represents the HTTP status code returned from the underlying HTTP GET
request. Because the Completed
event handler is only called after a successful download, there are only two expected status codes: 200 (with StatusText
set to “OK”) and 204 (with StatusText
set to “No content”). The downloader also has a URI
property set to whatever URL was passed to the Open
call. This is convenient for determining which download has completed if you’re handling more than one from the same event handler.
As with any network request, a download might fail unexpectedly. You can attach a handler to the downloader’s DownloadFailed
event and handle the failure in a custom way. DownloadFailed
event handlers are passed the downloader as the sender
and the same errorEventArgs
parameter passed to the ImageFailed
event mentioned in Chapter 5, “Brushes and Images.” By default, the error would be handled by the default onError
event handler attached to the control (such as the default_error_handler
function discussed in Chapter 1, “Getting Started”).
Tip
Although you can parse and load downloaded XAML in two easy steps inside a Completed
event handler (retrieving the ResponseText
and then sending it to CreateFromXaml
), the Silverlight control’s Content
property defines another XAML-parsing function optimized specifically for downloaded XAML. This function is called CreateFromXamlDownloader
, and it accepts the entire downloader object rather than a string. Therefore, instead of writing the following code inside a Completed
event handler
you should write the following:
var newContent = sender.GetHost().Content.CreateFromXamlDownloader(sender, "");
This is more efficient than using CreateFromXaml
because it avoids copying the XAML content into a temporary string. The second parameter to CreateFromXamlDownloader
is only relevant for downloading packages, which are described in the next section.
When downloading binary content (font files, images, or videos), you shouldn’t use the ResponseText
property on the downloader object. Instead, much like the CreateFromXamlDownloader
mechanism, UI elements that know how to display text (TextBlock
), images (Image
and ImageBrush
), and videos (MediaElement
) define a SetSource
(or SetFontSource
) function that accepts the downloader object and a part name. Chapter 4, “Text,” demonstrated this with the TextBlock
element.
Tip
For the best performance, you should detach all downloader event handlers and then set the downloader instance to null
when you’re done with it (inside the Completed
event handler, for example).
.ZIP
FileThe downloader supports retrieving a package containing multiple parts and (most importantly) selectively retrieving those parts after the download has finished. You can take advantage of this package support by compressing your files into a .ZIP
file (using a tool such as WinZip or the compressed folder functionality in Windows).
Initiating a download of a .ZIP
file is no different than initiating a download of any other file; simply give the appropriate URL to the downloader’s Open
function. The difference is in the consumption of the downloaded data. Rather than using the ResponseText
property to retrieve the downloaded content, you should call the downloader’s Get
ResponseText
function. This accepts a “part name” parameter, enabling you to specify which part of the package you want to retrieve. For .ZIP
files, the “part name” is simply the filename inside the .ZIP
file. Therefore, if you downloaded a .ZIP
file containing two XAML files and two JavaScript files, you could retrieve them inside a Completed
event handler as follows:
The second parameter of CreateFromXamlDownloader
is the same “part name” you can give to GetResponseText
(or an empty string if you didn’t download a package), so you can call it as follows to efficiently parse and load XAML that was downloaded inside a .ZIP
file:
The various SetSource
and SetFontSource
functions also accept a “part name” parameter. Therefore, to use binary content inside a .ZIP
file, be sure to pass the filename as the second parameter.
Silverlight handles uncompressing the content, so the efficiency gained by combining and compressing multiple files in a single .ZIP
file is practically abstracted away to your JavaScript code. (This is crucial because the ability to download a .ZIP
file wouldn’t be very interesting if you didn’t have a way to uncompress it!)
Tip
Your .ZIP
file can have any file extension and still work with the Silverlight downloader, unless it contains font files. Downloaded fonts inside a package can only be applied to a TextBlock
if the package extension is .ZIP
. This is simply a bug in Silverlight 1.0.
Because the downloader does its work asynchronously, you are free to show some sort of “loading” user interface while users wait for the download to complete. These days, graphics that make no commitment about how much progress remains are pretty popular, such as the spinning blue wait cursor in Windows Vista or the series of pulsing dots on websites such as expedia.com. You could certainly show such a graphic (sometimes called an indeterminate progress bar) after calling Send
, and then hide it after your Completed
event handler is called. But Silverlight makes it possible for you to go a step further and provide a determinate progress bar, thanks to an event and a property that tells you exactly how much of the download remains.
This event is called DownloadProgressChanged
, and it is called throughout the download process, whenever progress changes by at least .05%. The downloader’s relevant property is called DownloadProgress
, which is a number between 0 and 1—where 0 means that no progress has been made and 1 means that the download is finished.
The following two listings take advantage of the DownloadProgressChanged
event to show a custom progress bar before the main XAML content is loaded and rendered. Listing 8.1 contains the initial “loading” user interface with a simple progress bar. Listing 8.2 contains the code needed to download content, update the progress bar, and then replace the UI with the downloaded XAML when complete.
Listing 8.1 Loading.xaml
—The Initial “Loading” User Interface
Listing 8.2 Code to Handle the Download and Update the Progress Bar
The progress bar is created with two Rectangle
s: a static one for the background and a dynamically changing one for the foreground. A TextBlock
is also used to show the percentage complete in a numeric fashion. The two elements that need to be updated from JavaScript are given names.
In the corresponding JavaScript, the handler for the DownloadProgressChanged
event is pretty simple thanks to the downloader’s DownloadProgress
property. The downloader is passed as the first parameter to this handler, just as with the Completed
event. Because the progress bar’s width is 100, multiplying the DownloadProgress
value by 100 not only gives the percent complete, but also the desired width of the progressBar Rectangle
. (Math.floor
is used to round down the potentially fractional value when displayed as text, but this value is fine as is for Width
.)
The onCompleted
handler retrieves, parses, and loads the XAML content inside the .ZIP
file (assumed to be in a file called Main.xaml
), and then it replaces the progress bar with the “real” user interface in two easy steps. First, it clears all children from the root Canvas
, and then it adds the new content (which must have a single root of its own) as a single child to the existing Canvas
.
The result of running this code is demonstrated with a few snapshots in Figure 8.1.
Figure 8.1 The progress bar updates as the content gets downloaded.
If you use the technique from Chapter 5 to get a gradient with a crisp line, you could accomplish the same visual effect from Figure 8.1 with only one Rectangle
rather than two. The idea is to adjust the Offset
of GradientStop
s as progress is made. The following XAML accomplishes this:
if it is used with Listing 8.2 and the following update to onProgressChanged
:
The crisp line in the gradient, enabled by middleStop1
and middleStop2
, moves from left to right as the Offset
of these elements is set to the current value of the downloader’s DownloadProgress
property. The raw property value can be used directly because gradients operate on the same range of 0 to 1.
The progress bar from Figure 8.1 is very plain and simple, but you can use the same techniques to create really innovative progress indicators that match the design of your site. For example, the following XAML can be used with Listing 8.2 and the updated implementation of onProgressChanged
that updates the two GradientStop Offset
s:
By switching the Rectangle
to an Ellipse
and the LinearGradientBrush
to a RadialGradientBrush
(with different colors), you get a vastly different effect from Figure 8.1, shown in Figure 8.2.
Figure 8.2 The customized progress bar updates as the content gets downloaded.
Depending on the effect you want, you could remove the extra GradientStop
s that enable the crisp line:
and update a single Offset
with the current DownloadProgress
value:
This produces the result in Figure 8.3.
Figure 8.3 Another look for a customized progress bar, created by tweaking the GradientStop
s and colors.
Another simple idea for a customized progress bar would be to make the dynamic gradient become the Foreground
brush of a single TextBlock
:
The following implementation of onProgressChanged
moves the horizontal crisp line in the gradient from top to bottom:
The result is shown in Figure 8.4. Note that this example retrieves each GradientStop
by calling GetItem
on the GradientStop
s collection rather than via FindName
. Silverlight currently has a bug that can cause the setting of a TextBlock
’s Text
property to fail when its Foreground
is either set to a brush with a Name
or a brush containing an element (such as a GradientStop
) with a Name
.
Figure 8.4 The TextBlock
’s Foreground
serves as the progress bar.
Note that starting and ending points of the gradient extend above and below the text that is actually getting displayed. This is a result of the TextBlock
leaving room for characters with ascending or descending strokes (such as é or y
). Therefore, without additional tweaks, this TextBlock
-based progress bar looks almost empty at 25% and completely full at 75%.
Silverlight’s downloader object makes it easy to manage large content effectively. Although using Silverlight’s downloader is optional, it is a good idea to become acquainted with it. Something as simple as downloading content on demand can turn an otherwise unusable application into an application that appears to be extremely responsive. Improvements in raw performance and responsiveness that usually result from using the downloader can be tracked down to several potential factors:
GET
requests. (This would be true if you use the .ZIP
file support and if your web server doesn’t already compress the content it serves.)GET
requests can be consolidated into one (if you use the .ZIP
file support).3.17.203.68