为什么在绑定上放置no-op转换器会改变它的行为?

Rob*_*ney 7 wpf binding user-controls ivalueconverter

我正在测试我构建的用户控件,我遇到的东西对我来说是莫名其妙的.

该控件是ComboBox的扩展,用于处理特定自定义类型的值.它具有该自定义类型的依赖项属性,该属性是Binding的目标属性.

我在setter中有一个trace语句,我可以看到该属性已经设置好了.但它没有出现在我的用户控件中.

现在,通常我会说,好吧,我的用户控件中有一个错误.我可能会这样做,虽然我对此感到困惑.但这个问题不是要找到我控制中的错误.继续阅读; 这里是奇怪的地方.

我也使用Bea Stollnitz的小值转换器来帮助调试绑定:

public class DebuggingConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value; // Add the breakpoint here!!
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException("This method should never be called");
    }
}
Run Code Online (Sandbox Code Playgroud)

这背后的想法是我将这个转换器添加到我的Binding中,并且可以设置一个断点来查看推送到目标的值.好的,这很好用.我可以看到价值被推出.

事实上,它的工作有点精细了.如果DebuggingConverter附加到Binding,则用户控件将显示该值.如果不是,那就没有.

这怎么可能呢?如果一个值转换器不会影响绑定控件的行为?

编辑:

并不是说它可能有所帮助,但这是用户控件的XAML:

<a:CodeLookupBox
    Grid.Column="1"
    Grid.IsSharedSizeScope="True"
    MinWidth="100"
    Style="{Binding Style}">
    <a:CodeLookupBox.CodeLookupTable>
        <Binding Path="Codes" Mode="OneWay"/>
    </a:CodeLookupBox.CodeLookupTable>
    <a:CodeLookupBox.SelectedCode>
        <Binding Path="Value" Mode="TwoWay" ValidatesOnDataErrors="True"/>
    </a:CodeLookupBox.SelectedCode>
</a:CodeLookupBox>
Run Code Online (Sandbox Code Playgroud)

如果没有第二个绑定上的转换器,控件就像我没有设置一样SelectedCode.即使OnSelectedCodePropertyChanged处理程序中的trace语句显示e.Value确实包含正确的值.无论转换器是否连接,都会发生这种情况.

我一直试图通过一个思想实验对这个问题进行反向工程:如果你想创建一个绑定用户控件,如果一个no-op转换器连接到它的绑定,其行为会改变,你会怎么做?我不太了解绑定来得出答案.

Rob*_*ney 2

好消息是,我知道为什么SelectedCode当我不使用值转换器时没有被设置。坏消息是,我仍然有一些未解之谜,但问题已经被推到了食物链的上游,而且我有一个解决方法。

该控件本质上是一个强类型组合框,具有许多附加功能,因为它知道其中包含哪些项目。和属性是强类型的,它们隐藏了底层的和属性,而SelectedCode事实并非如此。(顺便说一句,这就是为什么这是一个用户控件而不是 的子类;我不希望这些属性可见,因为如果它们设置不当,可能会发生很多事情,但没有一个是好的。)CodeLookupTableSelectedItemItemsSourceComboBox

这就是发生的事情。这是附加值转换器时的调试输出(数字是控件的哈希码,因为我有一堆在程序初始化时同时绘制的值):

14626603: OnCodeLookupTablePropertyChanged
   CodeLookupTable property set to Proceedings.Model.CodeLookupTable
   box.MainComboBox.ItemsSource = MS.Internal.Data.EnumerableCollectionView
14626603: OnSelectedCodePropertyChanged:
   SelectedCode property set to Unlicensed Driver [VC12500(A)]
   box.MainComboBox.ItemsSource = MS.Internal.Data.EnumerableCollectionView
Run Code Online (Sandbox Code Playgroud)

这是预期的行为。该CodeLookupTable属性已设置,因此设置SelectedCode为该集合中的项目之一可以正确设置SelectedItem底层ComboBox.

但如果没有值转换器,我们会得到:

16143157: OnSelectedCodePropertyChanged:
   SelectedCode property set to Unlicensed Driver [VC12500(A)]
   box.MainComboBox.ItemsSource = 
16143157: OnCodeLookupTablePropertyChanged
   CodeLookupTable property set to Proceedings.Model.CodeLookupTable
   box.MainComboBox.ItemsSource = MS.Internal.Data.EnumerableCollectionView
Run Code Online (Sandbox Code Playgroud)

在这里,属性是在属性之前SelectedCode设置的。因此,当该方法尝试设置底层时,不会发生任何事情,因为为 null。 CodeLookupTableSelectedItemComboBoxItemsSource

这就是问题的根源。我愚蠢地假设绑定更新其目标的顺序与它们在 XAML 中声明的顺序相同。(我将绑定表示为元素而不是属性的原因之一是因为 XML 文档中元素的顺序是确定性的,而属性的顺序则不是。我并不是没有考虑到这一点。)显然并非如此。

我还假设(也许不那么愚蠢),绑定更新其目标的顺序并不取决于它们是否附加了值转换器。嗯,确实如此。我想知道这还取决于什么。

幸运的是,我有办法解决这个问题。由于我的CodeLookup对象包含对 的引用CodeLookupTable,因此我可以让SelectedCodesetter 首先设置CodeLookupTable(以及ItemsSource)属性(如果尚未设置)。这将使这个问题消失,而不必在绑定上粘贴一个假值转换器,并希望绑定的行为方式永远不会改变。

编辑

属性声明如下所示:

#region SelectedCode

public static readonly DependencyProperty SelectedCodeProperty = DependencyProperty.Register(
    "SelectedCode", typeof(CodeLookup), typeof(CodeLookupBox),
    new FrameworkPropertyMetadata(OnSelectedCodePropertyChanged));

private static void OnSelectedCodePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
    CodeLookupBox box = (CodeLookupBox)source;
    CodeLookup code = e.NewValue as CodeLookup;
    // this right here is the fix to the original problem:
    if (box.CodeLookupTable == null && code != null)
    {
        box.CodeLookupTable = code.Table;
    }
    box.MainComboBox.SelectedItem = e.NewValue;
}

public CodeLookup SelectedCode
{
    get { return GetValue(SelectedCodeProperty) as CodeLookup; }
    set { SetValue(SelectedCodeProperty, value); }
}

#endregion

#region CodeLookupTable

public static readonly DependencyProperty CodeLookupTableProperty = DependencyProperty.Register(
    "CodeLookupTable", typeof(CodeLookupTable), typeof(CodeLookupBox),
    new FrameworkPropertyMetadata(OnCodeLookupTablePropertyChanged));

private static void OnCodeLookupTablePropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
    CodeLookupBox box = (CodeLookupBox)source;
    CodeLookupTable table = (CodeLookupTable)e.NewValue;

    box.ViewSource = new CollectionViewSource { Source = table.Codes };
    box.View = box.ViewSource.View;
    box.MainComboBox.ItemsSource = box.View;

}

public CodeLookupTable CodeLookupTable
{
    get { return GetValue(CodeLookupTableProperty) as CodeLookupTable; }
    set { SetValue(CodeLookupTableProperty, value); }
}

#endregion
Run Code Online (Sandbox Code Playgroud)