Click here to Skip to main content
15,867,330 members
Articles / Desktop Programming / WPF

WPF Evaluated Value Binding Using Converter Parameter

Rate me:
Please Sign up or sign in to vote.
4.53/5 (10 votes)
26 Jan 2012CPOL11 min read 41.7K   197   13   14
A value binding converter for WPF.

Introduction

When I first started working with WPF, I remember the frustration I had in that it was not possible to make changes in bound values before consumption by a control. At the time I was working on custom controls, and was binding sizes/locations to ancestor values. I just needed a little adjustment, and the ability to do simple mathematical equations to the bound value would have been great.

I have been doing a lot of work recently with value converters, and finally got around looking at this problem. I knew that value converters would be a good solution using the ConverterParameter to pass the string to be used for processing the bound value. The problem was that I needed to have a decent calculation engine to process the equation. There are quite a few calculation engines around to process numbers, but only being able to process numbers would limit the flexibility of the converter since it could not even handle enumerations. At the minimum, I would want a calculation engine that could handle both numbers and strings.

When I finally had some time, I started searching the internet for a calculation engine that would serve for this value converter (did not really want to have to build one myself). There were actually quite a few good options available to me:

  1. Use the JavaScript Eval function - “Evaluate Expressions from C# using JavaScript's Eval() Function”: http://www.codeproject.com/Articles/46350/Evaluate-Expressions-from-C-using-JavaScript-s-Eva.
  2. Compile dynamically using the Microsoft complier - “Evaluating Mathematical Expressions by Compiling C# Code at Runtime”: http://www.codeproject.com/Articles/3988/Evaluating-Mathematical-Expressions-by-Compiling-C.
  3. Use a C# program that evaluates functions such as the function in “The expression evaluator revisited (Eval function in 100% managed .NET)”: http://www.codeproject.com/Articles/13779/The-expression-evaluator-revisited-Eval-function-i. There are probably many more, but I needed one that will work with strings.
  4. There was also another very interesting idea that caught my attention and that was published in “C# Formula Evaluator”: http://www.jarloo.com/c-formula-evaluator/. This uses Regular Expressions. Obviously the performance would be terrible, and I would have had to extend it to handle strings, but it is quite elegant.

I chose to use the JavaScript solution since I thought it was the easiest, is quite powerful (with all the JavaScript functionality), and should have no issues with memory leaks; I have heard that using the Microsoft compiler could have memory leak issues, although probably not much of an issue for this use since the equations would be seldom changed. To use C# compiled code, it would be best to maintain a compiled version in a dictionary. The advantage is that it would give all the power of C# and compatibility with WPF.

The last option meant I was using a lot of somebody else’s code, and would not have the power of either JavaScript or C#.

As I was starting this project, I realized that I could just reverse Convert and ConvertBy and have a converter that would quickly add the capability for users to input numbers as equations. Since I had some questions about multiple value converter implementations, I did that project first, and published it in CodeProject (Value Converter to Evaluate User Equation Input). JavaScript was definitely not as good for this capability, but I liked the simplicity; having to evaluate many different equations using the Microsoft compiler would have possibly caused memory leak issues.

Implementation

To use the JavaScript eval() function, a JavaScript function must be created:

JavaScript
package JavascriptEvaluator
{
    class JavascriptEvaluator
    {
      public function Evaluate(expr : String) : String
      {
        return eval(expr, "unsafe");
      }
    }
}

I have put this code into a file named “JavascriptEvaluator.js”.

Next, this must be complied into an assembly. This assembly will be created in the file “JavascriptEvaluator.dll” by executing the following command in the Visual Studio command prompt:

jsc /target:library JavascriptEvaluator.js

I have put this command in a batch file named “JavascriptEvaluatorCompile.bat”.

In the project using this function, references must be added to “JavascriptEvaluator.js” and “Microsoft.Jscript”. You will have to browse to the location of the “JavascriptEvaluator.js” file to add it, and the “Microsoft.Jscript” assembly is included in the Framework assemblies.

