Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Binding SG] Add an API for creating instances of TypedBinding #23239

Merged

Conversation

simonrozsival
Copy link
Member

@simonrozsival simonrozsival commented Jun 25, 2024

Description of Change

We recently enabled a source generator that allows developers to call the SetBinding extension method to define bindings on their bindable objects using Func instead of string paths. This PR builds on top of that feature and adds a second supported API that will not only set the binding directly to the object, but it will return the binding object instance and allow the developer to pass it to some other method and use it later.

Use-cases

1. Setters

Before:

BindingBase automationIdBinding = TypedBinding.ForSingleNestingLevel(
nameof(Element.AutomationId),
static (Element element) => element.AutomationId,
static (element, val) => element.AutomationId = val);
defaultGridClass.Setters.Add(new Setter { Property = Element.AutomationIdProperty, Value = automationIdBinding });

After:

BindingBase automationIdBinding = Binding.Create(static (Element element) => element.AutomationId); 
defaultGridClass.Setters.Add(new Setter { Property = Element.AutomationIdProperty, Value = automationIdBinding });

2. DataTrigger:

Before:

DataTrigger newDataTrigger = new DataTrigger(typeof(ProgressBar)) { Binding = new Binding(nameof(ListItemViewModel.State)), Value = ListItemViewModel.InstallableState.Downloading };
newDataTrigger.Setters.Add(new Setter { Property = ProgressBar.IsVisibleProperty, Value = true });

After:

DataTrigger newDataTrigger = new DataTrigger(typeof(ProgressBar))
{
    Binding = Binding.Create(static (ListItemViewModel vm) => vm.State),
    Value = ListItemViewModel.InstallableState.Downloading,
};
newDataTrigger.Setters.Add(new Setter { Property = ProgressBar.IsVisibleProperty, Value = true });

3. MultiBinding

Before:

entry1.SetBinding(Entry.TextProperty,
new MultiBinding
{
Bindings = new Collection<BindingBase>
{
new Binding(nameof(Entry.FontFamily), source: RelativeBindingSource.Self),
new Binding(nameof(Entry.FontSize), source: RelativeBindingSource.Self),
new Binding(nameof(Entry.FontAttributes), source: RelativeBindingSource.Self),
},
Converter = new StringConcatenationConverter()
});

After:

 entry1.SetBinding(Entry.TextProperty, 
 	new MultiBinding 
 	{ 
 		Bindings = new Collection<BindingBase> 
 		{ 
 			Binding.Create(static (Entry entry) => entry.FontFamily, source: RelativeBindingSource.Self), 
 			Binding.Create(static (Entry entry) => entry.FontSize, source: RelativeBindingSource.Self), 
 			Binding.Create(static (Entry entry) => entry.FontAttributes, source: RelativeBindingSource.Self), 
 		},
 		Converter = new StringConcatenationConverter() 
 	}); 

Issues Fixed

Contributes to #22384

/cc @jkurdek @StephaneDelcroix @jonathanpeppers

@simonrozsival simonrozsival requested a review from a team as a code owner June 25, 2024 12:56
@simonrozsival simonrozsival force-pushed the binding-factory-generated-bindings-api branch from a75ab00 to abd3426 Compare June 25, 2024 13:56
src/Controls/src/Core/BindingFactory.cs Outdated Show resolved Hide resolved

namespace Microsoft.Maui.Controls
{
public static class BindingFactory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@StephaneDelcroix what do you think about just putting the method here on Binding?

Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reran some tests, as I think the Xcode provisionator failures are fixed now.

Comment on lines +20 to +23
/// <param name="targetNullValue">The value to supply for a bound property when the target of the binding is <see langword="null" />.</param>
/// <exception cref="ArgumentNullException"></exception>
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCodeMessage",
Justification = "The Binding.Create<TSource, TProperty>() method does not create an instance of Binding but an instance of TypedBinding<TSource, TProperty>.")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like there might be mixed tabs/spaces in this file.

@simonrozsival

This comment was marked as outdated.

@StephaneDelcroix
Copy link
Contributor

Having a Binding.Create() method allows the capture of the binding, and potentially a reuse of it, which isn't supported. I would stick with SetBinding...

@jonathanpeppers
Copy link
Member

@StephaneDelcroix I think the three examples are Setter, DataTrigger, and MultiBinding (see description). I don't think you can use these APIs with data-binding on NativeAOT without an API that creates a BindingBase?

@PureWeen PureWeen merged commit 5071c83 into dotnet:net9.0 Jul 15, 2024
48 checks passed
@samhouts samhouts added the fixed-in-net9.0-nightly This may be available in a nightly release! label Aug 2, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Sep 13, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-xaml XAML, CSS, Triggers, Behaviors fixed-in-net9.0-nightly This may be available in a nightly release!
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants