-
-
Save Keboo/0d6e42028ea9e4256715 to your computer and use it in GitHub Desktop.
| /* | |
| WARNING: This MultiBinding implementation only works when it is directly applied to its target property. | |
| It will fail if used inside of a setter (such is the case when used within a trigger or style). | |
| */ | |
| using System; | |
| using System.Collections.Generic; | |
| using System.ComponentModel; | |
| using System.Globalization; | |
| using System.Linq; | |
| using System.Runtime.CompilerServices; | |
| using Xamarin.Forms; | |
| using Xamarin.Forms.Proxy; | |
| using Xamarin.Forms.Xaml; | |
| namespace MultiBindingExample | |
| { | |
| [ContentProperty(nameof(Bindings))] | |
| public class MultiBinding : IMarkupExtension<Binding> | |
| { | |
| private BindableObject _target; | |
| private readonly InternalValue _internalValue = new InternalValue(); | |
| private readonly IList<BindableProperty> _properties = new List<BindableProperty>(); | |
| public IList<Binding> Bindings { get; } = new List<Binding>(); | |
| public string StringFormat { get; set; } | |
| public IMultiValueConverter Converter { get; set; } | |
| public object ConverterParameter { get; set; } | |
| public Binding ProvideValue(IServiceProvider serviceProvider) | |
| { | |
| if (string.IsNullOrWhiteSpace(StringFormat) && Converter == null) | |
| throw new InvalidOperationException($"{nameof(MultiBinding)} requires a {nameof(Converter)} or {nameof(StringFormat)}"); | |
| //Get the object that the markup extension is being applied to | |
| var provideValueTarget = (IProvideValueTarget)serviceProvider?.GetService(typeof(IProvideValueTarget)); | |
| _target = provideValueTarget?.TargetObject as BindableObject; | |
| if (_target == null) return null; | |
| foreach (Binding b in Bindings) | |
| { | |
| var property = BindableProperty.Create($"Property-{Guid.NewGuid().ToString("N")}", typeof(object), | |
| typeof(MultiBinding), default(object), propertyChanged: (_, o, n) => SetValue()); | |
| _properties.Add(property); | |
| _target.SetBinding(property, b); | |
| } | |
| SetValue(); | |
| var binding = new Binding | |
| { | |
| Path = nameof(InternalValue.Value), | |
| Converter = new MultiValueConverterWrapper(Converter, StringFormat), | |
| ConverterParameter = ConverterParameter, | |
| Source = _internalValue | |
| }; | |
| return binding; | |
| } | |
| object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) | |
| { | |
| return ProvideValue(serviceProvider); | |
| } | |
| private void SetValue() | |
| { | |
| if (_target == null) return; | |
| _internalValue.Value = _properties.Select(_target.GetValue).ToArray(); | |
| } | |
| private sealed class InternalValue : INotifyPropertyChanged | |
| { | |
| public event PropertyChangedEventHandler PropertyChanged; | |
| private object _value; | |
| public object Value | |
| { | |
| get { return _value; } | |
| set | |
| { | |
| if (!Equals(_value, value)) | |
| { | |
| _value = value; | |
| OnPropertyChanged(); | |
| } | |
| } | |
| } | |
| private void OnPropertyChanged([CallerMemberName] string propertyName = null) | |
| { | |
| PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); | |
| } | |
| } | |
| private sealed class MultiValueConverterWrapper : IValueConverter | |
| { | |
| private readonly IMultiValueConverter _multiValueConverter; | |
| private readonly string _stringFormat; | |
| public MultiValueConverterWrapper(IMultiValueConverter multiValueConverter, string stringFormat) | |
| { | |
| _multiValueConverter = multiValueConverter; | |
| _stringFormat = stringFormat; | |
| } | |
| public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| if (_multiValueConverter != null) | |
| { | |
| value = _multiValueConverter.Convert(value as object[], targetType, parameter, culture); | |
| } | |
| if (!string.IsNullOrWhiteSpace(_stringFormat)) | |
| { | |
| var array = value as object[]; | |
| // ReSharper disable once ConvertIfStatementToNullCoalescingExpression | |
| if (array != null) | |
| { | |
| value = string.Format(_stringFormat, array); | |
| } | |
| else | |
| { | |
| value = string.Format(_stringFormat, value); | |
| } | |
| } | |
| return value; | |
| } | |
| public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |
| { | |
| throw new NotImplementedException(); | |
| } | |
| } | |
| } | |
| } |
Step 1. Basically include the namespace in XAML, where you have implemented above class, like below
xmlns:multi="clr-namespace:Mobi.Helpers"
Step 2. Use the multi wherever you need multibinding support, may use Converter or StringFormat
<!--Place you need-->
<TapGestureRecognizer.CommandParameter>
<multi:MultiBinding StringFormat="" Converter="">
<Binding Path=""/>
<Binding Path=""/>
</multi:MultiBinding>
</TapGestureRecognizer.CommandParameter>
Done!
Thanks a lot, the missing MultiBinding really drove me crazy
Hi,
Can we use this MultiBinding for an ObservableCollection?
I would like to highlight an item of this collection, according a test related to another Property of the ViewModel.
The ideal should be to pass a parameter to the Converter:
IsVisible="{Binding Key, Converter={StaticResource isVisibleConverter}, ConverterParameter={Binding .}}"
But it's not possible throught the ConverterParameter.
So I've tried this:
<StackLayout Grid.Column="1"
Orientation="Horizontal">
<StackLayout.IsVisible>
<extensions:MultiBinding Converter="{StaticResource isVisibleMultipleConverter}">
<Binding Path="Key" />
<Binding Source="Key" />
<Binding Path="." />
</extensions:MultiBinding>
</StackLayout.IsVisible>
But all the values returned in the Converter are still null...
@Goldstrike Have you had any updates about this issue?
Note that 1) the IMultiValueConverter class has to be copied from the Proxy project and 2) for .NetStandard projects < 2.0 must include nuget package of Sytem.ComponentModel to have access to IServiceProvider
This does work for the most part, but I noticed it fails once you start working with regular updates. For instance, if a bound property should update x times over a certain period, references will eventually turn up null (the MultiBinding is not nested in a trigger or style, either). In other words, it's great for one-time conversions, but that's it. If I find a fix for this, I'll post it here.
I've spent the last day trying to figure this out, but no luck. To reproduce the issue, set a timer and call OnPropertyChanged on the property to update periodically. MultiBinding.SetValue is called several times, but then stops; the timer, meanwhile, keeps running. I've tried rewriting MultiBinding several times, but I can't fix this!
Hello,
I have strange result in my converter Convert method when I loop over the values object.
For example, I have 2 binded properties in XAML, but I have 3 items in values object collection, with null for first value...
Any idea?
Regards
I use the DataTemplate in "ItemsView"
I use in DataTrigger, but the BindingContext of my control is always null
<DataTemplate> <StackLayout> <Label > <Label.Text> <Binding Path="Index"/> //working normally </Label.Text> </Label> <Button> <Button.Triggers> <DataTrigger TargetType="Button" Value="True"> <DataTrigger.Binding> <local:MultiBinding Converter="{StaticResource tConverter}"> <Binding Source="{x:Reference caruselView}" Path="Position"/> //working normally <Binding Path="Index"/> //Always null </local:MultiBinding> </DataTrigger.Binding> <Setter Property="BackgroundColor" Value="#551133FF"/> </DataTrigger> </Button.Triggers> </Button> </StackLayout> </DataTemplate>
Hey this functionnality looks great. Do you think you could show a simple example on how to use it from Xamarin.Forms ?
Thanks