Friday, April 24, 2009

Using IronPython For Converters in Silverlight

{

 

Tim Heuer has a great intro post on Silverlight data binding and value converters. The concept is a really nice one but I’m not wild about having to write a class that implements the IValueConverter interface every time I need some ad-hoc tweaking of the values I get from being data bound and how I’d like to use them within XAML. What set me off this track was a case where I simply needed to invert a boolean – what should just be “not myvalue” ended up requiring a class, an interface, and so on.

Dynamic languages excel at this sort of thing and since they support the ability to evaluate code on the fly I thought it would make sense to write a single implementation of IValueConverter and host Python inside of it to evaluate binding expressions that are passed in. Here is what that might look like:

public class PythonExpConverter : IValueConverter
{
private ScriptEngine _engine;

public PythonExpConverter()
{

ScriptRuntimeSetup setup = Python.CreateRuntimeSetup(null);
setup.DebugMode = true;
var runtime = new ScriptRuntime(setup);
_engine = Python.GetEngine(runtime);

}

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
object result;
string python = parameter.ToString()
.Replace("value", value.ToString());
try
{
result = _engine.Execute(python);
}
catch (Exception ex)
{
// pass, just use original value
result = value;
}
return result;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// same concept as above
return value;
}
}


No magic, just instantiate the Python runtime and then proceed to leverage it in the Convert phase by taking a string as the parameter for the conversion method. Use value as a keyword in your Python expression to get at the value of the underlying data.  Once this is added as a static resource, you can leverage it in many ways. For example, if I had a class “Person”:



public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool OnVacation { get; set; }
public double Balance { get; set; }
}


Let’s say I’m binding to an instance of Person (yeah, very contrived) with the following template conversions:

1. Uppercase last name.


2. If OnVacation is true, disable a link to their schedule.


3. Format balance using some currency specific format.


Here is the XAML that leverages my single converter for all of the above:



<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="First Name" Width="100" />
<TextBlock Text="Last Name" Width="100" />
<TextBlock Text="Is Active" Width="100" />
<TextBlock Text="Rating" Width="100" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" Width="100" />
<TextBlock Text="{Binding LastName, Converter={StaticResource PythonExpConverter}, ConverterParameter=\'value\'.upper()}" Width="100" />
<HyperlinkButton Width="100" NavigateUri="http://foo" Content="View"
IsEnabled="{Binding OnVacation, Converter={StaticResource PythonExpConverter}, ConverterParameter=not value}" />
<TextBlock Text="{Binding Balance, Converter={StaticResource PythonExpConverter}, ConverterParameter=\'%2.2f\' % value}" Width="100" />
</StackPanel>
</StackPanel>


This is a very powerful approach since the limitations you hit will probably be born by a limitation in skill with Python or your dynamic language of choice rather than the approach. 



}