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.
Snowball.be - The blog of Gill Cleeren
Click here to see the original post