Some Simple Silverlight 2.0 Particles, with sticky tape ….

image

Live demo here.

There are a few demos of using the HTML Canvas element floating around the web (which exists natively in WebKit, Firefox, and Opera and as a custom class using excanvas for Internet Explorer). I was inspired to clone the particle demo using Silverlight (and add a silly sticky tape feature).

My implementation went a bit overboard so the amount of code I wrote exceeds the original HTML+canvas implementation by several times. Those who like fields and types declared explicitly won’t mind the extra code length — those who are into dynamic languages and everything being declared on the fly won’t (as much). :)

I used the Clip property of the Canvas to restrain any errant particles from leaving their host and a DispatchTimer to update the user interface.

RectangleGeometry clipper = new RectangleGeometry();
clipper.Rect = new Rect(0, 0, this.Width, this.Height);
LayoutRoot.Clip = clipper;

DispatcherTimer tmr = new DispatcherTimer();
tmr.Interval = TimeSpan.FromMilliseconds(10);
tmr.Tick += new EventHandler(UpdateParticles);
tmr.Start();

The DispatchTimer becomes the “game loop”, and every 10 milliseconds moves the particles to their new position.

I couldn’t find a way to get the current mouse position, so I tracked the mouse position manually. If anyone knows of a method equivalent to Mouse.GetPosition in Silverlight, please leave a comment!

The “Random” number generator burned me a few times when I created instances of the Random class and found that most of the particles all had the same starting position and vectors. The Random class in Silverlight uses the TickCount property of the Environment object to seed the Random number generator. By creating a bunch of instances nearly instantly, each was seeded with the same number — which means that the “random” number generator was generating the same numbers for each particle! My fix was to create a static instance of the Random class and reuse that over the lifetime of the Particle classes. Simple fix — and one of those “duh” moments.

Source code.

Bookmarking your way to happiness…

I discussed the issue of the lack of linkability a while back in the context of JavaFX:

There’s a tutorial you can watch … (I’d love to link to it, but their web site is too “AJAX-y”).

Here‘s a broader plea (with a few more examples).

Developers — when you do Ajax style web sites (or applications), remember the bookmark feature (and the ability to send a link in an e-mail or post it on the WEB for JavaScript’s Sake!  :) ).

Silverlight (and Flex), not for LOB applications?

Shawn suggests that line of business applications should not be written using Silverlight (and hence Flex), instead XBAPs (the run in browser Windows-only WPF solution).

IT shops, with trimmed budgets and staff, have little time to maintain workstations and troubleshoot interactions between various installed applications. Web applications, although they may be more challenging to write (regardless of adding complex RIA components), offer long term benefits for maintenance, updates, deployment and access control that are extremely difficult to emulate using any modern non-browser based alternative, from terminal servers to WPF XBAPs.

And no, I am not a firm believer that a user’s needs should be weighed less than the needs of the IT shop. By no means. But, I do believe that the experience and requirements of many LOB applications can be readily constructed within a web application. Moreover, many LOB applications should be web applications for portability and access reasons.

Not all LOB applications will benefit from the added functionality that would be provided by a Silverlight or Flash/Flex component. In fact, many users may needlessly suffer from a nearly pointless developer adventure into new technology explorations rather than being able to perform the same business task more efficiently with what might amount to a fully functional, yet boring LOB application.

Many applications would benefit however from added pizzazz. There’s plenty of power brewing within the likes of Silverlight and Flex. It needs to be measured and watched carefully, so that it doesn’t explode into the user’s face though.

I do take issue with suggesting that an XBAP is typically a better solution. WPF, even in its most reduced form, is heavy weight and complex. Being browser embedded, it also won’t behave like either a browser application or a fully installed application. So, neither experience is fully replicated for the end user. Eventually, it may be that every desktop has the latest and greatest version of the .NET runtime to allow for the latest XBAPs to work — but there in lies more of the problem — the desktop is making a key decision into whether an application may run, rather than the deploying machine. That stinks — and fewer companies and users want that.

Yes, a WPF application using XBAPs may, at some levels, be easier to write. It depends a lot on the developer and on the application needed. Many WPF applications are as hard to write as a web application (if not harder). It’s give and take. Some features are easier in one and vice versa. RIA code using Silverlight is easier to create than their WPF counterparts in some ways, only because the functionality is limited (and a few things simplified). But, throw in the web mix for interaction, and I agree that the complexities increase.

So much depends on the expectations and needs of the user. If you want a web experience with pages, etc., I wouldn’t consider an XBAP to be an ideal choice. If it’s mostly a single page where much of the interaction occurs, I could see XBAPs being a reasonable choice for Windows-only organizations. But, those must consider deployment, hardware requirements, .NET versioning, and more.

