<Image
Source="{Binding Content}"
Canvas.Left="{Pfz:MathBinding [CenterX]-[Width]/2}"
Canvas.Top="{Pfz:MathBinding [CenterY]-[Height]/2}"
Width="{Binding Width}"
Height="{Binding Height}"
/>
Introduction
In this article I will present a WPF markup extension that allows users to declare bindings using complex math formulas.
The article is divided in two main parts:
- How to use the
MathBinding
;
- How it works under the hood.
The first part is probably what most developers will want, even those who don't have experience with WPF. The reason is simple: Using the MathBinding
developers will be able to avoid the creation of lots of converter classes, avoiding the need to instantiate those converters as resources in the XAML, to reference them as converters in the bindings and making those bindings easier to read.
The second part, well, it is an advanced topic. It explains how to parse the math expressions, tokenize them, parse the tokens, generate LINQ expressions (that are compiled), to finally explain how to make the markup extension capable of using all this. Yeah, it is a lot and it is complex, yet I hope I am making it simpler than most documents that explain how to create a compiler.
1. Using the MathBinding
Starting from the most basic items, you need to add the libraries Pfz.MathEvaluation.dll and Pfz.MathEvaluation.Wpf.dll to your project. You can optionally add the MathParser.cs, the MathVariable.cs and the MathBinding.cs files directly into your project and avoid the DLL references.
Then, in any XAML that's going to use the MathBinding
you will need to add its namespace. For example, in the sample application you will see this code in the MainWindow.xaml:
<Window x:Class="MathExpressionEvaluatorSampleWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:App="clr-namespace:MathExpressionEvaluatorSampleWPF"
xmlns:Pfz="clr-namespace:Pfz.MathEvaluation.Wpf;assembly=Pfz.MathEvaluation.Wpf"
Height="350" Width="525" Title="MathExpression Test Application"
>
The bolded line shows the use of the namespace where the MathBinding
is found. In this case, I used the name Pfz to reference the namespace, but you are free to use any name you want. Also, it is important to adapt the clr-namespace
or the assembly
declaration if you decide to put the files directly in your project or if you decide to rename the namespaces.
Considering you keep the Pfz
name, you can declare any math binding by using Pfz:MathBinding expression
. To see a real example, it can be like this:
<TextBlock Text="{Pfz:MathBinding [Width]*[Height]}"/>
In the previous example, the expression will multiply the value coming from a property named Width
with the value from a property named Height
of the DataContext
of the TextBlock
.
What can we do with the MathBinding?
The MathBinding
supports 5 operators (addition +, subtraction -, multiplication *, division / and modulo %), constant values, defining precedence with parenthesis, any property path by writing it inside square brackets (so, using [Property.SubProperty.Array[0]]
is valid if such path is valid in a normal binding) and it can support any function that you register as long as such function uses only double
as the input parameter types (if it has input parameters at all) and returns double
.
To register functions, we should use the MathParser.StaticRegisterFunction()
before the binding is loaded (so, registering all the math functions during the initialization of the application is a good idea).
For example, to register the Abs
function, we can do:
MathParser.StaticRegisterFunction(Math.Abs);
Actually the StaticRegisterFunction
has many overloads, but all of them are there only to make it simpler to register functions from 0 to 3 parameters without declaring the delegate type explicitly.
If you want to register a function with more parameters, for example, you can use the more basic version of the StaticRegisterFunction
, which receives any Delegate
, giving the delegate type you want. For example:
MathParser.StaticRegisterFunction(new MyDelegateWith10Parameters(MethodWith10Parameters));
And, to avoid naming conflicts as the math parser doesn't support function overloading or simply to give a specific name to your function (needed if you use anonymous delegates) you can give the name as the first parameter. For example:
MathParser.StaticRegisterFunction
(
"IsGreaterThan",
(x, y) =>
{
if (x > y)
return 1.0;
return 0.0;
}
);
When using a function that receives more than one parameter in the math binding, you should put the entire expression inside single quotes, or else the compiler will complain that the MathBinding
doesn't have a constructor that receives 2 (or more) parameters.
So, to use the IsGreaterThan
function, we can use this line:
<Label Content="{Pfz:MathBinding 'IsGreaterThan([Width], [Height])'}"/>
Data Source
By default, like any binding, the data source comes from the object's DataContext
property. But we have two ways to set different data sources:
-
ElementName: By setting this property, all the paths inside the square brackets will be considered to come from that source. For example:
<Label Content="{Pfz:MathBinding ElementName=alternativeSource, Expression=[ActualWidth]*2}"/>
-
Sub-tags: By declaring the bindings as a sub-tag, we are free to declare more inner bindings. This is quite similar to how MultiBinding
s work, so each sub-binding can have its own source. This version is more verbose, but allows you to use many different sources in the same final expression. It is important to note that the variables that will hold the values for those sub-bindings will be named b0
, b1
, b2
etc. It is also possible to mix bindings declared in square brackets and bindings declared as sub-bindings. Example:
<Label>
<Label.Content>
<Pfz:MathBinding Expression="[Width]-b0">
<Binding ElementName="labelOtherSource" Path="ActualWidth"/>
</Pfz:MathBinding>
</Label.Content>
</Label>
C# Code
If you want to create a MathBinding
from the code behind, you can. You should use the MathBinding.Create()
method to achieve this. The Create()
method is what the ProvideValue()
calls to create the real binding, so it is preferable that you call it directly instead of creating a MathBinding
to then call its ProvideValue
. Everything that you can do with the XAML can be done in the C# code and I really believe the C# version has some advantages when setting different sources.
In the sample application you can find this code:
var actualHeightBinding = new Binding("ActualHeight");
var mathBinding = MathBinding.Create("Sqrt(b0-[Width]+[Width])", actualHeightBinding);
Obviously doing a -[Width]+[Width]
becomes a no-operation (we subtract and then add the same value) but it is in the sample only to show that you can continue to use the normal way of writing bindings, plus the code-behind specific way of declaring bindings which, in this case, is the actualHeightBinding
that must be accessed by the name b0
.
The MathParser class
Well, you probably saw that there are two DLLs. One is the DLL with the MathBinding
class, which is WPF specific, and the other is the one that does all the math parsing and is not WPF specific.
The MathBinding
only does the job of translating things like [Width]
into a variable name (like b0
). The real math parsing (which actually involves a real compilation) is done by the MathParser
class, and that's why you need to register the functions to the MathParser
class, not to the MathBinding
.
The MathParser
class can be used directly if you simply need to parse math expressions for other reasons that aren't binding related.
The important things to know about the MathParser
are:
- You can register functions to specific instances. You are not forced to register all functions using the
StaticRegisterFunction
;
- The variables you want to use from the expression are declared by using the
DeclareVariable
method. The variable name must start with a lower-case character. You must store the object returned by such method, as you need to change the Value
of such a returned object to affect the variable of the compiled expression;
- You can get a LINQ expression from a string expression, before it is compiled, if you want to have a LINQ expression for any reason;
- Two or more expressions can be compiled using the same
MathParser
and they will share the MathVariable
instances, so changing the variable for one of the generated delegates will affect the value of the other delegate if it is executed so you should usually only compile a single string math expression per MathParser
, yet you are free to use it differently.
Performance
The algorithm is pretty slow for a single evaluation. This happens because it is slower to create a LINQ expression than it is to simply calculate the final value, as the expression generation involves creating new objects. Also, the LINQ expression is not immediately executable, so we need to compile such expression, wasting time again.
Yet, in the end there's a delegate that executes compiled code, avoiding any new parsing and being extremely fast. So, if you compile the expression once and then execute it many, many times, it will be faster than reparsing the expression every time. If you want a single execution, well, at this moment there's no optimization for this. It will be slow. Yet, slow is a relative term, as you will probably don't notice anything if you compile only 5 or 10 expressions. It will be slow if you compile thousands of expression and use them only once.
Fortunately, bindings compile their expressions only once and reutilise the compiled expression to recalculate their values when there's a change to a property they are bound to.
2. Under the hood
The easy part is finished. If you only want to use the library, you can download the sample application and you can start writing your own MathBinding
expressions.
Now it is the moment in which I will explain how to create such a solution. I am trying to make it simple but, as any compiler, it is an advanced topic.
How it works?
The algorithm is divided in three steps:
- The string expression is split into tokens;
- The tokens are visited generating a LINQ expression;
- The LINQ expression is compiled.
If the LINQ expressions didn't exist, I would probably create my own classes that represent the same thing and I would need to make make all of them capable of compiling their content to a DynamicMethod
. Even if this looks hard, it is actually easier than the parsing, yet it will be boring. Fortunately, I avoided this fatigating step by using LINQ expressions.
So, let's undertand each step.
Step 1 - Tokenization
-x * Sqrt(y) + 5.7 / (-y + x)
The first step is tokenization. During this step we want to isolate and categorize each entity. For example, in the previous expression, the Sqrt
is composed of four characters, yet we consider it as a single token, of type function call. The 5.7
is in a similar situation, being composed of 3 characters and representing only a single token, of type value.
There are cases in which a single character really represents a single token, be it in a small value, like 1
(yet numbers are of variable size) or on specific situations, like the operators and parenthesis. The important thing is that we split those tokens, so in the next step we will be able to go from one token to the next (or even to the previous) without having to deal with the string anymore, so we can advance to the next token or return to the previous one by adding one or subtracting one
from an index.
Also, it is important to note that spaces don't count as a token (yet they end a token, so 1 1
will not be interpreted as 11
).
So, an expression like:
1+ 57.12345/Sqrt(x)
Will generate the following tokens:
Type |
Content (if any) |
Value |
1 |
Addition |
|
Value |
57.12345 |
Division |
|
FunctionName |
Sqrt |
OpenParenthesis |
|
VariableName |
x |
CloseParenthesis |
|
To make things pretty simple, I am immediately capable of identifying function names and variable names without having to evaluate if the next token is a parenthesis opening. Uppercase starting words are always function names while lowercase starting words are always variable names. It was my option to do this to make the next step simpler. Most programming languages use the same token type for both string types (lowercase and uppercase starting words), making the next step more complicated.
Note: Some very optimized parsers could read each token as part of the second step, having a special logic if they ever need to return to a previous token. Yet I prefer to read the tokens first, generating a list, then work over the list of tokens in the second step. This allows me to read my own code with ease and to navigate forward and backwards without problems.
Step 2 - LINQ Expression generation and priorities
-x * Sqrt(y) + 5.7 / (-y + x)
I will return to this bizarre expression. It actually doesn't generate any meaningful result to me. I am only using it because it has all the important traits I want to deal with.
It starts with a symbol. Expressions are usually composed of a math variable or value plus a variable number of operators followed by another sub-expression. But when we start with a -, we are starting with an operator.
That expression also uses variables, functions calls, values (constants, if you prefer), parenthesis (to force a priority) and operators with different priorities (we must not forget that a division has a higher priority than an addition, so the 5.7 must be divided before being added to the sub-expression at its left).
Well, maybe the most interesting trait is that we start dealing with the tokens from left to right. So, how can we make the division and the parenthesis have a higher priority than things coming at their left?
This is where we use recursive coding. I will not write the real code here, but I will try to present a simplified algorithm:
Parse
GetTokens and then
ParseOneOrMoreIncludingSigns
ParseOneOrMoreIncludingSigns
if currentToken is addition
AdvanceToken, effectively ignoring this token
return ParseOneOrMoreLowPriority
if currentToken is subtraction
AdvanceToken
leftLinqExpression = Expression.Subtract(0, the result from GetValue)
return ParseOneOrMoreLowPriorityLoop giving our left LINQ expression as the initial expression.
return the ParseOneOrMoreLowPriority directly
ParseOneOrMoreLowPriority
leftLinqExpression = ParseOneOrMoreHighPriority
return ParseOneOrMoreLowPriorityLoop giving our left LINQ expression as the initial expression.
ParseOneOrMoreLowPriorityLoop(currentLinqExpression)
while currentToken is a low priority operator
AdvanceToken
otherExpression = ParseOneOrMoreHighPriority
currentLinqExpression = makeLinqExpression(currentLinqExpression, operator, otherExpression)
return currentLinqExpression
ParseOneOrMoreHighPriority(currentLinqExpression)
currentLinqExpression = GetValue
while currentToken is a high priority operator
AdvanceToken
otherExpression = GetValue
currentLinqExpression = makeLinqExpression(currentLinqExpression, operator, otherExpression)
return currentLinqExpression
GetValue
switch(currentToken type)
case variableName: Generate a LINQ expression that access a MathVariable
case functionName: for each parameter the function expects, call a ParseOneOrMoreIncludingSigns, looking for a comma as separator
case value: return a LINQ Expression.Constant(the value itself)
case parenthesisOpening: calls ParseOneOrMoreIncludingSigns telling that the end character is a parenthesis closing.
anything else: an exception is thrown.
This approach actually solves all the ordering problems. Each time that we do something like:
a + b / c + d
The low-priority parsing method will process x +
and will ask the high priority method to do its parsing. Such high priority method will be able to parse the b / c
but will not be able to process the next + operator
, returning a LINQ expression that already "did" the division task to the low priority parser, which will put such entire division sub-expression as the right expression that it was waiting and will continue the loop, being capable of doing the + d
operation.
As an alternative explanation, we could see the flow like this:
- We start analyzing if there's a signal (+ or -). There isn't, so we go to the low-priority method directly.
- The low priority method asks a high-priority method to run, which in turns ask to read a value, which returns a read-variable expression (the
a
). When it returns, we are in the high-priority method again but, as the + operator
isn't a high-priority operator, it returns to the low-priority method.
- The low-priority method then enters its loop, asking the high-priority method to do its job again. This time the high-priority method is capable of doing
b/c
before returning.
- In any case, the loop had a "currentLinqExpression" that was originally filled with an access to the
a
variable, and it combines such expression with the new result b / c
expression, becoming a correctly ordered a + (b/c)
. This entire expression becomes the current expression of the loop and a new addition is encountered. It calls a high-priority method again, yet it ends-up only getting a single variable, and so the loop combines the current expression it had with the new operation and variable, becoming equivalent to (a + (b/c)) + d
So, even if we didn't add any parenthesis in our expression, it will generate the exactly same LINQ expression it would've generated if we really gave the expression entirely ordered by parenthesis. So, to the compiled expression, the use of excessive parenthesis will not affect the performance at all, because the parenthesis simply don't exist.
Step 3 - Compiling
Well, I hope I made it clear how the LINQ expression is generated, because the last step is simply wrapping it with a Expression<Func<double>>
and asking it to be compiled. As I already said, I avoided the step of generating a DynamicMethod
with IL code by using LINQ expressions.
With this we have finished with the MathParser
and we have compiled a math expression given as string.
MathBinding
In my opinion, the MathParser
may be the hardest logic part, yet I consider the MathBinding
to be the hardest one by lack of knowledge on how WPF bindings and markup extensions work. Maybe it is my problem, maybe it is really some lack of documentation, I don't know.
I already saw many "Math Parsers" for WPF and I never saw one that actually compiled the expressions like the MathBinding
. Yet, what usually made me avoid them is one of these factors:
- They work over a single property, so I can add, subtract, multiply or divide values by a constant, but nothing else;
-
They use a very verbose approach, having to declare each property set as a sub-tag, the binding as a sub-tag of that tag and then adding one object per sub-binding. For example, instead of this:
<Image
Source="{Binding Content}"
Canvas.Left="{Pfz:MathBinding [CenterX]-[Width]/2}"
Canvas.Top="{Pfz:MathBinding [CenterY]-[Height]/2}"
Width="{Binding Width}"
Height="{Binding Height}"
/>
We would need to write something similar to this:
<Image Source="{Binding Content}" Width="{Binding Width}" Height="{Binding Height}">
<Canvas.Left>
<Pfz:MathBinding Expression="x-y/2">
<Binding Path="CenterX" />
<Binding Path="Width" />
</Pfz:MathBinding>
</Canvas.Left>
<Canvas.Top>
<Pfz:MathBinding Expression="x-y/2">
<Binding Path="CenterY" />
<Binding Path="Height" />
</Pfz:MathBinding>
</Canvas.Left>
</Image>
Well, I think you understand why I wanted something different.
As I already explained, the MathParser
is independent from WPF. It doesn't know about object properties, Dependency Properties or the like. I didn't want to make it WPF specific, as I think the math parsing capability is very powerful in other situations.
The only communication we have to an expression after it is compiled are the variables and, considering the actual version of the code, it is our responsibility to declare the variables before compiling the expression. The variable are also lower-case, so we can't simply write an expression like:
CenterX-Width/2
So, I decided to add a pre-parsing step. To make such pre-parser work without replicating the entire tokenization logic, I decided to use the []
as scape characters. In fact, I wanted to use the {}
characters, which are the most common ones on many different string interpolation algorithms, but such characters already have a special meaning in XAML.
So, property paths must be written like this:
[Property]
or
[Property.SubProperty.AnotherSubProperty]
And this is why we write:
[CenterX]-[Width]/2
Instead of writing:
CenterX-Width/2
Even if it has some extra characters, I really think it is still small and clear, avoiding large amounts of boilerplate code.
MarkupExtension, MultiBinding and Pre-parsing
Normal Bindings can only be bound to a single property, but an expression like [CenterX]-[Width]/2
must be bound to two expressions.
Well, WPF supports MultiBinding
s, so my initial idea was to sub-class it. Apparently I didn't know how Binding
s are expected to work and I didn't found a document talking about it. I should not inherit from a Binding
or from a MultiBinding
, even if those classes aren't sealed. There are no useful methods to override. Inheriting from the BindingBase
, which is the base class for a Binding
, is also useless, as the overridable method is internal.
Yet, Binding
s in general are MarkupExtension
s, and we can create our own MarkupExtension
s. A MarkupExtension
has one very important method: ProvideValue
. Such method is responsible for, well, providing the value that will fill the property. Yet a Binding
doesn't provide a single value and the provide value will not be magically called every time the value of a property we want to observe is changed.
Well, my first try was to return a MultiBinding
instead of returning a direct value. That didn't work. Again, this was my lack of knowledge about how Binding
s work.
In any case, I can create a MultiBinding
configured the way I need and call its ProvideValue
from the ProvideValue
I am implementing. It will actually return a BindingExpression
. I really didn't look how that class works. It works and my MathBinding
is working by delegating the real Binding
work to a private multi-binding instead of inheriting from one. This is composition and for some things is much better than inheritance, as I don't want users to manipulate the MathBinding
as if it was a normal MultiBinding
.
So, the important thing that I still didn't explain is: How do we create that MultiBinding?
This task is actually divided into three steps:
- Pre-parsing the math expression generating as many sub-bindings as needed;
- Building a new expression replacing path properties by variable names;
- Creating an
IMultiValueConverter
that uses the arguments to fill the math variables and invokes the compiled delegate generated by the original MathParser
.
Pre-parsing and new expression building
I already explained a little about the pre-parsing. Binding paths are encoded inside []
characters. It would be possible to write the expressions like CenterX-Width/2
, but this will require another expression parser, and I wanted something pretty simple. The real parser was already made and independent from WPF. I didn't want to change it and I didn't want to replicate part of its code here. So, the pre-parser enters a loop to find a [
character. If it finds one, it searcher for a ]
too. If it doesn't find one it will throw an exception, as this is something it needs to find.
The text written inside the [
and ]
are used directly as the path to create a normal Binding
, which is added to the private MultiBinding
. This same search also declares a new MathVariable
or reutilises one if the same path was already used before. At the same time a new string is built using the variable names instead of the [property paths].
The expression inside the []
characters can contain more []
characters, as long as each opening [
is matched with a closing ]
. Such matching is needed by the normal bindings, so I don't think it will ever become a problem and it allows us to access indexed properties. For example: [Array[0]]
.
I chose to name the generated variables that are given to the MathParser
as b0
, b1
, b2
etc. The b
is from binding and it is lowercase because math parser only accepts lower-case names.
MultiBinding Converter
The MultiBinding
is only responsible for calling its Converter
providing all the new property values when at least one of them changes. There's no big secret here. If we use an expression that binds to two properties, our converter will receive two values in an array.
Considering the expression compiled with the MathParser uses the MathVariables, the job of the converter is to read all those values, convert them to double (as they may be of any type) and fill the appropriate MathVariables
, which are in the same order in an array generated when the MathExpression
was compiled.
After filling the variables, it is enough to call it and receive the value. As a final detail, the converter uses a Convert.ChangeType()
over that value to convert it to the expected value type, so it is possible to use a MathBinding
over decimal
or int
properties, independently of the fact that the MathParser
only deals with double
s.
The Sample
The sample application has a window showing a table where the left column is composed of small formulas using its own Width and Height and the right column shows the actual result of those formulas.
You can resize the window to see the values changing. Obviously, the application doesn't do anything useful and its only purpose is to be a source code sample that shows how to use many different formulas, including user declared functions and bindings done in C# code.
I hope you like to use the MathBinding
or even the MathParser
directly.
Version History
- 22 Jun 2016. Added download for Store Apps. Unfortunately, only the MathParser is available, the MathBinding is not;
- 16 Sep 2014. The MathBinding now supports setting the ElementName, which will apply to all inner bindings declared in the expression, as well as it supports declaring sub-bindings the same way a MultiBinding works, which become named as b0, b1 etc;
- 14 Sep 2014. Made the RegisterFunction methods work with delegates implemented by methods with a different number of parameters. This is rare, but can happen as optimizations for static methods or when open delegates are used;
- 11 Sep 2014. Initial version.