ValidationRule binding to windows context - wpf

I'm trying to make ValidationRule which depends on some property from (for example) data model.
I have TextBox with validator which have to know about the model object "Scheme". I've tryed to add Scheme into Resources but this didn't work. And after I've found some solution relying on dependency properties.
According to this http://dedjo.blogspot.com/2007/05/fully-binded-validation-by-using.html I've made:
/// <summary>
/// Check text value for emptiness and uniqueness
/// </summary>
public class EmptyAndUnique : ValidationRule
{
public UniqueChecker UniqueChecker { get; set; }
public string ErrorMessage { get; set; }
public override ValidationResult Validate(object value,
CultureInfo cultureInfo)
{
string inputString = (value).ToString();
var result = new ValidationResult(true, null);
// Check uniqueness
if(this.UniqueChecker.Scheme.Factors.Any(f => f.Uid == inputString))
{
result = new ValidationResult(false, this.ErrorMessage);
return result;
}
// Check emptiness
if (inputString.Trim().Equals(string.Empty))
{
result = new ValidationResult(false, this.ErrorMessage);
return result;
}
return result;
}
}
/// <summary>
/// Wrapper for DependencyProperty. (trick)
/// </summary>
public class UniqueChecker : DependencyObject
{
public static readonly DependencyProperty SchemeProperty =
DependencyProperty.Register("Scheme", typeof(Scheme),
typeof(UniqueChecker), new FrameworkPropertyMetadata(null));
public Scheme Scheme
{
get { return (Scheme)GetValue(SchemeProperty); }
set { SetValue(SchemeProperty, value); }
}
}
This does not work either.
1) According to article:
Scheme="{Binding ElementName=expressionFactorEditorWindow, Path=Scheme}"
this does not work because:
cos our dependency object is not part
of logical tree, so you can not use
ElementName or DataContext as source
for internal data binding.
But why it's not part of logical tree?
2) How can I bind properties of my ValidationRule to some dynamic resources
UPDATE
While watching for better solution I made this:
Add Event which checks uniqueness into ValidationRule
Add Handler into Window class
Raise event from ValidationRule and check result from EventArgs

What are you binding to that you are trying to add Validation for. How I usually handle this by having the object I am binding to implement IDataErrorInfo. Then I can put my error handling in that object and have access to anything I need. This would be possible if you are in control of the object you are Binding to.

Related

WPF: Why is my markup extension binding not working?

