Programming Silverlight 1.0 with C# - Photo Carousel (Part 3)

Script# supports programming against Silverlight 1.0. You can start writing your RIA and associated components and controls in C# rather than in JavaScript... today! This series of posts will build a photo carousel one step at a time to illustrate this approach. Part 3 completes the series by adding data-binding and templating to the carousel and by querying Flickr using JSONP to fetch the actual photos to display in our photo viewer scenario.

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.

Posted on Monday, 9/24/2007 @ 1:45 PM | #Silverlight


Comments

33 comments have been posted.

Brent

Posted on 9/25/2007 @ 7:49 AM
Great idea.

The search was slow.
If you click on an image while it is in motion, you get a AG_E_RUNTIME_DELEVENT Error. Error code 2206, in Control photoviewer.

Not a very impressive experience at this point.

WooBoo

Posted on 9/25/2007 @ 9:20 AM
Great series! Gives full in depth view into Script#.

Nikhilk, I don't know if you though about it, but would be cool to have script handler in output assembly (with scripts in resources) of script library, that could be referenced in dest. project and wired up in web.config. It would simplify project management (putting one project in bin folder of another project... well... weird for me).
Another thing. Script# libs provide AJAX integration, but I couldn't find any information about any script lib for AJAX Control Toolkit. I tried to build it by myself (wrapped some base classes), even tried to build tool to build it for me, but it will take me years before i finish. Do you know/have any solution that someone already did? Maybe AJAX Control Toolkit team have something... Didn't asked them yet.
One again. Great job! :)
WooBoo

Nikhil Kothari

Posted on 9/25/2007 @ 11:35 AM
Brent - sorry it wan't an impressive experience for you. My goal was to demonstrate some concepts with the sample, and I didn't do a full round of stress testing.

WooBoo - the reason the nested project is in the bin folder is that the source code shouldn't be browsable. The reason the scripts aren't packaged as script resources is because at the end of the day for performance reasons you'll want them to be static files any way. Regarding a script library for the AJAX control toolkit, that is certainly possible, though I don't think one exists. Maybe you could make a suggestion to the AJAX control toolkit team. Better yet, maybe some of the AJAX control toolkit could be written in script# to begin with - then you have both the final scripts + assemblies to reference in your app! Script# does support asp.net ajax, so that shouldn't be a problem.

Frank

Posted on 9/25/2007 @ 5:00 PM
Very cool! Are you going to release an installer for VS2008? If not, maybe just the template files?

Nikhil Kothari

Posted on 9/25/2007 @ 8:32 PM
Frank, yes, I'll be releasing an updated installer - the problem with just releasing the templates is that you have to install them using the right vs commands, register stuff in the registry so that the project system/msbuild can find assembly references, msbuild target files etc. I'll look into releasing something mid next-week (I am at an all day training for the rest of this week).
If you absolutely want something sooner, contact me via my contact form, and I'll try to email it to you at the end of the day when I get back from class.

Chris Rothery

Posted on 9/27/2007 @ 2:29 AM
Loving the carousel series Nikhil :). I've been intrigued by script# but this is the walk through that has got me to pull my finger out and have a go with it (tie together javascript based silverlight, a photo based project and c# = bingo!).

Yesterdays update of the Script# zip seems to have busted the download for it though?

Carlo

Posted on 9/27/2007 @ 3:37 AM
I find your projects very informative. I haven't gotten to play with Silverlight yet,but after seeing this, I'll see what I can whip up.

Sam

Posted on 9/28/2007 @ 9:39 AM
Nikhil, Love the sample so far. It has given me plenty to think about and work on in my own app. However I ran into a problem when trying to public the it to my hosting site in that there is an assembly used by the script# piece "nStuff.ScriptSharp.Web.UI" is not present. My hosting site (GoDaddy) is not going to let me install anything in their GAC like this so I was wondering if there is another way to use this assembly to make the project work. (It works just fine on my test machine - just not in the ISP host machine).

I tried the obvious thing of copying the assembly to the local directory but it is not happy there either... Any clues would be great since I prefer c# over JScript any day.

Nikhil Kothari

Posted on 9/28/2007 @ 4:56 PM
Chris - please try to download again. For some reason I've seen some flaky behavior with that binary download - sometimes it works, sometimes it doesn - I've updated/re-uploaded and that has caused it to work (fingers crossed).

Sam - Unfortunately there is some code in nStuff.ScriptSharp.Web.UI that requires full trust to get at something in asp.net that isn't exposed to medium trust... hence the GAC requirement. I'll see what can be done there going forward. However, in your case I would recommend the following: use the precompiled scriptlet file by avoiding the use of inline c# code within the scriptlet that is compiled into script at runtime (my carousel sample used this approach) - at that point all the scriptlet control does is generate a main method, and a method to initialize the script loader by using the PrecompiledScriptlet property and the set of assembly references and argument list. This is almost boiler plate code... that you can just include without actually using the scriptlet control. Essentially you'd be working in a mode where Script# is a development thing for you, and at runtime you just have generated plain old script files. Hope that helps...

