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
The second text block is not binding as expected. If we try to have a look on the windows output
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
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="textListener" type="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:
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
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:
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!!!