为什么在绑定上放置无操作转换器会改变其行为?

发布于 2024-08-21 13:25:20 字数 1773 浏览 11 评论 0原文

我正在测试我构建的用户控件,并且遇到了一些让我无法解释的事情。

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

我在设置器中有一个跟踪语句,我可以看到该属性正在设置。但它没有出现在我的用户控件中。

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

我还使用 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");
    }
}

其背后的想法是,我将此转换器添加到我的绑定中,并可以设置一个断点来查看哪些值被推送到目标。好的,效果很好。我可以看到价值正在被推出。

事实上,它的效果有点太好了。如果 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>

第二个绑定上没有转换器,控件的行为就好像我没有设置 SelectedCode。尽管 OnSelectedCodePropertyChanged 处理程序中的跟踪语句显示 e.Value 确实包含正确的值。无论是否连接转换器,都会发生这种情况。

我一直在尝试通过思想实验对这个问题进行逆向工程:如果您想创建一个绑定用户控件,如果将无操作转换器附加到其绑定,该控件的行为就会发生变化,您会怎么做?我对绑定了解不够,无法给出答案。

I'm in the midst of testing a user control I've built, and I'm encountering something that's inexplicable to me.

The control's an extension of the ComboBox that handles values of a specific custom type. It has a dependency property of that custom type that is the target property of a Binding.

I've got a trace statement in the setter, and I can see that the property is getting set. But it's not appearing in my user control.

Now, ordinarily I'd say, okay, I've got a bug in my user control. I probably do, though I'm baffled about it. But this question isn't about finding the bug in my control. Read on; here is where it gets weird.

I'm also using Bea Stollnitz's little value converter to help debug the Binding:

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

The idea behind this is that I add this converter to my Binding and can set a breakpoint to see what value is being pushed out to the target. Okay, that works just fine. I can see that the value is being pushed out.

In fact, it works a little too fine. If the DebuggingConverter is attached to the Binding, the user control displays the value. If it's not, it doesn't.

How is that even possible? How could a value converter that does nothing affect the behavior of a bound control?

Edit:

Not that it's likely to help, but here's the XAML for the user control:

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

Without the converter on the second binding, the control behaves as though I didn't set SelectedCode. Even though a trace statement in the OnSelectedCodePropertyChanged handler shows that e.Value does indeed contain the correct value. This happens irrespective of whether the converter's attached or not.

I've been trying to reverse-engineer this problem with a thought experiment: if you wanted to create a bound user control whose behavior changed if a no-op converter were attached to its binding, how would you do it? I don't know enough about binding to come up with an answer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

窝囊感情。 2024-08-28 13:25:20

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

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

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

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

这是预期的行为。设置了 CodeLookupTable 属性,因此将 SelectedCode 设置为该集合中的一项可以正确设置底层 ComboBoxSelectedItem代码>.

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

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

这里,SelectedCode 属性的设置先于 CodeLookupTable 属性。因此,当该方法尝试在底层 ComboBox 上设置 SelectedItem 时,不会发生任何事情,因为 ItemsSource 为 null。

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

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

幸运的是,我有办法解决这个问题。由于我的 CodeLookup 对象包含对 CodeLookupTable 的引用,因此我可以使 SelectedCode 设置器设置 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

Well, the good news is, I know why SelectedCode isn't being set when I'm not using a value converter. The bad news is, I still have something of a mystery, but the problem's been pushed up the food chain a bit, and I have a workaround.

This control is essentially a strongly-typed combo box with a bunch of additional features that are made possible by the fact that it knows what kind of items are in it. The SelectedCode and CodeLookupTable properties are strongly typed, and they hide the underlying SelectedItem and ItemsSource properties, which aren't. (This, by the way, is why this is a user control and not a subclass of ComboBox; I don't want those properties to be visible because a lot of things can happen if they get set improperly, none of them good.)

Here's what's happening. This is my debugging output when the value converter is attached (the number is the hash code of the control, because I've got a bunch of them that all get drawn simultaneously when the program's initialized):

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

This is the expected behavior. The CodeLookupTable property is set, so setting SelectedCode to one of the items in that collection correctly sets SelectedItem on the underlying ComboBox.

But without the value converter, we get this:

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

Here, the SelectedCode property is being set before the CodeLookupTable property is. So when the method tries to set SelectedItem on the underlying ComboBox, nothing happens, because the ItemsSource is null.

And here is the root of the problem. I've foolishly assumed that the order that bindings update their target in is the same as the order they're declared in the XAML. (One of the reasons I've expressed the bindings as elements instead of attributes is because the order of elements in an XML document is deterministic and the order of attributes isn't. It's not like I didn't think about this.) This is apparently not the case.

I've also assumed, maybe a little less foolishly, that the order in which bindings update their target isn't dependent on whether or not they have attached value converters. Well, it is. I wonder what else it depends on.

Mercifully, I have a way to work around this. Since my CodeLookup object contains a reference to the CodeLookupTable, I can make the SelectedCode setter set the CodeLookupTable (and thus the ItemsSource) property first, if it hasn't already been set. That'll make this problem go away without having to stick a fake value converter on the binding and hope that the way bindings behave never changes.

Edit

Here's what the property declarations look like:

#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
和我恋爱吧 2024-08-28 13:25:20

也尝试实现 ConvertBack 函数,您正在使用双向绑定,因此问题可能是“xaml”将忽略异常,但是当您进入调试模式时,您可能会停止“发送值回”操作?

try to implement the ConvertBack function too, you are using two-way binding so the problem may be that the exception will be ignored by the "xaml" but when you step in debug mode you maybe stop the "send value back"-operation?

春庭雪 2024-08-28 13:25:20

嗯……很奇怪。我在处理具有多个转换器的多重绑定时看到了类似的行为,但在简单绑定上却没有看到类似的行为。出于好奇,您如何在控件上定义 DP? (包括回调、元数据选项等)

一些要尝试的东西(删除转换器挂钩后):

  • 默认模式(即删除 TwoWay)
  • 删除 ValidatesOnDataErrors
  • 将 AffectsRender 添加到 SelectedCode DP

Hmm...very strange. I've seen similar behavior when dealing with MultiBindings with multiple converters, but not on a simple binding. Out of curiosity, how are you defining your DPs on the control? (including the callbacks, metadata options, etc)

Some stuff to try (after removing the converter hook):

  • Default the mode (ie, remove the TwoWay)
  • Remove the ValidatesOnDataErrors
  • Add an AffectsRender to the SelectedCode DP
请止步禁区 2024-08-28 13:25:20

设置SelectedCode有什么作用?您可以发布属性更改处理程序的代码吗?

您建议调试显示该属性已设置为正确的值,因此最明显的建议是提供您预期行为的代码不正确。

What does setting SelectedCode do? Can you post the code for the property changed handler?

You've suggested that debugging shows the property is being set to the correct value, so the most obvious suggestion is that the code providing your intended behaviour is incorrect.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文