65.9K
CodeProject 正在变化。 阅读更多。
Home

调试 WPF 数据绑定

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (19投票s)

2011年8月22日

CPOL

7分钟阅读

viewsIcon

81730

解释如何使用跟踪消息或 IValueConverter 来调试 WPF 数据绑定。

引言

数据绑定是 WPF 中最广泛使用的功能之一。然而,调试数据绑定却鲜为人知,我将尝试通过本文来纠正这一点。

本质上,当数据绑定不抛出异常时,我们可以使用两种方法来调试它们。

  1. 使用 IValueConverter 进入调试器。
  2. 使用跟踪消息。

使用 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() 处中断,然后您可以单步执行以查看是哪个属性被设置以及在哪里被设置。

Sample Image

何时使用 IValueConverter 方法

此方法的实用性相当有限。它实际上什么都不做,只是允许您找到更改属性的代码片段。所以,当您的绑定正在工作,但您没有得到预期的值时,您应该使用它。如果您的绑定源使用了 INotifyPropertyChanged,那么您只需在属性的 setter 中放置一个断点,就可以避免使用转换器。

一个小技巧,如果您打算使用此方法,您可能想阅读marlongrech 的这篇文章,他演示了如何使用 MarkupExtension,这样您就不必为您的转换器创建资源条目。

Notice

如果您使用 Debugger.Break() 而忘记从绑定中删除转换器,那么在发布版本时您会不喜欢结果。

这就是在没有调试器附加到它时运行应用程序时会发生的情况。根据文档,它应该会询问用户是否要附加调试器,但在我这边并没有发生。

使用跟踪消息

当绑定无法找到绑定源或源上的属性时,您的输出窗口将显示有关绑定错误的详细信息。您可以使用快捷键 Ctrl+Alt+O 来激活输出窗口。数据绑定的错误将以“System.Windows.Data Error: ##”开头。

Visual Studio output window, the highlighted line is the description of a data binding error

绑定错误

'<MyProperty>' 属性在对象 ''<MySource>'' 上未找到

原因

  1. <MyProperty> 的名称拼写错误。
  2. 该属性已从 <MySource> 类型中删除或尚未添加。
  3. <MySource> 类型不符合预期。例如,它是 MyViewModel,但我们期望它是 MyOtherViewModel

解决方案

  1. <MyProperty> 的正确名称(复制粘贴属性名称可确保 100% 准确;请记住,实际属性名称也可能拼写错误)。
  2. 如果删除了,那么视图可能需要重新设计,因此在重新实现属性(如果可能的话)之前,请找出属性被删除的原因。如果尚未添加,请耐心等待,您的同事可能和您一样忙。
  3. 如何解决这个问题取决于源的确定方式。
    1. 源是 DataContext。处理目标元素的 DataContextChanged 事件,以便进入调试器并找出 DataContext 未设置为预期值的原因(就像 使用 IValueConverter 时一样)。
    2. 使用了 RelativeSource请参见:找不到具有引用的源进行绑定 - 解决方案 1。
    3. 设置了绑定的 Source 属性;转到设置此属性的 XAML 或代码并进行更正。

找不到具有引用的源进行绑定

原因

  1. 设置了绑定的 ElementName 属性,但找不到具有该名称的元素。
  2. 设置了绑定的 RelativeSource 属性并使用了 FindAncestor 模式,但未找到满足 RelativeSource 的源。

解决方案

  1. 确保我们没有拼写错误(请进行复制粘贴以确保 100% 准确);如果不起作用,那么具有给定名称的元素不在适当的名称空间中,请参阅WPF XAML 名称空间
  2. 我们应该做的第一件事是验证 RelativeSource 的设置是否符合预期。这意味着要确保 AncestorType 是正确的类型,如果使用了 AncestorLevel,请确保级别正确,请记住它是从 1 开始的。如果绑定仍然找不到绑定源,那么我们可以使用PresentationTraceSources.TraceLevel 来增加从绑定引擎获得的信息。请注意,PresentationTraceSources.TraceLevel 是在 .NET 3.5 中首次引入的,因此如果您使用的是 .NET 3.0,则必须更改目标框架以进行调试。
  3. 让我们看一下跟踪 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(在本例中)所在的位置,然后相应地修复绑定。

我的绑定不起作用,但没有收到任何错误消息

原因

  1. 绑定属性 UpdateSourceTrigger 缺失或使用不当。
  2. 绑定使用的绑定源是 DataContext,而 DataContextnull
  3. Path 中指定的属性之一为 null,例如,路径“User.FirstName”中的 User 为 null

解决方案

  1. 确保 UpdateSourceTrigger 设置为预期值。如果绑定的 UpdateSourceTrigger 属性未设置,则它默认为目标 DependencyProperty 指定的值,例如 TextBox.Text 的默认 UpdateSourceTrigger 值为 LostFocus,这意味着源仅在 TextBox 失去焦点时更新。如果我们希望绑定在目标属性更改时进行更新,则将 UpdateSourceTrigger 设置为 PropertyChanged
  2. 如果 DataContext 或祖先元素的 DataContext 是通过绑定设置的,则调试该绑定。
  3. 处理目标元素的 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

让我们看一个例子。以下绑定失败,因为 DataContextnull

<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 中的描述进行调试。

结论

我希望本文内容翔实。我的目标是传达如何以及在哪里可以找到有关数据绑定错误的信息,以及如何解释它们。

欢迎您提出任何反馈。

© . All rights reserved.