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
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}}" />
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With