CommunityToolkit / CommunityToolkit/Maui.Markup
[Proposal] Add `RelativeBindingSource` Support to Typed Bindings Extensions
- Dominant language
- C#
- Stars
- 595
- Forks
- 40
- Avg merge
- 10h 33m
- Merged PRs (30d)
- 12
Description
### Feature name
Add `RelativeBindingSource` Support to Typed Bindings
### Link to discussion
None
### Progress tracker
- [ ] Android Implementation
- [ ] iOS Implementation
- [ ] MacCatalyst Implementation
- [ ] Windows Implementation
- [ ] Tizen Implementation
- [ ] Unit Tests
- [ ] Samples
- [ ] Documentation
### Summary
This Proposal improves support for [`RelativeBindingSource`](https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/relative-bindings) to our TypedBinding extensions.
This is not a breaking change and improves our API surface to better match .NET MAUI's TypedBinding API for its `TypedBinding.Source` property.
`RelativeBindingSource` is the recommended way for .NET MAUI developers to creating a binding referencing themselves or an ancestor without needing to pass in an instance of that object
- Self Binding
- `RelativeBindingSource.Self`
- Binding to an ancestor in the UI hierarchy
- `new RelativeBindingSource(RelativeBindingSourceMode.FindAncestor, typeof(Page))`)
- Binding to an ancestor in the BindingContext hierarchy
- `new RelativeBindingSource(RelativeBindingSourceMode.FindAncestorBindingContext, typeof(MyViewModel))`
#### Current Workaround
The current workaround to use `RelativeBindingSource` with our existing TypedBinding APIs is to first use `.Assign()` to assign the control to a variable, then pass that variable in as the `source` parameter.
```cs
.Assign(out CarouselView carouselView)
.Bind(CarouselView.CurrentItemChangedCommandParameterProperty, static (CarouselView view) => view.CurrentItem, source: carouselView)
```
### Motivation
All of our TypedBinding APIs (example below) require the `source` parameter type to match the `TBindingContext` used in by the `getter` and `setter` parameter:
- `Expression> getter`
- `Action? setter = null `
- `TBindingContext? source = default` // Cannot be of type `RelativeBindingSource`
```cs
public static TBindable Bind(
this TBindable bindable,
BindableProperty targetProperty,
Expression> getter,
Action? setter = null,
BindingMode mode = BindingMode.Default,
string? stringFormat = null,
TBindingContext? source = default) where TBindable : BindableObject);
```
### Detailed Design
The best way to support `RelativeBindingSource`, is to lower the `Type` of the API's `source` parameter from `TBindingContext?` to `object?`.
Using `object?` for the `source` parameter matches the [.NET MAUI API for the `TypedBinding.Source` property](https://github.com/dotnet/maui/blob/7c793675c645ddb85e2b92ac6e4bac9e52448410/src/Controls/src/Core/TypedBinding.cs#L46-L54).
**Current Source Parameter**
```cs
TBindingContext? source = default
```
**Updated Source Parameter**
```cs
object? source = null
```
**Example Updated API**
```cs
/// Bind to a specified property
public static TBindable Bind(
this TBindable bindable,
BindableProperty targetProperty,
Expression> getter,
Action? setter = null,
BindingMode mode = BindingMode.Default,
string? stringFormat = null,
object? source = null) // Lowered from TBindingContext? source = default
where TBindable : BindableObject;
```
### Usage Syntax
```cs
.Bind(CarouselView.CurrentItemChangedCommandParameterProperty, static (CarouselView view) => view.CurrentItem, source: RelativeBindingSource.Self)
```
### Drawbacks
I don't see any drawbacks.
We will need to update our Unit Tests to validate `RelativeBindingSource`.
We will also need to update our Unit Tests with invalid `source` parameters to ensure .NET MAUI throws an exception if a user provides an invalid `source` parameter to our API now that it supports any `object` type.
### Alternatives
An alternative to lowering our existing API surface from `TBindingContext? source = default` to `object? source = null` would be to create new APIs specific to `RelativeBindingSource` (example below).
I **do not** recommend this approach for two reasons:
- It doubles our API surface for TypedBinding support, increasing our maintenance cost
- Requires one TypedBinding API for `TBindingContext? source = default` and a second TypedBinding API for `object? source = null`
- Lowering the parameter Type to `object?` better matches [.NET MAUI's TypedBinding.Source API (`public object? Source`)](https://github.com/dotnet/maui/blob/7c793675c645ddb85e2b92ac6e4bac9e52448410/src/Controls/src/Core/TypedBinding.cs#L46-L54)
**Example API using `RelativeBindingSource`**
```cs
public static TBindable Bind(
this TBindable bindable,
BindableProperty targetProperty,
Expression> getter,
Action? setter = null,
BindingMode mode = BindingMode.Default,
string? stringFormat = null,
RelativeBindingSource? source = null) where TBindable : BindableObject);
```
### Unresolved Questions
None
Contributor guide
Assessment
This issue has not been assessed yet.