February 2010
Volume 25 Number 02
Dynamic .NET - Creating Interactive Bing Maps with Silverlight and IronRuby
By Ashish Ghoda | February 2010
One of the notable features of Silverlight is its support for dynamic languages such as IronRuby and IronPython. This integration capability enables the development of Rich Internet Applications (RIAs) using the Silverlight platform—XAML for the presentation layer and dynamic languages for the code-behind. This article demonstrates the integration capability of Silverlight with dynamic languages and the Microsoft Bing Map controls. I’ll start with a high-level overview of dynamic languages, then dive into Silverlight’s support for these languages. I’ll wrap up by showing you how to build an interactive 3-D animated location-finding Silverlight application using the Microsoft Bing Map Silverlight control and IronRuby.
Dynamic Language Basics
Read-Eval-Print Loop (REPL) environments provide lightweight “play as you go” programming capability for developers through the use of what are known as dynamic programming languages—languages that are dynamically typed and compiled at runtime. You do not need to declare variables of particular data types. Everything is handled by the runtime through the context of expressions.
The more familiar languages such as C# and Visual Basic are statically typed languages that are more rigid in nature. Development and deployment using dynamic languages is simpler compared to those static languages, which require compilation and distribution of output. However, you still need to do proper validation and testing for type safety when using dynamically typed languages.
With dynamic languages, you can create a function and assign it to a variable or pass it as a parameter to another function. This makes things like closures and passing functions as parameters much easier. In general, two defining characteristics of closures are the ability to assign a block of code (a function) to a variable, and this block of code’s ability to retain access to variables that were accessible where it was created.
The following traditional ShortWords method in C# returns a subset of a list of words that matches the criteria of a maximum word length of 3 or fewer letters:
public static List<string> ShortWords(List<string> wordList) {
List<string> shortWordList = new List<string>();
int maximumWordLength = 3;
foreach(string word in wordList) {
if(word.Length <= maximumWordLength) {
shortWordList.Add(word);
}
}
return(shortWordList);
}
With LINQ, you can achieve similar functionality in a much more effective way, as you can see in the following code snippet:
public static List<string> ShortWords(List<string> wordList) {
var maximumWordLength = 3;
return wordList.Where(w => w.Length <=
maximumWordLength).ToList<string>();
end
Implementing the same method in a dynamic language such as IronRuby—an implementation of the Ruby programming language for the Microsoft .NET Framework—is similar to the C# using LINQ approach and is significantly shorter than the traditional approach:
def ShortWords(wordList)
maximumWordLength = 3
return wordList.select {|w| w.Length <= maximumWordLength}
end
Just comparing these two implementations of the same algorithm reveals much about IronRuby (and dynamic languages in general). The IronRuby code is concise, and nowhere do you see a data type keyword such as string or int.
The most interesting aspect of this block of IronRuby code is the closure, located between the curly braces. Here the closure, essentially a function, is being passed to the select method. The select method uses a closure to extract a subset of a collection. The code that forms the closure actually executes within the select method (here, the closure extracts strings within the collection wordList that meet the criterion), but it retains access to the variables in its original scope (in this case, the maximumWordLength variable).
Closures are much more powerful than this simple example illustrates. They are similar to using LINQ or passing a delegate to a method such as Exists or Find in C#, with the additional benefit of retaining access to their original scope. You can get more details on closures from the book I wrote with Jeff Scanlon, “Accelerated Silverlight 3” (Apress, July 2009).
Dynamic Languages for Silverlight
Silverlight currently supports the IronRuby and IronPython dynamic languages via the Microsoft Dynamic Language Runtime (DLR) engine—a generic platform and hosting model for dynamic languages to run on top of the Microsoft .NET Framework Common Language Runtime (CLR).
The DLR is a set of .NET Framework libraries and services that dynamically discover types at runtime using reflection, so that code written in dynamic languages can be executed on the .NET platform.
There are five DLR scripting assemblies that provide the runtime scripting environment—bridging the dynamic languages with Silverlight:
- Microsoft.Scripting.dll
- Microsoft.Scripting.Core.dll
- Microsoft.Scripting.Silverlight.dll
- Microsoft.Scripting.ExtensionAttribute.dll
- Microsoft.Scripting.Debugging.dll
Microsoft.Scripting.Silverlight.dll contains classes that let developers write Silverlight applications using dynamic languages. One of the key classes is DynamicApplication, which inherits directly from System.Windows.Application. This class represents the Silverlight-based dynamic application object by providing access to visual elements from the dynamic language code, as well as an entry point for dynamic language applications to host on Silverlight hosts. It provides additional properties that extend the Host, Resources, and RootVisual properties already supplied by the Application class.
IronRuby (ironruby.net) is an open source implementation of the Ruby programming language that provides integration between Ruby and the .NET Framework.
The current version of IronRuby (1.0-rc1) supports the .NET Framework 3.5 and the .NET Framework 4 beta. Note that IronRuby 1.0-rc1 provides both .zip and .msi downloads. For Silverlight applications, it’s better to use the .zip version.
IronPython (ironpython.codeplex.com) is an open source implementation of the Python programming language that, like IronRuby, allows integration of the Python language with the .NET Framework. Currently you can download IronPython 2.6 for the .NET Framework 3.5 and the .NET Framework 4 beta.
IronRuby and IronPython each have two assemblies that support the specific language, providing capabilities such as parsing the language and communicating with the host environment. They are IronPython.dll and IronPython.Modules.dll for IronPython, and IronRuby.dll and IronRuby.Libraries.dll for IronRuby.
Note that both IronRuby and IronPython are in continuous development. Visit their homepages to access the latest releases and documentation. You can also get the related source code, along with the source code of the DLR, by visiting dlr.codeplex.com.
Installing Development Components
There are two approaches to developing dynamic language-based Silverlight applications:
- The traditional approach using the Chrion.exe development utility
- The just-text approach using inline browser scripting
In this article, I will provide an overview of both approaches and then will develop a sample Microsoft Bing Maps application using the newer just-text approach.
Since the introduction of Silverlight 2, along with the DLR scripting libraries, Microsoft has provided a dynamic language-based Silverlight application development environment via the Chiron.exe development utility and IronRuby and IronPython Silverlight application project templates.
To get started, download and install the DLR and either IronRuby or IronPython from the sites mentioned earlier. Samples, documentation, utilities and some additional important components are installed along with IronRuby and IronPython.
The Silverlight templates for dynamic languages provide core application files, which are available under the Silverlight\script\templates\ruby and Silverlight\script\templates\python folders. Figure 1 shows some details about these application template files.
Figure 1 Core Files for Dynamic Language-Based Silverlight Applications
IronRuby | IronPython Installed with IronRuby | IronPython Installed Independently | Description |
index.html | index.html | index.html | Hosts the dynamic language-based Silverlight application. |
app\app.rb | app\app.py | python\app.py | Main startup file for the Silverlight application. |
app\app.xaml | app\app.xaml | python\app.xaml | Main XAML user interface file. |
css\screen.css | css\screen.css | stylesheets\screen.cs | Defines application styles. |
Not provided | Not provided | stylesheets\error.css | Defines application error styles and format. |
js\error.js | js\error.js | javascripts\error.js | Manages unhandled application errors. |
The Script folder includes the sl.bat file, which will help you to create a preliminary dynamic language-based Silverlight application. The following shows the command-line format:
sl [ruby|python] <ApplicationPath>
Chiron.exe, the Silverlight development utility, dynamically packages a set of files into an .xap file for deployment. (For further discussion of Chiron.exe and compilation, see blog.jimmy.schementi.com/2009/03/state-of-dlr-for-silverlight.html.)
You can launch an application using Chiron.exe with the /b (browser) option:
Chiron /b
One of the interesting features of Chiron.exe is that any time you modify a file within the application directory, Chiron.exe will repackage the application into an .xap and reload it. You must still refresh any active browser sessions, though.
The Just-Text Approach
The traditional DLR-based development approach makes using the Chrion.exe utility mandatory and follows the old edit-compile-refresh development model.
It is now possible to write IronRuby, IronPython and XAML code within (X)HTML markup directly in the browser (see ironruby.com/browser for details). This is called the just-text approach. No need to install any components to create and run the DLR-based application. The just-text approach follows the write-save-refresh development model and removes the need for Chrion.exe.
With the just-text approach, you don’t even need local copies of the DLR scripting assemblies, along with the IronRuby and IronPython language-specific assemblies mentioned earlier. Though the Gestalt sample package available from ironruby.com/browser contains the binaries, you can also reference the dlr.js from a well-known server, and that requires that you have nothing installed. However, you do need a way to host Silverlight controls and enable DLR integration within the HTML page.
The Gestalt project capitalizes on the existing Silverlight.js approach, which uses the JavaScript API to create the Object tag that hosts the Silverlight control. It also enables error management and detecting the browser and Silverlight plug-in requirements on the client machine. The Mix Online Lab team enhanced the Silvelright.js file to include inline scripting and DLR integration capabilities and renamed the file as dlr.js.
To get started, the Gestalt project provides the cross-browser, cross-platform library built on the DLR. You can get the compressed library file, gestalt.zip, from visitmix.com/labs/gestalt/downloads. Figure 2 provides details on the core files included in the .zip file.
Figure 2 Core Library Files for the Just-Text Approach
IronRuby File | Description |
dlr\dlr.js | Enhanced Silverlight.js file to host the dynamic language-based Silverlight application and enable inline scripting on HTML pages. |
dlr\ gestaltmedia.js | Enables HTML5 video and audio playback. |
dlr\dlr.xap | Includes the AppManifest.xaml file that references Microsoft.Scripting.slvx and points to Microsoft.Scripting.Silverlight.dll as an entry point assembly. Also includes languages.config to provide configuration. information for DLR languages. |
dlr\IronRuby.slvx | Includes the IronRuby.dll and IronRuby.Libraries.dll files to enable development of IronRuby-based Silverlight applications. |
dlr\IronPython.slvx | Includes the IronPython.dll and IronPython.Modules.dll files to enable development of IronPython-based Silverlight applications. |
dlr\ Microsoft.Scripting.slvx | Includes five DLR scripting assemblies (Microsoft.Scripting.dll, Microsoft.Scripting.Core.dll, Microsoft.Scripting.Silverlight.dll, Microsoft.Scripting.ExtensionAttribute.dll and Microsoft.Scripting.Debugging.dll) that provide the runtime scripting environment, bridging the dynamic languages with Silverlight. |
samples/getting.started/*.html | Sample Web pages demonstrating inline scripting, IronRuby, IronPython and XAML capabilities. |
Note that from Silverlight 3, the Transparent Silverlight Extensions capability enables developers to package the commonly used assembly files as a separate reusable library with an .slvx filename extension. The .slvx files can be deployed on a common Internet location or client-specific location. The required .slvx files must be referenced in the AppManifest.xaml file within the ExternalParts section as an ExtensionPart with the correct path.
Jimmy Schementi’s excellent paper on the just-text approach (ironruby.com/browser/sl-back-to-just-text.pdf) provides some very helpful guidance. This paper also details how to change the default DLR settings of the dlr.js file.
You need a Web server instance such as IIS or Apache to host and run the DLR-based inline scripted Web applications. From gestalt.zip, place the Dlr and Samplesfolders in the root of the Web server. If you do not install these folders at the root of the Web server, you need to modify the dlr.js file appropriately.
Next add MIME types for .rb, .py and .slvx files, like the following:
- For .rb and .py files set the MIME-type to: text/plain
- For .slvx files set the MIME-type to: application/octet-stream
To validate the environment, visit the samples/get.started folder and browse the 05_final.html file. The Web page demonstrates the IronPython, HTML and XAML-based graphics with animation-integration capabilities, as shown in Figure 3.
Figure 3 Running the Gestalt Project’s Sample Application
Silverlight, IronRuby and the Just-Text Approach
Let’s start by defining the skeleton of a DLR-based Silverlight application using IronRuby, following the just-text approach. Once you copy the Gestalt files to the Web server root, simply open a text editor and start writing your HTML file. It’s that simple!
The good thing is, dlr.js adds a Silverlight control to the page and provides all the necessary core requirements to enable dynamic language integration capabilities. For this, simply include the dlr.js file on the HTML page:
<head>
<script src="/dlr/dlr.js" type="text/javascript"></script>
</head>
Note that including dlr.js will configure default settings for your DLR-based Silverlight application. If you would like to customize the settings, you need to override the defaults by writing custom script code within the HTML file. For details, see Jimmy Schementi’s paper mentioned earlier.
Now you are all set to write XAML and IronRuby or IronPython code in the HTML file, within a script tag. To write inline IronRuby code, you need to add the script tag with the appropriate type and class information and place the IronRuby code within the tag, like so:
<script type="application/ruby"
class="Class Name Goes Here">
IronRuby Code Goes Here
</script>
To write inline IronPython code, simply do the same with the appropriate substitutions:
<script type="application/python"
class="Class Name Goes Here">
IronPython Code Goes Here
</script>
To write inline XAML code, add the script tag with the appropriate type, ID, width, and height information and place the XAML code within the tag:
<script type="application/xml+xaml" id="Place ID here"
Width="400" Height="400">
<UserControl ...>
XAML Code Goes Here
</UserControl>
</script>
You can access the XAML controls and implement the event integration using code, which I will demonstrate while developing the application in the next section. Once you finish with the code, just browse the page and you should see the application outcome right away.
Bing Map Integration
Now that you’ve seen the basic skeleton of the dynamic language Silverlight application using the inline scripting just-text approach, let’s take it a step further with the integration of Microsoft Bing maps (formerly known as Virtual Earth).
The Microsoft Bing Maps Silverlight Control SDK version 1 was released in November 2009 (msdn.microsoft.com/library/ee681884). The installer is called BingMapsSilverlightControlv1.0.1Installer.msi. Note that you need at least Silverlight 3 to work with this control. The installation includes Microsoft.Maps.MapControl.dll and Microsoft.Maps.MapControl.xml, Microsoft.Maps.MapControl.Common.dll and Microsoft.Maps.MapControl.Common.xml and offline documentation.
Before you start building applications using the Silverlight Bing Maps control, you must create a Bing Maps Developer account to receive the application authentication key. To do so, visit bingmapsportal.com.
The Microsoft Bing Maps Silverlight control CTP release was available before the release of Version 1 of the SDK. There are considerable changes and enhancements in version 1 compared to the CTP release. See msdn.microsoft.com/library/ee681889 to understand the key differences between the CTP and version 1 releases.
Now create a SilverlightMap.html file and include the dlr.js file as described in the previous section.
You need to modify the AppManifest.xaml file (available in the dlr.xap file) and include Microsoft.Maps.MapControl.dll and Microsoft.Maps.MapControl.Common.dll files to load as part of the application start up. To do this, rename dlr.xap to dlr.xap.zip and extract the AppManifest.xaml and languages.config files from the file. Then add Microsoft.Maps.MapControl.dll and Microsoft.Maps.MapControl.Common.dll files as AssemblyPart, as shown in Figure 4.
Figure 4 Modified AppManifest.xaml File
<Deployment
xmlns="https://schemas.microsoft.com/client/2007/deployment"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
RuntimeVersion="2.0.31005.0"
EntryPointAssembly="Microsoft.Scripting.Silverlight"
EntryPointType="Microsoft.Scripting.Silverlight.DynamicApplication"
ExternalCallersFromCrossDomain="ScriptableOnly">
<Deployment.Parts>
<AssemblyPart Source="Microsoft.Maps.MapControl.dll" />
<AssemblyPart Source="Microsoft.Maps.MapControl.Common.dll" />
</Deployment.Parts>
<Deployment.ExternalParts>
<ExtensionPart Source="Microsoft.Scripting.slvx" />
</Deployment.ExternalParts>
</Deployment>
Now zip up the modified AppManifest.xaml, existing langugages.config, Microsoft.Maps.MapControl.dll and Microsoft.Maps.MapControl.Common.dll files and rename the .zip file to dlr.xap. Overwrite the existing dlr.xap file (under the Dlr folder) on the Web server with the new one.
Next, open the SilverlightMap.html file, add the script tag for the XAML code, then add the UserControl with the name Silverlight_map and a reference to the map control to create the necessary namespace (see Figure 5).
Figure 5 Referencing Map Controls in the HTML File
<Deployment
xmlns="https://schemas.microsoft.com/client/2007/deployment"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
RuntimeVersion="2.0.31005.0"
EntryPointAssembly="Microsoft.Scripting.Silverlight"
EntryPointType="Microsoft.Scripting.Silverlight.DynamicApplication"
ExternalCallersFromCrossDomain="ScriptableOnly">
<Deployment.Parts>
<AssemblyPart Source="Microsoft.Maps.MapControl.dll" />
<AssemblyPart Source="Microsoft.Maps.MapControl.Common.dll" />
</Deployment.Parts>
<Deployment.ExternalParts>
<ExtensionPart Source="Microsoft.Scripting.slvx" />
</Deployment.ExternalParts>
</Deployment>
<script type="application/xml+xaml" id="sl_map"
Width="1350" Height="575">
<UserControl x:Name="silverlight_map"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" Width="200"
Height="280" Background="Black"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl">
</UserControl>
</script>
Finally, add the Canvas control as the main container and the Map element under the Grid control. Notice that I kept the same width and height of Canvas and Grid controls and set the Width and Height properties of the Map control to 800 and 400, respectively:
<Canvas x:Name="container" Width="1350" Height="575">
<Grid x:Name="layout_root" Width="1350" Height="575">
<m:Map CredentialsProvider="AuthenticationKey"
Width="800" Height="400" Grid.Column="1"
HorizontalAlignment="Center"/>
</Grid>
</Canvas>
</UserControl>
In the code snippets shown here, you need to replace “AuthenticationKey” with your authentication key for the Map control.
Copy the file to the existing Sample/Getting.started folder on the Web server and browse the page. You should see the Map with the default Road mode (see Figure 6).
Figure 6 DLR-Based Bing Map Silverlight Application in Default Road Map Mode
Map Modes and 3-D Animation
Let’s change the map mode to aerial with labels as the default view. Let’s also introduce 3-D animation to the map.
To change the default map mode, first give the Map element a name (in this example, I used map_in_ironruby) so it can be referenced in the IronRuby-based inline code. I’ll also apply 3-D projection to the Map object. This was a new feature in Silverlight 3. To do that I set the Projection property of the Map object to PlanProjection and I set the RotationX property to -20. This transforms the Map object, giving a slightly skewed viewing angle:
<Grid x:Name="layout_root" Width="1350" Height="575" Background="Black">
<m:Map x:Name="map_in_ironruby"
CredentialsProvider="AuthenticationKey"
Width="800" Height="400">
<m:Map.Projection>
<PlaneProjection RotationX="-20"/>
</m:Map.Projection>
</m:Map>
</Grid>
Notice that I also changed the background of the Grid to black.
Now add the script tag for IronRuby and write the following lines of code to include the required assemblies (including MapControl.dlls) and turn on aerial view with labels for the Map control.
Notice that with the new just-text approach, I can reference the Map object by name. In the current version of Gestalt libraries, you need to reference objects with me or xaml shorthand (here I used the me shorthand). In the future, XAML elements with the x:Name set can be accessed through root_visual shorthand:
<script type="application/ruby" class="sl_map">
require "Microsoft.Maps.MapControl.dll"
require "Microsoft.Maps.MapControl.Common.dll"
include System::Windows
include System::Windows::Controls
include Microsoft::Maps::MapControl
sm = me.silverlight_map
sm.map_in_ironruby.mode = AerialMode.new(true)
</script>
Note that I kept the class name of the IronRuby-related script tag as sl_map, which is similar to the ID of the XAML-related script tag.
Now if you run the application, you’ll see the black background, skewed 3-D angle and labeled aerial view, as shown in Figure 7.
Figure 7 Map Mode Set to Aerial with Labels Mode and with 3-D Projection
One popular demonstration at the MIX09 conference was Silverlight and Microsoft Bing Map integration with spinning capabilities for the Map object. Let’s implement something similar in IronRuby.
To implement this feature, you first need to define the Grid with two columns using ColumnDefinitions:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="1100"/>
</Grid.ColumnDefinitions>
Next, add three buttons named Rotate Map, Pause, and Stop and Reset, along with title text in the XAML file within the Border. All of this is in the first column on the Grid (see Figure 8).
Figure 8 Adding Controls to the Grid Object
<StackPanel Grid.Column="0" Orientation="Vertical">
<Border CornerRadius="20" Margin="0,50,0,5" Width="150"
Background="DarkBlue" HorizontalAlignment="Center">
<StackPanel Orientation="Vertical">
<TextBlock Text="3D Rotation"
HorizontalAlignment="Center"
FontSize="12" Foreground="White" Margin="0,5,0,10"/>
<Button x:Name="RotateMap" Height="25"
Content="rotate_map" Width="100" Margin="0,0,0,10"
Foreground="Black" VerticalAlignment="Center"
HorizontalAlignment="Center" />
<Button x:Name="pause_resume" Height="25"
Content="Pause" Background="DarkGoldenrod"
Foreground="Black" Width="100" Margin="0,0,0,10"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
<Button x:Name="stop_reset" Height="25"
Content="Stop and Reset" Background="DarkGoldenrod"
Foreground="Black" Width="100" Margin="0,0,0,10"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</StackPanel>
</Border>
</StackPanel>
Now add the Map object to the second column of the Grid:
<m:Map x:Name="map_in_ironruby"
CredentialsProvider="AuthenticationKey"
Width="800" Height="400" Grid.Column="1"
HorizontalAlignment="Center">
<m:Map.Projection>
<PlaneProjection RotationX="-20" RotationY="0"
RotationZ="0"/>
</m:Map.Projection>
</m:Map>
In the next step, you’ll need to create some rather complex XAML update code. I recommend creating the XAML file using a development environment such as Visual Studio or Expression Blend to take advantage of their editing and IntelliSense features. Then you can copy the completed XAML to the app.xaml file of your project.
Create a Storyboard with the name map_animation targeted for the Map object named map_in_ironruby. (An excerpt is shown in Figure 9. See the code download for this article for the entire Storyboard code block.). The Storyboard defines the keyframes for the PlaneProjection animation properties RotationZ, RotationY, GlobalOffsetX and GlobalOffsetZ. Add the StoryBoard as a UserControl resource.
Figure 9 Creating the Storyboard for Animation
<StackPanel Grid.Column="0" Orientation="Vertical">
<Border CornerRadius="20" Margin="0,50,0,5" Width="150"
Background="DarkBlue" HorizontalAlignment="Center">
<StackPanel Orientation="Vertical">
<TextBlock Text="3D Rotation"
HorizontalAlignment="Center"
FontSize="12" Foreground="White" Margin="0,5,0,10"/>
<Button x:Name="RotateMap" Height="25"
Content="rotate_map" Width="100" Margin="0,0,0,10"
Foreground="Black" VerticalAlignment="Center"
HorizontalAlignment="Center" />
<Button x:Name="pause_resume" Height="25"
Content="Pause" Background="DarkGoldenrod"
Foreground="Black" Width="100" Margin="0,0,0,10"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
<Button x:Name="stop_reset" Height="25"
Content="Stop and Reset" Background="DarkGoldenrod"
Foreground="Black" Width="100" Margin="0,0,0,10"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</StackPanel>
</Border>
</StackPanel>
<UserControl.Resources>
<Storyboard x:Name="map_animation">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="map_in_ironruby"
Storyboard.TargetProperty=
"(UIElement.Projection).(PlaneProjection.RotationZ)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="15"/>
<EasingDoubleKeyFrame KeyTime="00:00:01" Value="0"/>
<EasingDoubleKeyFrame KeyTime="00:00:02" Value="-15"/>
<EasingDoubleKeyFrame KeyTime="00:00:03" Value="0"/>
<EasingDoubleKeyFrame KeyTime="00:00:04" Value="15"/>
</DoubleAnimationUsingKeyFrames>
...
</Storyboard>
</UserControl.Resources>
The next step is to add the Click events to the inline IronRuby code to start, pause, resume and stop the animation. First you need to add a reference to System.Windows.Media and System.Windows.Media.Animation:
include System::Windows::Media
include System::Windows::Media::Animation
Disable the pause_resume button during the initialization process of the application:
sm.pause_resume.is_enabled = false
Now implement the Click event of each button. Start with the rotate_map button. First set the Map object to left horizontal alignment in order to best use the available space for the animation. Then set the RepeatBehavior property of the StoryBoard animation to Forever and begin the animation. Finally, enable the pause_resume button and set the button content to Pause:
sm.rotate_map.click do |s,e|
sm.map_in_ironruby.horizontal_alignment = HorizontalAlignment.Left
sm.map_animation.repeat_behavior = RepeatBehavior.Forever
sm.map_animation.begin
sm.pause_resume.is_enabled = true
sm.pause_resume.content = "Pause"
end
Next, implement the pause_resume button Click event. Here, depending on whether you’re in a paused state or a running state, you want to either resume or pause the Storyboard animation and change the button content:
sm.pause_resume.click do |s,e|
strbtnContent = sm.pause_resume.content.ToString
if strbtnContent == "Pause"
sm.pause_resume.content = "Resume"
sm.map_animation.pause
else
sm.pause_resume.content = "Pause"
sm.map_animation.resume
end
end
Finally, implement the stop_reset button Click event. Here you stop the Storyboard animation, disable the pause_resume button and reset the button content to Pause. You also reset the alignment of the Map object:
sm.stop_reset.click do |s,e|
sm.map_animation.stop
sm.pause_resume.is_enabled = false
sm.pause_resume.content = "Pause"
sm.map_in_ironruby.horizontal_alignment = HorizontalAlignment.Center
end
Compile and run the project using Chiron /b command to see the map with animation. Figure 10 shows the rotating map.
Figure 10 3-D Map Animation
Targeting Predefined Locations
Now let’s highlight three predefined locations on the map: New York, San Francisco and Vancouver. This is demonstrated in C# as part of the documentation of the Microsoft Bing Map Control for Silverlight CTP. I will show you how to implement this feature using IronRuby.
First you need to update the inline XAML code to add three additional buttons in a new section on the left side of the screen, one for each location—New York, San Francisco and Vancouver. These are implemented much like the previous buttons. One notable change is the addition of the Tag attribute to each Button element. The Tag attribute defines specific location coordinates and the zoom level of the map.
The following code snippet shows the XAML code for adding a Button for the New York location:
<Button x:Name="newyork" Height="25" Width="100"
Content="New York" Margin="0,0,0,10" Foreground="Black"
VerticalAlignment="Center" HorizontalAlignment="Center"
Tag="40.7199,-74.0030,0.0000 12.0000"/>
This attribute provides the coordinate information for each location. When the user clicks the button, this information is used to retarget the map. See the code download for the entire location-finder buttons code.
Most applications have a title, and this should be no different. I added the title “Microsoft Bing Maps Silverlight Control and IronRuby Integration” in the second column of the Grid by replacing the existing Map element to place it under the StackPanel along with the title TextBlock control:
<StackPanel Grid.Column="1" Orientation="Vertical">
<TextBlock VerticalAlignment="Top"
HorizontalAlignment="Center" FontSize="20"
Foreground="Red" Margin="0,5,0,0"
Text="Microsoft Bing Maps Silverlight Control and IronRuby Integration" />
<m:Map x:Name="map_in_ironruby" Width="800" Height="400"
HorizontalAlignment="Center" Margin="0,50,0,20">
...
Now the presentation layer is complete. If you execute the application at this point, you should see the additional three buttons under the new Locate Location section. However, the map will not be moved to the corresponding location if you click on any of the newly added buttons. For that you need to implement code behind for each button Click event.
The Click events are the same for all three buttons. Based on the value of the Tag property of the corresponding clicked button, pass those coordinates and zoom level as the view specification to create the new map view. Here I used the Split method to split the coordinates and the zoom level and set the map view using the SetView method of the Maps control. The new map view will show the defined location:
sm.newyork.click do |s,e|
tag_information = s.Tag.split
location_Converter = LocationConverter.new
location_info = location_Converter.ConvertFrom(tag_information[0].ToString)
sm.map_in_ironruby.SetView(location_info, tag_information[1]);
end
You also need to add the reference Microsoft.Maps.MapControl.Design to the program to create a new map view.
include Microsoft::Maps::MapControl::Design
And that’s the finished application. As you can see, it would be easy to customize the views, add other location targets and implement additional features.
Moving Forward
Before finishing the article, I would like to quickly introduce the externalizing inline script code (XAML and IronRuby/IronPython) approach.
To modularize your programming model, first copy the final SilverlightMap.html file and rename it to SilverlightMap-ExternalScript.html. Then cut and paste the inline XAML code from the SilverlightMap-ExternalScript.html file to a new blank text file and save as SilverlightMap.xaml file. Next, cut and paste the IronRuby code from the SilverlightMap-ExternalScript.html file to the new blank text file and save as SilverlightMap.rb file.
Now, update the XAML and IronRuby script tags of the SilverlightMap-ExternalScript.html file with the src attribute defining path of the external XAML and IronRuby files:
<html><head>
<script src="/dlr/dlr.js" type="text/javascript"></script>
</head>
<body>
<script type="application/xml+xaml"
src="/samples/getting.started/SilverlightMap.xaml"
id="sl_map" Width="1350" Height="575">
</script>
<script type="application/ruby"
src="/samples/getting.started/SilverlightMap.rb"
class="sl_map">
</script>
</body> </html>
Finally, copy three new files—SilverlightMap-ExternalScript.html, SilverlightMap.xaml and SilverlightMap.rb—to the Sample/Getting.started Web server folder. Now if you browse the SilverlightMap-ExternalScript.html file, you will get the same rotating map with location-finding capabilities.
Please visit SilverlightStuff.net to read my article on the same application created using the traditional approach (using Chiron.exe).
Ashish Ghoda is founder and president of Technology Opinion LLC, and associate director at a Big Four accounting firm. Visit his sites technologyopinion.com and SilverlightStuff.net, or contact Ghoda directly at askashish@technologyopinion.com.
Thanks to the following technical experts for reviewing this article: Laurence Maroney and Jimmy Schementi