NuGet package here
MAUI version here
MAUI NuGet here
Amended the example showing
... Status == 'Administrator' ...
to
Status == \'Administrator\' ...
Added notes about escaping strings below
All csharp value types supported including nullable types.
Casting is supported. Cast to any type found in ExpressionParserZero.Operands.OperandType,
e.g. (NullableULong) 42
z:Bind is a xaml markup extension that allows you to bind directly to an expression
rather than just a property
Put simply, it allows you to do things like this:
<StackLayout
IsVisible="{z:Bind '(Item.Count != 0) AND (Status == \'Administrator\' '}" > ...Expressions are re-evaluated only if a property on which they depend changes
- Full parsing and evaluation of ViewModel and/or view properties
- Alisaing of operators, so you can represent (for example)
&&in xaml asANDrather than&& - Registration of methods that can be called directly from
xaml
Use the package manager to add the NugetPackage FunctionZero.zBind
Add a namespace alias to your xaml, like this:
xmlns:z="clr-namespace:FunctionZero.zBind.z;assembly=FunctionZero.zBind"And that's all there is to it, you can now z:Bind to any properties in your ViewModel
Just like the Xamarin.Forms Binding object, z:Bind binds to properties in your BindingContext
by default,
or you can specify any suitable alternative by setting the Source property:
| Sample | Notes |
|---|---|
{z:Bind Count} |
Bind to Count |
{z:Bind IsExpanded, Source={x:Reference MyExpander}} |
Bind to a property on an element named MyExpander |
{z:Bind Count * 2} |
Bind to an expression that yields Count * 2 |
{z:Bind (Count * 2) LT 10} |
True if (Count * 2) < 10 |
{z:Bind Sin(Count / 25.0)} |
Calls a function (see below) |
Similar to xaml, string literals can be enclosed within 'single quotes' or "double quotes" with appropriate use
of xml escape-sequences.
If your expression string has commas in it, you must hide them from the xaml parser, otherwise z:Bind will be given an incomplete string and things won't work as expected.
You can do this by enclosing the string inside quotes, like this:
{z:Bind 'SomeFunction(param1, param2)'}If your expression string has string literals in it, you must 'escape' them, otherwise z:Bind will be given an incorrect string and things won't work as expected.
For example:
{z:Bind Status == \'Administrator\'}If your expression is getting bogged down in escape-sequences and commas and quotes, or if that's just the way you roll, you can use the long-form of expressing a z:Bind expression:
<Label>
<Label.Text>
<z:Bind>
'Score: '+ Count + ' points'
</z:Bind>
</Label.Text>
</Label>Casting of function parameters follows csharp rules, so for example Sin(someFloat) is fine even though Sin expects a double.
You can explicitly cast to any csharp type if you so wish, e.g. Sin((Double)someFloat)
See ExpressionParserZero.Operands.OperandType for all the types you can cast to.
Just like c#, the underlying expression parser supports short-circuit, so expressions like
(thing != null) AND (thing.part == 5) will work even if thing is null
Error reporting is quite good, so check the debug output if things aren't working as expected.
| Alias | Operator |
|---|---|
| NOT | ! |
| MOD | % |
| LT | < |
| GT | > |
| GTE | >= |
| LTE | <= |
| BAND | & |
| XOR | ^ |
| BOR | | |
| AND | && |
| OR | || |
All csharp value types are supported, see the enum ExpressionParserZero.Operands.OperandType
for a complete list
string, object
z:Bind uses FunctionZero.ExpressionParserZero
to do the heavy lifting, so take a look at the documentation
if you want to take a deeper dive. Here is a taster ...
Sin, Cos and Tan and more are
registered by default,
as are the aliases listed above.
<Label TranslationX="{z:Bind Sin(Count / 25.0) * 100.0}" ...Note: Lerp is also pre-registered; the following is just for example.
Suppose you wanted a new function to to do a linear interpolation between two values, like this:
double Lerp(double a, double b, double t)
{
return a + t * (b - a);
}For use like this: (Note the expression is enclosed in quotes because it contains commas)
<Label Rotation="{z:Bind 'Lerp(0, 360, rotationPercent / 100.0)'}" ...First you will need a reference to the default ExpressionParser
var ep = ExpressionParserFactory.GetExpressionParser();Then register a function that takes 3 parameters:
ep.RegisterFunction("Lerp", DoLerp, 3);Finally write the DoLerp method referenced above, with the following signature:
private static void DoLerp(Stack<IOperand> operandStack, IBackingStore backingStore, long paramCount)
{
// Pop the correct number of parameters from the operands stack, ** in reverse order **
// If an operand is a variable, it is resolved from the backing store provided
IOperand third = OperatorActions.PopAndResolve(operandStack, backingStore);
IOperand second = OperatorActions.PopAndResolve(operandStack, backingStore);
IOperand first = OperatorActions.PopAndResolve(operandStack, backingStore);
double a = (double)first.GetValue();
double b = (double)second.GetValue();
double t = (double)third.GetValue();
// The result is of type double
double result = a + t * (b - a);
// Push the result back onto the operand stack
operandStack.Push(new Operand(-1, OperandType.Double, result));
}Get a reference to the default ExpressionParser:
var ep = ExpressionParserFactory.GetExpressionParser();Then register a new operator and use the existing matrix for &&
(See the ExpressionParserZero source and documentation for more details)
ep.RegisterOperator("AND", 4, LogicalAndMatrix.Create());Suppose you want to add a long to a string, yielding a result of type string
Get a reference to the default ExpressionParser:
var ep = ExpressionParserFactory.GetExpressionParser();Then simply register the overload like this
// Overload that will allow a long to be appended to a string
// To add a string to a long you'll need to add another overload
parser.RegisterOverload("+", OperandType.String, OperandType.Long,
(left, right) => new Operand(OperandType.String, (string)left.GetValue() + ((long)right.GetValue()).ToString()));and to add a string to a long:
// Overload that will allow a string to be appended to a long
// To add a long to a string you'll need to add another overload
parser.RegisterOverload("+", OperandType.Long, OperandType.String,
(left, right) => new Operand(OperandType.String, (long)left.GetValue() + ((string)right.GetValue()).ToString()));Putting the above into action, you can then start to really have some fun
<Label
Text="{z:Bind 'Player 1 score ' + playerOne.Score + 'points'}"
Rotation="{z:Bind Lerp(0, 360, rotationPercent / 100.0)}"
/>If that's not enough, you can entirely replace the ExpressionParser with your own by calling
ExpressionParserFactory.ReplaceDefaultExpressionParser(..) This is how z:Bind created the
default parser before these registrations were moved to
ExpressionParserZero and can be used as a
guide to creating your own:
var ep = new ExpressionParser();
ep.RegisterFunction("Sin", DoSin, 1, 1);
ep.RegisterFunction("Cos", DoCos, 1, 1);
ep.RegisterFunction("Tan", DoTan, 1, 1);
...
ep.RegisterOperator("AND", 4, LogicalAndMatrix.Create());
ep.RegisterOperator("OR", 4, LogicalOrMatrix.Create());
ep.RegisterOperator("LT", 4, LessThanMatrix.Create());
ep.RegisterOperator("LTE", 4, LessThanOrEqualMatrix.Create());
ep.RegisterOperator("GT", 4, GreaterThanMatrix.Create());
ep.RegisterOperator("GTE", 4, GreaterThanOrEqualMatrix.Create());
ep.RegisterOperator("BAND", 4, BitwiseAndMatrix.Create());
ep.RegisterOperator("BOR", 4, BitwiseOrMatrix.Create());
ReplaceDefaultExpressionParser(ep, false);Here are the methods referenced above:
private static void DoSin(Stack<IOperand> stack, IBackingStore store, long paramCount)
{
IOperand first = OperatorActions.PopAndResolve(stack, store);
double val = (double)first.GetValue();
var result = Math.Sin(val);
stack.Push(new Operand(-1, OperandType.Double, result));
}
private static void DoCos(Stack<IOperand> stack, IBackingStore store, long paramCount)
{
IOperand first = OperatorActions.PopAndResolve(stack, store);
double val = (double)first.GetValue();
var result = Math.Cos(val);
stack.Push(new Operand(-1, OperandType.Double, result));
}
private static void DoTan(Stack<IOperand> stack, IBackingStore store, long paramCount)
{
IOperand first = OperatorActions.PopAndResolve(stack, store);
double val = (double)first.GetValue();
var result = Math.Tan(val);
stack.Push(new Operand(-1, OperandType.Double, result));
}Set the property Sample.Value
<Label Text="{z:Bind 'Sample.Value = Interest * Complexity'}" />Evaluate multiple expressions, provide the result of the last
<Label Text="{z:Bind 'Test.Value = Count, Count * 2'}" /><!-- This will (of course) lock up the main thread,
because it is re-evaluated every time `a` changes -->
<Label Text={z:Bind a = a + 1} />Expressions are reevaluated only when one of the properties they reference change.