Working Example with "Binding":
I have a UserControl which I use like this in my MainWindow:
<userControls:NoMarkupControl/>
The ViewModel of my MainWindow contains this property:
private string _exampleText = "example";
public string ExampleText
{
get { return _exampleText; }
set
{
_exampleText = value;
OnPropertyChanged();
}
}
inside the UserControl I bind my ViewModel to this property:
<TextBlock Text="{Binding ExampleText}"/>
as a result "example" gets displayed when I start the app. Everything works.
Not working example with Custom Markup Extension:
Now I have a MarkupExtension:
public class ExampleTextExtension : MarkupExtension
{
private static readonly List<DependencyProperty> StorageProperties = new List<DependencyProperty>();
private readonly object _parameter;
private DependencyProperty _dependencyProperty;
public ExampleTextExtension(object parameter)
{
_parameter = parameter;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
DependencyObject targetObject;
if (target?.TargetObject is DependencyObject dependencyObject &&
target.TargetProperty is DependencyProperty)
{
targetObject = dependencyObject;
}
else
{
return this;
}
_dependencyProperty = SetUnusedStorageProperty(targetObject, _parameter);
return GetLocalizedText((string)targetObject.GetValue(_dependencyProperty));
}
private static string GetLocalizedText(string text)
{
return text == null ? null : $"markup: {text}";
}
private static DependencyProperty SetUnusedStorageProperty(DependencyObject obj, object value)
{
var property = StorageProperties.FirstOrDefault(p => obj.ReadLocalValue(p) == DependencyProperty.UnsetValue);
if (property == null)
{
property = DependencyProperty.RegisterAttached("Storage" + StorageProperties.Count, typeof(object), typeof(ExampleTextExtension), new PropertyMetadata());
StorageProperties.Add(property);
}
if (value is MarkupExtension markupExtension)
{
var resolvedValue = markupExtension.ProvideValue(new ServiceProvider(obj, property));
obj.SetValue(property, resolvedValue);
}
else
{
obj.SetValue(property, value);
}
return property;
}
private class ServiceProvider : IServiceProvider, IProvideValueTarget
{
public object TargetObject { get; }
public object TargetProperty { get; }
public ServiceProvider(object targetObject, object targetProperty)
{
TargetObject = targetObject;
TargetProperty = targetProperty;
}
public object GetService(Type serviceType)
{
return serviceType.IsInstanceOfType(this) ? this : null;
}
}
}
Again I have a UserControl which I use like this in my MainWindow:
<userControls:MarkupControl/>
The ViewModel of my MainWindow stays the same like above.
inside the UserControl I bind to my TextBlock Text property like this:
<TextBlock Text="{markupExtensions:ExampleText {Binding ExampleText}}"/>
as a result my UserControl displays nothing. I would have expected to display "markup: example"
The binding somehow does not work in this case.
Does anybody know how to fix this?
Additional information:
it works when used like this (dependency property MarkupText is created in user control):
<userControls:MarkupControl MarkupText={markupExtensions:ExampleText {Binding ExampleText}}/>
<TextBlock Text="{Binding Text, ElementName=MarkupControl}"/>
Firstly, you need to refactor your extension to simplify the implementation. You don't need a static context here. Getting rid of the class context will make the tracking of the created attached properties obsolete. You can drop the related collection safely. In your case, it's more efficient to store values in an instance context. Attached properties are also a convenient solution to store values per instance especially in a static context.
Secondly, you got a timing issue. The first time the extension is called, the Binding is not initialized properly: it doesn't provide the final value of the Binding.Source.
Additionally, your current implementation does not support property changes.
To fix this, you would have to track the Binding.Target updates when a value is sent from the Binding.Source (for a default BindingMode.OneWay). You can achieve this by listening to the Binding.TargetUpdated event (as stated in my previous comment) or register a property changed handler with the attached property (recommended).
To support two way binding, you would also have to track the target property (the property your MarkupExtension is assigned to).
A fixed and improved version could look as follows:
public class ExampleTextExtension : MarkupExtension
{
private static DependencyProperty ResolvedBindingSourceValueProperty = DependencyProperty.RegisterAttached(
"ResolvedBindingSourceValue",
typeof(object),
typeof(ExampleTextExtension),
new PropertyMetadata(default(object), OnResolvedBindingSourceValueChanged));
// Use attached property to store the target object
// for reference from a static context without dealing with class level members that are shared between instances.
private static DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached(
"TargetProperty",
typeof(DependencyProperty),
typeof(ExampleTextExtension),
new PropertyMetadata(default));
private Binding Binding { get; }
// Accept BindingBase to support MultiBinding etc.
public ExampleTextExtension(Binding binding)
{
this.Binding = binding;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTargetService = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (provideValueTargetService?.TargetObject is not DependencyObject targetObject
|| provideValueTargetService?.TargetProperty is not DependencyProperty targetProperty)
{
return this;
}
targetObject.SetValue(ExampleTextExtension.TargetPropertyProperty, targetProperty);
AttachBinding(targetObject);
return string.Empty;
}
private static string GetLocalizedText(string text)
=> String.IsNullOrWhiteSpace(text)
? string.Empty
: $"markup: {text}";
// By now, only supports OneWay binding
private void AttachBinding(DependencyObject targetObject)
{
switch (this.Binding.Mode)
{
case BindingMode.OneWay:
case BindingMode.Default:
HandleOneWayBinding(targetObject); break;
default: throw new NotSupportedException();
}
}
private void HandleOneWayBinding(DependencyObject targetObject)
{
BindingOperations.SetBinding(targetObject, ExampleTextExtension.ResolvedBindingSourceValueProperty, this.Binding);
}
// Property changed handler to update the target of this extension
// with the localized value
private static void OnResolvedBindingSourceValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string localizedText = GetLocalizedText(e.NewValue as string);
var targetProperty = d.GetValue(ExampleTextExtension.TargetPropertyProperty) as DependencyProperty;
d.SetValue(targetProperty, localizedText);
}
}
Remarks
There are better solutions to introduce localization without compromising the general syntax or legacy code. For example, introducing this MarkupExtension to existing code will break this code as all relevant data bindings (C# and XAML) have to be modified.
The most common approach is to use satellite assemblies and localized resources. Instead of converting text values during data binding you should localize the value source directly (so that the Binding transfers already localized values).
In other words, make sure that the data source is localized. Let the binding source expose the text by fetching it from a localized repository.

WPF Simplifying OnPropertyChanged [duplicate]

I'm pretty new in WPF programming environment. I'm trying to write a program out using MVVM design pattern.
I've did some studies and read up some articles related to it and many of a time I came across this thing called
ViewModelBase
I know what it is.. But may I know specifically where should I begin with to be able to write out my own ViewModelBase? Like... Really understanding what's happening without getting too complicated. Thank you :)
It's worth nothing to use MVVM frameworks if you don't know what's going on inside.
So let's go step by step and build your own ViewModelBase class.
ViewModelBase is class common for all your viewmodels. Let's move all common logic to this class.
Your ViewModels should implement INotifyPropertyChanged (do you understand why?)
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
the [CallerMemberName] attribute is not required, but it will allow you to write:
OnPropertyChanged(); instead of OnPropertyChanged("SomeProperty");, so you will avoid string constant in your code. Example:
public string FirstName
{
set
{
_firstName = value;
OnPropertyChanged(); //instead of OnPropertyChanged("FirstName") or OnPropertyChanged(nameof(FirstName))
}
get{ return _firstName;}
}
Please note, that OnPropertyChanged(() => SomeProperty) is no more recommended, since we have nameof operator in C# 6.
It's common practice to implement properties that calls PropertyChanged like this:
public string FirstName
{
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
Let's define SetProperty in your viewmodelbase:
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
It simply fires PropertyChanged event when value of the property changes and returns true. It does not fire the event when the value has not changed and returns false. The basic idea is, that SetProperty method is virtual and you can extend it in more concrete class, e.g to trigger validation, or by calling PropertyChanging event.
This is pretty it. This is all your ViewModelBase should contain at this stage. The rest depends on your project. For example your app uses page base navigation and you have written your own NavigationService for handling navigation from ViewModel. So you can add NavigationService property to your ViewModelBase class, so you will have access to it from all your viewmodels, if you want.
In order to gain more reusability and keep SRP, I have class called BindableBase which is pretty much the implementation of INotifyPropertyChanged as we have done here. I reuse this class in every WPF/UWP/Silverligt/WindowsPhone solution because it's universal.
Then in each project I create custom ViewModelBase class derived from BindableBase:
public abstract ViewModelBase : BindableBase
{
//project specific logic for all viewmodels.
//E.g in this project I want to use EventAggregator heavily:
public virtual IEventAggregator () => ServiceLocator.GetInstance<IEventAggregator>()
}
if I have app, that uses page based navigation I also specify base class for page viewmodels.
public abstract PageViewModelBase : ViewModelBase
{
//for example all my pages have title:
public string Title {get; private set;}
}
I could have another class for dialogs:
public abstract DialogViewModelBase : ViewModelBase
{
private bool? _dialogResult;
public event EventHandler Closing;
public string Title {get; private set;}
public ObservableCollection<DialogButton> DialogButtons { get; }
public bool? DialogResult
{
get { return _dialogResult; }
set { SetProperty(ref _dialogResult, value); }
}
public void Close()
{
Closing?.Invoke(this, EventArgs.Empty);
}
}
The below class can be used as a ViewModelBase in WPF projects:
public abstract class ViewModelBase : INotifyPropertyChanged
{
/// <summary>
/// Multicast event for property change notifications.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Checks if a property already matches the desired value. Sets the property and
/// notifies listeners only when necessary.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to a property with both getter and setter.</param>
/// <param name="value">Desired value for the property.</param>
/// <param name="propertyName">Name of the property used to notify listeners.This
/// value is optional and can be provided automatically when invoked from compilers that
/// support CallerMemberName.</param>
/// <returns>True if the value was changed, false if the existing value matched the
/// desired value.</returns>
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
// Log.DebugFormat("{0}.{1} = {2}", this.GetType().Name, propertyName, storage);
this.OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// Notifies listeners that a property value has changed.
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers
/// that support <see cref="CallerMemberNameAttribute"/>.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
And an example of ViewModel class is:
public class MyViewModel : ViewModelBase
{
private int myProperty;
public int MyProperty
{
get { return myProperty; }
set { SetProperty(ref myProperty, value); }
}
}
For ease of writing, below snippet can be used:
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets
xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>OnPropertyChanged</Title>
</Header>
<Snippet>
<SnippetTypes>
<SnippetType>SurroundsWith</SnippetType>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
<Declarations>
<Literal>
<ID>TYPE</ID>
<ToolTip>Property type</ToolTip>
<Default>int</Default>
</Literal>
<Literal>
<ID>NAME1</ID>
<ToolTip>Property name</ToolTip>
<Default>MyProperty</Default>
</Literal>
</Declarations>
<Code Language="CSharp">
<![CDATA[private $TYPE$ _$NAME1$;
public $TYPE$ $NAME1$
{
get => _$NAME1$;
set => SetProperty(ref _$NAME1$, value);
}]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
The full code could be downloaded from here.
You have some nuget package to implement MVVM
MVVM light
MVVM Cross
Prism
For me the easier for a beginner is MVVM light because it provide some code sample.
So the better is to install this nuget package, have a look about the generated code and back to us for more explanations if you need.
In most MVVM frameworks, the base ViewModel classes actually contain very little code - usually just an implementation of INotifyPropertyChanged and some helper functions.
Take a look at the source code for MVVM Light's ViewModelBase and ObservableObject classes. ObservableObject is mostly the INotifyPropertyChanged implementation - using a lambda expression rather than "magic strings" for the property name. ViewModelBase extends ObservableObject and is mostly a utility method to determine if you're running inside the Visual Studio designer
I like this BaseVewModel it gives a nice clean style to your view models. Check out the various 'before' and 'after' comparisons. Of course, nothing is mandatory - if you don't like a feature that the BaseViewModel provides then don't use it. Or modify it because you have the source code. In particular note that there are three different ways to implement properties with change notification - choose the level of sophistication that you understand/feel comfortable with.
To revisit this answer today, I'd like to offer additional productivity improvements when writing MVVM code for Visual Studio.
The Intellisense in Visual Studio can automatically create the SetProperty boilerplate method. To do so, I set the ViewModel in the XAML of my Window (see below). Then, whenever I reference a {Binding Path=NewProperty}, Right Click and Select Quick Actions and Refactoring... (or via Ctrl .). If the SetProperty method isn't made, it will automatically be created for you within your ViewModel class. Further, it'll generate the property and field required for the Binding.
<Window x:Class="My.Project.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:My.Project"
mc:Ignorable="d"
xmlns:viewmodel="clr-namespace:My.Project.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodel:MainWindowViewModel}"
Title="MainWindow" Height="360" Width="1000">
...
</Window>
However, this method has drawbacks
The INotifyPropertyChanged is not implemented and the OnPropertyChanged method is not implemented (if you need it)
This would need to be done in every ViewModel
This is specific for Visual Studio
Benefits:
Once the SetProperty method is defined in the project, using the Quick Actions and Refactoring... option will only generate the necessary property and field for you. This also works if you inherit from a ViewModelBase.
Here is the SetProperty method as generated by Visual Studio.
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
// Log.DebugFormat("{0}.{1} = {2}", this.GetType().Name, propertyName, storage);
this.OnPropertyChanged(propertyName);
return true;
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using System.Runtime.CompilerServices;
using System.ComponentModel;
namespace CollectionViewMVVM.ViewModel
{
public class BaseViewModel : INotifyPropertyChanged
{
/*Referencia: https://www.youtube.com/watch?v=PXEt1esjnZ0&ab_channel=Codigo369*/
public INavigation Navigation;
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnpropertyChanged([CallerMemberName] string nombre = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nombre));
}
private ImageSource foto;
public ImageSource Foto
{
get { return foto; }
set
{
foto = value;
OnpropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public async Task DisplayAlert(string title, string message, string cancel)
{
await Application.Current.MainPage.DisplayAlert(title, message, cancel);
}
public async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
{
return await Application.Current.MainPage.DisplayAlert(title, message, accept, cancel);
}
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if(EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
/*Ejemplo para declarar entrys*/
private string _title;
public string Title
{
get { return _title; }
set
{
SetProperty(ref _title, value);
}
}
/*Para tipos bool*/
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
SetProperty(ref _isBusy, value);
}
}
protected void SetValue<T>(ref T backingFielded, T value, [CallerMemberName] string propertyName = null)
{
if(EqualityComparer<T>.Default.Equals(backingFielded, value))
{
return;
}
backingFielded = value;
OnPropertyChanged(propertyName);
}
}
}

How to add validation to view model properties or how to implement INotifyDataErrorInfo

I have a data collection of type ObservableCollection (say instance as myClassTypes). After some user operation, this myClassTypes populated with values in ViewModel. In view, there is a TextBox where user can enter text. I need to validate textbox data against myClassTypes values. So if myClassTypes contains the text inserted by User in textbox, Validation is passed otherwise it will fail.
My code snippet is:
ViewModel:
public ObservableCollection < MyClassType > ViewModelClassTypes {
get {
return _myClassTypes;
}
set {
_myClassTypes = value;
NotifyOfPropertyChange(() = >MyClassTypes);
}
}
public class TestValidationRule: ValidationRule {
public ObservableCollection < MyClassType > MyClassTypes {
get = >(ObservableCollection < MyClassType > ) GetValue(MyClassTypesProperty);
set = >SetValue(MyClassTypesProperty, value);
}
}
FYI : MyClassTypesProperty is a dependency property
My View.xaml is :
<TextBox>
<TextBox.Text>
<Binding UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validationRules:TestValidationRule MyClassTypes="{Binding ViewModelClassTypes}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
I am not able to get ViewModelClassTypes populated value in MyClassTypes. Can anyone please suggest what's wrong I am doing ?
Since .Net 4.5, the preferred way to implement data validation is to implement INotifyDataErrorInfo (example from Technet, example from MSDN (Silverlight)).
Note: INotifyDataErrorInfo replaces the obsolete IDataErrorInfo.
The new framework infrastructure related to the INotifyDataErrorInfo interface provides many advantages like
support of multiple errors per property
custom error objects and customization of visual error feedback (e.g. to adapt visual cues to the custom error object)
asynchronous validation using async/await
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 and subscribe to the INotifyDataErrorInfo.ErrorsChanged event.
If the ErrorsChanged event of the binding source is raised and INotifyDataErrorInfo.HasErrors evaluates to true, the binding engine will invoke the INotifyDataErrorInfo.GetErrors(propertyName) method for the actual source property to retrieve the corresponding error message and then apply the customizable validation error template to the target control to visualize the validation error.
By default a red border is drawn around the element that has failed to validate.
In case of an error, which is when INotifyDataErrorInfo.HasErrors returns true, the binding engine will also set the attached Validation properties on the binding target, for example Validation.HasError and Validation.ErrorTemplate.
To customize the visual error feedback, we can override the default template provided by the binding engine, by overriding the value of the attached Validation.ErrorTemplate property (see example below).
The described validation procedure only executes when Binding.ValidatesOnNotifyDataErrors is set to true on the particular data binding and the Binding.Mode is set to either BindingMode.TwoWay or BindingMode.OneWayToSource.
How to implement INotifyDataErrorInfo
The following examples show three variations of property validation using
ValidationRule (class to encapsulate the actual data validation implementation)
lambda expressions (or delegates)
validation attributes (used to decorate the validated property).
Of course, you can combine all three variations to provide maximum flexibility.
The code is not tested. The snippets should all work, but may not compile due to typing errors. This code is intended to provide a simple example on how the INotifyDataErrorInfo interface could be implemented.
Preparing the view
MainWindow.xaml
To enable the visual data validation feedback, the Binding.ValidatesOnNotifyDataErrors property must be set to true on each relevant Binding i.e. where the source of the Binding is a validated property. The WPF framework will then show the control's default error feedback.
Note: to make this work, the Binding.Mode must be either OneWayToSource or TwoWay (which is the default for the TextBox.Text property):
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<!-- Important: set ValidatesOnNotifyDataErrors to true to enable visual feedback -->
<TextBox Text="{Binding UserInput, ValidatesOnNotifyDataErrors=True}"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}" />
</Window>
The following is an example of a custom validation error template.
The default visual error feedback is a simple red border around the validated element. In case you like to customize the visual feedback e.g., to allow showing error messages to the user, you can define a custom ControlTemplate and assign it to the validated element (in this case the TextBox) via the attached property Validation.ErrorTemplate (see above).
The following ControlTemplate enables showing a list of error messages that are associated with the validated property:
<ControlTemplate x:Key="ValidationErrorTemplate">
<StackPanel>
<Border BorderBrush="Red"
BorderThickness="1">
<!-- Placeholder for the DataGridTextColumn itself -->
<AdornedElementPlaceholder x:Name="AdornedElement" />
</Border>
<Border Background="White"
BorderBrush="Red"
Padding="4"
BorderThickness="1,0,1,1"
HorizontalAlignment="Left">
<ItemsControl ItemsSource="{Binding}" HorizontalAlignment="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}"
Foreground="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</StackPanel>
</ControlTemplate>
The view model is responsible for validating its own properties to ensure the data integrity of the model.
I recommend moving the implementation of INotifyDataErrorInfo into a base class (e.g. an abstract ViewModel class) together with the INotifyPropertyChanged implementation 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 example's implementation details of INotifyDataErrorInfo to meet requirements.
1 Data validation using ValidationRule
ViewModel.cs
When using ValidationRule, the key is to have separate ValidationRule implementations for each property or rule.
Extending ValidationRule is optional. I chose to extend ValidationRule because it already provides a complete validation API and because the implementations can be reused with binding validation if necessary.
Basically, the result of the property validation should be a bool to indicate fail or success of the validation and a message that can be displayed to the user to help him to fix his input.
All we have to do in case of a validation error is to generate an error message, add it to a private string collection to allow our INotifyDataErrorInfo.GetErrors(propertyName) implementation to return the proper error messages from this collection and raise the INotifyDataErrorInfo.ErrorChanged event to notify the WPF binding engine about the error:
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
// Example property, which validates its value before applying it
private string userInput;
public string UserInput
{
get => this.userInput;
set
{
// Validate the value
bool isValueValid = IsPropertyValid(value);
// Optionally reject value if validation has failed
if (isValueValid)
{
this.userInput = value;
OnPropertyChanged();
}
}
}
// Constructor
public ViewModel()
{
this.Errors = new Dictionary<string, IList<object>>();
this.ValidationRules = new Dictionary<string, IList<ValidationRule>>();
// Create a Dictionary of validation rules for fast lookup.
// Each property name of a validated property maps to one or more ValidationRule.
this.ValidationRules.Add(nameof(this.UserInput), new List<ValidationRule>() { new UserInputValidationRule() });
}
// Validation method.
// Is called from each property which needs to validate its value.
// Because the parameter 'propertyName' is decorated with the 'CallerMemberName' attribute.
// this parameter is automatically generated by the compiler.
// The caller only needs to pass in the 'propertyValue', if the caller is the target property's set method.
public bool IsPropertyValid<TValue>(TValue propertyValue, [CallerMemberName] string propertyName = null)
{
// Clear previous errors of the current property to be validated
_ = ClearErrors(propertyName);
if (this.ValidationRules.TryGetValue(propertyName, out List<ValidationRule> propertyValidationRules))
{
// Apply all the rules that are associated with the current property
// and validate the property's value
IEnumerable<object> errorMessages = propertyValidationRules
.Select(validationRule => validationRule.Validate(propertyValue, CultureInfo.CurrentCulture))
.Where(result => !result.IsValid)
.Select(invalidResult => invalidResult.ErrorContent);
AddErrorRange(propertyName, errorMessages);
return !errorMessages.Any();
}
// No rules found for the current property
return true;
}
// Adds the specified errors 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 Errors collection changes.
// A property can have multiple errors.
private void AddErrorRange(string propertyName, IEnumerable<object> newErrors, bool isWarning = false)
{
if (!newErrors.Any())
{
return;
}
if (!this.Errors.TryGetValue(propertyName, out IList<object> propertyErrors))
{
propertyErrors = new List<object>();
this.Errors.Add(propertyName, propertyErrors);
}
if (isWarning)
{
foreach (object error in newErrors)
{
propertyErrors.Add(error);
}
}
else
{
foreach (object error in newErrors)
{
propertyErrors.Insert(0, error);
}
}
OnErrorsChanged(propertyName);
}
// Removes all errors of the specified property.
// Raises the ErrorsChanged event if the Errors collection changes.
public bool ClearErrors(string propertyName)
{
this.ValidatedAttributedProperties.Remove(propertyName);
if (this.Errors.Remove(propertyName))
{
OnErrorsChanged(propertyName);
return true;
}
return false;
}
// Optional method to check if a particular property has validation errors
public bool PropertyHasErrors(string propertyName) => this.Errors.TryGetValue(propertyName, out IList<object> propertyErrors) && propertyErrors.Any();
#region INotifyDataErrorInfo implementation
// The WPF binding engine will listen to this event
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
// This implementation of GetErrors returns all errors of the specified property.
// If the argument is 'null' instead of the property's name,
// then the method will return all errors of all properties.
// This method is called by the WPF binding engine when ErrorsChanged event was raised and HasErrors return true
public System.Collections.IEnumerable GetErrors(string propertyName)
=> string.IsNullOrWhiteSpace(propertyName)
? this.Errors.SelectMany(entry => entry.Value)
: this.Errors.TryGetValue(propertyName, out IList<object> errors)
? (IEnumerable<object>)errors
: new List<object>();
// Returns 'true' if the view model has any invalid property
public bool HasErrors => this.Errors.Any();
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnErrorsChanged(string propertyName)
{
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
// Maps a property name to a list of errors that belong to this property
private Dictionary<string, IList<object>> Errors { get; }
// Maps a property name to a list of ValidationRules that belong to this property
private Dictionary<string, IList<ValidationRule>> ValidationRules { get; }
}
UserInputValidationRule.cs
This example validation rule extends ValidationRule and checks if the input starts with the '#' character. If not, it returns an invalid ValidationResult with an error message that can be displayed to the user to help him to fix his input.
public class UserInputValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (!(value is string userInput))
{
return new ValidationResult(false, "Value must be of type string.");
}
if (!userInput.StartsWith("#"))
{
return new ValidationResult(false, "Input must start with '#'.");
}
return ValidationResult.ValidResult;
}
}
2 Data validation using lambda expressions and delegates
As an alternative approach, the ValidationRule can be replaced (or combined) with delegates to enable the use of Lambda expressions or Method Groups.
The validation expressions in this example return a tuple containing a boolean to indicate the validation state and a collection of string error objects for the actual messages. Since all error object related properties are of type object, the expressions can return any custom data type, in case you need more advanced error feedback and string is not a sufficient error object. In this case, we must also adjust the validation error template, to enable it to handle the data type.
ViewModel.cs
// Example uses System.ValueTuple
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
// This property is validated using a lambda expression
private string userInput;
public string UserInput
{
get => this.userInput;
set
{
// Validate the new property value.
bool isValueValid = IsPropertyValid(value,
newValue => newValue.StartsWith("#")
? (true, Enumerable.Empty<object>())
: (false, new[] { "Value must start with '#'." }));
// Optionally, reject value if validation has failed
if (isValueValid)
{
// Accept the valid value
this.userInput = value;
OnPropertyChanged();
}
}
}
// Alternative usage example property, which validates its value
// before applying it, using a Method Group.
private string userInputAlternative;
public string UserInputAlternative
{
get => this.userInputAlternative;
set
{
// Use Method Group
if (IsPropertyValid(value, IsUserInputValid))
{
this.userInputAlternative = value;
OnPropertyChanged();
}
}
}
// Constructor
public ViewModel()
{
this.Errors = new Dictionary<string, IList<object>>();
}
// The validation method for the UserInput property
private (bool IsValid, IEnumerable<object> ErrorMessages) IsUserInputValid(string value)
{
return value.StartsWith("#")
? (true, Enumerable.Empty<object>())
: (false, new[] { "Value must start with '#'." });
}
// Example uses System.ValueTuple
public bool IsPropertyValid<TValue>(
TValue value,
Func<TValue, (bool IsValid, IEnumerable<object> ErrorMessages)> validationDelegate,
[CallerMemberName] string propertyName = null)
{
// Clear previous errors of the current property to be validated
_ = ClearErrors(propertyName);
// Validate using the delegate
(bool IsValid, IEnumerable<object> ErrorMessages) validationResult = validationDelegate?.Invoke(value) ?? (true, Enumerable.Empty<object>());
if (!validationResult.IsValid)
{
AddErrorRange(propertyName, validationResult.ErrorMessages);
}
return validationResult.IsValid;
}
// Adds the specified errors 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 Errors collection changes.
// A property can have multiple errors.
private void AddErrorRange(string propertyName, IEnumerable<object> newErrors, bool isWarning = false)
{
if (!newErrors.Any())
{
return;
}
if (!this.Errors.TryGetValue(propertyName, out IList<object> propertyErrors))
{
propertyErrors = new List<object>();
this.Errors.Add(propertyName, propertyErrors);
}
if (isWarning)
{
foreach (object error in newErrors)
{
propertyErrors.Add(error);
}
}
else
{
foreach (object error in newErrors)
{
propertyErrors.Insert(0, error);
}
}
OnErrorsChanged(propertyName);
}
// Removes all errors of the specified property.
// Raises the ErrorsChanged event if the Errors collection changes.
public bool ClearErrors(string propertyName)
{
this.ValidatedAttributedProperties.Remove(propertyName);
if (this.Errors.Remove(propertyName))
{
OnErrorsChanged(propertyName);
return true;
}
return false;
}
// Optional method to check if a particular property has validation errors
public bool PropertyHasErrors(string propertyName) => this.Errors.TryGetValue(propertyName, out IList<object> propertyErrors) && propertyErrors.Any();
#region INotifyDataErrorInfo implementation
// The WPF binding engine will listen to this event
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
// This implementation of GetErrors returns all errors of the specified property.
// If the argument is 'null' instead of the property's name,
// then the method will return all errors of all properties.
// This method is called by the WPF binding engine when ErrorsChanged event was raised and HasErrors return true
public System.Collections.IEnumerable GetErrors(string propertyName)
=> string.IsNullOrWhiteSpace(propertyName)
? this.Errors.SelectMany(entry => entry.Value)
: this.Errors.TryGetValue(propertyName, out IList<object> errors)
? (IEnumerable<object>)errors
: new List<object>();
// Returns 'true' if the view model has any invalid property
public bool HasErrors => this.Errors.Any();
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnErrorsChanged(string propertyName)
{
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
// Maps a property name to a list of errors that belong to this property
private Dictionary<string, IList<object>> Errors { get; }
}
3 Data validation using ValidationAttribute
This is an example implementation of INotifyDataErrorInfo with ValidationAttribute support e.g., MaxLengthAttribute. This solution combines the previous Lambda version to additionally support validation using a Lambda expression/delegate simultaneously.
While validation using a lambda expression or a delegate must be explicitly invoked by calling the TryValidateProperty method in the properties setter, the attribute validation is executed implicitly from the OnPropertyChanged event invocator (as soon the property was decorated with validation attributes):
ViewModel.cs
// Example uses System.ValueTuple
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
private string userInput;
// Validate property using validation attributes
[MaxLength(Length = 5, ErrorMessage = "Only five characters allowed.")]
public string UserInput
{
get => this.userInput;
set
{
// Optional call to 'IsPropertyValid' to combine attribute validation
// with a delegate
bool isValueValid = IsPropertyValid(value, newValue => newValue.StartsWith("#")
? (true, Enumerable.Empty<object>())
: (false, new[] { "Value must start with '#'." }));
// Optionally, reject value if validation has failed
if (isValueValid)
{
this.userInput = value;
}
// Triggers checking for validation attributes and their validation,
// if any validation attributes were found (like 'MaxLength' in this example)
OnPropertyChanged();
}
}
// Constructor
public ViewModel()
{
this.Errors = new Dictionary<string, IList<object>>();
this.ValidatedAttributedProperties = new HashSet<string>();
}
// Validate property using decorating attributes.
// Is invoked by 'OnPropertyChanged' (see below).
private bool IsAttributedPropertyValid<TValue>(TValue value, string propertyName)
{
this.ValidatedAttributedProperties.Add(propertyName);
// The result flag
bool isValueValid = true;
// Check if property is decorated with validation attributes
// using reflection
IEnumerable<Attribute> validationAttributes = GetType()
.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
?.GetCustomAttributes(typeof(ValidationAttribute)) ?? new List<Attribute>();
// Validate using attributes if present
if (validationAttributes.Any())
{
var validationContext = new ValidationContext(this, null, null) { MemberName = propertyName };
var validationResults = new List<ValidationResult>();
if (!Validator.TryValidateProperty(value, validationContext, validationResults))
{
isValueValid = false;
AddErrorRange(validationResults.Select(attributeValidationResult => attributeValidationResult.ErrorMessage));
}
}
return isValueValid;
}
public bool IsPropertyValid<TValue>(
TValue value,
Func<TValue, (bool IsValid, IEnumerable<object> ErrorMessages)> validationDelegate = null,
[CallerMemberName] string propertyName = null)
{
// Clear previous errors of the current property to be validated
ClearErrors(propertyName);
// Validate using the delegate
(bool IsValid, IEnumerable<object> ErrorMessages) validationResult = validationDelegate?.Invoke(value) ?? (true, Enumerable.Empty<object>());
if (!validationResult.IsValid)
{
// Store the error messages of the failed validation
AddErrorRange(validationResult.ErrorMessages);
}
bool isAttributedPropertyValid = IsAttributedPropertyValid(value, propertyName);
return isAttributedPropertyValid && validationResult.IsValid;
}
// Adds the specified errors 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 Errors collection changes.
// A property can have multiple errors.
private void AddErrorRange(string propertyName, IEnumerable<object> newErrors, bool isWarning = false)
{
if (!newErrors.Any())
{
return;
}
if (!this.Errors.TryGetValue(propertyName, out IList<object> propertyErrors))
{
propertyErrors = new List<object>();
this.Errors.Add(propertyName, propertyErrors);
}
if (isWarning)
{
foreach (object error in newErrors)
{
propertyErrors.Add(error);
}
}
else
{
foreach (object error in newErrors)
{
propertyErrors.Insert(0, error);
}
}
OnErrorsChanged(propertyName);
}
// Removes all errors of the specified property.
// Raises the ErrorsChanged event if the Errors collection changes.
public bool ClearErrors(string propertyName)
{
this.ValidatedAttributedProperties.Remove(propertyName);
if (this.Errors.Remove(propertyName))
{
OnErrorsChanged(propertyName);
return true;
}
return false;
}
// Optional
public bool PropertyHasErrors(string propertyName) => this.Errors.TryGetValue(propertyName, out IList<object> propertyErrors) && propertyErrors.Any();
#region INotifyDataErrorInfo implementation
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
// Returns all errors of a property. If the argument is 'null' instead of the property's name,
// then the method will return all errors of all properties.
public System.Collections.IEnumerable GetErrors(string propertyName)
=> string.IsNullOrWhiteSpace(propertyName)
? this.Errors.SelectMany(entry => entry.Value)
: this.Errors.TryGetValue(propertyName, out IList<object> errors)
? (IEnuemrable<object>)errors
: new List<object>();
// Returns if the view model has any invalid property
public bool HasErrors => this.Errors.Any();
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Check if IsAttributedPropertyValid Property was already called by 'IsValueValid'.
if (!this.ValidatedAttributedProperties.Contains(propertyName))
{
_ = IsAttributedPropertyValid(value, propertyName);
}
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnErrorsChanged(string propertyName)
{
this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
// Maps a property name to a list of errors that belong to this property
private Dictionary<string, IList<object>> Errors { get; }
// Track attribute validation calls
private HashSet<string> ValidatedAttributedProperties { get; }
}

