调试 WPF 数据绑定






4.91/5 (19投票s)
解释如何使用跟踪消息或 IValueConverter 来调试 WPF 数据绑定。
引言
数据绑定是 WPF 中最广泛使用的功能之一。然而,调试数据绑定却鲜为人知,我将尝试通过本文来纠正这一点。
本质上,当数据绑定不抛出异常时,我们可以使用两种方法来调试它们。
- 使用 IValueConverter进入调试器。
- 使用跟踪消息。
使用 IValueConverter
这种方法相当直接。您将创建一个实现 IValueConverter 接口的类,然后在绑定中使用此转换器,以便您可以进入调试器。
// Either you can use Debugger.Break() or put a break point
// in your converter, inorder to step into the debugger.
// Personally I like Debugger.Break() as I often use
// the "Delete All Breakpoints" function in Visual Studio.
public class MyDebugConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
           object parameter, CultureInfo culture)
    {
        System.Diagnostics.Debugger.Break();
        return value;
    }
    public object ConvertBack(object value, Type targetType, 
           object parameter, CultureInfo culture)
    {
        System.Diagnostics.Debugger.Break();
        return value;
    }
}
<TextBox Text="{Binding MyProperty, Converter={StaticResource MyDebugConverter}}"/>
现在,每当您的绑定提供新值时,调试器都会在转换器中的 Debugger.Break() 处中断,然后您可以单步执行以查看是哪个属性被设置以及在哪里被设置。

何时使用 IValueConverter 方法
此方法的实用性相当有限。它实际上什么都不做,只是允许您找到更改属性的代码片段。所以,当您的绑定正在工作,但您没有得到预期的值时,您应该使用它。如果您的绑定源使用了 INotifyPropertyChanged,那么您只需在属性的 setter 中放置一个断点,就可以避免使用转换器。
一个小技巧,如果您打算使用此方法,您可能想阅读marlongrech 的这篇文章,他演示了如何使用 MarkupExtension,这样您就不必为您的转换器创建资源条目。
Notice
如果您使用 Debugger.Break() 而忘记从绑定中删除转换器,那么在发布版本时您会不喜欢结果。

这就是在没有调试器附加到它时运行应用程序时会发生的情况。根据文档,它应该会询问用户是否要附加调试器,但在我这边并没有发生。
使用跟踪消息
当绑定无法找到绑定源或源上的属性时,您的输出窗口将显示有关绑定错误的详细信息。您可以使用快捷键 Ctrl+Alt+O 来激活输出窗口。数据绑定的错误将以“System.Windows.Data Error: ##”开头。

绑定错误
'<MyProperty>' 属性在对象 ''<MySource>'' 上未找到
原因
- <MyProperty>的名称拼写错误。
- 该属性已从 <MySource>类型中删除或尚未添加。
- <MySource>类型不符合预期。例如,它是- MyViewModel,但我们期望它是- MyOtherViewModel。
解决方案
- <MyProperty>的正确名称(复制粘贴属性名称可确保 100% 准确;请记住,实际属性名称也可能拼写错误)。
- 如果删除了,那么视图可能需要重新设计,因此在重新实现属性(如果可能的话)之前,请找出属性被删除的原因。如果尚未添加,请耐心等待,您的同事可能和您一样忙。
- 如何解决这个问题取决于源的确定方式。
- 源是 DataContext。处理目标元素的DataContextChanged事件,以便进入调试器并找出DataContext未设置为预期值的原因(就像 使用IValueConverter时一样)。
- 使用了 RelativeSource;请参见:找不到具有引用的源进行绑定 - 解决方案 1。
- 设置了绑定的 Source属性;转到设置此属性的 XAML 或代码并进行更正。
找不到具有引用的源进行绑定
原因
- 设置了绑定的 ElementName属性,但找不到具有该名称的元素。
- 设置了绑定的 RelativeSource属性并使用了FindAncestor模式,但未找到满足RelativeSource的源。
解决方案
- 确保我们没有拼写错误(请进行复制粘贴以确保 100% 准确);如果不起作用,那么具有给定名称的元素不在适当的名称空间中,请参阅WPF XAML 名称空间。
- 我们应该做的第一件事是验证 RelativeSource的设置是否符合预期。这意味着要确保AncestorType是正确的类型,如果使用了AncestorLevel,请确保级别正确,请记住它是从 1 开始的。如果绑定仍然找不到绑定源,那么我们可以使用PresentationTraceSources.TraceLevel来增加从绑定引擎获得的信息。请注意,PresentationTraceSources.TraceLevel是在 .NET 3.5 中首次引入的,因此如果您使用的是 .NET 3.0,则必须更改目标框架以进行调试。
让我们看一下跟踪 PresentationTraceSources.TraceLevel=High 生成的内容
public class MyViewModel
{
    public string CurrentUserName {get; set}
    public IEnumerable<string /> UserActions { get { ... } }
}
<Window ...>
    <Window.DataContext>
        <MyViewModel>
    </Window.DataContext>
        <ListView ItemsSource="{Binding UserActions}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding DataContext.CurrentUserName,
                      RelativeSource={RelativeSource FindAncestor, 
                                      AncestorType={x:Type Button}},
                      PresentationTraceSources.TraceLevel=High}"/>
                    <TextBlock Text="{Binding}"/>
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Window>
System.Windows.Data Warning: 63 : BindingExpression (hash=49385318): 
       Resolving source  (last chance)
