Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

x:Bind Converter and FallbackValue not collaborating (UWP 10)

I have a problem that involves a bunch of code, but I've isolated it down. If you want a TL;DR; jump to it further down. If you want a bit of context, here's my situation:

I have created three data converters for my bindings. One of them is a "string prefixer": it prefixes whatever you put in with a fixed string. In the current example, that fixed string is "ms-appx:///cache/". The second one turns a string type into an ImageSource, and the third one chains multiple converters together.

I've then created a Xaml resource which is called LocalCacheFile. Everything works as you would think. Xaml code for this looks like so:

<Image Source="{x:Bind imageSource,Converter={StaticResource LocalCacheFile}}" />

However, I'm having the following problem. If I try to use the FallbackValue to put a placeholder image for when imageSource is empty, I get weird behaviour in x:Bind only.

The following code works as one would expect:

<Image Source="{Binding imageSource,FallbackValue='ms-appx:///Assets/default.png',Converter={StaticResource LocalCacheFile}}" />

But

<Image Source="{x:Bind imageSource,FallbackValue='ms-appx:///Assets/default.png',Converter={StaticResource LocalCacheFile}}" />

does not!

I've isolated it down to just one converter and it is DependencyProperty.UnsetValue that x:Bind seems not to be handling.

TL;DR; Here is the code for my string prefixer, which if I use alone as a test triggers the same faulty behaviour:

public class StringPrefix : IValueConverter
{
    public string prefix { get; set; }

    public object Convert(object value, Type typeName, object parameter, string language)
    {
        if (value == DependencyProperty.UnsetValue || value == null || (string)value == "")
            return DependencyProperty.UnsetValue ;

        return (prefix + value.ToString());
    }

    public object ConvertBack(object value, Type typeName, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

The above converter works as you would expect it to (i.e. if the input string is empty, the fallback value is properly used) when using Binding. It raises a type exception when used with x:Bind.

What's up with this?


Edit: details about the exception.

This is the generated code:

        private void Update_project_imageSource(global::System.String obj, int phase)
        {
            if((phase & ((1 << 0) | NOT_PHASED | DATA_CHANGED)) != 0)
            {
                XamlBindingSetters.Set_Windows_UI_Xaml_Controls_Image_Source(this.obj16, (global::Windows.UI.Xaml.Media.ImageSource)this.LookupConverter("LocalCacheFile").Convert(obj, typeof(global::Windows.UI.Xaml.Media.ImageSource), null, null), null);
            }
        }

Exception details:

System.InvalidCastException was unhandled by user code
  HResult=-2147467262
  Message=Unable to cast object of type 'System.__ComObject' to type 'Windows.UI.Xaml.Media.ImageSource'.
  Source=Test
  StackTrace:
       at Test.Pages.ProjectView.ProjectView_obj1_Bindings.Update_project_imageSource(String obj, Int32 phase)
       at Test.Pages.ProjectView.ProjectView_obj1_Bindings.Update_project(Project obj, Int32 phase)
       at Test.Pages.ProjectView.ProjectView_obj1_Bindings.Update_(ProjectView obj, Int32 phase)
       at Test.Pages.ProjectView.ProjectView_obj1_Bindings.Update()
       at Test.Pages.ProjectView.<.ctor>b__6_0(FrameworkElement s, DataContextChangedEventArgs e)
  InnerException: 

(to me, it looks like the generated code just doesn't deal with the default value possibility. Btw, that __ComObject is the DependencyProperty.UnsetValue.

Edit 2: I should add that if I change the Convert function to return null instead of DependencyProperty.UnsetValue, x:Bind functions, but then neither x:Bind nor Binding do their expected job of using the FallbackValue

like image 823
MB. Avatar asked Feb 04 '16 12:02

MB.


2 Answers

The FallbackValue in Binding and x:Bind is different.

In Binding, FallbackValue is the value to use when the binding is unable to return a value.

A binding uses FallbackValue for cases where the Path doesn't evaluate on the data source at all, or if attempting to set it on the source with a two-way binding throws an exception that's caught by the data binding engine. FallbackValue is also used if the source value is the dependency property sentinel value DependencyProperty.UnsetValue.

But in x:Bind, FallbackValue specifies a value to display when the source or path cannot be resolved. It can't work with DependencyProperty.UnsetValue.

As you've already know, x:Bind generates code at compile-time and it's strongly typed. When you use Converter in x:Bind, it will regard the Converter's return value of the same type as the target property and cast it like in your code:

(global::Windows.UI.Xaml.Media.ImageSource)this.LookupConverter("LocalCacheFile").Convert(obj, typeof(global::Windows.UI.Xaml.Media.ImageSource), null, null)

If you return DependencyProperty.UnsetValue in your Converter, it will throw exception as DependencyProperty.UnsetValue can't cast to ImageSource.

For your scenario, you can use TargetNullValue.

TargetNullValue is a similar property with similar scenarios. The difference is that a binding uses TargetNullValue if the Path and Source do evaluate, but the value found there is null.

For example using following code is XAML.

<Image Source="{x:Bind imageSource, TargetNullValue='ms-appx:///Assets/default.png', Converter={StaticResource LocalCacheFile}}" />

And in the Convert, return null instead of DependencyProperty.UnsetValue.

This works when running the app and the imageSource is empty. But to gain design time benefit, we still need use FallbackValue. So we can use x:Bind like following:

<Image Source="{x:Bind imageSource, TargetNullValue='ms-appx:///Assets/default.png', FallbackValue='ms-appx:///Assets/default.png', Converter={StaticResource LocalCacheFile}}" />
like image 50
Jay Zuo Avatar answered Nov 08 '22 08:11

Jay Zuo


In x:Bind the FallBackValue is really only used for designtime data. Now, let's talk about something more important. Why use x:Bind. With the cost of spinning up an IValueConverter, are you convinced x:Bind is worth it? I'm not. When I see developers struggling to get x:Bind to work right for bindings OUTSIDE of a list, my recommendation is to switch to binding. Every time. Inside a list, compiled binding has a "repeat" value, but anywhere else, you have to prove to me that it is worth the effort - if it is otherwise difficult. Typically x:bind is great. But in cases like this, and cases like UpdateSourceTrigger falling back to or defaulting to binding is perfectly fine.

like image 29
Jerry Nixon Avatar answered Nov 08 '22 08:11

Jerry Nixon