Last week, Microsoft organized the Belgian edition of the Techdays, for the first time in Antwerp. After reading (Twitter, blogs…) and hearing quite a lot of feedback, the event was a success.
For me personally, it was also an exciting week: for the first time in my career, I was doing a keynote. I presented the Silverlight part of this talk, together with 2 other Regional Directors from Belgium: Peter Himschoot took the WPF part and Grégory Renard handled the Surface. Also, Katrien De Graeve (Microsoft) showed Windows 7 and Azure, while Hans Verbeeck (also Microsoft) glued all bits and pieces into a nice session.
In this article, you’ll get an overview of the demo we created for the keynote, called “Silverlight on the bike”.
The scenario
Hans, when not behind his laptop, loves to ride his bike. While on his bike, he wears a small device from Garmin that monitors his heart rate and also retrieves the entire route that he followed via GPS. When combining these 2 bits of information, you can see where the heartbeat went higher (because of a slope for example).
Garmin must like developers, because they expose this data as XML. Pure clean XML that any developer can read out. This data was the start for our scenario: plot out the route that Hans did on his bike on a map, show the heartbeat on a graph, throw in some pictures he took along the road and expose all this in a familiar looking interface in the browser.
The demo also needed to run as a standalone application as well as on the surface. Because of the portability of the code between Silverlight and WPF (Surface applications are WPF as well), a large amount of code could simply be copied from one platform to another.
And here’s how we created it…
While the demo contains too much code to explain here, I’ll go over some of the most interesting parts that really make Silverlight shine.
Step 1: Design is everything (sort of…)
The first thing we did was going to a designer and explain him the needs of our application. A request from our side was of course that he needed to create the interface in Blend. So he came up with a design, completely in XAML, as shown below.
A cool thing when working with Silverlight is a nice workflow between designers working in Blend and developers working in Visual Studio. Since designers work with the same files as developers, there’s no need to cut and paste the work that the designer did: he can make changes while developers are creating their code and these changes will be incorporated without any hassle.
Step 2: Get me that data
Design is one thing, coding is another. Our application is built around data (remember the XML file from the Garmin device), so the first problem that needs solving is getting that data into the application. Silverlight 2 supports several ways to connect with data: WCF, webservices, reading remote files… For the sake of simplicity, we are going to use the latter: we’ll drop the XML file in the web application. Silverlight now needs to connect with the file using the WebClient, a class that’s also available in the full version of the .NET framework.
Whenever Silverlight needs to go out fetching data, it will do so asynchronously. If it would perform this action synchronously, the browser would hang while data flows from server to client or vice-versa.
Codesnippet 1 shows the code needed for the data access and the result is shown.
1: WebClient client = new WebClient();
2: Uri address = new Uri("http://localhost:" + HtmlPage.Document.DocumentUri.Port + "/" + fileName, UriKind.Absolute);
3: client.OpenReadCompleted += client_OpenReadCompleted;
4: client.OpenReadAsync(address);
CodeSnippet 1
Step 3: Let’s parse XML (still yuck?)
Now that we are able to connect with the data, we need to do something with it, we only have it in a string at this point. We need to parse the XML and create objects that represent the data in memory. Parsing XML using the “traditional” way, using XmlDocument classes and the like, is not my favorite part of my development life. This API is quite difficult and often requires XPath knowledge to access the correct data.
Since .NET 3.5 (in fact also in 3.0 as beta), LINQ and LINQ to XML were introduced and the great thing is that these are also included in Silverlight. Using the LINQ to XML API, we can very easily parse the XML and create objects representing the data. Codesnippet 2 shows the XML, codesnippet 3 shows the type that we’ll be creating. In Codesnippet 4, the code to parse the XML and to create a generic list of TrackPoint instances is shown.
1: <Trackpoint>
2: <Time>2009-02-14T14:13:10Z</Time>
3: <Position>
4: <LatitudeDegrees>51.3509752</LatitudeDegrees>
5: <LongitudeDegrees>4.6816549</LongitudeDegrees>
6: </Position>
7: <AltitudeMeters>20.3249512</AltitudeMeters>
8: <DistanceMeters>0.0343911</DistanceMeters>
9: <HeartRateBpm >
10: <Value>111</Value>
11: </HeartRateBpm>
12: <SensorState>Absent</SensorState>
13: </Trackpoint>
14: <Trackpoint>
15: <Time>2009-02-14T14:13:11Z</Time>
16: <Position>
17: <LatitudeDegrees>51.3509765</LatitudeDegrees>
18: <LongitudeDegrees>4.6816523</LongitudeDegrees>
19: </Position>
20: <AltitudeMeters>20.3249512</AltitudeMeters>
21: <DistanceMeters>0.0000000</DistanceMeters>
22: <HeartRateBpm >
23: <Value>110</Value>
24: </HeartRateBpm>
25: <SensorState>Absent</SensorState>
26: </Trackpoint>
27: <Trackpoint>
Codesnippet 2
1: public class TrackPoint
2: {
3:
4: private DateTime _time;
5:
6: public DateTime Time
7: {
8: get { return _time; }
9: set { _time = value; }
10: }
11:
12: private Point _position;
13:
14: public Point Position
15: {
16: get { return _position; }
17: set { _position = value; }
18: }
19:
20:
21: public double X
22: {
23: get { return _position.X; }
24: set { _position.X = value; }
25: }
26:
27: public double Y
28: {
29: get { return _position.Y; }
30: set { _position.Y = value; }
31: }
32:
33: private int _cadence;
34:
35: public int Cadence
36: {
37: get { return _cadence; }
38: set { _cadence = value; }
39: }
40:
41: private double _distance;
42:
43: public double Distance
44: {
45: get { return _distance; }
46: set { _distance = value; }
47: }
48: }
Codesnippet 3
1: public List<TrackPoint> Load(Stream filename)
2: {
3: XElement doc = XElement.Load(filename);
4: List<XElement> tps = doc.Descendants("Trackpoint").ToList<XElement>();
5:
6: TrackPoint tp = null;
7:
8: foreach (XElement point in tps)
9: {
10: try
11: {
12: tp = new TrackPoint();
13: tp.Position = new Point(double.Parse(point.Descendants("LatitudeDegrees").First().Value) / 10000000,
14: double.Parse(point.Descendants("LongitudeDegrees").First().Value) / 10000000);
15: tp.Distance = double.Parse(point.Descendants("DistanceMeters").First().Value) / 10000000;
16:
17: if (tp.Distance > _totalDistance)
18: _totalDistance = tp.Distance;
19:
20: tp.Cadence = int.Parse(point.Descendants("HeartRateBpm").First().Value);
21:
22: _trackPoints.Add(tp);
23: }
24: catch (Exception)
25: {
26:
27:
28: }
29: }
30: return _trackPoints;
31: }
Codesnippet 4
Step 4: Design: OK! Data: OK! UI: To Do!
Now we have the data from the device ready on the client-side within our Silverlight application as a generic list. We can now go ahead and add the UI elements to the interface.
Up first is a ribbon. We want to create a user interface that feels familiar to a user of the application. A great way to achieve this, is using a ribbon known from Office 2007. Currently, Silverlight does not contain a ribbon out-of-the-box yet, but there are some custom-built ones available. For the sake of simplicity, I created a usercontrol containing the ribbon instantiation. This keeps my Page.xaml code cleaner. Codesnippet 5 contains the code for the ribbon and codesnippet 6 contains the usercontrol that we’ll put on the page.
1: <rbn:Ribbon.QuickLaunchButtons>
2: <rbn:RibbonButton SmallImageSource="Images/Save.png" />
3: <rbn:RibbonButton SmallImageSource="Images/Undo.png" />
4: <rbn:RibbonButton SmallImageSource="Images/Repeat.png" />
5: </rbn:Ribbon.QuickLaunchButtons>
6:
7: <!-- Tabs -->
8:
9: <!-- Home -->
10: <rbn:RibbonTab Title="Home">
11: <rbn:RibbonTabGroup Title="Actions">
12: <rbn:RibbonButton Text="New data" LargeImageSource="Images/addxml.png" />
13: <rbn:RibbonButton Text="Change data" LargeImageSource="Images/addxml.png" ButtonClick="RibbonButton_ButtonClick" />
14: <rbn:RibbonButton Text="Images" LargeImageSource="Images/addimages.png" />
15: </rbn:RibbonTabGroup>
16:
17: <rbn:RibbonTabGroup Title="Reporting">
18: <rbn:RibbonButton Text="New report" LargeImageSource="Images/addreport.png" />
19: <rbn:RibbonButton Text="View reports" LargeImageSource="Images/addreport.png" />
20: </rbn:RibbonTabGroup>
21: </rbn:RibbonTab>
22:
23: <!-- Help -->
24: <rbn:RibbonTab Title="Help">
25: <rbn:RibbonTabGroup Title="Help">
26: <rbn:RibbonButton Text="About" LargeImageSource="Images/about.png" />
27: <rbn:RibbonButton Text="Help" LargeImageSource="Images/help2.png" />
28: </rbn:RibbonTabGroup>
29:
30: </rbn:RibbonTab>
31:
32: </rbn:Ribbon>
Codesnippet 5
1: <!-- Ribbon -->
2: <usercontrols:RibbonControl Grid.Row="0" Grid.Column="0"
3: x:Name="mainRibbon" VerticalAlignment="Top"></usercontrols:RibbonControl>
Codesnippet 6
Next, we’ll add a Telerik Coverflow control that will enable us to flip through the images. Telerik as well as Infragistics (and many other vendors) have been busy creating controls suites, giving you many more controls to work with. Codesnippet 7 shows the code for this control.
1: <telerikNavigation:RadCoverFlow x:Name="coverFlow" CameraY="-80" ItemMaxHeight="100"
2: SelectedIndex="5" VerticalAlignment="Top" CenterOffsetY="15">
3: <Image Source="Pictures/1.jpg" />
4: <Image Source="Pictures/2.jpg" />
5: <Image Source="Pictures/3.jpg" />
6: <Image Source="Pictures/4.jpg" />
7: <Image Source="Pictures/5.jpg" />
8: </telerikNavigation:RadCoverFlow>
One of the main goals of the application is of course the display of the map that will also display the route that Hans did on his bike. A perfect candidate for this is Virtual Earth. On Codeplex, a project called DeepEarth, allows us to display Virtual Earth maps inside a Silverlight application. It also includes all the necessary stuff to show paths, icons etc and allows for easy zooming and panning. We’ll use this control to display the route.
Of course, we need to convert our data for the map to use. This is very simple code shown in codesnippet 8. What we’re doing here is simply converting our generic list of Trackpoints to a list of points the DeepEarth control can work with. Codesnippet 9 shows the code for displaying the map.
1: private void AddPolygon()
2: {
3: ConfigShapeLayer();
4: var points = new List<Point> { new Point(0, 0), new Point(20, 0), new Point(20, 20), new Point(0, 20) };
5: var polygon = new DeepEarth.Geometry.Polygon { Points = points };
6: shapeLayer.Add(polygon);
7: }
Codesnippet 8
1: <DeepEarth:Map x:Name="map" >
2: <DeepControls:NavControl>
3: <DeepControls:MapSourceControl SelectedSource="Hybrid">
4: <DeepVE:TileLayer MapMode="Aerial" />
5: <DeepVE:TileLayer MapMode="Hybrid"/>
6: <DeepVE:TileLayer MapMode="Road" />
7: </DeepControls:MapSourceControl>
8: </DeepControls:NavControl>
9: <DeepControls:CoordControl/>
10: </DeepEarth:Map>
Codesnippet 9
Finally, we need to display the heartbeat, also based on the data in the generic list. We can do this in several ways (for example using the controls from the Silverlight toolkit), but here, I choose to use a listbox. Displaying a heartbeat in a listbox might not sound that normal, as we are used to having the listbox show a list of text items. However, using Silverlight, we can completely restyle the listbox using the data template (Codesnippet 10). The data template allows for complete restyling of the items as well as the listbox’ display area. The item is replaced with an ellipse, absolutely positioned from the top and the display area is replaced with a drawing canvas. (To see the entire code, download the sample). The result is shown below.
1: <DataTemplate>
2: <Canvas Canvas.Left="10" Canvas.Top="10">
3: <Ellipse Fill="Blue"
4: Tag="{Binding}"
5: Width="10"
6: Height="10"
7: Stroke="Black"
8: StrokeThickness=".5"
9: MouseLeftButtonDown="Ellipse_MouseLeftButtonDown"
10: MouseEnter="Ellipse_MouseEnter"
11: MouseLeave="Ellipse_MouseLeave"
12: >
13: </Ellipse>
14: </Canvas>
15: </DataTemplate>
Codesnippet 10
The final application
The following image shows the complete application running in the browser. You can download the entire source package by clicking here (Note that I left in all the source code for the other projects like DeepEarth. This way it’s easier for you to experiment with the demo).
(Due to the Virtual Earth webservice being down, the map is not displaying, as can be seen on the screenshot)
Download the code here.