System.Windows.Data Warning: 66 : BindingExpression (hash=49385318): 
       Found data context element: <null /> (OK)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried StackPanel (hash=17059405)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ContentPresenter (hash=29475730)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried Border (hash=51339472)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ListViewItem (hash=28574894)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried VirtualizingStackPanel (hash=25181126)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ItemsPresenter (hash=59408853)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ScrollContentPresenter (hash=56152722)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried Grid (hash=43844556)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ScrollViewer (hash=26847985)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried Border (hash=199777)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ListView (hash=8990007)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried ContentPresenter (hash=1897140)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried AdornerDecorator (hash=18262443)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried Border (hash=16503569)
System.Windows.Data Warning: 69 :     Lookup ancestor of type Button:  
       queried MainWindow (hash=4463106)
System.Windows.Data Error: 4 : Cannot find source for binding 
       with reference 'RelativeSource FindAncestor, 
       AncestorType='System.Windows.Controls.Button', AncestorLevel='1''. 
       BindingExpression:Path=DataContext.CurrentUserName; DataItem=null; 
       target element is 'TextBlock' (Name=''); 
       target property is 'Text' (type 'String')
请注意,绑定引擎并不轻易放弃,因此我们会看到多次重复的警告。只需找到错误消息之前最后的“BindingExpression (hash=##...): Resolving source”警告。完成此操作后,我们将对绑定引擎查找源所走的路径有一个概述。现在就看我们是否可以在 XAML/代码中遵循该路径,找到我们预期 Button(在本例中)所在的位置,然后相应地修复绑定。
我的绑定不起作用,但没有收到任何错误消息
原因
- 绑定属性 UpdateSourceTrigger缺失或使用不当。
- 绑定使用的绑定源是 DataContext,而DataContext为null。
- Path中指定的属性之一为- null,例如,路径“User.FirstName”中的 User 为- null。
解决方案
- 确保 UpdateSourceTrigger设置为预期值。如果绑定的UpdateSourceTrigger属性未设置,则它默认为目标DependencyProperty指定的值,例如TextBox.Text的默认UpdateSourceTrigger值为LostFocus,这意味着源仅在TextBox失去焦点时更新。如果我们希望绑定在目标属性更改时进行更新,则将UpdateSourceTrigger设置为PropertyChanged。
- 如果 DataContext或祖先元素的DataContext是通过绑定设置的,则调试该绑定。
- 处理目标元素的 DataContextChanged事件,以便进入调试器并找出DataContext设置为null的原因。如果没有代码将DataContext设置为null,因此DataContextChanged事件未触发,那么我们就必须调试用于设置目标元素或祖先元素的DataContext的代码。
在这种情况下,您可以使用IValueConverter 方法。
监控现有绑定的错误
当我们修复 bug、实现新功能或优化代码时,我们就有可能引入新 bug,例如破坏绑定。查看输出窗口中的绑定错误可能会很烦人,因为我们必须自己过滤掉所有无关的信息。为了过滤掉所有相关信息,我们可以使用监听器和开关将跟踪消息记录到一个文件中,然后我们可以在该文件中查找绑定错误。通过将以下内容添加到 app.config 的配置部分,我们可以记录数据绑定跟踪消息。
<system.diagnostics>
    <sources>
        <source name="System.Windows.Data" switchName="mySwitch">
            <listeners>
                <add name="myListener" />
            </listeners>
        </source>
    </sources>
    <switches>
        <add name="mySwitch" value="All" />
    </switches>
    <sharedListeners>
        <add name="myListener" type="System.Diagnostics.TextWriterTraceListener"
             initializeData="DataBindingTrace.txt" />
    </sharedListeners>
</system.diagnostics>
这里我们将开关设置为 All;这将为我们提供尽可能多的数据绑定信息。将其设置为 Error 将只记录数据绑定错误。要完全关闭日志记录,请将其设置为 Off。
让我们看一个例子。以下绑定失败,因为 DataContext 为 null。
<TextBox DataContext="{x:Null}" Text="{Binding MyProperty}"/>
这会在我们的日志文件中产生以下输出
System.Windows.Data Information: 40 : BindingExpression path error: 'MyProperty' 
  property not found for 'object' because data item is null. This could happen 
  because the data provider has not produced any data yet. BindingExpression:Path=MyProperty; 
  DataItem=null; target element is 'TextBox' (Name=''); 
  target property is 'Text' (type 'String')
System.Windows.Data Information: 19 : BindingExpression 
  cannot retrieve value due to missing information. 
  BindingExpression:Path=MyProperty; DataItem=null; target element 
  is 'TextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 20 : BindingExpression cannot 
  retrieve value from null data item. This could happen when binding 
  is detached or when binding to a Nullable type that has no value. 
  BindingExpression:Path=MyProperty; DataItem=null; target element 
  is 'TextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 10 : Cannot retrieve value 
  using the binding and no valid fallback value exists; 
  using default instead. BindingExpression:Path=MyProperty; 
  DataItem=null; target element is 'TextBox' (Name=''); target property 
  is 'Text' (type 'String')
从这里,我们可以在第一行看到绑定源为 null,因此是错误的来源,我们按照“我的绑定不起作用,但没有收到任何错误消息”的解决方案 1 中的描述进行调试。
结论
我希望本文内容翔实。我的目标是传达如何以及在哪里可以找到有关数据绑定错误的信息,以及如何解释它们。
欢迎您提出任何反馈。

