## The Math Converter

August 20, 2011

Quite often I have wanted to adjust a bound value in my XAML by a mathematical formula.

For example, I might want to size a control to 50% of it’s parent’s size. Or I’ll want to position it 30% down and 20% left of a parent panel. Or I might even want to make a control’s width something like 50% of (WindowHeight – 200). Of course, I could always write converters for this, but I got tired of doing that repeatedly so sat down one day to write one big MathConverter.

### Using the Math Converter

Using the MathConverter is simple. All you have to do is pass the Converter a math equation as the ConverterParameter, substituting @VALUE for your bound value. The equation can consist of numbers, basic operators (+, -, *, /, %), parentheses, and @VALUE.

For example, if you wanted a control whose height was 50% of the current Window Size, you would create a binding that looked like this:

```Height="{Binding ElementName=RootWindow, Path=ActualHeight,
Converter={StaticResource MathConverter},
ConverterParameter=@VALUE/2}"```

Or if you wanted to make the control’s width equal to 30% of (WindowWidth – 200) your binding might look like this:

```Width="{Binding ElementName=RootWindow, Path=ActualWidth,
Converter={StaticResource MathConverter},
ConverterParameter=((@VALUE-200)*.3)}"```

### The Math Converter

Here’s the actual converter code.

It takes the ConverterParameter, substitutes the bound value for @VALUE, then starts reading the equation from left-to-right. If it encounters an operator, it performs the specified operation on the previous number and the next number in the list. If it encounters a parentheses, it handles the equation inside the group first, then replaces the grouped equation in the string with the result of the grouped equation. Any character it finds that is not a number, operator, or parentheses throws an exception.

```// Does a math equation on the bound value.
// Use @VALUE in your mathEquation as a substitute for bound value
// Operator order is parenthesis first, then Left-To-Right (no operator precedence)
public class MathConverter : IValueConverter
{
private static readonly char[] _allOperators = new[] { '+', '-', '*', '/', '%', '(', ')' };

private static readonly List<string> _grouping = new List<string> { "(", ")" };
private static readonly List<string> _operators = new List<string> { "+", "-", "*", "/", "%" };

#region IValueConverter Members

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Parse value into equation and remove spaces
var mathEquation = parameter as string;
mathEquation = mathEquation.Replace(" ", "");
mathEquation = mathEquation.Replace("@VALUE", value.ToString());

// Validate values and get list of numbers in equation
var numbers = new List<double>();
double tmp;

foreach (string s in mathEquation.Split(_allOperators))
{
if (s != string.Empty)
{
if (double.TryParse(s, out tmp))
{
}
else
{
// Handle Error - Some non-numeric, operator, or grouping character found in string
throw new InvalidCastException();
}
}
}

// Begin parsing method
EvaluateMathString(ref mathEquation, ref numbers, 0);

// After parsing the numbers list should only have one value - the total
return numbers[0];
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}

#endregion

// Evaluates a mathematical string and keeps track of the results in a List<double> of numbers
private void EvaluateMathString(ref string mathEquation, ref List<double> numbers, int index)
{
// Loop through each mathemtaical token in the equation
string token = GetNextToken(mathEquation);

while (token != string.Empty)
{
// Remove token from mathEquation
mathEquation = mathEquation.Remove(0, token.Length);

// If token is a grouping character, it affects program flow
if (_grouping.Contains(token))
{
switch (token)
{
case "(":
EvaluateMathString(ref mathEquation, ref numbers, index);
break;

case ")":
return;
}
}

// If token is an operator, do requested operation
if (_operators.Contains(token))
{
// If next token after operator is a parenthesis, call method recursively
string nextToken = GetNextToken(mathEquation);
if (nextToken == "(")
{
EvaluateMathString(ref mathEquation, ref numbers, index + 1);
}

// Verify that enough numbers exist in the List<double> to complete the operation
// and that the next token is either the number expected, or it was a ( meaning
// that this was called recursively and that the number changed
if (numbers.Count > (index + 1) &&
(double.Parse(nextToken) == numbers[index + 1] || nextToken == "("))
{
switch (token)
{
case "+":
numbers[index] = numbers[index] + numbers[index + 1];
break;
case "-":
numbers[index] = numbers[index] - numbers[index + 1];
break;
case "*":
numbers[index] = numbers[index] * numbers[index + 1];
break;
case "/":
numbers[index] = numbers[index] / numbers[index + 1];
break;
case "%":
numbers[index] = numbers[index] % numbers[index + 1];
break;
}
numbers.RemoveAt(index + 1);
}
else
{
// Handle Error - Next token is not the expected number
throw new FormatException("Next token is not the expected number");
}
}

token = GetNextToken(mathEquation);
}
}

// Gets the next mathematical token in the equation
private string GetNextToken(string mathEquation)
{
// If we're at the end of the equation, return string.empty
if (mathEquation == string.Empty)
{
return string.Empty;
}

// Get next operator or numeric value in equation and return it
string tmp = "";
foreach (char c in mathEquation)
{
if (_allOperators.Contains(c))
{
return (tmp == "" ? c.ToString() : tmp);
}
else
{
tmp += c;
}
}

return tmp;
}
}```

One day I plan on expanding this further to use an IMultiValueConverter that accepts multiple bindings, but I haven’t had a need for that yet so it hasn’t gotten done.