In the following class, the itemssource of a listbox should bind to the Interfaces property.
public class BaseClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private const string TYPE_TITLE = "Type";
private string _Type;
public string Type
{
get { return _Type; }
set { _Type = value; this.NotifyPropertyChanged(PropertyChanged, TYPE_TITLE); }
}
public ObservableCollection<string> Interfaces { get; set; }
public BaseClass()
{
Interfaces = new ObservableCollection<string>();
}
public void Reset()
{
_Type = null;
Interfaces.Clear();
}
}
In that list box the selected item should be able to edit as the inline edit scenario,
<DataTemplate x:Key="BaseClass_Interfaces_InlineEdit_Template">
<TextBox Text="{Binding Mode=TwoWay, Path=., NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}" TextChanged="TextBox_TextChanged"/>
</DataTemplate>
<DataTemplate x:Key="BaseClass_Interfaces_InlineView_Template">
<TextBlock Text="{Binding Path=., UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}" x:Key="BaseClass_Iterfaces_ItemStyle_Template">
<Setter Property="ContentTemplate" Value="{StaticResource BaseClass_Interfaces_InlineView_Template}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource BaseClass_Interfaces_InlineEdit_Template}" />
</Trigger>
</Style.Triggers>
</Style>
The ListBox has a container as a parent hierarchy which its DataContext property bind to an instance of BaseClass hence the ListBox could bind to the Interfaces property.
<ListBox Grid.Row="2" Grid.ColumnSpan="2" Margin="3" ItemsSource="{Binding Interfaces, Mode=TwoWay}" SelectionMode="Single"
ItemContainerStyle="{StaticResource ResourceKey=BaseClass_Iterfaces_ItemStyle_Template}" />
The list box before select any item
Editing the selected item
Another item select after edit and the changes doesn't affected
There are two problems :
The TextBox should have "Path=." otherwise the "Two-way binding requires Path or XPath." exception message received.
With consider the above problem, the ObservableCollection items never updated after text changed!!!!!!
I found the answer!
My wondering was about the text box which the text property changed but the changes does not propagated to the source, based on the link the binding mechanism works on the properties of sources in the other words the change of the properties monitors not the object itself.
The solution is a wrapper class around the string, i wrote this wrapper before for another primitive type (bool).
public class Wrapper<T>
{
public T Item { get; set; }
public Wrapper(T value = default(T))
{
Item = value;
}
public static implicit operator Wrapper<T>(T item)
{
return new Wrapper<T>(item);
}
public static implicit operator T(Wrapper<T> item)
{
if (null != item)
return item.Item;
return default(T);
}
}
So the editing data template change as follow
<DataTemplate x:Key="BaseClass_Interfaces_InlineEdit_Template">
<TextBox Text="{Binding Mode=TwoWay, Path=Item, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
And every thing work as charm!!!
Related
I have a DataGrid like so:
<DataGrid CanUserSortColumns="False" CanUserAddRows="True" ItemsSource="{Binding Vertices}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn x:Name="YColumn" Width="*" Header="Latitude">
<DataGridTextColumn.Binding>
<Binding Path="Y">
<Binding.ValidationRules>
<validation:DoubleValidationRule />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTextColumn x:Name="XColumn" Width="*" Header="Longitude">
<DataGridTextColumn.Binding>
<Binding Path="X">
<Binding.ValidationRules>
<validation:DoubleValidationRule />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
I have two columns that have the same validation rule (checking to see if the value in the cell is a double):
public class DoubleValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value != null)
{
double proposedValue;
if (!double.TryParse(value.ToString(), out proposedValue))
{
return new ValidationResult(false, "'" + value.ToString() + "' is not a whole double.");
}
}
return new ValidationResult(true, null);
}
}
This works fine, and a red border is displayed around the cells if the user entered value is not a double. Now I would like to disable a button if there is a validation error with any of the cells.
Following some other posts on this topic, I achieved this using MultiDataTriggers:
<Button>
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=XColumn}" Value="False" />
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=YColumn}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
This isn't working though. The button never disables even when there is a validation error. What am I doing wrong?
Edit: Here's my model and related code in the view model:
public class CustomVertex
{
public double X { get; set; }
public double Y { get; set; }
public CustomVertex()
{ }
}
public class CustomPolygonViewModel : ViewModelBase
{
public ObservableCollection<CustomVertex> Vertices { get; set; }
public CustomPolygonViewModel()
{
Vertices = new ObservableCollection<CustomVertex>();
}
}
My DataContext is set up correctly and I verified that the model's x and y are being updated on changing the value. The validation rule is being hit properly.
You have to let your view model implement INotifyDataErrorInfo MSDN. Example. Example from MSDN (Silverlight).
Since .Net 4.5 this is the recommended way to introduce validation to your view models and will help you to solve your propblem.
When implementing this interface you will have to provide a HasErrors property that you can bind to. INotifyDataErrorInfo replaces the obsolete IDataErrorInfo.
Binding to the Validation.HasError directly, as you did in your triggers, will not work since Validation.HasError is a read-only attached property and therefore doesn't support binding. To prove this I found this statement on MSDN:
... read-only dependency properties aren't appropriate for many of the scenarios for which dependency properties normally offer a solution (namely: data binding, directly stylable to a value, validation, animation, inheritance).
How INotifyDataErrorInfo works
When the ValidatesOnNotifyDataErrors property of Binding is set to true, the binding engine will search for an INotifyDataErrorInfo implementation on the binding source to subscribe to the ErrorsChanged event.
If the ErrorsChanged event is raised and HasErrors evaluates to true, the binding will invoke the GetErrors() method for the actual property to retrieve the particular error message and apply the customizable validation error template to visualize the error. By default a red border is drawn around the validated element.
How to implement INotifyDataErrorInfo
The CustomVertex class is actually the ViewModel for the DataGrid columns since you are binding to it's properties. So it has to implement the INotifyDataErrorInfo. It could look like this:
public class CustomVertex : INotifyPropertyChanged, INotifyDataErrorInfo
{
public CustomVertex()
{
this.errors = new Dictionary<string, List<string>>();
this.validationRules = new Dictionary<string, List<ValidationRule>>();
this.validationRules.Add(nameof(this.X), new List<ValidationRule>() {new DoubleValidationRule()});
this.validationRules.Add(nameof(this.Y), new List<ValidationRule>() {new DoubleValidationRule()});
}
public bool ValidateProperty(object value, [CallerMemberName] string propertyName = null)
{
lock (this.syncLock)
{
if (!this.validationRules.TryGetValue(propertyName, out List<ValidationRule> propertyValidationRules))
{
return;
}
// Clear previous errors from tested property
if (this.errors.ContainsKey(propertyName))
{
this.errors.Remove(propertyName);
OnErrorsChanged(propertyName);
}
propertyValidationRules.ForEach(
(validationRule) =>
{
ValidationResult result = validationRule.Validate(value, CultuteInfo.CurrentCulture);
if (!result.IsValid)
{
AddError(propertyName, result.ErrorContent, false);
}
}
}
}
// Adds the specified error to the errors collection if it is not
// already present, inserting it in the first position if isWarning is
// false. Raises the ErrorsChanged event if the collection changes.
public void AddError(string propertyName, string error, bool isWarning)
{
if (!this.errors.ContainsKey(propertyName))
{
this.errors[propertyName] = new List<string>();
}
if (!this.errors[propertyName].Contains(error))
{
if (isWarning)
{
this.errors[propertyName].Add(error);
}
else
{
this.errors[propertyName].Insert(0, error);
}
RaiseErrorsChanged(propertyName);
}
}
// Removes the specified error from the errors collection if it is
// present. Raises the ErrorsChanged event if the collection changes.
public void RemoveError(string propertyName, string error)
{
if (this.errors.ContainsKey(propertyName) &&
this.errors[propertyName].Contains(error))
{
this.errors[propertyName].Remove(error);
if (this.errors[propertyName].Count == 0)
{
this.errors.Remove(propertyName);
}
RaiseErrorsChanged(propertyName);
}
}
#region INotifyDataErrorInfo Members
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (String.IsNullOrEmpty(propertyName) ||
!this.errors.ContainsKey(propertyName)) return null;
return this.errors[propertyName];
}
public bool HasErrors
{
get { return errors.Count > 0; }
}
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private double x;
public double X
{
get => x;
set
{
if (ValidateProperty(value))
{
this.x = value;
OnPropertyChanged();
}
}
}
private double y;
public double Y
{
get => this.y;
set
{
if (ValidateProperty(value))
{
this.y = value;
OnPropertyChanged();
}
}
}
private Dictionary<String, List<String>> errors;
// The ValidationRules for each property
private Dictionary<String, List<ValidationRule>> validationRules;
private object syncLock = new object();
}
The View:
<DataGrid CanUserSortColumns="False" CanUserAddRows="True" ItemsSource="{Binding Vertices}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn x:Name="YColumn"
Width="*"
Header="Latitude"
Binding="{Binding Y, ValidatesOnNotifyDataErrors=True}"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}" />
<DataGridTextColumn x:Name="XColumn"
Width="*"
Header="Longitude"
Binding="{Binding X, ValidatesOnNotifyDataErrors=True}"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}" />
</DataGrid.Columns>
</DataGrid>
The following is the validation error template, in case you like to customize the visual representation (optional). It is set on the validated element (in this case the DataGridTextColumn) via the attached property Validation.ErrorTemplate (see above):
<ControlTemplate x:Key=ValidationErrorTemplate>
<StackPanel>
<!-- Placeholder for the DataGridTextColumn itself -->
<AdornedElementPlaceholder x:Name="textBox"/>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
The Button that will be disabled when the validation fails (since I don't know where this button is located in the visual tree I will assume that it shares the DataContext of a DataGrid column, the CustomVertex data model):
<Button>
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=HasErrors}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
There are many examples on the web. I updated the links to provide some content to start with.
I recommend moving the implementation of INotifyDataErrorInfo into a base class together with INotifyPropertyChanged and let all your view models inherit it. This makes the validation logic reusable and keeps your view model classes clean.
You can change the implementation details of INotifyDataErrorInfo to meet requirements.
Remarks: The code is not tested. The snippets should work, but are intended to provide an example how the INotifyDataErrorInfo interface could be implemented.
What is the use of implementing the INotifyPropertyChanged, when the below code is working just fine without it?
<DataGrid ItemsSource="{Binding Items}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding Name}"/>
<DataGridComboBoxColumn Header="Color"
SelectedItemBinding="{Binding Color}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Colors}"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Colors}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
<Button Content="Change Colors" Click="Change"/>
public class Data
{
private ObservableCollection<Item> _items;
public ObservableCollection<Item> Items
{
get { return _items; }
}
public Data()
{
_items = new ObservableCollection<Item>();
_items.Add(new Item() { Name = "A" });
_items.Add(new Item() { Name = "B" });
}
public void Change()
{
_items[0].Colors.RemoveAt(1);
}
}
public class Item
{
public string Name { get; set; }
public string Color { get; set; }
private IList<string> _colors;
public IList<string> Colors
{
get { return _colors; }
}
public Item()
{
_colors = new List<string> { "Green", "Blue" };
Color = _colors[0];
}
}
An ObservableCollection<T> implements the INotifyCollectionChanged and INotifyPropertyChanged interfaces so if you simply want to be able to add and remove items from the source collection at runtime there is no need for you to implement the INotifyPropertyChanged interface.
You would have to implement it in your custom Item class if you wanted to be able to for example update the Name or Color property dynamically though.
If you set the Name property of an Item to a new value this won't get reflected in the view unless the Item class implements the INotifyPropertyChanged interface and raises the PropertyChanged event in the setter of the Name property.
What if I want to have a custom functionality when the collection changes! For example throw a message! Should I use List instead of ObservableCollection and throw that message in the property's `´set´?
The you could handle the CollectionChanged event of the ObservableCollection<T>.
I am trying to link the visibility of a TextBlock to a bool property which is also linked to a checkbox using WPF and c#. I have the following code in two different sections of the same xaml file (one section is a summary, and the other is settings. I am very new to WPF, and am learning as I go. Currently, the TextBlock is visible no matter what the value of IsSecondaryMessageFilePath is.
<TextBlock Name="secondaryfolderinfo" Foreground="Red">
<ContentControl Content="Secondary message folder" Foreground ="Black" />
<ContentControl Content = "{Binding Path=SecondaryMessageFilePath}" ContentStringFormat="" ClipToBounds="False"></ContentControl>
<ContentControl Content = " "></ContentControl>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSecondaryMessageFilePath}" Value="True">
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Further down I have:
<CheckBox IsChecked="{Binding Path=IsSecondaryMessageFilePath, Mode=TwoWay}"
Name="SecondaryPathCheckBox"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="320,7,0,0">Save additional locations</CheckBox>
Finally, in the code-behind, I have:
public bool IsSecondaryMessageFilePath
{
get { return _isSecondaryMessageFilePath; }
set
{
if (_isSecondaryMessageFilePath != value)
{
_isSecondaryMessageFilePath = value;
OnPropertyChanged("IsSecondaryMessageFilePath");
}
}
}
private bool _isSecondaryMessageFilePath;
public string SecondaryMessageFilePath
{
get { return _secondaryMessageFilePath; }
set
{
if (_secondaryMessageFilePath != value)
{
_secondaryMessageFilePath = value;
OnPropertyChanged("SecondaryMessageFilePath");
}
}
}
private string _secondaryMessageFilePath;
Any assistance would be appreciated.
EDIT
Working from the suggestion below, I tried adding the BooleanToVisibilityConverter, but am getting a missing assembly reference for it, and am to new to WPF to figure out how to resolve it. My opening code is as follows:
<UserControl x:Class="Sender_Receiver.SenderReceiverSetup"
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"
xmlns:m=...
xmlns:
<UserControl.Resources>
<BooleanToVisibiltyConverter x:Key="BooleanToVisibilityConverter"/>
...
Your code looks ok at first glance, but you really don't need to use a data trigger for this. WPF comes with a BooleanToVisibilityConverter class that you declare in your resources:
<BooleanToVisibiltyConverter x:Key="BooleanToVisibilityConverter"/>
Then in your TextBlock, you bind Visibility:
<TextBlock Visibility="{Binding Path=IsSecondaryMessageFilePath, Converter={StaticResource BooleanToVisibilityConverter}}"/>
Just so you know, there may be a simpler way to do this, just bind to the IsChecked property itself!
<CheckBox x:Name="UseSecondaryPath"/>
<TextBlock Visibility="{Binding ElementName=UseSecondaryPath, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}"/>
Of course if you need the bool for something else that wouldn't be an ideal solution, but it is a little cleaner if its just for the UI.
The code for a custom BooleanToVisibilityConverter, if you are interested, is:
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert (object value, ...)
{
if ((bool)value)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, ...)
{
return Binding.DoNothing;
}
}
Let me know if I can clarify anything or assist further.
private Boolean _IsChecked;
//Bind this to your checkbox
public Boolean IsChecked
{
get { return _IsChecked; }
set { _IsChecked= value; OnPropertyChanged("IsChecked"); OnPropertyChanged("TextBoxVis"); }
}
//Bind this to your TextBox's Visibility Property
public Visibility TextBoxVis
{
get { return IsChecked ? Visibility.Visible : Visibility.Collapsed; }
}
In my WPF application I am using the MVVM pattern. My view has a treeview which I bind an observableCollection of objects as defined below. What I want to do is to change the colour of a tree item name when the bound object sets it’s dirty property to true. I can get it to set the colour when I first populate the tree but then it doesn’t reflect the changes when the property changes between false and true.
public class HierarchicalItem
{
private readonly ObservableCollection<HierarchicalItem> _children = new ObservableCollection<HierarchicalItem>();
public ViewModelBase ViewModel { get; set; }
public string Name
{
get { return ViewModel.ViewModelName; }
}
public ICollection<HierarchicalItem> Children
{
get { return _children; }
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
if (_isSelected)
EventSystem.Publish(new SelectedViewModelMessage { SelectedViewModel = ViewModel });
}
}
public bool IsDirty
{
get { return ViewModel.IsDirty; }
}
}
This is the treeview xaml:
<TreeView Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=Views}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=OneWayToSource}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:HierarchicalItem}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDirty}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Here is the collection that gets bound to the tree:
private readonly ObservableCollection<HierarchicalItem> _views = new ObservableCollection<HierarchicalItem>();
public ObservableCollection<HierarchicalItem> Views
{
get { return _views; }
}
The ViewModels that are referenced in the HierarchicalItem collection all derive from a base class that exposes the “IsDirty” property. This is definantly changing state so I’m not sure if I’ve made a coding mistake or if what I want to achieve can’t be done this way. The classes all use the “INotifyPropertyChanged” interface. Here is the “IsDirty” property in from the ViewModel base class:
public class ViewModelBase : ValidatableModel
{
#region Properties
private bool _isDirty;
public bool IsDirty
{
get { return _isDirty; }
protected set
{
_isDirty = value;
OnPropertyChanged("IsDirty");
}
}
.
.
.
Etc
It's because your HierarchicalItem (the one you are having issues with) does not use a full INPC approach for its IsDirty property. The viewmodel does, but that is not enough, as the DataTemplate will be using the IsDirty property of the HierarchicalItem, so that needs to be full INPC property too
Changed that to this and it should be ok.
private bool _isDirty;
public bool IsDirty
{
get { return _isDirty; }
protected set
{
_isDirty = value;
OnPropertyChanged("IsDirty");
}
}
Though for your use case you will need to figure out some way to fire that. Or another thing you could try would be to change the binding in HierarchicalItem DataTemplate to this
<DataTrigger Binding="{Binding ViewModel.IsDirty}" Value="True">
I have a combobox and button on my form. The combobox has categories in them. I want to allow/disallow pending on if they are a 'system category' based on a boolean.
Here is my xaml:
<Window.Resources>
<Style TargetType="{x:Type ComboBox}">
<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>
This is the stack panel with the two controls in them:
<StackPanel Grid.Column="1" Grid.Row="1">
<Label Content="Delete Category" Height="28"/>
<ComboBox x:Name="comboBox_DeleteCategory"
Grid.Row="1"
Height="29"
ItemsSource="{Binding Path=CategorySelected.Items, ValidatesOnDataErrors=true, NotifyOnValidationError=true}"
SelectedItem="{Binding Path=CategorySelected.SelectedItem ,ValidatesOnDataErrors=True, NotifyOnValidationError=true}"
DisplayMemberPath="Name"/>
<Button Content="Delete" Height="25" Margin="0,5,0,0" HorizontalAlignment="Right" Width="103.307" Command="{Binding DeleteCommand}"/>
</StackPanel>
I am trying to get the combobox to show a tooltip if it is determined that it is a system category.
The DeleteCommand is working fine so I am not having issues with the button being disabled when I get a hit on the system category.
This is my code to show the tooltip:
#region IDataErrorInfo Members
public string Error { get; set; }
public string this[string columnName]
{
get
{
Error = "";
switch (columnName)
{
case "comboBox_DeleteCategory":
if (CategorySelected.SelectedItem != null && CategorySelected.SelectedItem.IsInternal)
{
Error = CategorySelected.SelectedItem.Name + " is an system category and cannot be deleted.";
break;
}
break;
}
return Error;
}
}
#endregion
Any suggestions?
Thanks,
Eroc
The indexer (public string this[string columnName]) is called with the property name that has been changed by the latest binding update. That is, filtering for "comboBox_DeleteCategory" (the control name) won't help here. You have to filter for the property that was updated by the control's binding and determine if it is in the expected state. You can put a breakpoint in the indexer and watch the columnName's value. What is more, the Error property is not used by WPF at all. Thus, it is not necessary to set it. A simple example:
public class Contact : IDataErrorInfo, INotifyPropertyChanged
{
private string firstName;
public string FirstName
{
// ... set/get with prop changed support
}
#region IDataErrorInfo Members
public string Error
{
// NOT USED BY WPF
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
// null or string.Empty won't raise a validation error.
string result = null;
if( columnName == "FirstName" )
{
if (String.IsNullOrEmpty(FirstName))
result = "A first name please...";
else if (FirstName.Length < 5)
result = "More than 5 chars please...";
}
return result;
}
}
#endregion
}