问题
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
回答1:
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}}" />
回答2:
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.
来源:https://stackoverflow.com/questions/35200838/xbind-converter-and-fallbackvalue-not-collaborating-uwp-10