当一个转换值在 UnsetValue 中转换时,使用转换回来的多重绑定不起作用

本文关键字:转换 回来 不起作用 绑定 一个 UnsetValue | 更新日期: 2023-09-27 18:31:12

我有以下多重绑定:

 <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 是一个依赖属性。MyBoolProperty是一个普通属性。MyMultiValueConverter的实现是重要的:

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};
    }
}

现在的场景是:我做 smth. 触发了 ConvertBack-Method的调用,这意味着我在那里遇到了一个断点。之后,我在 MyVisibilityDependencyProperty 的 OnPropertyChangedCallback 中遇到了一个断点。在那里,我可以看到MyVisibilityDependencyProperty的新值是在ConvertBack-Method中设置的值。

现在我不明白的问题。 我将 ConvertBack-Method 的实现更改为:

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return new[] { value, DependencyProperty.UnsetValue};
    }

现在我遵循完全相同的场景。我做嘘。触发了对转换回来方法的调用,这意味着我在那里遇到了一个断点。在那之后什么也没发生。不调用 OnPropertyChangedCallback,也不会更新 MyVisibilityDependencyProperty。为什么?

似乎如果数组中的一个值是 DependencyProperty.UnsetValue,则所有值的传播都会停止。不仅针对该值,而且针对数组中的所有值。以下行为支持此功能:

return new[] { Binding.DoNothing, false };

这会导致 MyBoolProperty 的二传手调用。

return new[] { DependencyProperty.UnsetValue, false };

这不会调用MyBoolProperty的setter。

我在文档中找不到任何解释的提示,在我看来这没有意义。

当一个转换值在 UnsetValue 中转换时,使用转换回来的多重绑定不起作用

我在文档中找不到任何解释的提示,在我看来这没有意义。

我不记得曾经在文档中看到过它,但您的观察是正确的:

如果IMultiValueConverter.ConvertBack结果中的任何元素被UnsetValue,则整个建议的值集将被拒绝,即转换失败,并且没有一个子绑定更新其源值。

相关代码可以在MultiBindingExpression类中找到。 以下是简短的摘录。

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;
}

至于它是否有意义,我认为是有道理的。 结果数组中的值 DoNothing 表示应跳过相应的子绑定,即不应更新其源值。 这实际上提供了一种部分更新的机制。 如果您仔细考虑一下,我们唯一关心的场景是:

  1. 所有源均已成功更新
  2. 某些源已成功更新
  3. 总故障:未更新源

正常行为提供(1),使用DoNothing可以满足(2)。 可以说,使用UnsetValue来表示完全失败是有意义的。 这也与单值转换器的含义一致:UnsetValue表示转换失败。 唯一的区别是ConvertBack返回object[],因此您不能直接返回UnsetValue。 但是,您可以返回一个包含 UnsetValue 的数组:因为它的存在意味着整个结果被抛出,因此数组长度实际上不必与子绑定的数量匹配。

前段时间我遇到了类似的问题 [ 使用 IMultiValueConverter 解复用 ]

对我有用的是"延迟"DependencyProperty.UnsetValue的分配:由于MultiBinding在某种程度上是绑定的"集合",因此您可以为每个涉及的 1-1 绑定分配一个IValueConverter,如下所示:

(1) XAML 标记:为所涉及的 1:1 绑定引入转换器

<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>

(请注意第二个绑定中引入的Converter="UnsetValueConverter"

(2) 实现 MyMultiValueConverter:创建所提供元素的副本

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)实现(1)中引入的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;
    }
}

这样,处置就会延迟到更新价值交付之前

是的,这不是你问题的答案,但Mike S.在解释它方面做得很好,这试图给出一个通用的解决方案方法

根据MSDN:

UnsetValue 是一个哨兵值,用于 WPF 属性系统无法确定请求的依赖项属性值的情况。使用 UnsetValue 而不是 null,因为 null 可以是有效的属性值,也可以是有效(且经常使用的)默认值。

实际上,您无法将DependencyProperty设置为UnsetValue,您可以与它进行比较。设置为"未设置值"不起作用,您可以自己尝试。

试试这个:

将源值转换为绑定目标的值。数据绑定引擎在将值从源绑定传播到绑定目标时调用此方法。

    ///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 );
    }