Saturday, July 18, 2009

Silverlight freezing when calling XMLHttpRequest?

I had a javascript handler for a Silverlight event that modifies the XAML, uses XMLHttpRequest synchronously, then modifies the XAML again, and I noticed that the browser would sometimes not show the first XAML change while the XMLHttpRequest was pending.

Example XAML:
<?xml version="1.0" encoding="utf-8" ?> 
<Canvas xmlns="http://schemas.microsoft.com/client/2007">
  <TextBlock Name="Txt1" Canvas.Left="25" Canvas.Top="25" FontSize="12" >Click the box</TextBlock>
  <Rectangle Name="Rect1" Canvas.Left="25" Canvas.Top="40" Width="200" Height="35" Fill="#8080FF" MouseLeftButtonDown="rect1_onMouseDown" />
</Canvas>
Example javascript:
function rect1_onMouseDown(sender, mouseEventArgs) {
  var txt1 = sender.findName("Txt1");
  var rect1 = sender.findName("Rect1");

  txt1.text = "In Progress...";
  rect1.Cursor = "Wait";

  var xmlhttp = new XMLHttpRequest();
  xmlhttp.open("GET", "long-running-url.asp", false);
  xmlhttp.send(null);

  rect1.Cursor = "Default";
  txt1.text = "Complete.";
}

Firefox shows "In Progress..." and then "Complete", but IE waits until everything is finished and then shows "Complete". So when exactly does Silverlight redraw itself when changes are made?

The answer is that changes are not drawn immediately after they are made. They are made the next available chance when no scripts are actively running.

Example that does not redraw on either Firefox or IE.
function rect1_onMouseDown(sender, mouseEventArgs) {
  var txt1 = sender.findName("Txt1");
  var rect1 = sender.findName("Rect1");

  txt1.text = "In Progress...";
  rect1.Cursor = "Wait";

  // keep busy for 0.5 sec
  var start = new Date();
  while ((new Date()).getTime() - start.getTime() < 500) {
    // do nothing
  }
  
  rect1.Cursor = "Default";
  txt1.text = "Complete.";
}

So if you want a redraw while an XMLHttpRequest is pending, do it asynchronously.
function rect1_onMouseDown(sender, mouseEventArgs) {
  var txt1 = sender.findName("Txt1");
  var rect1 = sender.findName("Rect1");

  txt1.text = "In Progress...";
  rect1.Cursor = "Wait";

  var fnOnStateChange = function() {
    if (xmlhttp.readyState == 4) {
      rect1.Cursor = "Default";
      txt1.text = "Complete.";
    }
  };
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.onreadystatechange = fnOnStateChange;
  xmlhttp.open("GET", "long-running-url.asp", true);
  xmlhttp.send(null);
}

Here are a few more examples shown live. The first and third will be redrawn inconsistently and the second one will always redraw during the intermediate "In Progress..." stage. Click the download links below to see the source code for this.

Download source.html, source.xaml