(Don’t get me wrong, I really like WPF — just installed as a traditional application. :) ).

Oh — and more importantly — don’t switch to either technology if there’s no business reason (sorry developers).

Silverlight Weather Demonstration

Demonstration available here. (You’ll need to wait for a moment while it loads the first time).

Stale Weather data for Madison Wisconsin as this isn't live ...

I’ve created a reasonably simple, yet multi-technology (and discipline) demonstration using Silverlight for the user interface and ASP.NET as the back-end. The demonstration uses:

  • Silverlight 2.0
  • Data binding
  • Delayed downloading of images
  • “Web services”
  • ASP.NET
  • LINQ
  • Extension Methods
  • Value Converters
  • XAML
  • IHttpAsyncHandler
  • HttpWebRequest
  • Google’s Weather Web Service (more of a weather XML end point)
  • and more!!! :)

 

The Silverlight Weather control is a UserControl with a few bound TextBlocks, an Image, and a few rectangles, and the data input controls.

<TextBlock Margin="8,10,0,10" 
           Grid.ColumnSpan="2" 
           Grid.Row="5" 
           Text="{Binding CurrentWeather.Location}" 
           TextWrapping="Wrap" 
           x:Name="txtLocation1" 
           VerticalAlignment="Center" 
           Foreground="#FFF1F1F1" 
           FontSize="12" 
           FontWeight="Normal"/>

 

Standard Silverlight data binding … grabbing the CurrentWeather’s Location property (which in the example in the screen shot is Madison, WI). What I continue to find is that Silverlight does not allow data binding to the UserControl itself — which is a common trick I use in WPF all the time. I don’t know if it’s a bug or a feature though. So, instead of simple code like this:

public Page()
{
    InitializeComponent();
    this.Loaded += new RoutedEventHandler(Page_Loaded);
}

void Page_Loaded(object sender, RoutedEventArgs e)
{
    this.DataContext = this;
}

I’m forced to rely on a secondary layer for binding:

public Page()
{
    InitializeComponent();
    this.Loaded += newRoutedEventHandler(Page_Loaded);
    _binding = newPageDataBinding();
}

void Page_Loaded(objectsender, RoutedEventArgs e)
{
    LayoutRoot.DataContext = _binding;
}

This technique adds what isn’t a terribly useful layer for a project like this, but it shouldn’t be necessary. To work around the issue, I created a new class, called PageDataBinding which has the few properties I wanted to expose to the user interface:

public class PageDataBinding: INotifyPropertyChanged
{
    private WeatherCondition _currentWeather;

    public WeatherCondition CurrentWeather
    {
        get { return _currentWeather; }
        set
        {
            if (_currentWeather != value)
            {
                _currentWeather = value;
                RaisePropertyChanged("CurrentWeather");
            }
        }
    }

    #region INotifyPropertyChanged Members

    protected void RaisePropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
    
}

Above you should see the CurrentWeather property which I’ve used in the Silverlight UI. In addition to the typical button for activating the control, I’ve made the Enter key also trigger the weather fetching.

private void txtLocation_KeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Enter)
    {
        FetchWeather();
        e.Handled = true;
    }
}

Above, after (starting the process of) fetching the weather, I’ve set the Handled property to true, which indicates no further processing of the key is needed.

I wrapped the basic request to Google into a class called Weather. It’s the job of the Weather class to package the request and submit it to my exposed web service. Google’s web servers do not have any of the cross domain permission files needed by Silverlight for it to directly access them; so, I created a proxy web service just for the purpose.

Using the HttpWebRequest object is quite simple:

public void Download()
{
    try
    {
        HttpWebRequest request = WebRequest.Create(
            new Uri(Application.Current.Host.Source,
                string.Format(
                "../GetWeather.ashx?location={0}", _location))) as HttpWebRequest;
        request.Method = "GET";
        this._responseResult = 
            request.BeginGetResponse(new AsyncCallback(RequestCallback), request);
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);
    }
}

I’ve just made the request to my web server and passed the location parameter directly. If it’s gibberish, Google just returns an error, so I don’t bother with any special processing (and in the end, it’s just a demonstration).

When the response is received, the RequestCallback method is called:

private void RequestCallback(IAsyncResult result)
{
    HttpWebRequest request = result.AsyncState as HttpWebRequest;

    HttpWebResponse response = (HttpWebResponse) request.EndGetResponse(result);

    Stream s = response.GetResponseStream();

    StreamReader reader = new StreamReader(s);
    string webResult = reader.ReadToEnd();
    Debug.WriteLine(webResult);
    reader.Close();
    response.Close();

    if (WeatherDataDownloaded != null)
    {
        WeatherDataDownloaded(this, new WeatherDataDownloadedEventArgs(webResult));
    }
}

