How to Debug WPF Data Binding markup extension

Knowing what happens when a data binding is bad configured is one of the frequent problems that a WPF or a Silverlight developer could encounter during the development process. Sometimes a whole project is stopped for minutes or even hours to fix a problem like the situation where a bound image doesn't display anything, in spite of the fact that the Binding markup extension is apparently well configured. The problem is that we can't refer the error in order to fix it. Instead we try to imagine scenarios and cases, something that probably not related with the right thing to do. Then we begin by asking those senseless questions such as: What if we set the Mode to TwoWay or the UpdateSourceTrigger to OnPropertyChanged and the time, Of Corse, laps without finding a solution. The problem with the data binding and the other markup extensions that it is impossible to set a break point and start debugging the data binding like the code behind as the XAML doesn't support debugging.

I can say yes it happened to me a lot in the past and I suffered a lot form that issue but the good thing is that I have learned a lot from that issue and therefore I will share my little experience through this article to keep the live more easier for other developers those encounter such trouble.

The most challenging ones are the easiest ones

In most case, you have to begin by verifying the properties values like the Path property whether it is set correctly or not.

To make things clear, let's imagine those both situations:

The first one is a Faute de frappe as the French people say:

        <TextBlock Padding="2" Name="FirstTextBlock" Text="My first text block" Grid.RowSpan="2" />
        <TextBlock Padding="2"
                   Text="{Binding ElementName=FirstTextBlock, Path=Txet,Mode=OneWay"
                   Grid.Row="1"
                   Height="53"
                   VerticalAlignment="Top" />

The second one is a mistake of a developer who is preparing a test plan in a separate thread in his mind without configuring a lock on his brain. Then the result will be an unsafe XAML code

Ha ha ha just for kidding.

        <TextBlock Padding="2" Name="FirstTextBlock" Text="My first text block" Grid.RowSpan="2" />
        <TextBlock Padding="2"
                   Text="{Binding ElementName=FirstTextBlock,
            Path=Test,Mode=OneWay"
                   Grid.Row="1"
                   Height="53"
                   VerticalAlignment="Top" />

As you can see, everything is well configured except the Path property which is set to Txet or Test instead of Text. It is a stupid and no sense fault I know, but it could happen to anyone including me.

The System.Diagnostics namespace

There are a lot of debugging techniques but most efficient and determinant one is the technique that I will expose in the following lines. But before that let's try the following code

        <TextBlock Padding="2"
                   Name="FirstTextBlock" Text="My first text block"></TextBlock>
        <TextBlock Padding="2"
                   Text="{Binding ElementName=FirstTextBlock,
            Path=Txet,Mode=OneWay}"
                   Grid.Row="1"
                   Height="53"
                   VerticalAlignment="Top" />

The result although you will not receive any compile or runtime error, an unexpected result will be observed

1.gif

The second text block is not binding as expected. If we try to have a look on the windows output

2.gif

Then we will face a huge quantity of information. Of Corse, it is possible to find out the error there but it will take an important amount of time and endeavor especially with big projects.

The solution to reduce that is represented as follow:

At the beginning, we add a configuration file to the application

3.gif

Once the file is added, we add this XML block within the configuration section so that the file content looks like below:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.diagnostics>
        <sources>
            <source name="System.Windows.Data" switchName="SourceSwitch" >
                <listeners>
                    <add name="textListener" />
                </listeners>
            </source>
        </sources>
        <trace autoflush="true" indentsize="4"></trace>
        <sharedListeners>
            <add name="textListenertype="System.Diagnostics.TextWriterTraceListener"
initializeData="E:\temp\DebugBinding.txt" />
        </sharedListeners>
        <switches>
            <add name="SourceSwitch" value="2" />
        </switches>
    </system.diagnostics>
</configuration>

Once this is done, we go to the App.xaml.cs file and add a code to clear out the previous result of the log file E:\temp\DebugBinding.txt where information about the binding(s) anomaly is logged. Of Corse, it is possible to set another location of the log file. Anyway, the App.xaml.cs file should look as follow:

namespace WpfApplication
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            ClearFile();
            base.OnStartup(e);
        }
        //This method will clear the previous data within the log file
        private static void ClearFile()
        {
            FileStream stream = new FileStream(@"E:\temp\DebugBinding.txt",
            FileMode.Truncate);
            StreamWriter writer = new StreamWriter(stream);
            writer.Write(string.Empty);
        }
    }
}

Once this is done, we define a preprocessor variable in the code behind where the data binding that causes error resides. Then we define a decorated method with the Conditional attribute as follow.

        [Conditional("DEBUG")]
        private void BindingDebug()
        {
            process = Process.Start(@"E:\temp\DebugBinding.txt");
        }

This method must be parameterless and void, its mission is to lunch a process that opens the log file E:\temp\DebugBinding.txt.

#define DEBUG
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Diagnostics;
using System.Configuration;
using System.IO;

namespace WpfApplication
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }
        //Define a process to open the log file when the window1 is lunched
        Process process;
        //This method is triggered only if the DEBUG variable is defined
        [Conditional("DEBUG")]
        private void BindingDebug()
        {
            process = Process.Start(@"E:\temp\DebugBinding.txt");
        }
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            BindingDebug();
        }
    }
}

Now, if we try to test this peace of XAML

        <TextBlock Padding="2" Name="FirstTextBlock" Text="My first text block" Grid.RowSpan="2" />
        <TextBlock Padding="2"
                   Text="{Binding ElementName=FirstTextBlock,
            Path=Txet,Mode=OneWay"
                   Grid.Row="1"
                   Height="53"
                   VerticalAlignment="Top" />

