Philippe
Philippe

Reputation: 33

Multibinding with converter on one of the bindings in .net MAUI

My issue is that when I use a converter on one of the multibinding's binding. It doesn't send the right thing to the converter. As per the DOC(https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/data-binding/multibinding?view=net-maui-7.0) in the Consume a IMultiValueConverter it should work, so I don't know what I'm doing wrong...

My multibinding class is the following:

public class BooleanAndConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values == null || !targetType.IsAssignableFrom(typeof(bool)))
        {
            return false;
        }

        foreach (var value in values)
        {
            if (!(value is bool b))
            {
                return false;
                
            }
            else if (!b)
            {
                return false;
            }
        }
        return true;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

I have a Boolean inverter class which is the following:

public class InverseBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (targetType != typeof(bool))
            throw new InvalidOperationException("The target must be a boolean");

        return !(bool)value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
         throw new NotSupportedException();
    }
}

Then in my XAML I'm using it like so:

<Button Text="Passer à la ronde suivante" Command="{Binding NextRoundCommand}">
    <Button.IsVisible>
        <MultiBinding Converter="{StaticResource BooleanAndConverter}">
            <Binding Path="isGameStarted"/>
            <Binding Path="isPlayersPlaying" Converter="{StaticResource InvertedBoolConverter}"/>
        </MultiBinding>
    </Button.IsVisible>
</Button>

When the inverter converter gets called in the multibinding, instead of receiving a TagetType of bool, it receive a "System.object", so it throws the InvalidOperationException. Why when using normal bindings it receive a targettype of bool and in multibinding it doesn't?

Thanks

Upvotes: 1

Views: 4270

Answers (3)

Stephen Quan
Stephen Quan

Reputation: 26179

This is one of the cases where DataTrigger can be used to combine two conditions:

    1. The DataTrigger, Binding, Value can be configured to setup the falsey case
    1. The Setter can be configured to setup the secondary Binding
<Button Text="Passer à la ronde suivante" Command="{Binding NextRoundCommand}" IsVisible="False">
    <Button.Triggers>
        <DataTrigger TargetType="Button" Binding="{Binding isPlayerPlaying}" Value="False">
            <Setter Property="IsVisible" Value="{Binding isGameStarted}" />
        </DataTrigger>
    </Button.Triggers>
</Button>

Another variation of the DataTrigger is you can use it with a MultiBinding to merge your Bindings into a single string and then trigger on that string, e.g.

<Button Text="Passer à la ronde suivante" Command="{Binding NextRoundCommand}" IsVisible="False">
    <Button.Triggers>
        <DataTrigger
            Binding="{MultiBinding {Binding isPlayerPlaying},
                                   {Binding isGameStarted},
                                   StringFormat='Magic-{0}-{1}'}"
            TargetType="Button"
            Value="Magic-False-True">
     <Setter Property="IsVisible" Value="True" />
 </DataTrigger>
    </Button.Triggers>
</Button>

Upvotes: 1

Tim Cooke
Tim Cooke

Reputation: 872

You can use MathConverter to do this without a custom IValueConverter. MathConverter doesn't support two-way bindings, but if all you need is a one-way binding, you don't need any custom C# converters for this.

https://www.nuget.org/packages/MathConverter.Maui

Here are a few ways you can do this binding:

  1. You can use the && operator (but that makes for some ugly XAML):
<Button IsVisible="{math:Convert 'x &amp;&amp; !y',
    x={Binding isGameStarted}, y={Binding isPlayersPlaying}}" ... />
  1. You can use the built-in And function
<Button IsVisible="{math:Convert 'And(x, !y)',
    x={Binding isGameStarted}, y={Binding isPlayersPlaying}}" ... />
  1. You can use a MathConverter on a traditional MultiBinding.
<!-- Define a MathConverter resource: -->
<Application.Resources>
    <math:MathConverter x:Key="Math" />
</Application.Resources>


<!-- Use the MathConverter in a MultiBinding: -->
<Button.IsVisible>
    <MultiBinding ConverterParameter='And(x, !y)' Converter="{StaticResource Math}">
        <Binding Path="isGameStarted"/>
        <Binding Path="isPlayersPlaying"/>
    </MultiBinding>
</Button.IsVisible>

Upvotes: 1

Jessie Zhang -MSFT
Jessie Zhang -MSFT

Reputation: 13889

I couldn't see other code, but I tested the sample code DataBindingDemos, it works on my side.

But I found that you didn't implement function ConvertBack for both of your (BooleanAndConverter and InverseBooleanConverter ).

And if I replace the code of ConvertBack to yous as follows, the app will throw exception.

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
     throw new NotSupportedException();
}
  

You can refer to the following code:

 public class InverterConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            bool? b = value as bool?;
            if (b == null)
            {
                return false;
            }
            return !b.Value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Convert(value, targetType, parameter, culture);
        }
    }

public class AllTrueMultiConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values == null || !targetType.IsAssignableFrom(typeof(bool)))
        {
            return false;
            // Alternatively, return BindableProperty.UnsetValue to use the binding FallbackValue
        }

        foreach (var value in values)
        {
            if (!(value is bool b))
            {
                return false;
                // Alternatively, return BindableProperty.UnsetValue to use the binding FallbackValue
            }
            else if (!b)
            {
                return false;
            }
        }
        return true;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        if (!(value is bool b) || targetTypes.Any(t => !t.IsAssignableFrom(typeof(bool))))
        {
            // Return null to indicate conversion back is not possible
            return null;
        }

        if (b)
        {
            return targetTypes.Select(t => (object)true).ToArray();
        }
        else
        {
            // Can't convert back from false because of ambiguity
            return null;
        }
    }
}

For more information, you can check: AllTrueMultiConverter.cs and InverterConverter.cs

Upvotes: 0

Related Questions