Lloyd.NET

Programing experiments

The Last IValueConverter

source code

  • update (2008/09/25): added a ScriptExtension and mentioned the LambdaExtension.

 

Introduction

Back when I started WPF I though Data Binding was great but, sometimes, I just couldn’t get the information I needed from the value easily enough.

Here I would describe a simple fictitious problem. Let says I want to display a list of integer, with a background color depending whether the number is even or odd.

image

The XAML for this might looks like that:

<ListBox ItemsSource="{Binding List, ElementName=root}"><ListBox.ItemTemplate><DataTemplate><TextBlock Text="{Binding}"Background="{?? what shall I put here ??}"/></DataTemplate></ListBox.ItemTemplate></ListBox>

 

Upon initial investigation

Initially I was thinking to write my own MarkupExtension which would have an expression property and run some sort of script.

It turns out:

  1. You can’t do that. The Binding extension do some things you can’t do. For example you can’t always know when a property change. You can’t find an object by name (as with the ElementName property of the Binding extension), etc…
  2. There is a better way. The Binding extension already does all the work, all you need is a smart IValueConverter to set to the Binding.Converter property.

 

What’s an IValueConverter

When you do data binding in WPF, the framework automatically do some conversion for you, so that the provided value can be used to set the target property. But what if you want to modify the behavior a little bit and decide yourself how to do the conversion?
You can, by setting the Converter property of the binding to an IValueConverter!

public class MyConverter : IValueConverter{#region IValueConverter Memberspublic object Convert(object value, Type targetType, object parameter, CultureInfo culture){throw new NotImplementedException();}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){throw new NotImplementedException();}#endregion}

 

Now the code can look like that:

Window1.xaml

<Window.Resources><local:ColorConverter x:Key="int2color"/></Window.Resources><Grid><ListBox ItemsSource="{Binding List, ElementName=root}"><ListBox.ItemTemplate><DataTemplate><TextBlock Text="{Binding}"Background="{Binding Converter={StaticResource int2color}}"/></DataTemplate></ListBox.ItemTemplate></ListBox></Grid>

ColorConverter.cs

public class ColorConverter : IValueConverter{public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (!(value is int))return null;int i = (int)value;return i % 2 == 0 ? Brushes.Red : Brushes.Green;}}

 

Now every time I encounter such a problem I can write a new IValueConverter.
Wait, NOOOOOOOOOOO!!!!…….

Seriously can’t I just write some build-in (in the XAML) quick code?

 

Script investigation

Initially I found Ken Boggart converters (Sorry, no link, my Google skills have apparently decreased with old age). They even have a script based converter.
I didn’t like them enough because it was using a quick and dirty script engine, tightly coupled with the value converter.

 

I also thought of using IronPython as the script engine. And while this is a great and mature project there is one thing which kills me with Python. Code blocks are defined with the indentation! Seriously…
And it’s just not an aesthetic problem. Imagine a moment: write some embedded python script into your xaml, “accidentally” reformat the document (CTRL+E,D) and voila, you have just broken your python script!
Tsss.. too bad.

 

I thought of writing my own simple interpreter. My initial plan was to use ANTLR. And while it’s a great project, with a great IDE, it’s a bit tedious to test / develop a grammar in C#, as all the tools are geared towards Java. Then I discovered Irony.
I haven’t used it much (compared to ANTLR) but my first impression is:

  • Writing the grammar is easier, better looking and definitely more C# friendly than ANTLR.
  • The next stage, the interpreter or AST visiting is much more tedious.
  • Plus ANTLR makes it very super easy to embed action directly into the parser, enabling a all in one parsing / interpreting phase for example.

But then, it turns out, I didn’t need to write my own Irony powered interpreter / script engine, there is already one: Script.NET.

 

The ScriptConverter

Script.NET is a quick and easy script engine that can, as all self-respecting .NET script engine, call upon all .NET methods and classes. It can easily be embedded into your application and the syntax is so simple, you can teach it yourself in 21 minutes! Great, just what I was looking for. And, of course, it uses curly braces to define code blocks!

The current version of Script.NET, at this time (Sept. 2008), doesn’t support the latest Irony. It ships as 3 DLLs on it own (plus the outdated Irony) and it has a dependency on Windows Forms. I decided to “fix” all of that and the version shipped in the zipped source code is not the original Script.NET source code, but a custom version which comes as only 1 DLL, with no WinForms dependency.

In my converter I had to, obviously, add an expression property (for the script). Less obvious but as critical, I had to add a known types property, so that the script could call on static property or on some classes constructor.

 

How does it works

Well to starts with you create a script interpreter with just one line

Script script = Script.Compile("my script string");

Then you can define some global variables or add known types as follows:

script.Context.SetItem("value", ContextItem.Variable, value);script.Context.SetItem("Brushes", ContextItem.Type, typeof(Brushes));

And then you can evaluate it in one line as well

return script.Execute();

 

A little problem which stopped me for a few minute was, how do I return something from my script?

Well a simple value is an expression and the last computed expression is the returned value. For example “1” is a valid script which return 1.

Script.NET doesn’t support the (bool_expr ? true_expr : false_expr) operator, but you can express it as:

if (bool_expr)true_expr;elsefalse_expr; 

 

First Solution

Now let’s write my script converter.

ScriptConverter.cs

