WPF IronPython Converters

By | May 24, 2014

I often want to do visual displays with loose WPF files that get loaded at runtime (using XamlReader.Load), but to really get work done, you have to create a bunch of converters to do all maths that is required for Storyboards and DoubleAnimations. These converters must exist in your main application as pre-compiled code so that the XamlReader can create the Xaml object at runtime.

So, the idea using IronPython to create scriptable converters at runtime (embedded in the loose Xaml files) came up. Here is the sample program I created to demonstrate it.

This is only a proof of concept app, and probably has many holes in it, but the idea does work.

1. Create an empty WPF Windows project

2. Add IronPython to the project using Nuget

NugetIronPython

 

3. Create the Python IValueConverter in code

   1:      public class PythonConverter : IValueConverter
   2:      {
   3:          /// <summary>
   4:          /// The default imports that python needs to create objects WPF can use
   5:          /// </summary>
   6:          string imports = "import clr\r\nclr.AddReferenceByPartialName(\"PresentationCore\")\r\nclr.AddReferenceByPartialName(\"PresentationFramework\")\r\nclr.AddReference('System.Data')\r\nfrom System.Windows import Media\r\n";
   7:  
   8:          #region IValueConverter Members
   9:  
  10:          /// <summary>
  11:          /// I expect the Python code to always have a method called "ReturnValue()" defined
  12:          /// </summary>
  13:          public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  14:          {
  15:              string v = value.ToString();
  16:              string p = parameter.ToString(); // parameter is the python code
  17:              p = p.Replace(@"\r\n", System.Environment.NewLine); // we want to add crlf since Python cares about them
  18:              // create the scripting engine
  19:              ScriptEngine pyEngine = Python.CreateEngine();
  20:              // add the default imports to the script
  21:              ScriptSource source = pyEngine.CreateScriptSourceFromString(imports + p, Microsoft.Scripting.SourceCodeKind.Statements);
  22:              ScriptScope scope = pyEngine.CreateScope();
  23:              // create the python object
  24:              var s = source.Execute(scope);
  25:              // get a reference to a method in the code called "ReturnValue"
  26:              // this always expects a string as input and object as output
  27:              Func<string, object> doit = scope.GetVariable("ReturnValue");
  28:              // execute the python method with the converter value
  29:              return doit(v);
  30:          }
  31:  
  32:          public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  33:          {
  34:              throw new NotImplementedException();
  35:          }
  36:  
  37:          #endregion
  38:      }

4. Create the converter in Xaml

   1:  <Window x:Class="PythonConverters.MainWindow"
   2:          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:          xmlns:local="clr-namespace:PythonConverters"
   5:          Title="MainWindow" Height="350" Width="525">
   6:      <Window.Resources>
   7:          <local:PythonConverter x:Key="pythonConverter"></local:PythonConverter>
   8:      </Window.Resources>
   9:      <StackPanel>
  10:          <TextBlock>
  11:              <TextBlock.Text>
  12:                  <!-- SETTING JUST PLAIN TEXT WITH PYTHON -->
  13:                  <Binding Path="PythonText"
  14:                           Converter="{StaticResource pythonConverter}"
  15:                           ConverterParameter="def ReturnValue(n): return n + 'hello world';"/>
  16:              </TextBlock.Text>
  17:          </TextBlock>
  18:  
  19:          <TextBlock Text="This should have color">
  20:              <TextBlock.Background>
  21:                  <!-- SETTING BACKGROUND WITH PYTHON-->
  22:                  <Binding Path="PythonText"
  23:                           Converter="{StaticResource pythonConverter}"
  24:                           ConverterParameter="def ReturnValue(n):\r\n  if n == '':\r\n    return Media.Brushes.Orange;\r\n  else:\r\n    return Media.Brushes.Green;\r\n"/>
  25:              </TextBlock.Background>
  26:          </TextBlock>
  27:  
  28:      </StackPanel>
  29:  </Window>

5. Run the app

FinalOutput

Leave a Reply

Your email address will not be published. Required fields are marked *