FunctionZero.Maui.zBind
contains an alternative to Microsoft.Maui.Controls.Binding
and allows DataBinding to an Expression
NuGet package here
Xamarin version here
Xamarin NuGet here
- Quickstart
- z:Bind
- The Great Escape, and other cautionary tales
- z:Function
- z:TapTrigger
- z:EdgeTrigger
- z:Latch
- Advanced Usage - Functions, aliases and operator-overloads
- Use the package manager to add the NugetPackage
FunctionZero.Maui.zBind
- Add a namespace alias to your xaml, like this:
xmlns:z="clr-namespace:FunctionZero.Maui.zBind.z;assembly=FunctionZero.Maui.zBind"
<Image Source="dotnet_bot.png"
TranslationX="{z:Bind Sin(Count / 25.0) * 100}"
TranslationY="{z:Bind Cos(Count / 15.0) * 100}"
Rotation="{z:Bind Sin(Count / 5.0) * 20}"
/>
<Button Text="Reset 'Count' in the ViewModel">
<Button.Behaviors>
<z:TapTrigger TapAction="{z:Function 'Count = 0'}"/>
</Button.Behaviors>
</Button>
<ContentPage.Behaviors>
<z:EdgeTrigger
Condition="{z:Bind '(Value LT 0.5)', Source={x:Reference TheSlider}}"
Rising="{z:Function 'RotationY = 90', Source={x:Reference TheImage}}"
Falling="{z:Function 'RotationY = 180', Source={x:Reference TheImage}}" />
</ContentPage.Behaviors>
Used to DataBind to an Expression. If the Expression is just a property-name then it is equivalent to Binding
A z:Bind
automatically updates if any referenced properties notify they have changed
TargetProperty = { z:Bind <some expression> [, Source=<some source>] }
Just like a standard Binding
, the data-source is the current BindingContext, unless Source
is specified.
If this data-source supports INotifyPropertyChanged
, changes will be tracked.
Sample Expression | Source | Notes |
---|---|---|
{z:Bind Count} |
BindingContext | Bind to Count , same as Binding |
{z:Bind Count * 2} |
BindingContext | Bind to an expression that yields Count * 2 |
{z:Bind '(Delta.X < 0.2) && (Delta.X > -0.2)' } |
BindingContext | True if (Delta.X < 0.2) && (Delta.X > -0.2) |
{z:Bind '(Delta.X LT 0.2) AND (Delta.X GT -0.2)' } |
BindingContext | As above, using aliases instead of escape-sequences |
{z:Bind (Count * 2) LT 10} |
BindingContext | True if (Count * 2) < 10 |
{z:Bind Sin(Count / 25.0)} |
BindingContext | Calls a function (see below) |
{z:Bind 'Value LT 0.2', Source={x:Reference MySlider}} |
An Element called MySlider | True if MySlider.Value < 0.2 |
<StackLayout IsVisible="{z:Bind '(Things.Count != 0)'}" ... > ...
<Image TranslationX="{z:Bind Sin(Count / 25.0) * 100}"
TranslationY="{ ... }" />
<Label Scale="{z:Bind Value * 3 + 0.1, Source={x:Reference TheSlider}}" > ...
As with 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
etc. 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:
Something="{z:Bind 'SomeFunction(param1, param2)'}"
or this
Something="{z:Bind \"SomeFunction(param1, param2)\"}"
and so on
If your expression string has string literals in it, you must 'escape' them, otherwise z:Bind
etc. will be given an incorrect string and things won't work as expected.
For example:
"{z:Bind Status == \'Administrator\'}"
or this
"{z:Bind Status == '\'Administrator\''}"
and so on
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 Text="{z:Bind '\'Score: \'+ Count + \' points\''}"
becomes
<Label>
<Label.Text>
<z:Bind>
'Score: '+ Count + ' points'
</z:Bind>
</Label.Text>
</Label>
Functions and assignments will try to cast to the correct type, so Sin(someFloat)
will work despite Sin
requiring a double
If you want to explicitly cast you can do so like this: Sin((Double)someFloat)
See ExpressionParserZero.Operands.OperandType
for details.
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 and their nullable
variants
string
, object
z:Function
is similar to, and has the same syntax as z:Bind
It is different because it does not self-evaluate and instead relies on its consumer to ask it to evaluate
z:Function
can be consumed by TapTrigger
, EdgeTrigger
and Latch
TargetProperty = "{ z:Function <some expression> [, Source=<some source>] }"
Where <some expression> is any valid expression, making use of properties on the BindingSource
Typically you would put either an assignment into a z:Function
TapAction="{z:Function 'Things.TapCount = Things.TapCount + 1'}"
or a function call
TapAction="{z:Function 'OpenUrl(Customer.LoginUrl, Customer.UserName)'}"
or both ...
TapAction="{z:Function 'Things.TapCount = Things.TapCount + 1, OpenUrl(Customer.LoginUrl)'}"
z:TapTrigger
is a Behaviour that evaluates a z:Function
when its host is tapped
<z:TapTrigger TapAction="{z:Function '<some expression>'}"/>
For example:
<Button Text="Reset 'Count' using a TapTrigger"
<Button.Behaviors>
<z:TapTrigger TapAction="{z:Function 'Count = 0'}"/>
</Button.Behaviors>
</Button>
z:EdgeTrigger is a Behaviour that evaluates a 'Rising' or 'Falling' z:Function
when a Condition changes
<ContentPage.Behaviors>
<z:EdgeTrigger
Condition="<some z:Bind>"
Rising="<some z:Function>"
Falling="<some z:Function>" />
</ContentPage.Behaviors>
For example, to show or hide UI based on a player data found in the ViewModel
<ContentPage.Behaviors>
<z:EdgeTrigger
Condition="{z:Bind '(Player.Score GTE 50) AND (Player.IsVerified == true)'}"
Rising="{z:Function 'IsVisible=true', Source={x:Reference TheAchevementUi}}"
Falling="{z:Function 'IsVisible=false', Source={x:Reference TheAchevementUi}}" />
</ContentPage.Behaviors>
Coming spoon *
Coming spoon *
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
are registered by default, as are the aliases listed above.
<Label TranslationX="{z:Bind Sin(Count / 25.0) * 100.0}" ...
Suppose you wanted a new function to to do a linear interpolation between two values, like this:
(Spoiler: Lerp is also pre-registered)
float Lerp(float a, float b, float t)
{
return a + t * (b - a);
}
For use like this:
<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.
private static void DoLerp(Stack<IOperand> stack, 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(operands, backingStore);
IOperand second = OperatorActions.PopAndResolve(operands, backingStore);
IOperand first = OperatorActions.PopAndResolve(operands, backingStore);
float a = Convert.ToSingle(first.GetValue());
float b = Convert.ToSingle(second.GetValue());
float t = Convert.ToSingle(third.GetValue());
// The result is of type float
float result = a + t * (b - a);
// Push the result back onto the operand stack
stack.Push(new Operand(-1, OperandType.Float, 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
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
ep.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
ep.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)'"}
/>
* There is no spoon.