In the last post from this on-going series, I created a basic carousel control in Silverlight 1.0 which will be the basis of the photo display in our photo viewer. The carousel control as it existed at the end of part 2 was a hard-coded control. Over the weekend, I added data-binding to get some real data to show up. To customize the display of items, while keeping the carousel itself lookless, I added templating as well. The similarities with the asp.net repeater control (I remember look-less being used to describe templating in the early asp.net v1.0 days) are very much intentional.
I will build a FlickrService component that uses the Flickr APIs to fetch a list of photos by tags. In particular I'll use Flickr's support for JSONP to workaround cross-domain restrictions, so I don't have to create a server-side proxy.
A screenshot of the final application is on the left, and is hosted live as well, so you can play with it without having to build it on your machine. For those who would like to do so, or tinker further with the code, you can download the sample package. It contains pre-built javascript files, but you'll need the latest version of script# if you'd like to build the code. If you're just joining, you may want to skim over part 1 and read part 2 before continuing...
Step 1: Creating a Template class and an ItemTemplate property
The templating feature will allow customizing the content, layout and behavior of each item within the carousel. Essentially a template is a factory that can be used to create multiple identical copies of visual elements and the associated script objects.
The Template class holds a XAML snippet and a script type, and represents a factory. It produces a TemplateInstance that contains an instantiated visual element tree, and a script object.
public sealed class Template {
private string _xaml;
private Type _controlType;
public Template(string xaml, Type controlType) {
_xaml = xaml;
_controlType = controlType;
}
public TemplateInstance InstantiateIn(Canvas container, object dataItem) {
TemplateInstance instance = new TemplateInstance();
instance.visual = (UIElement)container.GetHost().Content.CreateFromXaml(_xaml);
instance.control = Type.CreateInstance(_controlType, instance.visual, dataItem);
return instance;
}
}
public sealed class TemplateInstance {
public UIElement visual;
public object control;
}
The InstantiateIn method is used to instantiate an instance of the visual elements specified by the XAML snippet. This is accomplished by calling the parsing facility provided by the Silverlight createFromXaml API. Along with the visuals, the associated script object is created. In most scenarios, template instances are associated with some data, and that data is passed in into the script instance, so it can use it as initialization data.
Here is the addition of the ItemTemplate property to the Carousel control.
public class Carousel : IDisposable {
private Template _itemTemplate;
public Template ItemTemplate {
get {
return _itemTemplate;
}
set {
_itemTemplate = value;
}
}
}
The CarouselItem class has also been updated to use the template to create inner content within itself.
internal sealed class CarouselItem : IDisposable {
public CarouselItem(Carousel carousel, Template itemTemplate, object dataItem, Canvas containerCanvas) {
_carousel = carousel;
string xaml =
@"<Canvas RenderTransformOrigin=""0.5,0.5"">
<Canvas.RenderTransform>
<ScaleTransform />
</Canvas.RenderTransform>
</Canvas>";
_item = (Canvas)containerCanvas.GetHost().Content.CreateFromXaml(xaml, /* createNamescope */ true);
containerCanvas.Children.Add(_item);
...
TemplateInstance instance = itemTemplate.InstantiateIn(_item, dataItem);
_item.Children.Add(instance.visual);
_item.Width = instance.visual.Width;
_item.Height = instance.visual.Height;
_control = instance.control;
}
}
The CarouselItem creates a container Canvas element like the last time. Previously it was hard-coded to contain a Rectangle. Now, it has been updated to instantiate a visual tree defined by the item template, and add that visual tree to its children.
One important thing to note is the option to create a name-scope in the call to CreateFromXaml to create the container canvas. The instantiated template will contain named elements, and given the fact that it is will be instantiated multiple times, we need some mechanism to ensure unique names. We won't hack this by munging names prior to parsing the XAML. Instead the name-scoping mechanism is used. Essentially a name-scope provides a logical boundary for names. Names within a name-scope must be unique; names across different name-scopes need not be. For those familiar with asp.net, this is basically equivalent to INamingContainer.
Step 2: Add data-binding
We've now got a templating system in place, and basically need to add a mechanism to data-bind the carousel with actual data. Once we have that in place, the carousel will enumerate records in the list of data it is provided, and create one carousel item per record.
Here's the addition of the Data property to the Carousel, and the updated CreateItems method that now uses the specified list, rather than hard-coding a sequence of colors, as it used to in part 2.
public class Carousel : IDisposable {
private ArrayList _items;
private IEnumerable _data;
private Template _itemTemplate;
public IEnumerable Data {
get {
return _data;
}
set {
_data = value;
_updateStoryboard.Stop();
if (_items != null) {
ClearItems();
}
if ((_data != null) && (_itemTemplate != null)) {
CreateItems();
_updateStoryboard.Begin();
}
}
}
private void CreateItems() {
_items = new ArrayList();
foreach (object dataItem in _data) {
_items.Add(new CarouselItem(this, _itemTemplate, dataItem, _containerCanvas));
}
}
}
Essentially what you see is a simple Repeater control semantics. In fact if we wanted, we could create a Repeater control base class that does templating and data-binding, and then create a derived carousel control that simply added layout semantics.
Step 3: Implement our Flickr search
We've got our UI controls ready to go. Lets focus on the data aspects and integrate Flickr into our scenario.
Flickr exposes APIs to search photos by tags, which is what we're going to use here. Specifically Flickr has REST APIs that it provides, and furthermore it supports JSONP which allows using script tags to request data rather than using XMLHttp, and this allows us to circumvent cross-domain limitations.
Script# provides a networking API in the ScriptFX.Net namespace and also provides a JSONP transport that can be plugged in to replace the default XMLHttp-based transport (a transport is just a fancy word for the underlying communication mechanism, used to execute the HTTPRequest).
// Photo, PhotoCollection, and PhotoSearchResponse model the JSON data returned
// from the Flickr API - a convenient mechanism offered by Script# to create strong typing
// over JSON rather than simply use ArrayList and Dictionary.
public class Photo {
public string farm, id, secret, title, server;
public string thumbnail;
public string image;
}
public sealed class PhotoCollection {
public Photo[] photo;
}
public sealed class PhotoSearchResponse {
public PhotoCollection photos;
}
Internal sealed class FlickrService {
private const string FlickrSearchURLFormat = "...";
private const string FlickrImageURLFormat = "..."
private const string FlickrThumbnailURLFormat = "...";
private Photo[] _photos;
public Photo[] Photos {
get { return _photos; }
}
public event EventHandler PhotosAvailable;
public void SearchPhotos(string[] tags) {
// Create the URI
string uri = String.Format(FlickrSearchURLFormat, tags.Join("+"));
// Flickr has a custom callback param in its JSONP implementation,
// so create an encoded URI. The encoded URI also contains the intent
// to use a custom underlying HTTP transport.
Dictionary scriptTransportParams =
new Dictionary("callbackParameterName", "jsoncallback");
uri = HTTPTransport.CreateURI(uri, typeof(ScriptTransport), scriptTransportParams);
// Create the new request; this will create an HTTPRequest initialized
// with the ScriptTransport that lets us perform JSONP requests
HTTPRequest request = HTTPRequest.CreateRequest(uri, HTTPVerb.GET);
request.Invoke(OnRequestComplete, null);
}
private void OnRequestComplete(HTTPRequest request, object userContext) {
if (request.State == HTTPRequestState.Completed) {
IHTTPResponse response = request.Response;
if (response.StatusCode == HTTPStatusCode.OK) {
PhotoSearchResponse searchResponse = (PhotoSearchResponse)response.GetObject();
_photos = searchResponse.photos.photo;
// Initialize the computed properties
foreach (Photo photo in _photos) {
photo.image = String.Format(FlickrImageURLFormat,
photo.farm, photo.server, photo.id, photo.secret);
photo.thumbnail = String.Format(FlickrThumbnailURLFormat,
photo.farm, photo.server, photo.id, photo.secret);
}
if (PhotosAvailable != null) {
PhotosAvailable(this, EventArgs.Empty);
}
}
}
}
}
The first thing we do in our FlickrService is define the data objects we're dealing with that map to the JSON structures returned from the Flickr API. Our FlickrService class then encapsulates invoking the network request, and handling the async response.
The Flickr service exposes a SearchPhotos method. The code creates a Flickr query URI and an HTTPRequest object from that. The URI is munged to associate it with the ScriptTransport instead of the default XMLHttpTransport transport. The ScriptTransport completely encapsulates the JSONP pattern, so you don't have to work at the raw script tag level in much the same way that you don't have to work at the raw XMLHttp level. Your code still works against a regular HTTPRequest abstraction. For those curious about how it works, look at the implementation of ScriptTransport in ssfx.XDAjax.debug.js. The ScriptTransport function in script# is provided in the ssfx.XDAjax.dll assembly, so PhotoViewer project has been updated to reference that assembly.
Finally when the response is available, we hold on the photos collection, compute the actual image URIs from the individual bits, and raise the PhotosAvailable event so that the consumer of the FlickrService can work with the resulting photos.
Step 4: Hook things together
The last step is pretty simple. We need to have some search UI on our page to allow the user to specify some tags. And we need to add some glue code to tie together the FlickrService and Carousel components.
public class PhotoViewerScriptlet {
private InputElement _tagsTextBox;
private Carousel _carousel;
private FlickrService _service;
private PhotoViewerScriptlet(Dictionary arguments) {
_tagsTextBox = (InputElement)Document.GetElementById("tagsTextBox");
DOMElement searchButton = Document.GetElementById("searchButton");
searchButton.AttachEvent("onclick", new DOMEventHandler(OnSearchButtonClick));
_service = new FlickrService();
_service.PhotosAvailable += OnPhotosAvailable;
ControlParameters controlParameters =
new ControlParameters("PhotoCarousel.xaml",
Document.GetElementById("photoViewerContainer"), "photoViewer", null);
controlParameters.windowless = true;
controlParameters.background = "transparent";
ControlFactory.CreateSilverlight(controlParameters, OnLoaded, null, arguments);
}
private void OnLoaded(SilverlightControl control, object context) {
_carousel = new Carousel((Canvas)control.Content.Root);
Application.Current.RegisterDisposableObject(_carousel);
HTTPRequest request = HTTPRequest.CreateRequest("PhotoItemTemplate.xaml",
HTTPVerb.GET);
request.Invoke(new HTTPRequestCompletedCallback(OnTemplateRequestCompleted), null);
}
private void OnPhotosAvailable(object sender, EventArgs e) {
_carousel.Data = _service.Photos;
}
private void OnSearchButtonClick() {
string tags = _tagsTextBox.Value.Trim();
if (tags.Length != 0) {
_service.SearchPhotos(tags.Split(' '));
}
}
private void OnTemplateRequestCompleted(HTTPRequest request, object context) {
if (request.State == HTTPRequestState.Completed) {
IHTTPResponse response = request.Response;
if (response.StatusCode == HTTPStatusCode.OK) {
string xaml = response.GetText();
_carousel.ItemTemplate = new Template(xaml, typeof(PhotoItem));
}
}
}
}
The scriptlet hooks up to the search UI implemented in HTML. It creates the Silverlight control.
It then downloads a XAML snippet using XMLHttp to create a template that it assigns to the ItemTemplate property of the carousel.
Each time the user clicks the Search button, it issues a photo search using the FlickrService component we wrote earlier. The scriptlet listens for the PhotosAvailable event, and once the photos have been retrieved, it hands them over to the carousel by setting the Data property. This tells the carousel to do its job - data-bind them and visualize the photos as a set of PhotoItems, and animate them.
The PhotoItem class displays the photo thumbnail. Clicking on the thumbnail is handled within the PhotoItem class to display the larger version of the photo. This is the same PhotoControl that we started with in part 1 of this series, which had some basic mouse hover events and has been enhanced with the click behavior. As a result each item in the carousel displays a mouse hover behavior and the ability to see the full photo. Encapsulation and reuse is nice!
One interesting visualization that helps understand what is happening is to see the XAML and script object trees created by the application through the process of data-binding. Part 2 contained a similar visual, and so you can compare to see the difference as a result of adding the data-binding and templating.

Series Recap
I would definitely recommend playing around with the finished application to get a feel for what it does, and then going back to pick at the code for what interests you, as well as go back through the series to get better sense of the code and what it does. To summarize:
- Part 1: I initially focused on the integration of Script# and Silverlight, and built a Hello-world PhotoControl with mouse hover behaviors.
- Part 2: Here I created a simple carousel control. This part focused on getting the basic carousel visuals and animation.
- Part 3: This part focused on creating a more functional control carousel complete with data-binding and templating, as well as demonstrated using Flickr services to fetch photos to display in the carousel.
I've shown a bunch of code over the series ... all of it in C#. This code works against the scriptable Silverlight 1.0 APIs through the magic of script#, which compiles it down to JavaScript. Script# has provided a huge productivity boost to me personally, but if you'd like to see the resulting javascript as well, you can checkout PhotoViewer.debug.js and PhotoViewer.js in the sample download.
As always if you have questions about why I did something a particular way, or suggestions/ideas just drop me a note using the comment form below. Hope you enjoyed reading through these! Let me know if this format - long articles broken over multiple posts - works for you.