Untitled.png

I have actually implemented two different value converters, a basic single argument converter using the IValueConverter interface, and one that will handle more arguments derived from IMultiValueConverter.

The implementation for the IValueConverter is straightforward now that we have a reference to an assembly providing access to the JavaScript eval() function:

C#
partial class EquationValueConverter : IValueConverter
{
    private readonly JavascriptEvaluator.JavascriptEvaluator evaluator = 
            new JavascriptEvaluator.JavascriptEvaluator();
 
    public object Convert(object value, Type targetType, object parameter, 
        System.Globalization.CultureInfo culture)
    {
      if (parameter == null || parameter.ToString() == string.Empty)
        return value;
      string newValue = string.Format(parameter.ToString(), value);
      try
      {
        return evaluator.Evaluate(newValue);
      }
      catch (Exception)
      {
        return newValue;
      }
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, 
        System.Globalization.CultureInfo culture)
    {
      throw new NotImplementedException();
    }
}

As can be seen, only Convert is implemented since the conversion back would be difficult.

In the Convert method, first the parameter argument is checked to make sure it has some non-empty value to work with. If it does not, then at best, it would only be returning an empty value, or could have an exception. Then the string.Format method is used with the parameter and value arguments as arguments. This means that in order to create an equation, just put the equation in a string with the “{0}” placeholder for where the value should be inserted. Next, the JavaScript eval() function is executed with this string as an argument. It is in a try-catch block because an exception will be generated if the equation is invalid.

There are actually two cases handled by this code: one where the string.Format function returns a function that can be successfully executed by the JavaScript eval() function, and where string.Format returns something that is valid for the target, but will not be successfully executed by the JavaScript eval() function. string.Format is actually quite powerful, and can easily be used on its own as a converter for many uses (I will show a use later for the multi-value converter). In fact, it may be that you will want to use two different converters, one with the Eval() function, and one without. Creating a separate converter that just does the string.Format method has the advantage of not causing an exception if the string from the formatter is not a valid equation for the Eval() function, and possibly simplifying the parameter argument so that Eval() will not successfully execute the result of the string.Format function. Creating a parameter that will have the desired effect could be difficult.

Doing two separate converters would also mean that there could be better error trapping for debug information. I was not able to put in as much programmer help as I might have liked because I combined the two. I also had an example that was good to play with, but might cause many errors as the user played with input examples. You can see some of the debug information that I have used for another value converter in the article: Generic WPF/Silverlight Value Converter.

The implementation for IMultiValueConverter was actually much more difficult. The reason is that the IMultiValueConverter does not work quite the same way as the IValueConverter. Whereas the IValueConverter automatically does the conversion based on the targetType, this is apparently not true for the IMultiValueConverter. It is not that the solution is difficult, it was this figuring out why one worked and the other did not.

C#
partial class EquationValueConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, 
           System.Globalization.CultureInfo culture)
    {
      if (parameter == null || parameter.ToString() == string.Empty)
        return null;
      string newValue = string.Format(parameter.ToString(), values);
      try
      {
        return FixValue(targetType, evaluator.Evaluate(newValue));
      }
      catch (Exception)
      {
        return FixValue(targetType, newValue);
      }
    }
 
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, 
        System.Globalization.CultureInfo culture)
    {
      throw new NotImplementedException();
    }
 
    private static object FixValue(Type targetType, object value)
    {
      if (value.GetType() == targetType)
        return value;
      try
      {
        var converter = TypeDescriptor.GetConverter(targetType);
        return converter.ConvertFrom(value);
      }
      catch
      {
        return value;
      }
    }
}

To handle the problems with the conversion, I just used a method from the article Generic WPF/Silverlight Value Converter. Microsoft has provided a way of creating a class that will provide a converter for another class. This class can be associated with the associated class it translates for by using an attribute:

