Building A Silverlight 3 Dashboard With Composite UI
If you’ve been following me on Twitter (igtony), you’ve probably heard me grumbling about Visual Studio crashing, services not working, or various other gremlins I’ve been fighting with for the past few days. Well, I’ve finally got something to show for all of my pain!
My goal when I started building this application was to build a POC Silverlight Application which dynamically loaded sub-applications. In essence, I wanted to build a Silverlight shell that could load N applications unknown at compile time. I started out by choosing from the new Navigation project template in Silverlight 3. This gives a MasterPage scenario that works as a good starting point for an application which may have multiple distinct Views. I tweaked the base appearance of the template around just to make it look a little more presentable. The White background used in the <Frame> element and the big 10px borders reminded me of Windows 3.1, and IE4. I used a Border element to create the rounded edges which wrapped around my Frame, and I set my Frame background to be transparent. With some of the UI tweaks out of the way, it was down to business.
The first task I needed to tackle was loading a XAML page (UserControl) dynamically. It turns out this is a pretty simple task as long as that user control is in the same XAP file. The Frame element has a Navigate method which takes a URI and will fill the Frame’s Content with the UserControl specified via the URI. Unfortunately, the solution I was looking for places my UserControl in a separate XAP file, which meant I couldn’t use the simple Frame.Navigate(URI) method. Instead I needed to dynamically load the UserControl in question. Luckily, this is a pretty straight forward task with the help of the WebClient.OpenReadAsync method, and AssemblyPart class. Here’s the code I used:
private void NavButton_Click(object sender, RoutedEventArgs e) { Button navigationButton = sender as Button; //Create a WebClient instance to download our separate XAP file WebClient client=new WebClient(); client.OpenReadCompleted+=new OpenReadCompletedEventHandler(client_OpenReadCompleted); //Download the XAP file client.OpenReadAsync(new Uri("CompositeApp.xap",UriKind.Relative)); } void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e) { //Create a StreamResourceInfo object out of our XAP stream StreamResourceInfo sri = new StreamResourceInfo(e.Result, null); //Get a StreamResourceInfo for the Application's DLL //The one that houses our main XAML UserControl StreamResourceInfo mainAppSRI = Application.GetResourceStream(sri, new Uri("CompositeApp.dll", UriKind.Relative)); //Load the Assembly using AssemblyPart.Load AssemblyPart loader = new AssemblyPart(); Assembly assembly = loader.Load(mainAppSRI.Stream); //Now that the Assembly is loaded, create an instance of our UserControl this.Frame.Content = assembly.CreateInstance("CompositeApp.MainPage"); }
There’s one drawback to using this method to dynamically load a UserControl – dependencies are not automatically loaded for you. If your UserControl in the separate XAP has a dependency on 4 assemblies you must manually load those 4 assemblies before loading your UserControl or CreateInstance will fail. The hack solution I used was to include the assembly references in the main shell application. This doesn’t scale well though, since ideally you want the additional dependencies to be loaded dynamically as necessary. There’s no way you’ll know today every assembly reference each sub app will need for the entire life of the shell. It’s actually impossible since you’ll likely need a ‘new’ assembly at some point down the road. I saw some code fragments while looking for a solution which read the manifest in the XAP to determine what assemblies need to be loaded out of the (external) XAP. If I were building this as a real world app, I would go that route. If you know of a better way to handle this, I encourage you to share in the comments! In a perfect world Frame.Navigate would allow you to specify a XAML file in a separate XAP and do all of this work for you. Are you listening Silverlight team? ;)
Ok, main hurdle down (dynamic loading of UserControl from separate XAP), it was time for the next challenge – creating content! I started out with a grid, because you can’t have a good application without a grid in there somewhere, right? I used the XamWebGrid, and used it in a couple of places. One on the main page which showed hierarchical data (Accounts and Transactions)
Next I created another ‘page’ or View which Visualized data in a Map. In this scenario the map shapes came from my “Shapefile” and the data came from a SQL Database via WCF and LinqToSQL. I think this probably mirrors the major use case out there, where the data is kept separate from the Shapefile.
On the final Screen, I went with ‘live’ updating data (Image 1). Since this was just a demo, I used a DispatcherTimer to change my underlying datasource every second. Since my underlying datasource was an ObservableCollection of INotifyPropertyChanged objects, my grid immediately showed any changes made to the underlying datasource. The Grid does 2-way databinding by default. Since my View still looked empty, I dropped a chart in there as well, and added a new datapoint to the chart’s DataPoints collection each second (representing the history of changes made to the grid). The end result was a chart and grid showing updates every second, with the Chart “always in motion”. It was a pretty impressive experience. My next step is to see what this would look like with a real data feed. I’m guessing with proper queuing in place, the experience should mirror my mock up almost exactly.