Click here to Skip to main content
15,881,172 members
Articles / Desktop Programming / WPF

Value Converter to Evaluate User Equation Input

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
19 Jan 2012CPOL7 min read 28.8K   436   16   3
This value converter makes it very quick and easy to add the ability for the user to input equations in text boxes that are bound to numeric properties.

Introduction

I know there are many times that it would be nice to give the user the ability to enter an equation in a TextBox instead of letting the user calculate the value before entering it. I have worked on projects that have implemented this functionality.

History

I was working on a value converter that would allow the binding to a calculated value. This got me searching the internet to see what was available for evaluating a string. A number of ideas caught my eye, and I decided to use the JavaScript Eval() function. As I was working on this, I suddenly realized that I could actually do things in the opposite way, and get a value converter that would evaluate a string to return a calculated value to the binding element. It actually ended up working better than I thought.

Implementation

To use JavaScript, a JavaScript function must be created:

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

The value converter is actually very simple since only the ConvertBack method has any significant code, and it has very little:

class EvaluationValueConverter : IValueConverter
{
    private readonly JavascriptEvaluator.JavascriptEvaluator evaluator = 
       new JavascriptEvaluator.JavascriptEvaluator();

    public object Convert(object value, Type targetType, object parameter, 
           System.Globalization.CultureInfo culture)
    {
        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, 
           System.Globalization.CultureInfo culture)
    {
        if (value == null)
            return null;
        try
        {
            return evaluator.Evaluate(value.ToString());
        }
        catch (Exception e)
        {
            return null;
        }
    }

Since there is no processing on the value from the ViewModel, the Convert method just returns the value argument. ConvertBack only has to pass the value argument converted to a string to the JavaScript Eval() method through the Evaluate method of an instance of the JavaScriptEvaluator class. If there is an exception because the string cannot be evaluated, an exception will be thrown, and then just pass null to the ViewModel. A nice thing is that the WPF TextBox automatically handles the problem with the null value being passed to the ViewModel; where the property is of type decimal or some other non-Nullable type, it changes the TextBox border to red. What is nice is that the equation in the TextBox can still be edited to correct errors.

Obviously this is not ideal because the value of the property in the ViewModel does not correspond to what is displayed, and the ViewModel does not know that there is an issue with the user entry.

Calculation Engine Options

The JavaScript solution, although fairly good for my original requirements, is not really that great for this. The most significant issue is support of transcendental functions: JavaScript supports the most important transcendental functions, and the Eval() function will process these, but they are case sensitive, and must be preceded by “Math.”. Another disadvantage is that it does not support the exponent operator (“^”)--instead the pow function must be used. Ideally, a better evaluator can be created, and there are a number of good ones that can be found on the internet, but I wanted to avoid tramping on toes. The Eval() function is very powerful, and can do a lot more than anybody will probably want. A little bit of work could fix the problems with the pow function and case sensitivity.

I found a number of other options on the internet:

All are good options, several would be better than mine since it would be more intuitive for the user.

The Example in Use

Here a user has entered an equation, but has not left the TextBox, therefore the value converter has not processed the equation to determine if it is invalid:

After leaving the TextBox, the value converter has determined that there is an error in the user input, and returned a null to the property, causing an error to be identified by the View:

Here the user is entering an equation with a transcendental function (this is before the focus leaves the TextBox):

After the focus leaves the TextBox, the following is displayed:

Fixing User Input Error Detection

The main problem with this approach is that there is no way for the ViewModel to know about equation errors if bound to a number property. There is also the problem that the View really does not know about the error either, for disabling continuation. This could be fixed by making the number Nullable, but then the value displayed to the user would be reset, so that the user would not have the ability to fix the equation, also, the red error border would not show. If knowing about input errors is important, then either the ViewModel will have to be enhanced, and a multi-value converter would need to be used, or move the evaluation into the property (in this case, would attempt to evaluate the user input in the Property Set using the JavaScript Eval() method), which will be the type of string.

The last solution is the one I think is best. If an indication is needed on the form for an error, then a value converter could be used for some attribute (border, background, etc.) that would set the property to the invalid value if the property is non-numeric. The ViewModel would know that the property is invalid because it is non-numeric. The only thing left would be to convert the property between a string value and the proper numeric value for the Model. This is a little more work, but not bad for the functionality added.

What I would say is that this is a case that Microsoft missed an obvious feature in WPF; there is obviously an error, but no way to pass this information to the ViewModel.

Warning

Right now the value converter is not very smart, and if this converter is bound to a whole number type  (e.g., Integer) format, and the Eval() method returns a floating or fixed point value, it will cause an error. This can easily be fixed by adding code that determines the type of the property being bound to, and does the conversion before returning the value. This will also be a problem for overflow.

Conclusion

This value converter makes it very quick and easy to add the ability for the user to input equations in text boxes that are bound to numeric properties. It has a problem in that if the user inputs an invalid equation, the ViewModel will not know that there is a problem, and so it is possible to continue with an invalid equation in the text box. It this is acceptable, then this is a great option.

Also, this project shows how to use the JavaScript Eval() method to evaluate a string, and this can be used in other ways that may be more appropriate for the application.

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

 
QuestionUsing Iron Python Pin
Clifford Nelson1-Feb-12 14:13
Clifford Nelson1-Feb-12 14:13 
I had a recommendation to use IronPython for the caculation engine, and the following is the converter for using IronPython. It is better on this function than the other since it did not seem to work with strings, and still does not have transendental capability. Need to download IronPython from ironpython.net and then add references to IronPython and Microsoft.Scripting.

using System;
using System.Windows.Data;
using IronPython.Hosting;
using Microsoft.Scripting;

namespace EvaluationValueConverterExample
{
class EvaluationValueConverter : IValueConverter
{
private Microsoft.Scripting.Hosting.ScriptEngine engine = Python.CreateEngine();

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return null;
try
{
var source = engine.CreateScriptSourceFromString(value.ToString(), SourceCodeKind.AutoDetect);
object res = source.Execute();
}
catch (Exception e)
{
return null;
}
}
}
}
GeneralMy vote of 5 Pin
Kanasz Robert19-Jan-12 12:15
professionalKanasz Robert19-Jan-12 12:15 
GeneralRe: My vote of 5 Pin
Clifford Nelson20-Jan-12 6:15
Clifford Nelson20-Jan-12 6:15 

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.