Silverlight
Changing the source of an Image
Potential help with Silverlight Progressive Enhancement
Progressive Enhancement with Silverlight
Progressive Enhancement with Silverlight
I’ve been working with Yahoo’s YUI ajax libraries recently and I’ve been impressed (if frustrated) by their DataTable feature. What I especially liked about it was the idea to combine Progressive Enhancement (i.e. the ability to spruce up HTML by injecting more code and styles via javascript) with re-using the data inside the HTML table. Although I’m sure everyone wants to use nice REST/Web services to fetch their data there is still vast amounts of legacy code out there that is just pumping out HTML tables that have the potential for a good make-over. Now Ajax is fun and all, but for .NET developers Silverlight is the promised land since it uses almost exactly the same skill set. So my theory was, "why can’t I use Silverlight to enhance a HTML table and scrape the data from the HTML rather than a web service?". So that’s exactly what I did, please take look at the picture (I really should get a proper blog) to see what this all looks like.
The Start, boring HTML table
Here is a boring HTML table, no fancy bits, no resizing, no row selectors, no dragging, no cursor keyboard navigation, etc, etc.
<table>
<thead> <tr> <th>Surname <th>Forename <th class="bool">Available </th></tr>
<tbody> <tr> <td>Bassett <td>Berty <td> <tr> <td>Shore <td>Suzanne <td> <td> <input type="checkbox…
Fairly standard stuff, you should note that the table has an Id and the header for ‘available’ has the class ‘bool’, we’ll use those later. I put this Html into the SilverlightTest.aspx page you get when you create a new project.
How to pass arguments into Silverlight?
In order for Silverlight to work its magic over the Html table it first needs to know which table it should be working on, but how? Initially I decorated my classes with ScriptableMember and then registering the class with the browser using;
TableEnhancement tableEnhancement = new TableEnhancement();
HtmlPage.RegisterScriptableObject("SLTableEnhancement", tableEnhancement);
But it started to get muddy since I had to use clunky techniques in order to correctly discover which instance of the user control was running, etc. So I ended up simply passing the table’s id in Silverlights InitParameters (thanks to Mike Taulty for the hint);
<asp:Silverlight ID="Xaml1" runat="server" InitParameters="tableId=MyTable"…
Silverlight gathers that information in the Application, I chose to store it away in a public property;
private void Application_Startup(object sender, StartupEventArgs e)
{
string tableId = e.InitParams["tableId"];
this.TargetTableId = tableId;
this.RootVisual = new Page();
…
So now Silverlight knows the id of the element it should target.
How can Silverlight examine Html Elements?
This is quite easy too, Silverlight has access to the Dom;
HtmlDocument doc = HtmlPage.Document;
HtmlElement tableElement = doc.GetElementById(tableId);
…
Although this looks very easy the problem is that HtmlElement is as fine grain as it gets. So I’m grabbing a Html table which has lots of interesting properties and collections whereas HtmlElement boils down to GetProperty, GetAttribute and Children. Ok, so with a bit of to and fro-ing you can get the correct properties but I’d certainly like to see a few helper wrappers here.
How to create/configure the Silverlight DataGrid to represent the HTML table?
As with all grid technologies it’s really easy to get quite an advanced grid by simply binding to a source. But I wanted a little more control of the headers. So I set AutoGenerateColumns="False" and created the headers dynamically, be warned this is early prototype code so I’m making quite a few assumptions about the state of the HTML 😉
HtmlElement header = tableElement.GetProperty("tHead") as HtmlElement;
HtmlElementCollection headerRows = header.Children;
HtmlElementCollection headerCols = headerRows[0].Children;
HtmlElementCollection rows = tableElement.GetProperty("rows") as HtmlElementCollection;
Dictionary<int, DataGridColumn> columnHeader = new Dictionary<int, DataGridColumn>();
Dictionary<string, object> data = new Dictionary<string, object>();
HtmlElement trHeader = rows[0];
for (int i = 0; i < trHeader.Children.Count; i++)
{
HtmlElement td = trHeader.Children[i];
string headerText = ((string)td.GetProperty("innerText")).Trim();
string cssClass = td.GetAttribute("className");
if (cssClass != null && cssClass == "bool")
{
columnHeader.Add(i, new DataGridCheckBoxColumn
{
Header = headerText,
DisplayMemberBinding = new Binding(headerText)
});
}
else
{
columnHeader.Add(i, new DataGridTextColumn
{
Header = headerText,
DisplayMemberBinding = new Binding(headerText)
});
}
}
…
Ah, now you can see the role of the bool class, when a header is marked as bool I will create a checkbox column. You can probably guess that eventually I will pass that dictionary to the user control with the data grid so I’ll show that;
public override void CreateTable(Dictionary<int,DataGridColumn>headers, IEnumerable dataSource)
{
for(int i=0;i<headers.Count;i++)
{
DataGridColumn dataGridColumn = headers[i];
this.myDataGrid.Columns.Add(dataGridColumn
);
}
this.myDataGrid.ItemsSource = dataSource;
}
How to create a data source from the values in the HTML Table?
As you can see from the previous code snippet the data grid wants an IEnumerable data source. But my data is inside the rather nasty table hierarchy, so how do you create classes on the fly…well good old TypeBuilder that’s how. Now I must confess that I struck lucky here as while I was looking for exactly signature of a data source I happened across http://blog.bodurov.com/How-to-bind-Silverlight-DataGrid-from-IEnumerable-of-IDictionary. I used the sample there to create the classes on the fly, you have to watch that regEx – especially for spaces in your data. I also have to check the memory implications of on the fly creation cause I’ve been bitten by that before. But I’m pleased to say that it worked well.
How to replace the HTML table?
Standard DOM techniques work well for deleting the HTML table, a quick .Parent.RemoveChild worked fine. So everything worked? Well not quite. Silverlight takes a fraction of time to download after the document onload event fires. This means you can see the HTML table before it gets replaced. The simple answer to this is to style it hidden, if the Silverlight loads then it will be removed, if Silverlight doesn’t load then style it back. The problem was knowing if the user has Silverlight or not. The official line is to register the onPlugInError handler which fires when you don’t have Silverlight or just check to see of the Silverlight object is null. Indeed if you disable the Silverlight add-in in IE then it works fine. The problem is with other "unsupported" browsers, such as Opera, Chrome and Windows Safari. Although they don’t run Silverlight they do quite happily load the control. You can right-click and get all the properties of the Silverlight control…however although the light is on no-one is home. Nothing will run, no events will fire but the object is instantiated so the browser cheerfully reports nothing is wrong. It’s a real problem. Fortunately for my example I could simply show the element in the OnLoad and Silverlight will either run and delete it or not run and leave it there. I added a little timer to greatly reduce the opportunity of seeing the HTML version before the Silverlight kicks in;
<script type="text/javascript">
var timerId;
function showTable() {
clearTimeout(timerId);
var silverlightControl = document.getElementById("Xaml1");
// if the silverlight control hasn’t been loaded then…
if (silverlightControl == null) {
// show the original table
document.getElementById("MyTable").style.visibility = "visible";
// hide the download silverlight banner
document.getElementById("SLHost").style.display = "none";
return;
}
// just cause we’re here doesn’t mean Silverlight is active
// since other browsers still load the ActiveX face-plate
var tableElement = document.getElementById("MyTable");
if (tableElement != null) {
// I know the Silverligtht control deletes this element,
// so if it’s still here then show it, if I’m too quick for SL
// then it will still delete it any second now…
document.getElementById("MyTable").style.visibility = "visible";
}
}
function useSilverlight() {
// very quick timer to give everything a chance to settle down and
// process the message queue
timerId = setTimeout("showTable()", 5);
}
</script>
Silverlight performance difference between Mac & PC?
Silverlight User Group – Silverlight, XNA & Gaming
Completed event one frame after it is begun. So if you begin the
Storyboard again in the Completed event, you’ll have a timer that
executes once per frame’. Now it’s easy for me to say that without really knowing their game but there is a subtle difference between thinking that the storyboard will, "run as fast as possible" to ‘once per frame’. Richard also talked about Pete "slapping his wrists" for not using a time delta in the game loop (more on this in my aforementioned doc) but playing the game on my Mac it too suffers from the same problems I discovered. When you use a delta you effectively store up a number of moves. However, when you do suffer a "glitch" the effect (in this case) is that Minor Willy make sudden random "leaps" which means you end up crashing into the baddies. The game is almost unplayable for me 😦 Perhaps this is the curse of using Silverlight on a Mac??? Is was for a similar reason I removed the delta from Olop. Don’t get me wrong, I think the game is great, but I’d love to get to the bottom of the delta issue.
Overall I enjoyed the presentation and Pete’s enthusiasm for a area of computing that’s dear to me has certainly made me consider dusting off the game programming gloves and possibly even getting hold of XNA too.
Oh Pete (and other retro-gamers) the film you want to watch is King of Kong (a fistful of quarters)
Silverlight release candidate (RC0)
Inspired by Remix – a designers view of creating a Silverlight game
Silverlight game – Olop
Come play our really difficult (not) Silverlight (you’ll need the latest version) game.
Feedback welcome.
Controls, left/right cursor keys, space to fire, return to select power up (when lit)
Click to start (or resume when paused)
http://www.clearbreezedesign.com/olop.htm
I’ll post more about how it was created, the idea was really to try out Silverlight and the processes needed for a developer and a designer to work together. For now just go an help Olop save his Ice Flow 😉