Not necessarily production ready code, but it works for this demo. The result is returned, and gathered into a string. Once complete, the Weather class raises an event with the result to the host user interface.

I won’t replicate all the code here, but once the user interface event code is called, it begins to parse the XML data:

void WeatherDataDownloaded(object sender, WeatherDataDownloadedEventArgs e)
{
    XElement xd = XElement.Parse( e.Data, LoadOptions.None);

    var conditions = from current_condition in xd.Descendants("current_conditions")
                     select new WeatherCondition()
                     {
                         Condition = current_condition.GetChildElementAttributeValue("condition", "data"),
                         TemperatureF = current_condition.GetChildElementAttributeValue("temp_f", "data"),
                         TemperatureC = current_condition.GetChildElementAttributeValue("temp_c", "data"),
                         Humidity = current_condition.GetChildElementAttributeValue("humidity", "data"),
                         IconPath = current_condition.GetChildElementAttributeValue("icon", "data"),
                         Wind = current_condition.GetChildElementAttributeValue("wind_condition", "data")
                     };

As you can see, I use LINQ and an extension method I frequently use to obtain a named attribute from an element. The extension method, GetChildElementAttributeValue doesn’t do much really:

public static string GetChildElementAttributeValue(
    this XElement element, XName elementName, XName attributeName)
{
    XElement subElement = element.Element(elementName);
    if (subElement != null)
    {
        XAttribute attr = subElement.Attribute(attributeName);
        if (attr != null)
        {
            return attr.Value;
        }
    }

    return null;
}

The primary function (of this function!) is to protect against null elements when trying to read an attribute value.

The attribute values are stored in a WeatherCondition object. Once the object has been created, and a little more work is done, the code must notify the UI’s bound objects.

WAIT! If the notification happens now (on the currently executing thread), the Silverlight runtime will absolutely throw an exception, just like WPF would. The current executing thread is not the user interface thread, so I’ve used the Dispatcher to execute the update on the user interface thread:

Dispatcher.BeginInvoke(delegate()
{
    _binding.CurrentWeather = conditions.First<WeatherCondition>();
});

The remainder of the Silverlight code is mostly basic plumbing and user interface manipulation. There should be more error trapping than there is … but, I’m leaving that for another day (or for you!).

Rather than introducing a true web service into the project, I instead created an IHttpAsyncHandler in ASP.NET. This way, the handler could efficiently make requests of the Google Weather API and return them, without blocking further requests. In an ideal world, I’d add caching and more error handling ….

Here’s nearly the entire handler — as I think it’s useful to demonstrate a live, working usage of an ASP.NET async HttpHandler. If you’re not familiar with IHttpAsyncHandler, and you’re writing HttpHandlers, … get familiar with it if you care about scalability/performance.

public IAsyncResult BeginProcessRequest(HttpContext context,
AsyncCallback cb, object extraData) { _context = context; HttpWebRequest request = WebRequest.Create( new Uri( string.Format( http://www.google.com/ig/api?weather={0},
context.Request.QueryString["location"])) ) as HttpWebRequest; _completedCallback = cb; return request.BeginGetResponse(
new AsyncCallback(GetResponseCallback), request); } private void GetResponseCallback(IAsyncResult result) { Debug.WriteLine("GetResponseCallback"); HttpWebRequest request = result.AsyncState as HttpWebRequest; HttpWebResponse response = request.EndGetResponse(result) as HttpWebResponse; Stream s = response.GetResponseStream(); StreamReader reader = new StreamReader(s); string responseOutput = reader.ReadToEnd(); reader.Close(); response.Close(); _context.Response.ContentType = "text/xml"; _context.Response.Write(responseOutput); _completedCallback.Invoke(result); } public void EndProcessRequest(IAsyncResult result) { _context.Response.End(); }

I’ve also created an IValueConverter to take the path to an image and convert it to a bitmap image.

BitmapImage bi = new BitmapImage();
bi.UriSource = new Uri("/Images/" + path, UriKind.Relative);  

The images are stored on the web server rather than being embedded directly into the XAP file. Note that for this to work, the images must be stored in the ClientBin/Images folder, … not the /Images folder of the web server (as the path is relative to the location of the Silverlight download, not to the web application).

One issue that I ran into that had me puzzled for a while. AG_E_NETWORK_ERROR. This error occurred when trying to directly download the images Google uses for weather icons. That error had me baffled, until I realized that Silverlight cannot download images from other domains, unless they have the cross domain XML files properly configured for remote access. Google, sadly, does not have them, so I created my own images using Expression Design. The original file can be found in the download if you’re interested (as a series of layers).

Download source code for entire project (including graphics) here.

image image image

Enjoy!