Preface and Thanks
I am a .NET programmer, but a busy one. I do VB.NET and C#, ASP.NET / WinForms / WPF / WCF, Flash, Silverlight, the lot. Basically, I keep my toe in. But when I started writing this article series, I naturally chose my favourite language (which happens to be C#). I since got an email from an individual who requested that I publish this series with source code in VB.NET and C#. I simply stated I didn't have time. So this individual (Robert Ranck) volunteered to help out, and do the translation to VB.NET, based on my original C# projects.
So for that and the subsequent VB.NET projects that you will find here, I ask you to thank Robert Ranck. Cheers Robert, your contributions will surely make this series more open to all .NET developers.
And thanks also go out to Karl Shifflett (A.K.A. the blog/article machine, also known as the Molenator) for answering my dumb VB .NET questions. And I'd also like to mention that Karl has just started a more advanced series of WPF articles (which at present will be in VB.NET, but will hopefully appear in C# as well). Karl's new series is excellent, and I urge you all to encourage Karl on this series. It's not easy obligating oneself to write an entire series in one language, let alone two. Karl's first article is located right here, have a look for yourself. Personally, I love it.
Introduction
This article is the fifth in my series of beginner's articles for WPF. In this article, we will discuss databinding. The proposed schedule for this series will still be roughly as follows:
In this article, I'm aiming to cover a brief introduction into the following:
I will not be covering the following collection based binding areas, so if you want to know more about these, you'll have to do a bit of extra research using the links provided (see I'm 1/2 OK, at least I researched the correct links for you):
Databinding is actually not that new (OK, how it's done in WPF is new), but we have had binding in ASP.NET and WinForms for some time now. WPF has borrowed from both of these to create a really, really good binding framework. But what is this binding stuff, for those of you have never heard of it.
Well, basically, binding allows UI Elements to obtain their data either from other UI Elements or business logic objects/classes. So in theory, it's very simply, we have a source that provides a value, and we have a target that wants to use the source's value, and we kind of glue them together using binding.
A typical binding arrangement is as follows:
Typically, each binding has these four components: a binding target object, a target property, a binding source, and a path to the value in the binding source to use.
The target property must be a Dependency Property (which I've just covered). Most UIElement properties are Dependency Properties, and most Dependency Properties, except read-only ones, support data binding by default.
That's a very simplified version of what's going on in binding.
Of course, to facilitate these binding operations, there are many separate considerations and bits of syntax that need to be considered. In the following sub sections, you will look at some (no, not all, I'd be here all year) of the binding syntax and ideas/approaches to creating happy bindings.
There is one important thing to know before we get into the ins and outs of Databinding, and that is the DataContext
property that every FrameworkElement
has. DataContext
is a concept that allows elements to inherit information from their parent elements about the data source that is used for binding, as well as other characteristics of the binding, such as the path. DataContext
can be set directly to a Common Language Runtime (CLR) object, with the bindings evaluating to properties of that object. Alternatively, you can set the data context to a DataSourceProvider
object.
This Dependency Property inherits property values. If there are child elements without other values for DataContext
established through local values or styles, then the property system will set the value to be the DataContext
value of the nearest parent element with this value assigned.
In XAML, DataContext
is most typically set as a Binding declaration. You can use either property element syntax or attribute syntax. And is normally set something like this:
<Window.Resources>
<src:LeagueList x:Key="MyList" />
...
...
</Window.Resources>
...
...
<DockPanel DataContext="{Binding Source={StaticResource MyList}}">
You can also use code to set the DataContext
, simply by using the <someElement>.DataContext = <someValue>
format.
Another thing to note is that if some object that inherits from a parent's DataContext
omits which fields it will use to bind to, such as:
<MenuItem Tag="{Binding}">
This means that the entire object that is used as its parent's DataContext
will be used to assign to the Tag
property.
Before we can proceed onto looking at the nitty griity of databinding, there are several key areas which we need to cover first. Namely:
- Direction of the data flow
- What triggers source updates
So we'll just spend a little bit of time on these two subjects:
Direction of the Data Flow
As shown above in the The General Idea Behind Databinding section, flow of binding could be two-way. There are several possibilities that can be configured when using databinding. These are controlled using the Binding.Mode
values:
OneWay
binding causes changes to the source property to automatically update the target property, but changes to the target property are not propagated back to the source property. This type of binding is appropriate if the control being bound is implicitly read-only.
TwoWay
binding causes changes to either the source property or the target property to automatically update the other. This type of binding is appropriate for editable forms or other fully-interactive UI scenarios.
OneTime
causes the source property to initialize the target property, but subsequent changes do not propagate. This means that if the data context undergoes a change or the object in the data context changes, then the change is reflected in the target property. This type of binding is appropriate if you are using data where either a snapshot of the current state is appropriate to use or the data is truly static.
OneWayToSource
is the reverse of OneWay
binding; it updates the source property when the target property changes.
Default
causes the default Mode
value of the target property to be used.
Use the Binding.Mode
property to specify the direction of the data flow. To detect source changes in one-way or two-way bindings, the source must implement a suitable property change notification mechanism such as INotifyPropertyChanged
. For an example, see: How to: Implement Property Change Notification.
Change notification is such an important lesson to learn in databinding that we need to look at it right now. So, let's have a look at an example of using the interface INotifyPropertyChanged
.
To support OneWay
or TwoWay
binding to enable your binding target properties to automatically reflect the dynamic changes of the binding source, your class needs to provide the proper property changed notifications; this is where INotifyPropertyChanged
is used.
using System.ComponentModel;
namespace SDKSample
{
public class Person : INotifyPropertyChanged
{
private string name;
public event PropertyChangedEventHandler PropertyChanged;
public Person()
{
}
public Person(string value)
{
this.name = value;
}
public string PersonName
{
get { return name; }
set
{
name = value;
OnPropertyChanged("PersonName");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
And here's the VB.NET version:
Imports System.ComponentModel
Public Class Person
Implements INotifyPropertyChanged
Private personName As String
Sub New()
End Sub
Sub New(ByVal Name As String)
Me.personName = Name
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler
Implements INotifyPropertyChanged.PropertyChanged
Public Property Name() As String
Get
Return personName
End Get
Set(ByVal value As String)
personName = value
OnPropertyChanged("Name")
End Set
End Property
Protected Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
End Class
To implement INotifyPropertyChanged
, you need to declare the PropertyChanged
event and create the OnPropertyChanged
method. Then, for each property you want change notifications for, you call OnPropertyChanged
whenever the property is updated.
I can not tell you how important the INotifyPropertyChanged
interface is, but believe me, it's very, very important, and if you plan to use Binding in WPF, just get used to using the INotifyPropertyChanged
interface.
What Triggers Source Updates
Bindings that are TwoWay
or OneWayToSource
listen for changes in the target property and propagate them back to the source. This is known as updating the source. For example, you may edit the text of a TextBox
to change the underlying source value. As described in the last section, the direction of the data flow is determined by the value of the Binding.Mode
property of the binding.
However, does your source value get updated while you are editing the text or after you finish editing the text and point your mouse away from the TextBox
? The Binding.UpdateSourceTrigger
property of the binding determines what triggers the update of the source. The options available are as follows:
The following table provides an example scenario for each Binding.UpdateSourceTrigger
value using the TextBox
as an example:
UpdateSourceTrigger value |
When the Source Value Gets Updated |
LostFocus (default for TextBox.Text ) |
When the TextBox control loses focus |
PropertyChanged |
As you type into the TextBox |
Explicit |
When the application calls UpdateSource |
There are many properties that may be used within the Binding
class, as such, I will not have time to cover all of them, though I shall attempt to go through the most common buts of syntax. As most binding will usually be set in XAML, I will be concentrating on the XAML syntax, though it should be noted that anything that can be done in XAML can also be done in C#/VB.NET code behind.
OK, so let's have a look at the basic syntax (we will cover more advanced stuff in the sections below).
The most basic form of binding is to create a binding that binds to a value of an existing element (this is covered in more detail below). I just wanted to introduce the syntax and go back and show you how to do the Binding.Mode
and Binding.UpdateSourceTrigger
stuff first.
So here is probably one of the simplest Bindings that you will see. This example has two buttons: the first button (btnSource
) has a Yellow Background
property. The second button uses the first button (btnSource
) as the source for a Binding
where the first button (btnSource
) Background
value is being used to set the second button's Background
.
<Button x:Name="btnSource" Background="Yellow"
Width="150" Height="30">Yellow BUtton</Button>
<Button Background="{Binding ElementName=btnSource, Path=Background}"
Width="150"
Height="30">I am bound to be Yellow Background</Button>
That's fairly simple, right? But I just wanted to go back and have a quick look at how we could also use the Binding.Mode
and Binding.UpdateSourceTrigger
properties within the binding syntax.
Well, as it turns out, it's fairly easy. We just add the extra property and its desired value into the binding expression, such as:
<TextBox x:Name="txtSource" Width="150" Height="30"/>
<TextBox Width="150" Height="30" Text="{Binding ElementName=txtSource,
Path=Text, Mode=TwoWay, UpdateSourceTrigger=LostFocus }"/>
An Important Note
Recall from Part 2 that I mentioned that Binding
was a markup MarkupExtension
. As such, the XAML parser knows how to treat the { } sections. But really, this is just shorthand, that can (if you prefer) be expressed using the longer, more verbose, syntax as shown below:
<Button Margin="10,0,0,0" Content="Im bound to btnSource, using long Binding syntax">
<Button.Background>
<Binding ElementName="btnSource" Path="Background"/>
</Button.Background>
</Button>
This is a decision you will have to make yourself; me, personally, I prefer the { } syntax, though you don't get any intellisense help within Visual Studio if you do use the {} syntax.
When you set out to set up a Binding, there are several different things you need to consider:
- What property do I want to bind to?
- What property should we bind from?
- Does it need to be
OneWay
or TwoWay
etc.? If it needs to be a TwoWay
/OneWayToSource
Binding, was the source property a Dependency Property? It has to be to carry out a TwoWay
/OneWayToSource
Binding.
Once you know or have considered all this, it's really as easy as ABC. As part of the demo solution, you will find a project entitled "BindingToUIElements" which, when run, will look like the following:
This simple demo application shows three different Bindings going on. I will briefly discuss each of these now.
1. Simple Element Binding (Default Mode)
This simple example uses the first button's Background
as the source value for the other two Button
s' Background
.
The code for which is as follows:
<!---->
<Label Content="Simple Element Binding"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Margin="10,10,10,10" Background="Gainsboro">
<Label Content="Simple Element Binding"/>
<Button x:Name="btnSource" Margin="10,0,0,0"
Background="Pink" Content="Im btnSource"/>
<Button Margin="10,0,0,0" Background="{Binding ElementName=btnSource,
Path=Background}" Content="Im bound to btnSource"/>
<Button Margin="10,0,0,0"
Content="Im bound to btnSource, using long Binding syntax">
<Button.Background>
<Binding ElementName="btnSource" Path="Background"/>
</Button.Background>
</Button>
</StackPanel>
2. More Elaborate Binding (Default Mode)
This simple example uses the SelectedItem.Content
of a ComboBox
as the source for a Binding. Where the Background
of a Button
is changed dependant on the SelectedItem.Content
of the ComboBox
.
The code for which is as follows:
<Label Content="More Elaborate Binding"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Margin="10,10,10,10" Background="Gainsboro">
<Label Content="Choose a color"/>
<ComboBox Name="cmbColor" SelectedIndex="0">
<ComboBoxItem>Green</ComboBoxItem>
<ComboBoxItem>Blue</ComboBoxItem>
<ComboBoxItem>Red</ComboBoxItem>
</ComboBox>
<Button Margin="10,0,0,0" Background="{Binding ElementName=cmbColor,
Path=SelectedItem.Content}" Content="Im bound to btnSource"/>
</StackPanel>
3. TwoWay Binding Using UpdateSourceTrigger
This simple example uses two TextBox
es, where there is a TwoWay
Binding.Mode
applied, and the Binding.UpdateSourceTrigger
is set to PropertyChanged
, which means that the source of the Binding will be updated when the second TextBox
's value changes.
The code for which is as follows:
<!---->
<Label Content="Using UpdateSourceTrigger/Mode"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Margin="10,10,10,10" Background="Gainsboro">
<TextBlock TextWrapping="Wrap"
Text="This uses TwoWay Binding and
UpdateSourceTrigger=PropertyChanged.Type
in one textbox then the other,
and see them update each other"
Width="400"/>
<TextBox x:Name="txtSource" Width="50"
Height="25" Margin="5,0,0,0"/>
<TextBox Width="50" Height="25" Margin="5,0,0,0"
Text="{Binding ElementName=txtSource,
Path=Text, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged }"/>
</StackPanel>
XML is used a lot these days, both for configuration information and data exchange, and indeed even for UI design. Remember, XAML is an XML derivative.
But that's not all we can use XML data for. We can in fact bind to XML data. This is fairly easy to do in XAML. We can either have the XAML hold the XML data (though this is probably not the norm), or use external XML files.
Either way, the normal proceeding is to use an XmlDataProvider
within the XAML/code. Like I said earlier, I think most Bindings will be done in XAML, and I will stick to that.
As part of the demo solution, you will find a project entitled "BindingToXML" which, when run, will look like the following:
The top two sections of this demo app use XAML held XML data, and the bottom two use an external XML file.
XmlDataProvider Using XAML Held Data (Inline XML Data)
It is entirely possible to hold all the XML data within the XAML file, and use this data as a source for Binding. Let's see an example of this:
<Window x:Class="BindingToXML.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStartupLocation="CenterScreen"
Title="Window1" Height="800" Width="800">
<Window.Resources>
-->
<XmlDataProvider x:Key="xmlData" XPath="Films">
<x:XData>
<Films xmlns="">
<Film Type="Horror" Year="1987">
<Director>Sam Raimi</Director>
<Title>Evil Dead II</Title>
</Film>
<Film Type="Murder" Year="1991">
<Director>Jonathan Demme</Director>
<Title>Silence Of The Lambs</Title>
</Film>
<Film Type="Sc" Year="1979">
<Director>Ridley Scott</Director>
<Title>Alien</Title>
</Film>
</Films>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<ScrollViewer>
<StackPanel Orientation="Vertical">
-->
<Label Content="Show all Films (using inline XML data)"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal" Margin="10,10,10,10"
Background="Gainsboro">
<ListBox ItemsSource="{Binding Source={StaticResource xmlData},
XPath=Film/Title}"/>
</StackPanel>
-->
<Label Content="Show Only Films After 1991 (using inline XML data)"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal" Margin="10,10,10,10"
Background="Gainsboro">
<ListBox ItemsSource="{Binding Source={StaticResource xmlData},
XPath=*[@Year>\=1991]}"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
It can be seen from this example that we use an inline (in the XAML) XML dataset for the XmlDataProvider
. And that we use the XmlDataProvider
as the BindingSource
for the ListBox
. In this example, the first ListBox
shows all Films/Titles as we are just fetching the Film/Title nodeset using the Binding.XPath=Film/Title
, so we get all Titles shown.
The second ListBox
is a bit fussier, and uses a bit of XPath notation to traverse the attribute axis and only fetches those nodes that have Year > 1991, so we get less nodes returned.
XmlDataProvider Using External XML File
As I say, it's going to be more common to use external XML files with the XmlDataProvider
, which can be done as follows. Where the XmlDataProvider
's Source
property is set to the external XML file.
<XmlDataProvider x:Key="xmlDataSeperateFile"
XPath="Resteraunts" Source="XMLFile1.xml">
</XmlDataProvider>
Using this arrangement is much the same as we saw before where we can use XPath to fetch all the nodes or use XPath to only match those nodes where the attribute matches the requirements. In this example, the second ListBox
only shows "Mexican" restaurants from the XML file, using the following XPath:
<ListBox ItemsSource="{Binding Source={StaticResource
xmlDataSeperateFile}, XPath=*[@Type\=\'Mexican\']}"/>
And here is the full example:
<Window x:Class="BindingToXML.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStartupLocation="CenterScreen"
Title="Window1" Height="800" Width="800">
<Window.Resources>
-->
<XmlDataProvider x:Key="xmlDataSeperateFile" XPath="Resteraunts"
Source="XMLFile1.xml">
</XmlDataProvider>
</Window.Resources>
<ScrollViewer>
<StackPanel Orientation="Vertical">
-->
<Label Content="Show all Resteraunts (using seperate XML data)"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal" Margin="10,10,10,10"
Background="Gainsboro">
<ListBox ItemsSource="{Binding Source=
{StaticResource xmlDataSeperateFile},
XPath=Resteraunt/Name}"/>
</StackPanel>
-->
<Label Content="Show Only Mexican Resteraunts (using inline XML data)"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal" Margin="10,10,10,10"
Background="Gainsboro">
<ListBox ItemsSource="{Binding Source={StaticResource xmlDataSeperateFile},
XPath=*[@Type\=\'Mexican\']}"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
And here's the associated XML file (should you be curious about its structure):
="1.0" ="utf-8"
<Resteraunts xmlns="">
<Resteraunt Type="Meat">
<Name>The MeatHouse</Name>
<Phone>01237 78516</Phone>
<email>yada@here.com</email>
</Resteraunt>
<Resteraunt Type="Veggie">
<Name>VegHead</Name>
<Phone>99999</Phone>
<email>mmm-vegies@here.com</email>
</Resteraunt>
<Resteraunt Type="Mexican">
<Name>Mexican't (El Mariachi)</Name>
<Phone>464654654</Phone>
<email>mex@here.com</email>
</Resteraunt>
</Resteraunts>
Note: Using XML to provide Binding values is fine, but don't expect that you will be able to update XML simply by using a Binding.Mode
set to TwoWay
. That won't work. XML data binding is a simple one-way/not-updatable type of arrangement.
Binding to XLINQ
Although I'm not going to go into this, Beatriz The Binding Queen Costa has a good blog entry right here if you are curious.
Typically, WPF development will more than likely involve binding to an entire collection at some point. Now, this is very, very easy to do in WPF. As with most things, there are many ways to do this. I'll outline two possible ways, but of course, there will be more. I just like these ways, that's all.
One important thing to always keep in mind is change notification. Recall, we addressed that for individual classes by using the INotifyPropertyChanged
interface. But what about Collections that will hold objects, what should we do about them?
Well, as luck would have it, these days, there is a nice ObserverableCollection
that fills this spot quite nicely. This collection takes care of notifying the UI every time an element in the collection is added/removed. We still need to make sure that each of the held objects does its own change notification using the INotifyPropertyChanged
interface. But by using ObserverableCollection
and classes that implement INotifyPropertyChanged
, we are sitting pretty, no change will pass us by.
As part of the demo solution, you will find a project entitled "BindingToCollections" which, when run, will look like the following:
So binding to such a collection becomes a snap. Here are two possible ways to do this using ListBox
es:
Binding to Collections in Code-Behind
The first ListBox
has its ItemSource
set in code-behind as follows:
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.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace BindingToCollections
{
public partial class Window1 : Window
{
private People people = new People();
public Window1()
{
InitializeComponent();
people.Add(new Person { PersonName = "Judge Mental" });
people.Add(new Person { PersonName = "Office Rocker" });
people.Add(new Person { PersonName = "Sir Real" });
this.lstBox1.ItemsSource = people;
}
}
}
And in VB.NET:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes
Imports System.Collections.ObjectModel
Partial Public Class Window1
Inherits Window
Private people As New People()
Public Sub New()
InitializeComponent()
Dim _Person As New Person
_Person.PersonName = "Judge Mental"
people.Add(_Person)
_Person.PersonName = "Office Rocker"
people.Add(_Person)
_Person.PersonName = "Sir Real"
people.Add(_Person)
lstBox1.ItemsSource = people
End Sub
End Class
And the matching XAML for this Binding is as follows:
<Window x:Class="BindingToCollections.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingToCollections"
WindowStartupLocation="CenterScreen"
Title="Window1" Height="800" Width="800">
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
-->
<StackPanel Orientation="Vertical">
<Label Content="ListBox Source Set In Code Behind"
Margin="5,0,0,0" FontSize="14"
FontWeight="Bold" />
<ListBox x:Name="lstBox1"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
Binding to Collections in XAML
The second ListBox
has its ItemSource
set in XAML as follows:
<Window x:Class="BindingToCollections.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingToCollections"
WindowStartupLocation="CenterScreen"
Title="Window1" Height="800"
Width="800">
<Window.Resources>
<local:People x:Key="people">
<local:Person PersonName="Freddy Star"/>
<local:Person PersonName="Stick Head"/>
</local:People>
</Window.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
-->
<StackPanel Orientation="Vertical">
<Label Content="ListBox Source Set By Using Resources"
Margin="5,0,0,0" FontSize="14"
FontWeight="Bold" />
<ListBox x:Name="lstBox2"
ItemsSource="{Binding Source={StaticResource people}}"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
You can see that we have an instance of the People
object directly in the XAML, within a resources section, and that the declared People
object has several children of type Person
. This is all thanks to the XAML parser which knows that children should be added using the Add()
method of the ObserverableCollection
.
I should point out that these examples are merely demonstrating how to Bind to collections. In a production system, the collection would probably be part of a BAL layer/ or maybe part of a Model within a MVC/MVVM pattern. I am going for quick and dirty to get the point across.
One other thing that I would like to bring your attention to is the rendering of the items within the ListBox
. See how they simply show "BindingToCollection.Persons" as plain text.
This is obviously not the desired effect, but happens because WPF does not know what properties to show and how they should be shown for a Person
object. This will be the subject of my next article on Templates. I won't write any more about this, but just know that we can change the way a data item looks using a DataTemplate
. If you really can't wait, you can have a look at these links:
Note: As I stated earlier, I do not cover grouping/sorting/filtering etc., and if you really want to know more about these, please use the links provided at the start of this article.
Imagine a situation where we have a bound data value that we wish to format in some way, say by using a short date format instead of a long date format. Up until now, we have simply used the raw Binding value. This wouldn't be possible, and we would have to make sure the bound value has what we want to display. Luckily, WPF has a trick up its sleeve. We can use a class that implements the IValueConverter
interface to provide a new value for the binding.
ValueConverters are like the sprintf of the WPF world. You can use a ValueConverter to literally provide a new object to a Binding. This may be an object of the same type as the original object, or a totally new object. For example, in WPF, there is a principle of a Freezable Object which is an immutable object. If you try and animate a Frozen object, you will have problems. I have in the past circumnavigated this issue with using a Clone ValueConverter to provide a cloned object to a Binding which is then used in an animation.
But more typically, you may use ValueConverters for small formatting changes.
ValueConverters sit between the source value and the bound target property, and their sole purpose is to take a value and supply a different value.
The IValueConverter
interface contains the following methods that must be implemented:
object Convert(object value, Type targetType, object parameter, CultureInfo culture)
Which is used to convert from the source object into the new value which will be used by the target
object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
Which is used to convert from the target object back to the source. This will be used where a Bindings Binding.Mode
has been set to TwoWay
or OneWayToSource
. More often than not, this will not be used, and will simply throw an exception.
So how do we use these ValueConverters in our code? Well, quite simply, we use the normal Bindings expression, but we also state which converter to use for the Binding.Converter
property. Let's see an example, shall we?
As part of the demo solution, you will find a project entitled "ValueConverters" which, when run, will look like the following:
This small example actually uses two ValueConverters, the top one converts from words to a Brush
that is used to color a Rectangle
. The second value converter uses an explicitly set DataContext
in the code-behind where two Label
s have their DataContext
set to a new DateTime
. Let's see the code:
First, the XAML:
<Window x:Class="ValueConverters.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ValueConverters"
WindowStartupLocation="CenterScreen"
Title="Window1" Height="800" Width="800">
<Window.Resources>
<local:WordToColorConverter x:Key="wordToColorConv"/>
<local:DateConverter x:Key="dateConv"/>
</Window.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
-->
<Label Content="Using Vaue Converter To Convert Text To Fill Color"
Margin="5,0,0,0" FontSize="14"
FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Margin="10,10,10,10" Background="Gainsboro">
<TextBlock TextWrapping="Wrap"
Text="Using Vaue Converter. Type 'hot' or 'cold'
into the textbox and watch the rectangle change color"
Width="400"/>
<TextBox x:Name="txtSource" Width="50"
Height="25" Margin="5,0,0,0"/>
<Rectangle Width="50"
Height="25" Margin="5,0,0,0"
Fill="{Binding ElementName=txtSource, Path=Text,
Converter={StaticResource wordToColorConv}}" />
</StackPanel>
-->
<Label Content="Using Vaue Converter To Convert Date To Short Date"
Margin="5,0,0,0" FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Vertical"
Margin="10,10,10,10" Background="Gainsboro">
<StackPanel Orientation="Horizontal">
<Label Content="LongDate"/>
<Label x:Name="lblLongDate" Content="{Binding Path=Now}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="ShortDate thanks to value converter"/>
<Label x:Name="lblShortDate" Content="{Binding Path=Now,
Converter={StaticResource dateConv}}"/>
</StackPanel>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
And here is the code-behind for this Window
:
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.Navigation;
using System.Windows.Shapes;
namespace ValueConverters
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
lblLongDate.DataContext = new DateTime();
lblShortDate.DataContext = new DateTime();
}
}
}
And finally, the converters:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows.Media;
using System.Globalization;
namespace ValueConverters
{
[ValueConversion(typeof(String), typeof(SolidColorBrush))]
public class WordToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
string boundWord = value as string;
SolidColorBrush returnBrush = null;
switch (boundWord.ToLower().Trim())
{
case "hot":
returnBrush = new SolidColorBrush(Colors.Red);
break;
case "cold":
returnBrush = new SolidColorBrush(Colors.Green);
break;
default:
returnBrush = new SolidColorBrush(Colors.Yellow);
break;
}
return returnBrush;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new Exception("Cant convert back");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows.Media;
using System.Globalization;
namespace ValueConverters
{
[ValueConversion(typeof(DateTime), typeof(String))]
public class DateConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
DateTime date = (DateTime)value;
return date.ToShortDateString();
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
string strValue = value.ToString();
DateTime resultDateTime;
if (DateTime.TryParse(strValue, out resultDateTime))
{
return resultDateTime;
}
return value;
}
}
}
And here are the VB.NET versions; code-behind:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes
Partial Public Class Window1
Inherits Window
Public Sub New()
InitializeComponent()
lblLongDate.DataContext = New DateTime()
lblShortDate.DataContext = New DateTime()
End Sub
End Class
VB.NET converters:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows.Data
Imports System.Windows.Media
Imports System.Globalization
<ValueConversion(GetType(String), GetType(SolidColorBrush))> _
Public Class WordToColorConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type,
ByVal parameter As Object, ByVal culture As CultureInfo)
As Object Implements IValueConverter.Convert
Dim boundWord As String = TryCast(value, String)
Dim returnBrush As SolidColorBrush = Nothing
Select Case boundWord.ToLower().Trim()
Case "hot"
returnBrush = New SolidColorBrush(Colors.Red)
Exit Select
Case "cold"
returnBrush = New SolidColorBrush(Colors.Green)
Exit Select
Case Else
returnBrush = New SolidColorBrush(Colors.Yellow)
Exit Select
End Select
Return returnBrush
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type,
ByVal parameter As Object, ByVal culture As CultureInfo)
As Object Implements IValueConverter.ConvertBack
Throw New Exception("Cant convert back")
End Function
End Class
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows.Data
Imports System.Windows.Media
Imports System.Globalization
<ValueConversion(GetType(DateTime), GetType(String))> _
Public Class DateConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type,
ByVal parameter As Object, ByVal culture As CultureInfo)
As Object Implements IValueConverter.Convert
Dim [date] As DateTime = DirectCast(value, DateTime)
Return [date].ToShortDateString()
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type,
ByVal parameter As Object, ByVal culture As CultureInfo)
As Object Implements IValueConverter.ConvertBack
Dim strValue As String = value.ToString()
Dim resultDateTime As DateTime
If DateTime.TryParse(strValue, resultDateTime) Then
Return resultDateTime
End If
Return value
End Function
End Class
Note: I did not cover MultiBinding
because Josh Smith does such a great job of covering this in his excellent article, that I didn't feel there was any need to write more about this.
So far we've looked at binding to values/other elements/XML etc., and seen how we can convert values. Which is good. But one of the main concerns of using Binding is validating what data is entered, especially when we have a Binding.Mode
property set to TwoWay
or OneWayToSource
. We really need to make sure that we only allow valid data to be sent back to a source object. Not only is it the right thing to do, but it ensures that we don't send back rubbish data to our persistence store (database/file etc/). So how are we going to do that? We need some sort of validation applied to our Bindings.
Luckily, Microsoft knew about this, and have equipped us with the necessary ammunition to create good, well-behaved, validated Databindings. There are three main ways that validation may be performed, and we shall have a look at each of them below.
As part of the demo solution, you will find a project entitled "Validation" which, when run, will look like the following:
Visual Validation Changes
As part of the attached "Validation" project, I have included some Styles. Which is an area that we have not yet covered. There is no way out of this. The things you need to know for the moment are that a TextBox
entry that is invalid will cause the associated TextBox
to have its visual appearance changed by the use of a Style or a Template. And the other thing is how the TextBox.ToolTip
is showing the correct validation message. Well, this is also done via some clever binding, which is as follows:
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
As I say, I don't want to get too bogged down with Styles/Templates as they are the discussion of the next article, but I think that this one element deserves a mention.
Exception Based Validation
Probably the easiest way to use validation in Binding is to use the Exception based validation rule. Which basically means if an Exception is seen while trying to update a bound property, we can use the Exception to inform the user. Typically, this would be done by showing a message on a tooltip and changing the TextBox
visual as previously discussed. If we have a small test class to bind to as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Validation
{
public class TestClass
{
public int Age { get; set; }
public DateTime StartDate { get; set; }
public TestClass()
{
StartDate = DateTime.Now;
}
}
}
And here is the VB.NET version:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Public Class TestClass
Private m_Age As Integer = 1
Private m_StartDate As DateTime
Public Property Age() As Integer
Get
Return m_Age
End Get
Set(ByVal value As Integer)
m_Age = value
End Set
End Property
Public Property StartDate() As DateTime
Get
Return m_StartDate
End Get
Set(ByVal value As DateTime)
m_StartDate = value
End Set
End Property
Public Sub New()
m_StartDate = DateTime.Now
End Sub
End Class
And we set the Binding up in code-behind as follows (using the DataContext
):
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.Navigation;
using System.Windows.Shapes;
namespace Validation
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new TestClass();
txtIDataErrorInfoAge.DataContext = new Person();
txtIDataErrorInfoName.DataContext = new Person();
}
}
}
And in VB.NET:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes
Partial Public Class Window1
Inherits Window
Public Sub New()
InitializeComponent()
Me.DataContext = New TestClass
txtIDataErrorInfoAge.DataContext = New Person
txtIDataErrorInfoName.DataContext = New Person
End Sub
End Class
Then, we are able to use the Exception based validation as follows in the XAML:
<Window x:Class="Validation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Validation"
Title="Window1" Height="800" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
-->
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
-->
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red"
FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
</Window.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
-->
<Label Content="Exception Based Validitaion" Margin="5,0,0,0"
FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal" Margin="10,10,10,10"
Background="Gainsboro">
<TextBlock TextWrapping="Wrap" Text="Exception Based Validitaion,
type an non integer value" Width="400"/>
<TextBox Name="txtException"
Style="{StaticResource textStyleTextBox}"
Width="120" Height="25"
Margin="5,0,0,0"
Text="{Binding Path=Age,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnExceptions=True}" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
The important thing to note is that Binding.ValidatesOnExceptions=True
means that the WPF inbuilt ExceptionValidationRule
will be used to create the appropriate validation message that will be shown in TextBox.Tooltip
.
Custom ValidationRules Based Validation
Custom ValidationRule
s is similar in principle to the inbuilt ExceptionValidationRule
, but this time, we are using our own ValidationRule
to create the appropriate validation messages. Here is an example of a custom ValidationRule
, where the value entered for the binding must be a DateTime
in the future.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Globalization;
namespace Validation
{
class FutureDateValidationRule : ValidationRule
{
public override ValidationResult Validate(object value,
CultureInfo cultureInfo)
{
DateTime date;
try
{
date = DateTime.Parse(value.ToString());
}
catch (FormatException)
{
return new ValidationResult(false,
"Value is not a valid date.");
}
if (DateTime.Now.Date > date)
{
return new ValidationResult(false,
"Please enter a date in the future.");
}
else
{
return ValidationResult.ValidResult;
}
}
}
}
And here is the VB.NET version:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows.Controls
Imports System.Globalization
Public Class FutureDateValidationRule
Inherits ValidationRule
Public Overloads Overrides Function Validate(ByVal value As Object,
ByVal cultureInfo As CultureInfo) As ValidationResult
Dim [date] As DateTime
Try
[date] = DateTime.Parse(value.ToString())
Catch generatedExceptionName As FormatException
Return New ValidationResult(False, "Value is not a valid date.")
End Try
If DateTime.Now.[Date] > [date] Then
Return New ValidationResult(False, "Please enter a date in the future.")
Else
Return ValidationResult.ValidResult
End If
End Function
End Class
And we set the Binding up in the code-behind as follows (using the DataContext
):
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.Navigation;
using System.Windows.Shapes;
namespace Validation
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new TestClass();
txtIDataErrorInfoAge.DataContext = new Person();
txtIDataErrorInfoName.DataContext = new Person();
}
}
}
And in VB.NET:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes
Partial Public Class Window1
Inherits Window
Public Sub New()
InitializeComponent()
Me.DataContext = New TestClass
txtIDataErrorInfoAge.DataContext = New Person
txtIDataErrorInfoName.DataContext = New Person
End Sub
End Class
Then we are able to use this custom FutureDateValidationRule
in the XAML:
<Window x:Class="Validation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Validation"
Title="Window1" Height="800" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
-->
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
-->
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red"
FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
</Window.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
-->
<Label Content="ValidationRule Based Validitaion" Margin="5,0,0,0"
FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Margin="10,10,10,10"
Background="Gainsboro">
<TextBlock TextWrapping="Wrap"
Text="ValidationRule Based Validitaion,
type a future date" Width="400"/>
<TextBox Name="txtStartDate"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textStyleTextBox}"
Width="150" Height="25"
Margin="5,0,0,0">
<TextBox.Text>
-->
<Binding Path="StartDate"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:FutureDateValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
The important thing to note here is that because we needed to add a new FutureDateValidationRule
to Binding.ValidationRules
, we needed to use the Property as Elements syntax.
.NET 3.5 Way Using IDataErrorInfo
With the release of .NET 3.5 came LINQ, oh and also a few improvements to WPF. One of which is a new interface called IDataErrorInfo
which shifted where some of the validation was performed. It moved it from separate validation classes back into the actual business object themselves.
Let's see an example of a simple class that implements the IDataErrorInfo
interface:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace Validation
{
public class Person : IDataErrorInfo
{
public int Age { get; set; }
public string Name { get; set; }
public Person()
{
this.Age = 0;
this.Name = "sacha";
}
#region IDataErrorInfo Members
public string Error
{
get
{
return null;
}
}
public string this[string name]
{
get
{
string result = null;
switch (name)
{
case "Age":
if (this.Age < 0 || this.Age > 150)
{
result = "Age must not be less than 0 or greater than 150.";
}
break;
case "Name":
if (this.Name == string.Empty)
{
result = "Name can't be empty";
}
if (this.Name.Length > 5)
{
result = "Name can't be more than 5 characters";
}
break;
}
return result;
}
}
#endregion
}
}
And here is the VB.NET version:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.ComponentModel
Public Class Person
Implements IDataErrorInfo
Private m_Age As Integer
Private m_Name As String
Public Property Age() As Integer
Get
Return m_Age
End Get
Set(ByVal value As Integer)
m_Age = value
End Set
End Property
Public Property Name() As String
Get
Return m_Name
End Get
Set(ByVal value As String)
m_Name = value
End Set
End Property
Public Sub New()
Me.Age = 0
Me.Name = "sacha"
End Sub
#Region "IDataErrorInfo Members"
Public ReadOnly Property [Error]() As String Implements _
System.ComponentModel.IDataErrorInfo.Error
Get
Return Nothing
End Get
End Property
Default Public ReadOnly Property Item(ByVal Name As String)
As String Implements System.ComponentModel.IDataErrorInfo.Item
Get
Dim result As String = Nothing
Select Case Name
Case "Age"
If Me.Age < 0 OrElse Me.Age > 150 Then
result = "Age must not be less than 0 or greater than 150."
End If
Exit Select
Case "Name"
If Me.Name = String.Empty Then
result = "Name can't be empty"
End If
If Me.Name.Length > 5 Then
result = "Name can't be more than 5 characters"
End If
Exit Select
End Select
Return result
End Get
End Property
#End Region
End Class
Basically, the interface allows us to validate the property that was changed using the public string this[string name]
syntax. The XAML is a little different this time, where the Binding no longer needs to use a seperate validation class, so we can simply use the shorthand XAML syntax, the one with the { } braces. Again, if you prefer the longer, more verbose syntax, you can do that also.
<Window x:Class="Validation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Validation"
Title="Window1" Height="800" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
-->
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
-->
<Label Content="IDataErrorInfo Based Validitaion"
Margin="5,0,0,0"
FontSize="14" FontWeight="Bold" />
<StackPanel Orientation="Horizontal"
Margin="10,10,10,10"
Background="Gainsboro">
<TextBlock TextWrapping="Wrap"
Text="IDataErrorInfo Based Validitaion,
type a number below 0 or above 150 " Width="400"/>
-->
<Label Content="Age"/>
<TextBox Name="txtIDataErrorInfoAge"
Style="{StaticResource textStyleTextBox}"
Width="60" Height="25"
Margin="5,0,0,0"
Text="{Binding Path=Age,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnExceptions=True,
ValidatesOnDataErrors=True}" />
-->
<Label Content="Name"/>
<TextBox Name="txtIDataErrorInfoName"
Style="{StaticResource textStyleTextBox}"
Width="60" Height="25"
Margin="5,0,0,0"
Text="{Binding Path=Name,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
Notice that this time we have set Binding.ValidatesOnDataErrors=True
; this means that the DataErrorValidationRule
will be included, which uses the IDataErrorInfo
implementation we discussed.
Warning: Beware of Tabs
I recently got a rather cool email from Karl Shifllet, warning of a bug with validation error disappearing when you use tabs in WPF. You should familiarise yourself with Karl's blog entry about this.
We're Done
There is more to Databinding than I have covered here; for example, I have not covered grouping/sorting/filtering etc., and there will be databinding points and syntax that I have missed. I leave these areas as an exercise for the reader. This article was meant as an introduction to Databinding; hope you enjoyed it. If you liked it, please vote for it and leave a comment, and maybe read the next article in this series. Thanks!
- MSDN: Databinding overview
- MSDN: Multibinding
- MSDN: Binding class
- Data Validation in .NET 3.5
- Bind to XML Data Using an XMLDataProvider and XPath Queries
- Beatriz "The Binding Queen" Costa: Blog
- Josh Smith: A Guided Tour of WPF - Part 3 (Data binding)
- 14/02/08: Initial release.