How does FallbackValue work with a MultiBinding?

2020-07-06 07:47发布

问题:

I ask because it doesn't seem to work.

Assume we're binding to the following object:

public class HurrDurr
{
  public string Hurr {get{return null;}}
  public string Durr {get{return null;}}
}

Well, it would appear that if we used a MultiBinding against this the fallback value would be shown, right?

<TextBlock>
    <TextBlock.Text>                                
        <MultiBinding StringFormat="{}{0} to the {1}"
                        FallbackValue="Not set!  It works as expected!)">
            <Binding Path="Hurr"/>
            <Binding Path="Durr"/>
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

However the result is, in fact, " to the ". Even forcing the bindings to return DependencyProperty.UnsetValue doesn't work:

<TextBlock xmnlns:base="clr-namespace:System.Windows;assembly=WindowsBase">
    <TextBlock.Text>                                
        <MultiBinding StringFormat="{}{0} to the {1}"
            FallbackValue="Not set!  It works as expected!)">
            <Binding Path="Hurr"
                FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" />
            <Binding Path="Durr"
                FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

Tried the same with TargetNullValue, which was also a bust all the way around.

So it appears that MultiBinding will never ever use FallbackValue. Is this true, or am I missing something?


A little more messing around and I found that a converter can return the UnsetValue I need:

class MultiValueFailConverter : IMultiValueConverter
{
    public object Convert(
        object[] values, 
        Type targetType, 
        object parameter, 
        System.Globalization.CultureInfo culture)
    {
        if (values == null || 
            values.Length != 2 ||
            values.Any(x=>x == null))
            return System.Windows.DependencyProperty.UnsetValue;
        return values;
    }

    public object[] ConvertBack(
        object value, 
        Type[] targetTypes, 
        object parameter, 
        System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException("Too complex hurt brain.");
    }
}

However, this seems like a dirty filthy hack. I'd think a scenario like this would be accounted for in the framework. I can't find anything in Reflector, however.

回答1:

This is a bit of an old question, but it could use some explanation.

From the FallbackValue documentation:

A binding returns a value successfully if:

  1. The path to the binding source resolves successfully.
  2. The value converter, if any, is able to convert the resulting value.
  3. The resulting value is valid for the binding target (target) property.

If 1 and 2 return DependencyProperty.UnsetValue, the target property is set to the value of the FallbackValue, if one is available. If there is no FallbackValue, the default value of the target property is used.

In the example provided, the binding successfully resolves to the Hurr and Durr properties. Null is valid value for a string which means the binding is valid.

In other words, the FallbackValue is used when the binding is unable to return a value and in the example provided, the binding does provide a valid value.

Take for example each of the following snippets that are based off the original example:

Example 1
The Hurr and Durr properties are bound correctly; null is a valid value and the FallbackValue will never be seen.

<TextBlock>
    <TextBlock.Text>
        <MultiBinding FallbackValue="Binding is valid. I will never be seen." StringFormat="{}{0} to the {1}">
            <Binding Path="Hurr" />
            <Binding Path="Durr" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

Example 2
The Hurr and Durr properties are not bound correctly; the FallbackValue will be seen.

<TextBlock>
    <TextBlock.Text>
        <MultiBinding FallbackValue="Binding paths are invalid. Look at me." StringFormat="{}{0} to the {1}">
            <Binding Path="xHurr" />
            <Binding Path="xDurr" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

Example 3
If one binding path is invalid, then the FallbackValue will be seen.

<TextBlock>
    <TextBlock.Text>
        <MultiBinding FallbackValue="One binding path is invalid. Look at me." StringFormat="{}{0} to the {1}">
            <Binding Path="xHurr" />
            <Binding Path="Durr" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

Example 4
As with previous examples, the binding is correct, so the FallbackValue will not be used. Further, the FallbackValue for each of the child Binding properties of the MultiBinding parent should refer to a FallbackValue to be used for the target property of the MultiBinding, not for the child Bindings.

<TextBlock xmlns:base="clr-namespace:System.Windows;assembly=WindowsBase">
    <TextBlock.Text>
        <MultiBinding FallbackValue="Binding is valid. I will never be seen." StringFormat="{}{0} to the {1}">
            <Binding FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" Path="Hurr" />
            <Binding FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" Path="Durr" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

Example 5
The binding is still valid even though a path is not provided in Binding properties since the binding will use whatever object it is bound to.

<TextBlock xmlns:base="clr-namespace:System.Windows;assembly=WindowsBase">
    <TextBlock.Text>
        <MultiBinding FallbackValue="Binding is still valid. I will never be seen." StringFormat="{}{0} to the {1}">
            <Binding FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" />
            <Binding FallbackValue="{x:Static base:DependencyProperty.UnsetValue}" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

Example 6
Finally, if a converter is added to any of the Binding properties to force an UnsetValue, then the MultiBinding FallbackValue will be seen:

Converter

internal class ForceUnsetValueConverter : IValueConverter
{
    #region Implementation of IValueConverter

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

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

    #endregion
}

XAML

<TextBlock>
    <TextBlock.Text>
        <MultiBinding FallbackValue="Binding is valid, but look at me. I'm an UnsetValue." StringFormat="{}{0} to the {1}">
            <Binding Converter="{StaticResource ForceUnset}" Path="Hurr" />
            <Binding Path="Durr" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>