C#
[TypeConverter(typeof(MyClassTypeConverter))]
public class MyClass
{
    //Class implementation here
}

public class MyClassTypeConverter : TypeConverter
{
    public override object ConvertTo(ITypeDescriptorContext context,
        System.Globalization.CultureInfo culture,
        object value,
        Type destinationType)
    {
      //Conversion code here, returning object;
    }
}

In the FixValue method, I use the TypeDescriptor.GetConverter method to obtain the converter for the targetType, and then use this converter to generate an instance of the type that is needed. If there are any errors, which could be because there is no converter for the class, or the string is not recognized, then just return the value passed into the method.

ConverterParameter

ConverterParameter must be something that the JavaScript eval() function will recognize after the parameter is processed by the string.Format method, or it must stand on its own (after being processed by the string.Format method), and not be recognized as valid by the JavaScript eval() function. Because it is embedded in XML, any character that is not valid in XAML arguments will have to be replaced by the character entry references (e.g., replace a quote character with “"”).

If ConverterParameter is part of an attribute defining the binding, then there are a whole lot more issues to contend with. First thing is the Escape sequence for the curly bracket (“{“). For instance, to write the equation to add five to the value, the argument would be:

ConverterParameter ='{}{0} + 5'

The single quotes are needed because there are spaces in the ConverterParameter argument.

The following are notes for using the ConverterParameter argument within a binding attribute value:

  • The quote character (“"”) is replaced by &quote;.
  • To enter curly brackets (“{“) and the “{}” escape sequence needs to proceed the open curly bracket.
  • If there are blanks in the argument, surround the argument with single quotes {“'”).

The Example

In the example included, I have several examples of using the equation.

The first is quite basic, but allows for experimentations. Basically, there are two text boxes, one for entry, and the other for result. The equation in ConverterParameter is:

ConverterParameter={}{0}+5}

Basically, whatever is in the first text box has 5 added to it. There is the obvious support of numbers, but if text within quotes is entered in the first text box, the addition becomes a string append:

Since there are no spaces in the ConverterParameter value, all that is needed is to have the escape sequence “{}” for the curly bracket for the argument (“{0}”).

The second example is a slider just below the second text box: if this slider is to the right, the background color is pink, to the right blue. ConverterParameter is:

ConverterParameter='{}{0} > 0 ? "Pink" : "LightBlue"'

Here, all the special handling is required because there is the argument (“{0}”) which requires the escape character for the curly bracket, there are spaces, which require putting the equation inside of single quotes, and there are the double quotes for the strings that define the two colors, which require the XML. Here the equation is using the power of the JavaScript conditional operator (this is one of the reasons I used the JavaScript eval() function).

The third example is similar to the second except that the equation is actually defined as a resource. Here the border changes color depending on the position of the slider, but this time there are the colors of the rainbow instead of just two. ConverterParameter just uses a StaticResource.

ConverterParameter={StaticResource strParam}

Among other advantages of putting a string in as a static resource, it is a lot easier to write out an equation:

<sys:String x:Key="strParam">{0} > 3 ? "Violet" : {0} > 1 ? "Blue"
      : {0} > 0 ? "Green" : {0} > -1 ? "Yellow" : {0} > -2 ? "Orange" 
      :  {0} > -3 ? "Red" : "Violet"</sys:String>

I purposely used the “>” in this equation since the value of an element can contain this symbol, but cannot contain “<” (it would have to be replaced by “&gt;”). It is easy to see the advantage of defining a parameter as a resource.

The fourth example is a multi converter where three sliders control the background color for the form. ConverterParameter is:

ConverterParameter="#ff{0:X2}{1:X2}{2:X2}"

ConverterParameter is in a multi-binding, which requires the binding to be defined in its own element, and thus ConverterParameter has its own attribute. This means that the escape sequence for the curly bracket is not required. In this case, ConverterParameter only uses the power of the string.Format method, and is actually not compatible with JavaScript. When executed, an exception is thrown when the attempt is made to execute it with JavaScript, and the catch statement returns the string returned from the string.Format function. If the equation was slightly changed, then the JavaScript eval() function would successfully execute the equation (but would actually not do anything). This only requires putting the equation inside quotes, or in this case, the character sequence required for quotes:

C#
ConverterParameter="&quot;#ff{0:X2}{1:X2}{2:X2}&quot;"

In order to get this string.Format to work (because the format is hexadecimal, which is not compatible with floating point numbers), the values from the sliders must be bound to integer properties to force the values to be whole numbers. An attempt to bind directly to the sliders will result in the string.Format throwing an exception because of the attempt to convert non-whole numbers to hexadecimal numbers.

This is also the example where I discovered that the value conversion to the correct target type is not performed as part of the conversion, and this has to be explicitly done. The Text property of the TextBlock at the bottom of the Window was used at one point as the bound value to try and discover why the color was not changing. When that worked, it gave me the vital clue about why I was having trouble with the background color.

Conclusion

This converter is probably the most powerful one that I have created. With it, you will no longer have to do calculations in the ViewModel, exposing additional properties that should not be in the ViewModel. It may allow code to be removed from the code-behind. If the C# compiler is used instead of JavaScript, it should be even more powerful. I have successfully processed numbers and strings. I have not been frustrated in trying to figure out how to process DateTime values. It is easy to see the advantages of being able to write full C# code, but I am a C# developer, who has done only a little in JavaScript.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Clifford Nelson Consulting
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

Comments and Discussions

 
QuestionUse Roslyn - perfect use case for the new compiler as a service Pin
theperm1-Feb-12 0:24
theperm1-Feb-12 0:24 
AnswerRe: Use Roslyn - perfect use case for the new compiler as a service Pin
Clifford Nelson1-Feb-12 5:07
Clifford Nelson1-Feb-12 5:07 
GeneralRe: Use Roslyn - perfect use case for the new compiler as a service Pin
Sacha Barber1-Feb-12 22:48
Sacha Barber1-Feb-12 22:48 
QuestionI did something like this a few years back Pin
Sacha Barber27-Jan-12 0:57
Sacha Barber27-Jan-12 0:57 
I used IronPython to do it :Using IronPython in WPF to Evaluate Expressions, though I think I like your approach better. I recall seeing other javscript ones before, could never find the link though
Sacha Barber
  • Microsoft Visual C# MVP 2008-2012
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

AnswerRe: I did something like this a few years back Pin
Clifford Nelson27-Jan-12 7:21
Clifford Nelson27-Jan-12 7:21 
AnswerRe: I did something like this a few years back Pin
Clifford Nelson1-Feb-12 14:07
Clifford Nelson1-Feb-12 14:07 
GeneralRe: I did something like this a few years back Pin
Sacha Barber1-Feb-12 22:52
Sacha Barber1-Feb-12 22:52 
GeneralRe: I did something like this a few years back Pin
Clifford Nelson2-Feb-12 5:02
Clifford Nelson2-Feb-12 5:02 
GeneralRe: I did something like this a few years back Pin
Sacha Barber2-Feb-12 5:31
Sacha Barber2-Feb-12 5:31 
GeneralMy vote of 5 Pin
Shahin Khorshidnia26-Jan-12 21:20
professionalShahin Khorshidnia26-Jan-12 21:20 
GeneralRe: My vote of 5 Pin
Clifford Nelson1-Feb-12 5:47
Clifford Nelson1-Feb-12 5:47 
GeneralRe: My vote of 5 Pin
Shahin Khorshidnia1-Feb-12 10:22
professionalShahin Khorshidnia1-Feb-12 10:22 
GeneralRe: My vote of 5 Pin
Shahin Khorshidnia1-Feb-12 10:58
professionalShahin Khorshidnia1-Feb-12 10:58 
GeneralRe: My vote of 5 Pin
Clifford Nelson1-Feb-12 12:39
Clifford Nelson1-Feb-12 12:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.