Xamarin.forms: [Enhancement] MultiBinding

Created on 28 Nov 2018  ·  17Comments  ·  Source: xamarin/Xamarin.Forms

Summary

Add a MultiBinding binding type equivalent to WPF.

API Changes

// new
public class MultiBinding : BindingBase
{
     public Collection<BindingBase> Bindings {get; set;}
     public IMultiValueConverter Converter {get; set;}
}

// new
public interface IMultiValueConverter 
{
     object Convert(object[] values, Type targetType, object parameter);
     object[] ConvertBack(object value, Type[] targetTypes, object parameter);
}

Intended Use Case

Example XAML usage:

<Button>
     <Button.IsEnabled>
          <MultiBinding Converter="{StaticResource AllMustBeTrueMultiConverter}">
               <Binding Path="ActionIsAllowed"/>
               <Binding Path="SomeOtherRequirement"/>
          </MultiBinding>
     </Button.IsEnabled>
</Button>

Comment - It's challenging to build highly responsive UIs in XAML without MultiBinding. Currently there are two workarounds, which even together do not address all cases:

(1) Define a single property in a view model that resolves to a value based on other properties, and bind in XAML to the single property.

First, this makes view models more complex and more difficult to maintain. Suppose a property ActionIsEnabled is defined as:

public bool ActionIsEnabled => this.ActionIsAllowed && this.SomeOtherRequirement;

Each of the setters for the independent properties (ActionIsAllowed and SomeOtherRequirement) need to include property change notifications for ActionIsEnabled in addition to themselves. If the logic for ActionIsEnabled is changed, however, then those other property setters (and/or those of other properties) have to potentially be updated as well.

Moreover, this assumes the dependencies are all entirely within the same view model. If the independent properties are in different classes than the dependent property, however, there may be no practical way to send a property change notification for the dependent property when the independent properties in the other classes are changed. This workaround therefore doesn't work in all cases.

(2) Wrap a control in a new layer (Grid, etc) for each condition.

I've seen this done a lot in Angular using nested <div>s for each condition. Leaving aside the inefficiency, this does work for simple cases, the most common one being a cascading property like IsEnabled or IsVisible, where all the conditions must evaluate to true. Anything more complex, however, and this system doesn't work either - for example, if ANY rather than ALL conditions must be true.

MultiBinding IMHO provides the most robust and elegant means of specifying complex dependencies between control properties and data.

binding ⛓ in-progress high impact proposal-accepted enhancement ➕

Most helpful comment

Well PR is up. I went with WPF actual behavior. Look forward to any comments.

All 17 comments

I had a branch for that, a long long time ago...

We would accept a PR doing that, but don't hurry with it, I don't have a lot of time to discuss that in the coming weeks

Good deal. It'll probably be a few weeks for me as well.

For the approach this is what I was thinking:
(1) create an internal BindingProxy corresponding to each child binding, whose BindingContext is bound to that of the ultimate target
(2) Bind an object Value property of the BindingProxy using the child binding path/source/converter/etc
(3) On any change to the Value property, notify the parent MultiBinding of a need to reevaluate the entire expression; vice-versa for two-way binding/one-way-to-source.

The WPF reference code for MultiBinding and MultiBindingExpression is quite extensive, leading me to wonder if I'm missing something. For example I don't think sub-classing BindingExpression should be necessary, or only minimally if so.

Anyone have any other thoughts or suggestions?

Multi-bindings are perhaps the last major missing feature in Xamarin.Forms. Unfortunately, unless there's another more active issue, there seems to be little work put towards the goal. Could anyone please specify when we can expect for this to be added?

@ddobrev I have made a lot of progress on this in my own rep branch
https://github.com/legistek/Xamarin.Forms/tree/gh4565-multibinding

OneWay binding works very well but I have not yet implemented the other binding modes.

I was waiting until my other PR (#4375) was finished before finishing this one.

@legistek I'm extremely happy to hear it! There are some requested changes for that other PR which is also useful. I hope they aren't difficult to do and it gets merged soon so that this one can go in too. :)

Yes, the requested changes are trivial and I'd like to hear from @StephaneDelcroix before doing any more work on it so I don't wind up repeating effort. Once he approves the substance of it I'll get it ready for merging.

Any updates to this? It would be very helpful! :D

I was looking at #8099 and figured I needed this. Are you still working on this @legistek ?

Yeah I am working on it, I just haven't had much time lately to finish it. The branch is here. The last thing I was doing was making sure it would work with RelativeSource bindings and I implemented an inelegant solution that should be able to be much better.

Very nice @legistek. ❤️

Happy to report this is pretty much coded and working. Getting it to work with relative source bindings, correctly handling the Convert and ConvertBack methods (specifically UnsetValue vs. DoNothing vs. null), and correctly handling different BindingModes were the biggest challenges. The goal is for behavior to match WPF exactly so you can take existing IMultiValueConverter implementations and drop them in almost seamlessly.

In the process of making unit tests so the PR should be up within the week.

Very good news @legistek! Looking forward to removing a lot of unesseary code to work around not having multi bindings. ❤️

Actually I could use some opinions here on the null vs UnsetValue vs DoNothing issue. Or at least confirmation that I'm understanding the WPF intended behavior correctly.

For Convert I think it's straightforward and the WPF implementation makes sense. Specifically, depending on what gets returned from Convert, the target is updated as follows:
DoNothing - no change to the target
UnsetValue - target is updated with MultiBinding.FallbackValue, or property default
null - target is updated with MultiBinding.TargetNullValue, or property default

For ConvertBack (which returns object[]) it's a little more murky. According to the WPF documentation:

Return DoNothing at position i to indicate that no value is to be set on the source binding at index i.
Return DependencyProperty.UnsetValue at position i to indicate that the converter is unable to provide a value for the source binding at index i, and that no value is to be set on it.
Return null to indicate that the converter cannot perform the conversion or that it does not support conversion in this direction.

At first glance they seem to be saying that returning DoNothing or UnsetValue at position i have the same effect - the source for position i is not altered. Returning null on the other hand would indicate the entire operation failed.

However the actual behavior seems to be a little different. Returning DoNothing at position i definitely leaves source i alone but returning UnsetValue at _any_ position seems to be equivalent to returning null entirely, and so none of the sources get updated.

Granted maybe I'm reading the docs wrong (wouldn't be the first time) and I suppose the behavior should be the guide either way. Any thoughts?

Well PR is up. I went with WPF actual behavior. Look forward to any comments.

Well Done guys. Hats off!

What's the recommended way to use static strings instead of an individual binding? For example an IfThenElseMultiBindingConverter.

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
    var ifValue = (bool) values[0];
    return ifValue ? values[1] : values[2];
}

Lets say I want to set text on a button. Different text whether the first binding is true or not. The different texts are stored in resource files and are usually binded like this: Text="{x:Static resources:Strings.AuthorBy}"
How would I go about inserting such strings in 2nd and 3rd "binding"?

Nvm - I found something that works:

<Binding Source="{x:Static resources:Strings.AuthorBy}" />
Was this page helpful?
0 / 5 - 0 ratings