调试 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 中的描述进行调试。
结论
我希望本文内容翔实。我的目标是传达如何以及在哪里可以找到有关数据绑定错误的信息,以及如何解释它们。
欢迎您提出任何反馈。