As a result we receive the data binding error as illustrated below:

4.gif

If we try now to add another text bloc control with another binding anomaly

<TextBlock Padding="2"
Text="{Binding ElementName=FirrstTextBlock,
Path=Text,Mode=OneWay}"
Grid.Row="1"
Height="53"
VerticalAlignment="Top" />

Then we try to restart the application again

5.gif

All the binding anomalies are listed within the log file.

More details on the binding debuging

It is possible to provide more details on a given binding process by exposing it step by step from the beginning to the end. First, we begin by mapping the namespace (xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase") within the XAML in order to profit of the PresentationTraceSources.TraceLevel attached property which could set to one of those three levels:
  1. Low
  2. Medium
  3. High
Those values enable to expose the binding steps respectively form the less detailed to the most detailed.

<Window x:Class="WpfApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="Window1" Height="300" Width="300">

Then we locate the bound property origin of the problem and we add this attached property PresentationTraceSources.TraceLevel to the Binding markup extension at the end of this last one as follow:

<TextBlock Padding="2"
           Text="{Binding ElementName=FirstTextBlock,
    Path=Txet,Mode=OneWay,diagnostics:PresentationTraceSources.TraceLevel=High}"
           Grid.Row="1"
           Height="53"
           VerticalAlignment="Top" />

Afterward, we comment the switches tag within the configuration file as below

        </sources>
        <trace autoflush="true" indentsize="4"></trace>
        <sharedListeners>
          <add name="textListener"
          type="System.Diagnostics.TextWriterTraceListener"
          initializeData="E:\temp\DebugBinding.txt" />
        </sharedListeners>
        <!--<switches>
        <add name="SourceSwitch" value="2" />
        </switches>-->
      </system.diagnostics>

And we lunch the application then the debugging information will be represented within the log file as follow

System.Windows.Data Warning: 49 : Created BindingExpression (hash=10261382) for Binding (hash=22597652)
System.Windows.Data Warning: 51 : Path: 'Txet'
System.Windows.Data Warning: 54 : BindingExpression (hash=10261382): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 55 : BindingExpression (hash=10261382): Attach to System.Windows.Controls.TextBlock.Text (hash=59109011)
System.Windows.Data Warning: 60 : BindingExpression (hash=10261382): Resolving source
System.Windows.Data Warning: 63 : BindingExpression (hash=10261382): Found data context element: <null> (OK)
System.Windows.Data Warning: 67 : Lookup name FirstTextBlock: queried TextBlock (hash=59109011)
System.Windows.Data Warning: 71 : BindingExpression (hash=10261382): Activate with root item TextBlock (hash=42659827)
System.Windows.Data Warning: 103 : BindingExpression (hash=10261382): At level 0 - for TextBlock.Txet found accessor <null>
System.Windows.Data Error: 36 : BindingExpression path error: 'Txet' property not found on 'object' ''TextBlock' (Name='FirstTextBlock')'. BindingExpression:Path=Txet; DataItem='TextBlock' (Name='FirstTextBlock'); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Warning: 73 : BindingExpression (hash=10261382): TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 82 : BindingExpression (hash=10261382): TransferValue - using fallback/default value ''
System.Windows.Data Warning: 83 : BindingExpression (hash=10261382): TransferValue - using final value ''

If we try to replace the High value of the PresentationTraceSources.TraceLevel attached property with the Medium one then the log will be as follow:

System.Windows.Data Warning: 55 : BindingExpression (hash=10261382): Attach to System.Windows.Controls.TextBlock.Text (hash=59109011)
System.Windows.Data Warning: 60 : BindingExpression (hash=10261382): Resolving source
System.Windows.Data Warning: 63 : BindingExpression (hash=10261382): Found data context element: <null> (OK)
System.Windows.Data Warning: 67 : Lookup name FirstTextBlock: queried TextBlock (hash=59109011)
System.Windows.Data Warning: 71 : BindingExpression (hash=10261382): Activate with root item TextBlock (hash=42659827)
System.Windows.Data Warning: 103 : BindingExpression (hash=10261382): At level 0 - for TextBlock.Txet found accessor <null>
System.Windows.Data Error: 36 : BindingExpression path error: 'Txet' property not found on 'object' ''TextBlock' (Name='FirstTextBlock')'. BindingExpression:Path=Txet; DataItem='TextBlock' (Name='FirstTextBlock'); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Warning: 73 : BindingExpression (hash=10261382): TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 82 : BindingExpression (hash=10261382): TransferValue - using fallback/default value ''
System.Windows.Data Warning: 83 : BindingExpression (hash=10261382): TransferValue - using final value ''

Finally, if we set Low as value, the output will be as below:

System.Windows.Data Warning: 55 : BindingExpression (hash=10261382): Attach to System.Windows.Controls.TextBlock.Text (hash=59109011)
System.Windows.Data Warning: 60 : BindingExpression (hash=10261382): Resolving source
System.Windows.Data Warning: 63 : BindingExpression (hash=10261382): Found data context element: <null> (OK)
System.Windows.Data Warning: 67 : Lookup name FirstTextBlock: queried TextBlock (hash=59109011)
System.Windows.Data Warning: 71 : BindingExpression (hash=10261382): Activate with root item TextBlock (hash=42659827)
System.Windows.Data Error: 36 : BindingExpression path error: 'Txet' property not found on 'object' ''TextBlock' (Name='FirstTextBlock')'. BindingExpression:Path=Txet; DataItem='TextBlock' (Name='FirstTextBlock'); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

As we can see, there are more details on data binding at this level.

Good DotNeting!!!