After I wrote the “Intro to IronRuby/DLR Scripting in C# Silverlight 4 Application” post, I came across an interesting series on embedding DLR scripts in XAML with WPF. This is an interesting series, although the code doesn’t run in Silverlight, due to the fact that Silverlight is only a subset of WPF and doesn’t support the System.Windows.Markup.MarkupExtension class. I test out a couple things in Silverlight, and I was able to get similar DLR scripting functionality working under Silverlight using a combination of a simple, custom IValueConverter and a custom UserControl class.
If you are unfamiliar with value converters and the IValueConverter interface (same for both Silverlight and WPF), you may want to look it up and learn how to create your own. Value converters are tremendously helpful when performing data binding.
Embedded IronRuby within XAML
To start, here’s a few examples of IronRuby code embedded within a custom IValueConverter and custom UserControl. As you’ll see, this can be very effective to embed scripting to define how to display the values bound, or to manipulate the content of the custom user control. Although, you must keep in mind that this is not limited to simple tasks such as these. You have the full power of the DLR and .NET within the DLR scripts being executed, so there really is no limit to what could be coded within.
<UserControlx:Class="SLXamlEmbeddedScript.MainPage"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:system="clr-namespace:System;assembly=mscorlib"xmlns:local="clr-namespace:SLXamlEmbeddedScript"mc:Ignorable="d"d:DesignHeight="300"d:DesignWidth="400"x:Name="root"DataContext="test"><UserControl.Resources><!-- This converter uses a Ruby object's 'add' method to add 5 plus 5 and return the results --><local:DLRScriptValueConverterx:Key="FivePlusFiveConverter"xml:space="preserve"> class AddFive def add 5 + 5 end end a = AddFive.new a.add</local:DLRScriptValueConverter><!-- This converter uses the value being bound via data binding, passed through to IronRuby as 'ConverterValue', and generates a custom value to return. In this case it concatenates the FirstName and LastName properties --><local:DLRScriptValueConverterx:Key="GetFullNameConverter"xml:space="preserve"> ConverterValue.FirstName + " " + ConverterValue.LastName</local:DLRScriptValueConverter></UserControl.Resources><StackPanelx:Name="LayoutRoot"Background="White"><!-- Bind using the FivePlusFiveConverter defined above --><TextBlockText="{Binding Converter={StaticResource FivePlusFiveConverter}}"></TextBlock><!-- Bind using the GetFullNameConverter defined above --><TextBlockText="{Binding ElementName=root, Converter={StaticResource GetFullNameConverter}}"></TextBlock><!-- Use DLRScriptUserControl (custom UserControl) to embed IronRuby code within XAML and execute it on the Content of the control. --><local:DLRScriptUserControlx:Name="testusercontrol"><local:DLRScriptUserControl.Script><system:Stringxml:space="preserve"> Ctrl.FindName('txtName').Text = 'Hello from IronRuby'</system:String></local:DLRScriptUserControl.Script><local:DLRScriptUserControl.Content><TextBlockx:Name="txtName">Default</TextBlock></local:DLRScriptUserControl.Content></local:DLRScriptUserControl></StackPanel></UserControl>
One thing to not about the above usage code, is that you must use xml:space=”preserve” when embeding the IronRuby/DLR scripts. This will ensure the line breaks are preserved in the resulting string. Without it the code will not run, since the IronRuby syntax depends on those line breaks.
DLRScriptValueConverter - IValueConverter
This value converter is really simple, and allows you to specify ConvertScript and ConvertBackScript to allow the value converter to convert in two way mode.
Also, as you’ll see from the code, you could make this value converter work with both IronRuby and IronPython, as the Language property suggests. Although, for this simple example, the only supported language is IronRuby. To support IronPython it’s just a matter of instantiating the appropriate ScriptEngine class to Execute the script with.
using System;using System.Windows.Data;using System.Windows.Markup;using Microsoft.Scripting.Hosting;namespace SLXamlEmbeddedScript { [ContentProperty("ConvertScript")]publicclass DLRScriptValueConverter : IValueConverter {public DLRScriptValueConverter() {this.Language = "IronRuby"; }publicstring Language { get; set; }publicstring ConvertScript { get; set; }publicstring ConvertBackScript { get; set; }#region IValueConverter Memberspublicobject Convert(objectvalue, Type targetType, object parameter, System.Globalization.CultureInfo culture) {if (string.IsNullOrEmpty(this.Language)) {thrownew InvalidOperationException("DLRScriptValueConverter.Language property must be set"); }if (string.IsNullOrEmpty(this.ConvertScript)) {thrownew InvalidOperationException("parameter or DLRScriptValueConverter.ConvertScript property must be contain a value"); }if (this.Language.ToLowerInvariant() != "ironruby") {thrownew InvalidOperationException(string.Format("Unsupported DLR Language ({0}). Currently only IronRuby is supported.", this.Language)); }// Create Ruby ScriptEngine ScriptEngine engine = IronRuby.Ruby.CreateEngine();// Make the "value" to be converted available to the DLR engine.Runtime.Globals.SetVariable("ConverterValue", value);// Execute the script and return its resultreturn engine.Execute(this.ConvertScript); }publicobject ConvertBack(objectvalue, Type targetType, object parameter, System.Globalization.CultureInfo culture) {if (string.IsNullOrEmpty(this.Language)) {thrownew InvalidOperationException("DLRScriptValueConverter.Language property must be set"); }if (string.IsNullOrEmpty(this.ConvertBackScript)) {thrownew InvalidOperationException("parameter or DLRScriptValueConverter.ConvertBackScript property must be contain a value"); }if (this.Language.ToLowerInvariant() != "ironruby") {thrownew InvalidOperationException(string.Format("Unsupported DLR Language ({0}). Currently only IronRuby is supported.", this.Language)); }// Create Ruby ScriptEngine ScriptEngine engine = IronRuby.Ruby.CreateEngine();// Make the "value" to be converted available to the DLR engine.Runtime.Globals.SetVariable("ConverterValue", value);// Execute the script and return its resultreturn engine.Execute(this.ConvertBackScript); }#endregion } }
DLRScriptUserControl – Custom UserControl
Just like the value converter above, the DLRScriptUserControl control is really simple. It inherits from UserControl to allow you to set the controls Content property to the XAML you want it to display, and implements a Script property that allows you to define the IronRuby script to execute.
When the IronRuby script is executed during the controls Loaded event, the control passes IronRuby a global variable named ‘Ctrl’ that contains a reference to the DLRScriptUserControl object itself. Like the XAML example above, this allows you to execute any code you want against the control once it is Loaded.
using System.Windows;using System.Windows.Controls;using IronRuby;using Microsoft.Scripting.Hosting;namespace SLXamlEmbeddedScript {publicclass DLRScriptUserControl : UserControl {publicobject Script { get; set; }public DLRScriptUserControl() {this.Loaded += new RoutedEventHandler(DLRScriptUserControl_Loaded); }void DLRScriptUserControl_Loaded(object sender, RoutedEventArgs e) {// Create IronRuby Engine ScriptEngine engine = Ruby.CreateEngine();// Give IronRuby access to this control via a variable named 'Ctrl' engine.Runtime.Globals.SetVariable("Ctrl", this);// Execute the code engine.Execute(this.Script asstring, engine.CreateScope()); } } }
Conclusion
The above code is rather simple, and not meant for production use. It is just a basic prototype framework of how you might embed some DLR scripting within a Silverlight application. One thing I plan on looking into next (and I may or may not blog about it) is using the XamlReader class to dynamically load the XAML and its embedded DLR script at runtime. The combination of both would allow you to build some very simple plugin-like functionality into your applications.
Hope this helps someone.
Continued Here: Silverlight: Embed IronRuby within XAML Part 2