Notify parent ViewModel of changes in child ViewModels

I have read a few articles on here, that describe how to listen to notifications raised. However: I am still having trouble to apply those to my application.
I currently have an application with several "pages".
One of the pages contains a WPF Treeview control in it along with several ViewModels and data models.
public class FoldersSearchViewModel
{
private ReadOnlyCollection<DriveTreeViewItemViewModel> _drives;
public FoldersSearchViewModel(string[] logicalDrives)
{
_drives = new ReadOnlyCollection<DriveTreeViewItemViewModel>(
Environment.GetLogicalDrives()
.Select(s => new DriveInfo(s))
.Where(di => di.IsReady)
.Select(di => new DriveTreeViewItemViewModel(di))
.ToList()
);
}
public ReadOnlyCollection<DriveTreeViewItemViewModel> Drives
{
get { return _drives; }
}
}
This ViewModel contains DriveTreeViewItemViewModels and is bound via DataContext to the UserControl ("page").
The Drive- and DirectoryTreeViewItemViewModel classes contain a few attributes, but are otherwise based on TreeViewItemViewModel, which you can see here:
public class TreeViewItemViewModel : INotifyPropertyChanged
{
#region Data
static readonly protected TreeViewItemViewModel DummyChild = new TreeViewItemViewModel();
readonly ObservableCollection<TreeViewItemViewModel> _children;
readonly TreeViewItemViewModel _parent;
bool _isExpanded;
bool _isSelected;
#endregion // Data
#region Constructors
protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren)
{
_parent = parent;
_children = new ObservableCollection<TreeViewItemViewModel>();
if (lazyLoadChildren)
_children.Add(DummyChild);
}
// This is used to create the DummyChild instance.
private TreeViewItemViewModel()
{
}
#endregion // Constructors
#region Presentation Members
#region Children
/// <summary>
/// Returns the logical child items of this object.
/// </summary>
public ObservableCollection<TreeViewItemViewModel> Children
{
get { return _children; }
}
#endregion // Children
#region HasLoadedChildren
/// <summary>
/// Returns true if this object's Children have not yet been populated.
/// </summary>
public bool HasDummyChild
{
get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
}
#endregion // HasLoadedChildren
#region IsExpanded
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is expanded.
/// </summary>
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
// Expand all the way up to the root.
if (_isExpanded && _parent != null)
_parent.IsExpanded = true;
// Lazy load the child items, if necessary.
if (this.HasDummyChild)
{
this.Children.Remove(DummyChild);
this.LoadChildren();
}
}
}
#endregion // IsExpanded
#region IsSelected
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is selected.
/// </summary>
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
#endregion // IsSelected
#region LoadChildren
/// <summary>
/// Invoked when the child items need to be loaded on demand.
/// Subclasses can override this to populate the Children collection.
/// </summary>
protected virtual void LoadChildren()
{
}
#endregion // LoadChildren
#region Parent
public TreeViewItemViewModel Parent
{
get { return _parent; }
}
#endregion // Parent
#endregion // Presentation Members
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion // INotifyPropertyChanged Members
}
I have followed the tutorial and ideas described in http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode and everything works great so far.
My problem is: I would like to add a string "selected" as an attribute to FoldersSearchViewModel, which would contain the path of the selected child ViewModel. The DriveTreeViewItemViewModel and the DirectoryTreeViewItemViewModel each have a "Path" property, that contains the full path to the child.
So: once OnPropertyChanged("IsSelected") is called, I would like to notify FoldersSearchViewModel about it and have the method copy the Path-property from the selected TreeViewItemViewModel into the new "selected"(string) attribute.
I could achieve this by passing the FoldersSearchViewModel-object to the children and children's children etc. in the constructor - but is there no better way of doing this? I suppose I should hook the FoldersSearchViewModel to the PropertyChanged-event of every node and sub-node, but I would like to know what someone with experience in MVVM would do in such a case.
By the way: I could use the WPF Treeview.SelectedItem to get the currently selected TreeViewItemViewModel, but that does not sound right since I want to keep the view, the models and the viewmodels separate.
P.s.: I tried reading and making use of MVVM in WPF - How to alert ViewModel of changes in Model... or should I?, but sadly it does not seem to solve my problem.
Any help is greatly appreciate!
The way mine works is, the Messenger service is a singleton as discussed. I also use DI, so a VM that needs to use it gets the IMessengerService instance injected into it.
IMessengerService looks like:
public interface IMessengerService : IServiceBase
{
Message<T> GetMessage<T>() where T : IMessageBase;
}
Message "param" classes are available application wide, so you might have something like:
public class FolderOpened : IMessageBase
{
}
So, the FolderOpened class is available throughout the application, its defined at compile time obviously.
Any client that would care about this message will subscribe to the message in its VM constructor:
_messenger.GetMessage().Handler += ...
It doesn't matter if the sender has "registered" it yet, the messenger is just based on the message class type.
At any time, anybody can send the message:
_messenger.GetMessage().SendMessage(...);
YMMV, but my messenger will automatically disconnect disposed / non existant subscribers, but really, the correct way would be for a VM to unsubscribe in its finalizer or dispose method.
Does that clear it up?
The MVVM way to do it would be to use a messenger / event aggregator pattern and broadcast an event.

