Reputation: 764
I have the following Multibinding:
<Grid.Visibility>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}" Mode="TwoWay">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="MyVisibilityDependencyProperty" Mode="TwoWay"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="MyBoolProperty" Mode="TwoWay"/>
</MultiBinding>
</Grid.Visibility>
MyVisibilityDependencyProperty is a dependency property. MyBoolProperty is a normal property. The implementation of MyMultiValueConverter is the important thing:
public class MyMultiValueConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//Not interesting
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new[] { value, Binding.DoNothing};
}
}
Now the scenario: I do smth. that triggers a call of the ConvertBack-Method, which means I hit a break point there. Afterwards I hit a break point in the OnPropertyChangedCallback of MyVisibilityDependencyProperty. There I can see that the new value of MyVisibilityDependencyProperty is the value that was set in the ConvertBack-Method.
Now the issue that I do not understand. I change the implementation of the ConvertBack-Method to:
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new[] { value, DependencyProperty.UnsetValue};
}
Now I follow the exact same scenario. I do smth. that triggers a call of the ConvertBack-Method, which means I hit a break point there. After that nothing happens. The OnPropertyChangedCallback is not called and MyVisibilityDependencyProperty is not updated. Why?
It seems like that if one of the values in the array is DependencyProperty.UnsetValue, propagation of all values is stopped. Not only for that value but all values in the array. This is supported by the following behavior:
return new[] { Binding.DoNothing, false };
This results in a call of the setter of MyBoolProperty.
return new[] { DependencyProperty.UnsetValue, false };
This does not call the setter of MyBoolProperty.
I could not find any hints of explanation in documentation and it does not make sense in my opinion.
Upvotes: 2
Views: 6343
Reputation: 25623
I could not find any hints of explanation in documentation and it does not make sense in my opinion.
I don't recall ever seeing it in the documentation, but your observations are correct:
If any element in the result of IMultiValueConverter.ConvertBack
is UnsetValue
, the entire set of proposed values is rejected, i.e., the conversion fails, and the none of the child bindings have their source values updated.
The relevant code can be found in the MultiBindingExpression
class. Below is an abbreviated excerpt.
internal override object ConvertProposedValue(object value)
{
object result;
bool success = ConvertProposedValueImpl(value, out result);
{
result = DependencyProperty.UnsetValue;
...
}
return result;
}
private bool ConvertProposedValueImpl(object value, out object result)
{
result = GetValuesForChildBindings(value);
object[] values = (object[])result;
int count = MutableBindingExpressions.Count;
bool success = true;
// use the smaller count
if (values.Length < count)
count = values.Length;
for (int i = 0; i < count; ++i)
{
value = values[i];
...
if (value == DependencyProperty.UnsetValue)
success = false; // if any element is UnsetValue, conversion fails
values[i] = value;
}
result = values;
return success;
}
As to whether it makes sense, I think that it does. A value of DoNothing
in the result array indicates that the corresponding child binding should be skipped, i.e., its source value should not be updated. This, in effect, provides a mechanism for partial updates. If you think about it, the only scenarios we care about are:
The normal behavior provides (1), and the use of DoNothing
can satisfy (2). It arguably makes sense to use UnsetValue
to indicate a total failure. That is also consistent with its meaning for single-value converters: UnsetValue
means the conversion failed. The only difference is that ConvertBack
returns object[]
, so you cannot return UnsetValue
directly. You can, however, return an array containing only UnsetValue
: since its presence means the entire result gets thrown out, the array length doesn't actually have to match the number of child bindings.
Upvotes: 8
Reputation: 989
I had a similiar problem some time ago [ Demultiplexing using IMultiValueConverter ]
What worked for me was to 'delay' the assignment of DependencyProperty.UnsetValue
: Since a MultiBinding
is somewhat a 'collection' of Bindings, you can assign an IValueConverter
to each involved 1-1 bindings as follows:
(1) XAML-Markup: Introduce converters for the involved 1:1-bindings
<Grid.Visibility>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}" Mode="TwoWay">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="MyVisibilityDependencyProperty" Mode="TwoWay" />
<Binding Converter="UnsetValueConverter" RelativeSource="{RelativeSource TemplatedParent}" Path="MyBoolProperty" Mode="TwoWay"/>
</MultiBinding>
</Grid.Visibility>
(Note the introduced Converter="UnsetValueConverter"
in the second binding)
(2) Implement MyMultiValueConverter: Create copies of the supplied element
public class MyMultiValueConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//Not interesting
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new[] { value, value };
}
}
(3) Implement the in (1) introduced UnsetValueConverter
public class UnsetValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
This way the disposal is delayed until right before the updated value is delivered
And yes, this isn't an answer to your question, but Mike S. did a good job at explaining it and this attempts to give a generic solution approach
Upvotes: 2
Reputation: 25
try this:
Converts source values to a value for the binding target. The data binding engine calls this method when it propagates the values from source bindings to the binding target.
///param name="values"> The array of values taht the source bindings in the<see cref="T:System.Windows.Data.MultiBinding"/>produces.The value <see cref="F:System.Windows.DependencyProperty.UnsetValue"/> indicates that the source binding has no value to provide for conversion.</param>
/// <param name="targetType">The type of the binding target property.</param>
/// <param name="parameter">The converter parameter to use.</param>
/// <param name="culture">The culture to use in the converter.</param>
/// <returns>
/// A converted value.
/// If the method returns null, the valid null value is used.
/// A return value of <see cref="T:System.Windows.DependencyProperty"/>.<see cref="F:System.Windows.DependencyProperty.UnsetValue"/> indicates that the converter did not produce a value, and that the binding will use the <see cref="P:System.Windows.Data.BindingBase.FallbackValue"/> if it is available, or else will use the default value.
/// A return value of <see cref="T:System.Windows.Data.Binding"/>.<see cref="F:System.Windows.Data.Binding.DoNothing"/> indicates that the binding does not transfer the value or use the <see cref="P:System.Windows.Data.BindingBase.FallbackValue"/> or the default value.
/// </returns>
public object ConvertBack( object[] values, Type targetType, object parameter, System.Globalization.CultureInfo clture )
{
if( parameter == null )
{ return null;}
return String.Format( parameter.ToString(), values );
}
Upvotes: -3
Reputation: 17085
According to MSDN:
UnsetValue is a sentinel value that is used for scenarios where the WPF property system is unable to determine a requested DependencyProperty value. UnsetValue is used rather than null, because null could be a valid property value, as well as a valid (and frequently used) DefaultValue.
In fact you can't set a DependencyProperty
to UnsetValue
, you can just compare with it. Setting to UnsetValue does not have an effect, you can try that yourself.
Upvotes: 1