Thursday, February 16, 2017

Logging Binding Errors

This targets Framework 4.0

I recently had a perplexing problem that binding was failing on certain computers but not most of them. I was binding to indexed properties but this was failing on 32 bit computers and not on 64 bit. No exceptions were being thrown and I could not reproduce the error on any of my development machines.

The quick solution turned out to be to install Framework4.6.1 on the problem computers. The project targets Framework 4.0 Client so I don't understand why installing a later framework solved the problem. A colleague suggested it's a bug in Framework 4.0 and that's better than any theory I have.

While researching the problem I found an interesting post on StackOverflow.com that explains how to trap binding errors in code so you can log them. The article is written in C# so I'm reproducing a full project here in VB. Here is the post that I have derived my solution from. Credit to Dean Chalk and Crono.


Start a new VB WPFApplication in Visual Studio 2015 and call it LogBindingErrors.



Replace the MainWindow.xaml with the following XAML which simply defines the DataContext and a TextBlock bound to an, as yet non-existent, property.

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:LogBindingErrors"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource self}}">
    <Grid>
        <TextBlock Text="{Binding TextToDisplay}"></TextBlock>
    </Grid>
</Window>

Let's define the property now. We don't need INotifyPropertyChanged for this project.

Class MainWindow
    Public Property TextToDisplay As String = "Hello"
End Class

If we run the application now we get the following window.


Let's deliberately break the application by changing the name of the property like so...

Class MainWindow
    Public Property TextToDisplayX As String = "Hello"
End Class

Now the textblock will not be populated and we will see an error in the output window when we run the application.

System.Windows.Data Error: 40 : BindingExpression path error: 'TextToDisplay' property not found on 'object' ''MainWindow' (Name='')'. BindingExpression:Path=TextToDisplay; DataItem='MainWindow' (Name=''); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

When this only happens on non-development machines we can't see it so now we will add the code to log binding errors. 

Start by adding a new class to MainWindow.xaml.vb so that it looks like this...

Class MainWindow
    Public Property TextToDisplayX As String = "Hello"

    Public Class BindingErrorListener
        Inherits Diagnostics.TraceListener

        Private LogAction As Action(Of String)
        Public Shared Sub Listen(LogAction As Action(Of String))
            System.Diagnostics.PresentationTraceSources.DataBindingSource.Listeners.Add(New BindingErrorListener() With {.LogAction = LogAction})
        End Sub

        Public Overrides Sub Write(message As String)
        End Sub
        Public Overrides Sub WriteLine(message As String)
            LogAction(message)
        End Sub
    End Class

End Class

Basically we are defining a new TraceListener and adding to the Listeners collection so that when the listener invokes WriteLine the method defined by our listener's Listen method is invoked. You instantiate the class in the page's New method like this...

    Public Sub New()
        BindingErrorListener.Listen(Sub(m) MessageBox.Show(m))  ' Substitute your own logging mechanism here
        InitializeComponent()
    End Sub

Of course, you won't want to display a message box for every binding error. You will probably write to some kind of log file. When you run the application now you will see the following alert.



Now fix the property name and run the application again. You don't see an alert and the textblock is bound properly.

No comments:

Post a Comment