When do default converters kick in?

With the following code, although Text property is bound to a DateTime source property, I noticed WPF seems to automatically convert the text to a DateTime, without me needing to write a ValueConverter. Can someone please shed some light on how this is done
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525"
>
<StackPanel>
<DatePicker Height="25" Name="datePicker1" Width="213" Text="{Binding Path=DueDate,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Window>
public class P
{
private DateTime? dueDate = DateTime.Now;
public DateTime? DueDate
{
get { return dueDate; }
set
{
dueDate = value;
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
P p = new P();
this.DataContext = p;
}
}
It is using the DateTimeTypeConverter from the Base Class Library (EDIT: Well, it could have used a TypeConverter however it appears that from #DeviantSeev's answer that they did not).
There 'default' converters you are talking about are actually TypeConverters (MSDN) and they have been a part of the .NET Framework since v2.0 and they are used through-out the Base Class Libraries. Another example of TypeConverters in WPF is the ThicknessTypeConverter for Padding, Margin, and BorderThickness properties. It converts a comma-delimited string to a Thickness object.
There are plenty of articles available if you want to understand them further.
There are two parts to using a TypeConverter - implementation of the class and then marking up your properties/types with TypeConverterAttribute.
For example, I recently had a custom control that required a char[] that I wanted to set from Xaml like so:
<AutoCompleteTextBox MultiInputDelimiters=",;. " />
Usage
[TypeConverter(typeof(CharArrayTypeConverter))]
public char[] MultiInputDelimiters
{
get { return (char[])GetValue(MultiInputDelimitersProperty); }
set { SetValue(MultiInputDelimitersProperty, value); }
}
Implementation
public class CharArrayTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (Type.GetTypeCode(sourceType) == TypeCode.String);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
return ((string)value).ToCharArray();
return value;
}
}
When to use a TypeConverter?
You can only use TypeDescriptors if you are writing a custom control as you need to be able to mark-up the property with the TypeDescriptorAttribute. Also I would only use TypeConverter if the conversion is rather a straight-forward - as in the example above where I have a string and want a char[] - or if there are multiple possible formats that I want to convert from.
You write IValueConverter when you want more flexibility on how the value to converted by driving it by data or a passing a parameter. For example, a very common action in WPF is converting a bool to Visibility; there are three possible outputs from such a conversion (Visible, Hidden, Collapsed) and with only two inputs (true, false) it difficult to decide this in a TypeConverter.
In my applications, to achieve this two inputs to three output problem I have written a single BoolToVisibilityConverter with a TrueValue and FalseValue properties and then I instance it three times in my global ResourceDictionary. I'll post the code sample tomorrow morning, I don't it in front of me right now..
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BooleanToVisibilityConverter : IValueConverter
{
public Visibility FalseCondition { get; set; }
public Visibility TrueCondition { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)value) ? TrueCondition : FalseCondition;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
return TrueCondition;
return FalseCondition;
}
}
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" FalseCondition="Collapsed" TrueCondition="Visible"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityCollapsedConverter" FalseCondition="Visible" TrueCondition="Collapsed"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityHiddenConverter" FalseCondition="Visible" TrueCondition="Hidden"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityHiddenWhenFalseConverter" FalseCondition="Hidden" TrueCondition="Visible"/>
The DatePicker is a custom control that was initially part of the WPF Toolkit before being added as a standard control in .NET 4.
I just went to the source code repository for the control to find you the exact source code which is responsible for the conversion of the text to date:
#region Text
/// <summary>
/// Gets or sets the text that is displayed by the DatePicker.
/// </summary>
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
/// <summary>
/// Identifies the Text dependency property.
/// </summary>
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(DatePicker),
new FrameworkPropertyMetadata(string.Empty, OnTextChanged, OnCoerceText));
/// <summary>
/// TextProperty property changed handler.
/// </summary>
/// <param name="d">DatePicker that changed its Text.</param>
/// <param name="e">DependencyPropertyChangedEventArgs.</param>
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DatePicker dp = d as DatePicker;
Debug.Assert(dp != null);
if (!dp.IsHandlerSuspended(DatePicker.TextProperty))
{
string newValue = e.NewValue as string;
if (newValue != null)
{
if (dp._textBox != null)
{
dp._textBox.Text = newValue;
}
else
{
dp._defaultText = newValue;
}
dp.SetSelectedDate();
}
else
{
dp.SetValueNoCallback(DatePicker.SelectedDateProperty, null);
}
}
}
private static object OnCoerceText(DependencyObject dObject, object baseValue)
{
DatePicker dp = (DatePicker)dObject;
if (dp._shouldCoerceText)
{
dp._shouldCoerceText = false;
return dp._coercedTextValue;
}
return baseValue;
}
/// <summary>
/// Sets the local Text property without breaking bindings
/// </summary>
/// <param name="value"></param>
private void SetTextInternal(string value)
{
if (BindingOperations.GetBindingExpressionBase(this, DatePicker.TextProperty) != null)
{
Text = value;
}
else
{
_shouldCoerceText = true;
_coercedTextValue = value;
CoerceValue(TextProperty);
}
}
#endregion Text
In most cases I believe WPF is calling ToString() for you however if you look at the code for the date picker the important line is
(string)GetValue(TextProperty)
notice it casts the value you assigned to the "Text" property to a string? The whole point is there is no default converter in the more traditional sense of BooleanToVisibilityConverter or something like that.

Resources