dillon

Posted on 10/17/2007 @ 8:07 AM
Nikhil,

I can run successfully by your code. But I can not deploy it in ISS. Are there anything need me to notice about?

Thanks a lot

Nik

Posted on 11/3/2007 @ 4:47 AM
It's been a while since I looked at it, but Script# is really getting popular. Nikhil, were you involved with the compiler that is used for Volta or is it just customized Script# compiler?

Michael

Posted on 11/5/2007 @ 1:40 AM
Sehr schöne Animation

Jan

Posted on 11/7/2007 @ 3:08 AM
Hi nikhil,

I've been using Script# for a while now and I must say you've done an amazing job!
However, I've got 1 problem :

I create a silverlight element using createfromxaml.
In that element I call for a scripsharp produced void (e.g. ClassLibary$clickItem), because, logically, it doesn't know void clickItem.
That works, but when I call for a scripsharp function (clickItemSequel) after that I get an error : "Microsoft JScript runtime error: Object doesn't support this property or method".
I suppose it's because it's looking for the "Script# name" of that function..

So I tried the Type.Invokemethod(null,"new ClassLibrary$clickItemSequel), but no luck either, it says " 'targets.1' is null or not an object".

Is there any way to still pull this off?

Thanks in advance,
Jan.

Vikki

Posted on 11/7/2007 @ 9:50 AM
Hi,

I was wondering if there's a simple way to retrieve the value of some kind of select control from the hosting web page, to use in the scriptlet? I assumed the obvious way to do this was to retrieve the appropriate DOMElement object (as in the sample code) and cast it to the appropriate control, but I'm unable to do this, as there doesn't appear to be a dropdownlist or similar control(fair enough), and if I try to cast/create a new CheckBox control I receive the error that there is no CheckBox constructor when there seems to be one when you browse the DLL. Casting to an InputElement works fine, but I really need access to the other values.

Can anybody suggest an alternative or tell me where I'm going wrong, or is this not possible?

Cheers

thiago

Posted on 11/7/2007 @ 3:32 PM
tank you

Vikki

Posted on 11/8/2007 @ 2:52 AM
Hi,

I take it back; the CheckBox cast works fine, just needed a reference to the extra ssfx UI DLL in the scriptlet control in the consuming website, missed it yesterday, sorry!

Great example project BTW, I really like where Script# is going :)

Thanks

koong95

Posted on 11/14/2007 @ 3:15 PM
I am interested in silverlight.

Mayu

Posted on 12/5/2007 @ 3:56 AM
Is there any way to retrive the contents from database like SQL?

mvaditya

Posted on 12/20/2007 @ 6:25 AM
how to convert the following java script to c#.net in silver light
objsArr["image_"+i]=new Object()
objsArr["image_"+i].angle=i*((Math.PI*2)/num_imgs)

ㅋㅋㅋ

Posted on 1/15/2008 @ 8:22 PM
ㅋㅋ

hoangdackien

Posted on 1/18/2008 @ 8:26 AM
I like it

otto bridgen

Posted on 2/7/2008 @ 8:16 AM
am impressed with it

ali

Posted on 2/14/2008 @ 12:01 AM
I like it

kha

Posted on 2/19/2008 @ 11:49 PM
v v

kha

Posted on 2/19/2008 @ 11:50 PM
GGGGGGG GGGGGGGGGGG GGGG

Jonathan

Posted on 3/11/2008 @ 11:39 AM
Prueba del producto

Jonathan

Posted on 3/11/2008 @ 11:40 AM
PRUEBA DEL PRODUCTO

Jon

Posted on 3/18/2008 @ 6:24 AM
still learning silverlight,..I am sure I'm overlooking it, but where can I slow the carousel down? Thx

Paymon

Posted on 4/9/2008 @ 5:51 AM
This technology is getting somewhere.

I used to try and do as much as I could on the server side, simply for beauty of C#, compared to Actionscript, Javascript, etc.

With Silverlight, UI development is coming back home!

Thanks!

Masood

Posted on 4/30/2008 @ 7:27 AM
I am getting errors UIFactory not exist and The type ands Namespace could not find and ControlFactory not exist error

Dayan

Posted on 5/1/2008 @ 6:06 PM
-

Danilo Paganotti

Posted on 6/4/2008 @ 10:13 AM
legal

Sebastien Pouliot

Posted on 6/6/2008 @ 8:22 AM
Would it be possible to update your Silverlight detection logic ?

Right now it can't work for Linux/Moonlight since it checks userAgent.

Past that it's also very slow since it's asking the browser for all versions up to 1.1.99999.99999 to get the maximum version supported.

Thanks!
Sebastien
Post your comment and continue the discussion.
 
 
 

 

 
Refresh this form if the spam-protection code is not readable, or has expired. (Your input will be preserved)