天天看點

2019-11-29-WPF-如何在綁定失敗異常

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 ,實際上你看到隻有一行,因為第二個綁定寫錯了

2019-11-29-WPF-如何在綁定失敗異常

第二個在 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;
            }

        }
    }      

這時會發現代碼抛出異常

2019-11-29-WPF-如何在綁定失敗異常

但是抛出了異常建議彈出視窗,這樣開發者才會看到

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

2019-11-29-WPF-如何在綁定失敗異常

自動提示

我找到綁定失敗很多是因為寫錯了屬性,很多小夥伴不知道實際 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

參見: