title | author | date | CreateTime | categories |
WPF 如何在綁定失敗異常 | lindexi | 2019-11-29 10:13:57 +0800 | 2018-05-17 14:29:32 +0800 | WPF 調試 WPF調試 |
在開發 WPF 程式,雖然 xaml 很好用,但是經常會出現小夥伴把綁定寫錯了。因為預設的 VisualStudio 是沒有自動提示,這時很容易複制粘貼寫出一個不存在的屬性。 在 xaml 如果綁定失敗了,那麼内部會有一個異常,但是 WPF 不會把這個異常抛出來,這個異常也不會讓使用者拿到,隻是會在輸出視窗提示。但是異常會影響性能,而且會讓界面和設計的不一樣,是以我就想在找到綁定異常就抛出,彈出視窗告訴小夥伴。 本文會告訴大家如何找到綁定失敗,并且抛出異常,如何防止修改屬性名讓xaml綁定失敗。
在綁定失敗異常建議隻在調試下抛出,抛出異常建議彈出,告訴開發者現在你的界面有綁定異常
拿到綁定資訊
先來寫簡單的代碼,做一個 ViewModel ,裡面有兩個屬性
class ViewModel
{
public string Name { get; set; } = "lindexi";
public string JaslorbafelStojou { get; set; } = "lindexi.gitee.io";
}
可以看到第二個屬性是比較複雜的,現在來寫 xaml 界面,
<Grid>
<StackPanel Margin="10,10,10,10" HorizontalAlignment="Center">
<TextBlock Margin="10,10,10,10" Text="{Binding Name}"></TextBlock>
<TextBlock Margin="10,10,10,10" Text="{Binding JaslorbafelStoj}"></TextBlock>
</StackPanel>
</Grid>
然後在背景代碼添加這個代碼
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
現在運作一下,你猜是不是會顯示兩行,一行是 lindexi 一行是 lindexi.gitee.io ,實際上你看到隻有一行,因為第二個綁定寫錯了

第二個在 ViewModel 的屬性是 JaslorbafelStojou 但是 xaml 寫的是 JaslorbafelStoj ,如果這時看到了輸出,就會看到下面代碼
System.Windows.Data Error: 40 : BindingExpression path error: 'JaslorbafelStoj' property not found on 'object' ''ViewModel' (HashCode=16468652)'. BindingExpression:Path=JaslorbafelStoj; DataItem='ViewModel' (HashCode=16468652); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
那麼這個代碼是否可以用來判斷出現綁定失敗,是的,讓我來告訴大家如何拿到輸出
轉發綁定
因為綁定失敗輸出是使用 Trace ,關于 Trace 請看WPF 調試 獲得追蹤輸出
那麼如何拿到 Trace 的輸出?
首先需要定義一個類繼承 TraceListener ,下面定義一個 BindingErrorTraceListener 收到了消息就輸出
public class BindingErrorTraceListener : TraceListener
{
public override void Write(string message)
{
Trace.WriteLine(string.Format("[Write]{0}", message));
}
public override void WriteLine(string message)
{
Trace.WriteLine(string.Format("[WriteLine]{0}", message));
}
}
然後在構造函數加入,注意在 InitializeComponent 之前
public MainWindow()
{
PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.Error;
PresentationTraceSources.DataBindingSource.Listeners.Add(new BindingErrorTraceListener());
InitializeComponent();
DataContext = new ViewModel();
}
這時運作代碼可以看到輸出
[Write]System.Windows.Data Error: 40 :
[WriteLine]BindingExpression path error: 'JaslorbafelStoj' property not found on 'object' ''ViewModel' (HashCode=16468652)'. BindingExpression:Path=JaslorbafelStoj; DataItem='ViewModel' (HashCode=16468652); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
是以很容易就知道如何判斷是綁定輸出
綁定失敗異常
從上面代碼可以知道,所有的綁定輸出可以
PresentationTraceSources.DataBindingSource.Listeners
拿到,重寫方法就可以轉發
而且 TraceListener 是一個很強的類,支援了很多輸入,不隻字元串,還支援 object ,是以嘗試使用 TraceListener 可以做到比較好調試
因為需要在失敗抛出異常,就需要定義一個異常
public class BindingErrorException : Exception
{
public string SourceObject { get; set; }
public string SourceProperty { get; set; }
public string TargetElement { get; set; }
public string TargetProperty { get; set; }
public BindingErrorException()
: base()
{
}
public BindingErrorException(string message)
: base(message)
{
}
}
判斷目前存在綁定失敗很簡單,主要使用正則判斷
public class BindingErrorTraceListener : TraceListener
{
private const string BindingErrorPattern = @"^BindingExpression path error(?:.+)'(.+)' property not found(?:.+)object[\s']+(.+?)'(?:.+)target element is '(.+?)'(?:.+)target property is '(.+?)'(?:.+)$";
public override void Write(string message)
{
}
public override void WriteLine(string message)
{
var match = Regex.Match(message, BindingErrorPattern);
if (match.Success)
{
var exception = new BindingErrorException(message)
{
SourceObject = match.Groups[2].ToString(),
SourceProperty = match.Groups[1].ToString(),
TargetElement = match.Groups[3].ToString(),
TargetProperty = match.Groups[4].ToString()
};
throw exception;
}
}
}
這時會發現代碼抛出異常
但是抛出了異常建議彈出視窗,這樣開發者才會看到
public MainWindow()
{
PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.Error;
PresentationTraceSources.DataBindingSource.Listeners.Add(new BindingErrorTraceListener());
InitializeComponent();
DataContext = new ViewModel();
App.Current.DispatcherUnhandledException += DispatcherUnhandledException;
}
private void DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
if (e.Exception is BindingErrorException bindingErrorException)
{
MessageBox.Show($"Binding error. {bindingErrorException.SourceObject}.{bindingErrorException.SourceProperty} {bindingErrorException.TargetElement}.{bindingErrorException.TargetProperty}");
}
}
自動提示
我找到綁定失敗很多是因為寫錯了屬性,很多小夥伴不知道實際 xaml 是可以自動提示。
先在 對應的視窗寫入綁定的類型,使用
d:DataContext
可以告訴 xaml 使用的資料類型,這樣做綁定就可以自動提示
<Grid d:DataContext="{d:DesignInstance local:ViewModel}">
<StackPanel Margin="10,10,10,10" HorizontalAlignment="Center">
<TextBlock Text="{Binding Name}"></TextBlock>
<TextBlock Text="{Binding JaslorbafelStoj}"></TextBlock>
</StackPanel>
</Grid>
這時嘗試删除 JaslorbafelStoj 重新寫,就會提示 需要寫 JaslorbafelStojou ,這樣會自動提示就很難寫錯。
我很建議大家安裝 Resharper 這樣在修改變量名時,會自動修改 xaml 的屬性名
在有安裝 Resharper 的裝置,修改一個屬性名,然後按 Alt+enter 就會提示 apply rename factoring ,這樣會修改所有引用這個屬性的變量名
需要注意,必須添加
d:DataContext
或者這樣設定 ViewModel 才可以通過 Resharper 修改變量名
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
如果需要調試 Binding ,參見 WPF 如何調試 binding
參見: