CommunityToolkit / CommunityToolkit/Maui.Markup

[Proposal] Add `RelativeBindingSource` Support to Typed Bindings Extensions

Open
#200 0 comments 0 reactions 1 assignee Claimed by @TheCodeTraveler View on GitHub
blocked champion proposal
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.