-
-
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