Download MathEvalConverter.zip
Background
With the name Einstein, people typically assume I’m good at math. I have the utmost respect for the physicists and mathemeticians of our time, prior, and beyond. But to be honest, math is not my strong suit. In fact I have great difficulty simply adding or subtracting numbers without the use of a calculator. This deficiency actually worked to my advantage last month when I was observing the users of an application I had recently dogfed.
The application had several numeric input fields that took in costs, prices, commissions, etc. Like any run of the mill business application, these inputs affected various calculations that updated the UI accordingly. But while observing the users I kept noticing a very peculiar behavior. When they would type into a box for Cost, Price, etc. they would whip out a small desk calculator and add up some numbers before typing them into the box.
As it turns out, the values I took for granted as a pre-calculated input often require various "in-your-head" steps before arriving at what I assumed was a known "input".
"The cost of the product is normally $15 but I knocked it down by $3."
Given my mathematical challenges, I was very sympathetic to this situation. I don’t own a desk calculator but I am very familiar with the Start -> Run -> calc.exe ceremony. If I happen to have a PowerShell window open I’ll use that instead.
Did I miss a key requirement? Should there have been more input fields? Not really because the calculation steps are not necessarily formal aspects of the system. They’re mostly things people might normally do in their head had they not happen to have a calculator in front of them. And the type of calculations they perform vary from case to case.
Then I remembered… I encounter this same situation all the time when working in Excel. But Excel’s primary feature is the fact that you can type a "formula" or expression anywhere you’d put a constant value. Wouldn’t it be great if the text boxes in my application offered the same functionality? Well by the end of this post, they will!
The Plan
The idea is that my input boxes would allow the user to enter a number as usual, but if they entered an expression such as "2+2", committing the value would enter "4" into the field. The expression doesn’t need to be preserved, so the value of the field can still be backed by a simple number. Also, I only need to implement very basic expressions. I’ll stick to addition, subtraction, multiplication, division, grouping, and any combination of the above.
I thought about where I might add this functionality. I could subclass TextBox, but then the cells in a DataGrid would need to be addressed separately. I could probably have done it with a behavior but that didn’t seem appropriate either. In the end I decided to implement an IValueConverter. I was already using one to provide currency formatting in the control.
The end result would look like the working example shown below.
The Math Parser
Because this application was an in-browser Silverlight application, I could avoid having to write my own math parser by taking advantage of the HTML bridge and the Eval() method. In short, I would pass the expression to the JavaScript engine to evaluate as if it were a line of code. Of course, I would need to validate the input first to ensure a malicious user could not take advantage of this fact.
To isolate this shortcut into replacable component, I extracted an IMathEvaluator interface that would be defined as follows.
public interface IMathEvaluator {
bool TryEvaluate(string expression, out decimal value);
}
This allows me to include my JavaScript hack for the sake of this post while admitting that a less lazy developer could substitute a better implementation. One nice thing about this implementation is that I don’t need to determine if the user entered a number or expression. Even if they entered a simple number like 1, I still treat it as an expression.
The implementation is pretty simple too. It simply checks the input against a Regex to ensure it’s not going to pass some malicious payload to the JavaScript engine then calls Eval() via the Silverlight HTML bridge.
public sealed class JScriptMathEvaluator : IMathEvaluator
{
public bool TryEvaluate(string expression, out decimal value ) {
// When there's no text, just return zero
if ( expression == null || expression.Trim().Length == 0 ) {
value = 0;
return true;
}
// remove currency symbols and commas
// these are added when we format the number but we want to remove them before
// parsing the text value because it would invalidate the JavaScript syntax
var numberFormat = CultureInfo.CurrentCulture.NumberFormat;
expression = expression.Replace( numberFormat.CurrencySymbol, "" );
expression = expression.Replace( numberFormat.CurrencyGroupSeparator, "" );
expression = expression.Replace( numberFormat.NumberGroupSeparator, "" );
expression = expression.Replace( numberFormat.PercentGroupSeparator, "" );
// Ensure that a simple expression consisting of only digits,
// parenthases, and four operators (+, -, *, /) are entered.
// Never pass non-validated input to JavaScript!
if (Regex.IsMatch(expression, @"^[0-9\.\+\-\*\/\(\)\s]+$" ) ) {
try {
object eval = System.Windows.Browser.HtmlPage.Window.Eval( expression );
value = Convert.ToDecimal(eval);
return true;
} // try
catch ( Exception ex ) {
// trace exception or whatever
// but let the function return false
} // catch
} // if
value = 0;
return false;
}
}
The Value Converter
In order to convert the user’s input into a number I’ll need to invoke a conversion process when the value of the TextBox changes. I was already doing this with a FormattingConverter that would apply currency formatting to the number and then parse the text value with NumberStyles.
So let’s just derive a class from FormattingConverter with support for the ConvertBack method. We’ll call it MathEvalConverter.
public sealed class MathEvalConverter : FormattingConverter
{
public MathEvalConverter( )
{
MathEvaluator = new JScriptMathEvaluator( );
}
public IMathEvaluator MathEvaluator
{
get;
set;
}
protected override object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
{
// View -> ViewModel
string expression = System.Convert.ToString( value );
decimal numberValue;
if ( MathEvaluator != null && MathEvaluator.TryEvaluate( expression, out numberValue ) ) {
return numberValue;
} // if
// We couldn't parse the expression.
// Let silverlight reject it because:
// - if we throw an exception, it Silverlight bypasses validation events
// - if we return DependencyProperty.UnsetValue, the input will just disappear with no error!
// By returning the unconverted value back to Silverlight, at least they'll see a conversion error.
return base.ConvertBack( value, targetType, parameter, culture );
}
} // class
The converter doesn’t specifically use the JavaScript implementation. It goes through the IMathEvaluator interface which happens to be implemented by JScriptMathEvaluator.
MathEvalConverter In Action
We can apply the MathEvalConverter to any two-way binding between a numeric property on the binding source and a string property on a control. Since it derives from FormattingConverter, we can supply a format string to pretty format the numbers.
<UserControl>
<FrameworkElement.Resources>
<Local:MathEvalConverter x:Key="Eval" Format="C2" />
<Local:FormattingConverter x:Key="Date" Format="d" />
</FrameworkElement.Resources>
<Form:DataForm Header="Expense Report">
<StackPanel>
<Form:DataField Label="Reported By">
<TextBox Text="{Binding Name, Mode=TwoWay}" />
</Form:DataField>
<Form:DataField Label="Expense Date">
<TextBox Text="{Binding Date, Converter={StaticResource Date}, Mode=TwoWay}" />
</Form:DataField>
<Form:DataField Label="Meals">
<TextBox Text="{Binding Meals, Converter={StaticResource Eval}, Mode=TwoWay}" />
</Form:DataField>
</StackPanel>
</Form:DataForm>
</UserControl>
Summary
Enough talk. Download the code, and let me know what you think. I have no idea if anyone finds these posts useful if you don’t post comments!
Download MathEvalConverter.zip
Hi,
Good post! My personal favourite part of the blog entry was the background. To read about how you noticed that there was something lacking and how the conclusion that you needed the mathevaluator came to mind. Thanks for letting us learn from your thoughts!
/Robert
Hey, this is really neat, and very useful too. Great write-up.
Pete
Hi, Thanks. code is very Useful…
Hey, I used a similar trick in my Silverlight “Function Grapher” back in 2008. You can type in complex math functions and plot them. Try it here.