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 asAND
rather 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.