[ContentPropertyAttribute("Expression")]public class ScriptConverter : IValueConverter{Script script;TypeDictionary knownTypes = new TypeDictionary();string expression;public string Expression{get { return expression; }set{expression = value;script = Script.Compile(expression);SetTypes(script, knownTypes);}}public class TypeDictionary : Dictionary<string, Type> { }public TypeDictionary KnownTypes{get { return knownTypes; }set{knownTypes = value;SetTypes(script, knownTypes);}}static void SetTypes(Script ascript, TypeDictionary types){foreach (var item in types)ascript.Context.SetItem(item.Key, ContextItem.Type, item.Value);}public object Convert(object value, Type targetType, object parameter, CultureInfo culture){script.Context.SetItem("value", ContextItem.Variable, value);script.Context.SetItem("param", ContextItem.Variable, parameter);script.Context.SetItem("culture", ContextItem.Variable, culture); 
return script.Execute();}}

 

Armed with this ultra cool converter, my XAML is now ready to rock!
You will notice that I initialized the Script with a list of type it need to know to evaluate the expression.

Window1.xaml

<Window.Resources><local:ScriptConverter x:Key="int2color"><local:ScriptConverter.KnownTypes><local:TypeDictionary><x:Type x:Key="Brushes" TypeName="Brushes"/></local:TypeDictionary></local:ScriptConverter.KnownTypes>if( value % 2 == 0 )Brushes.Red;elseBrushes.Green;</local:ScriptConverter></Window.Resources><Grid><ListBox ItemsSource="{Binding List, ElementName=root}"><ListBox.ItemTemplate><DataTemplate><TextBlock Text="{Binding}"Background="{Binding Converter={StaticResource int2color}}"/></DataTemplate></ListBox.ItemTemplate></ListBox></Grid>

 

Further Simplification

Okay now I have done it all in XAML, and it works well. But I exchanged a useless C# file for a bloated XAML! smile_embaressed

What I would like to is just put the expression in the converter, like a lambda expression.

It’s where MarkupExtension(s) will come to the rescue. Basically extension are custom way to create object with code running in the XAML. {x:Type …}, {x:Static …}, {Binding …} are all MarkupExtensions.

Create your own extension is easy. Okay let’s write the simplified XAML I really would like to see, and then write the extension:

<ListBox ItemsSource="{Binding List, ElementName=root}"><ListBox.ItemTemplate><DataTemplate><WrapPanel Orientation="Horizontal"><TextBlock Text="{Binding}"Background="{Binding 
Converter={local:Script
'if(value%2==0) Brushes.Red; else Brushes.Green;',
{
x:Type Brushes}}}"/></WrapPanel></DataTemplate></ListBox.ItemTemplate></ListBox>

Here we go, it’s a much simpler XAML code! In the Converter parameter of the binding I passed an extension which directly created my converter. This extension will take the following parameter: a script string and a list of type necessary for the script to run.

Initially I was planning to use an extension constructor like that:

public ScriptExtension(string code, params Type[] types){//....}

But it won’t work because XAML won’t recognize the “params Type[]” construct, and it will expect 2 and only 2 parameter, the second being array of types. There are also a few other problems with markup extension parameter (XAML choose them sorely on the number of parameter, it doesn’t do any type matching). Finally I used this code

public class ScriptExtension : MarkupExtension{public ScriptExtension(string code) { Init(code); }public ScriptExtension(string code, Type type) { Init(code, type); }public ScriptExtension(string code, Type type, Type t2) { Init(code, type, t2); }protected ScriptExtension(string code, params Type[] types) { Init(code, types); }void Init(string code, params Type[] types){Code = code;TypeDictionary dict = new TypeDictionary();foreach (var aType in types)dict[aType.Name] = aType;KnownTypes = dict;}public string Code { get; private set; }public TypeDictionary KnownTypes { get; private set; }

// return the converter herepublic override object ProvideValue(IServiceProvider isp){return new ScriptConverter(Code) { KnownTypes = KnownTypes };}}

 

 

 

 

 

Designer Issue

It worked quite nicely at runtime. But there was a problem with the designer. It just could not work with those multiple constructor. In the end I created multiple ScriptExtension subclass called Script0Extension, Script1Extension, Script2Extension, etc… depending on the number of type parameters.

Hence the final XAML look like that

<ListBox.ItemTemplate><DataTemplate><WrapPanel Orientation="Horizontal"><TextBlock Text="{Binding}"Background="{Binding 
Converter={local:Script1
'if(value%2==0) Brushes.Red; else Brushes.Green;',
{
x:Type Brushes}}}"/></WrapPanel></DataTemplate></ListBox.ItemTemplate>

 

Alternate Solution

At this stage we got a pretty good solution for many problem. But then I investigated this LambdaExtension. It’s really cool and I added it to my personal toolbox and to this ExpressionExplorer project as well.

It lets you define converters with Lambda expression, for exemple:

<TextBlock Text='{Binding Source={x:Static s:DateTime.Now},Converter={fix:Lambda "dt=>dt.ToShortTimeString()"}}'>  

It’s pretty good and I imagine Lambda expression would be way faster that my script expression. However it suffers from a few problems:

  • you cannot use the other parameters of the converter method (parameter and culture).
  • you cannot reference any type, you cannot return a Brush, i.e. it cannot solve my simple problem!

But I guess that, even with these shortcomings, it can still make an excellent converter in DataTriggers.

 

Additional Information

Here are some people or pages that have inspired me while working on this code.

Later on I had these additional feedback which proved to be very valuable.

First Orcun Topdagi has improved his LambdaExtension further, as you can check out on his blog about WPFix2 and WPFix3. Wow those are real hot goodies. I might even ditch my extension in his favor! But I have to spend some more time playing with it first (kind of super busy right now).

Also Daniel Paull has some very interesting blog posts (here, here and here) on how to do something similar with the DLR. What I really like about these article it’s that they are an easy introduction to the DLR.


source code.

 

 


Categories: Source Code | WPF
Permalink | Comments (1) | Post RSSRSS comment feed
blog comments powered by Disqus