-
Notifications
You must be signed in to change notification settings - Fork 39
Description
Feature name
Automated Setter method for Two-Way Binding and Default BindableProperties
Link to discussion
Progress tracker
- Android Implementation
- iOS Implementation
- MacCatalyst Implementation
- Windows Implementation
- Tizen Implementation
- Unit Tests
- Samples
- Documentation
Summary
The Bind method behaves differently in the Typed Bindings approach compared to the classic String-based one.
In the classic approach, it is enough to specify the property to be bound. If the binding property's mode is two-way, the back update to the ViewModel property will happen automatically (similar to that of an XAML-based definition).
In the improved Typed Bindings approach, getters and setters are separated. Even for the default implementation, a setter method must be written to establish two-way data binding. This also didn't allow the use of default bindable properties.
A solution has now been found to address this gap using C# Expressions, without affecting performance or incurring runtime overhead as with reflection.
The resulting behavior will align with the classic model while being as extensible as Typed Binding.
The implementation will only take effect if the user has not defined a setter method. So it's backward compatible.
Motivation
Similar binding behavior across all approaches, permitting skill reuse.
Detailed Design
Defining a default setter method using C# Expressions:
A working sample is available here - https://github.com/egvijayanand/markup-issue-272
if (setter is null)
{
// Include the setter only if it is defined as two-way or if the mode is overridden.
if (targetProperty.DefaultBindingMode == BindingMode.TwoWay || mode == BindingMode.TwoWay)
{
// Already defined in the toolkit to retrieve the member name - To be reused
// Assuming MemberExpression for sample
var propertyName = ((MemberExpression)getter.Body).Member.Name;
var param1 = Expression.Parameter(typeof(TBindingContext), "context");
var param2 = Expression.Parameter(typeof(TSource), "value");
var memExp = Expression.Property(param1, propertyName);
var assignExp = Expression.Assign(memExp, param2);
// TODO: Need to check whether the action can be a static lambda
var action = Expression.Lambda<Action<TBindingContext, TSource>>(assignExp, [param1, param2]).Compile();
// Structure of the generated definition
// setter = (TBindingContext context, TSource value) => context.Property = value;
setter = action;
}
}
Usage Syntax
// As you can observe, the resulting code is quite simple. Easy to understand.
// This will improve the overall productivity
// Existing
// Requires the target property even if it is the default one, and a setter definition if it is two-way binding.
new Picker().Bind(Picker.SelectedIndexProperty, static (MyViewModel vm) => vm.Index, static (MyViewModel vm, int value) => vm.Index = value);
// Proposed
// SelectedIndexProperty is the default bindable property for Picker control
// It's two-way binding by default
new Picker().Bind(static (MyViewModel vm) => vm.Index);
Drawbacks
I don't think of any as it's compiled. Maybe a different perspective can fill-in.
Alternatives
Unresolved Questions
Static lambda for the setter action method to maintain the performance.
Explore the docs to ascertain whether the compiled method is static or if any further definition is required to make it static.