![]() |
Platforms, Frameworks & Libraries »
Windows Presentation Foundation »
General
Advanced
An Error Provider for Windows Presentation FoundationBy Paul StovellAn implementation of an ErrorProvider for Windows Presentation Foundation. |
C#, XML, .NET, Win2K, WinXP, Win2003, Vista, Visual Studio, XAML, WPF, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

For the past few months, I've been working on a shared source accounting application called Trial Balance. Trial Balance is a personal project of mine, and is designed to be a demonstration of how I think developers should approach creating a rich client application using the Windows Presentation Foundation.
Whilst developing the new UI for Trial Balance, one of the hurdles I ran into recently was the lack of an ErrorProvider control, similar to what there is in Windows Forms.
Under Windows Forms, if you have a group of controls (e.g., text boxes) that are data-bound to a given data source, you can drag an ErrorProvider component onto the form and set its DataSource to the same data source the text boxes use. The ErrorProvider will then automagically display any errors on your objects, with no need to write validation code on the UI.
In this article, I'll demonstrate my version of the ErrorProvider, written specifically for the Windows Presentation Foundation. I'm posting this because I expect a lot of people will be wondering how to emulate this behaviour. I've also implemented a Strategy Pattern for displaying the errors, to keep the provider as reusable as possible.
I'd like to point out right now that this isn't anywhere near finished, and should be considered a "proof of concept". Please report any issues or suggestions as a comment on this article.
Before I get started though, thanks go to Mike Brown from the MSDN WPF forums who showed me the use of the WPF LogicalTreeHelper class. This class is the one that makes it easy to recurse through the layers of WPF elements on a window. Thanks Mike!
Here's a very basic example of some XAML that makes use of the ErrorProvider:
<Window x:Class="PaulStovell.Samples.WpfValidation.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:validation="clr-namespace:PaulStovell.Samples.WpfValidation"
Title="WpfErrorProvider" Height="300" Width="300"
>
<StackPanel>
<validation:ErrorProvider x:Name="_errorProvider" />
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock>Name:</TextBlock>
<TextBox Text="{Binding Path=Name}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock>Description:</TextBlock>
<TextBox Text="{Binding Path=Description}" />
</StackPanel>
</StackPanel>
</StackPanel>
</Window>
The ErrorProvider itself is a FrameworkElement, so it can be used inside your XAML. The only constraint so far is that the ErrorProvider element must be "lower down" in the stack of framework controls. I'll explain why lower down.
The ErrorProvider gets its error messages from its DataContext. This needs to be a class that implements the System.ComponentModel's IDataErrorInfo interface, as well as INotifyPropertyChanged. You can look at the included Account.cs class to see a rough implementation of these, or read my (much longer) Delegates and Business Objects article for a more in-depth discussion of the topic.
By default, the DataContext of the ErrorProvider would be set to the data context of its parent (or its parents' parent, or its parents' par...), so to use it, you should really only need to place it onto the right place on your Window. However, you could also assign the DataContext property explicitly if you like.
The demonstration code I've uploaded contains three classes. The first is the ErrorProvider class itself. The second is an interface called IErrorDisplayStrategy. This class defines a couple of methods that you'll need to implement to create your own ways of displaying errors:
bool CanDisplayForElement(FrameworkElement element)
void DisplayError(FrameworkElement element, string errorMessage)
void ClearError(FrameworkElement element) When you add the ErrorProvider to your form, it maintains a list of IErrorDisplayStrategy objects. When it needs to display an error for a given WPF control, it will consult this list, looking for a strategy that will work on the given element.
The third class I've included is TextBoxErrorDisplayStrategy. This is an implementation of IErrorDisplayStrategy, designed to handle TextBoxes:
public class TextBoxErrorDisplayStrategy : IErrorDisplayStrategy {
private Dictionary<FrameworkElement, Brush> _savedBrushes;
private Dictionary<FrameworkElement, string> _savedToolTips;
private Color _errorBorderColor;
private static readonly Color _errorBorderColorDefault =
Color.FromRgb(0xFF, 0x42, 0x2F);
public TextBoxErrorDisplayStrategy() {
_savedBrushes = new Dictionary<FrameworkElement, Brush>();
_savedToolTips = new Dictionary<FrameworkElement, string>();
_errorBorderColor = _errorBorderColorDefault;
}
public Color ErrorBorderColor {
get { return _errorBorderColor; }
set { _errorBorderColor = value; }
}
public bool CanDisplayForElement(FrameworkElement element) {
return element is TextBox;
}
public void DisplayError(FrameworkElement element, string errorMessage) {
TextBox textBox = (TextBox)element;
if (!_savedBrushes.ContainsKey(element)) {
_savedBrushes.Add(element,
(Brush)textBox.GetValue(TextBox.BorderBrushProperty));
}
if (!_savedToolTips.ContainsKey(element)) {
_savedToolTips.Add(element,
(string)textBox.GetValue(TextBox.ToolTipProperty));
}
textBox.SetValue(TextBox.BorderBrushProperty,
new SolidColorBrush(_errorBorderColor));
textBox.SetValue(TextBox.ToolTipProperty, errorMessage);
}
public void ClearError(FrameworkElement element) {
TextBox textBox = (TextBox)element;
if (_savedBrushes.ContainsKey(element)) {
textBox.SetValue(TextBox.BorderBrushProperty,
_savedBrushes[element]);
_savedBrushes.Remove(element);
}
if (_savedToolTips.ContainsKey(element)) {
textBox.SetValue(TextBox.ToolTipProperty,
_savedToolTips[element]);
_savedToolTips.Remove(element);
}
}
}
When told to display an error for a TextBox, it simply changes the border color and sets a tool tip. It gets a little complicated because it needs to maintain a list of changes so that they can be rolled back when the errors are cleared.
Using the Strategy Pattern means you can manipulate the element any way you like, and restore it any way you like. This is a big advantage over the standard Windows Forms ErrorProvider, which simply gives you an icon and tooltip.
Externally, the ErrorProvider exposes these methods:
AddDisplayStrategy() - call this to make the error provider aware of other IErrorDisplayStrategy classes. Alternatively, you can subclass the ErrorProvider and override the CreateDefaultDisplayStrategies method.
RemoveDisplayStrategy()
Validate() - this method checks all the bound controls on the form. I'll discuss this more below.
Clear() - removes all displayed errors.
GetFirstInvalidElement() - this is something the Windows Forms ErrorProvider can't do. Calling this method simply gets the first FrameworkElement on the page that has an error displayed. This method is useful because you can simply call it, then call the Focus() method on the element, to direct the user's attention to the next error they need to fix. Calling Validate() works like this:
ErrorProvider goes through every FrameworkElement on its parent control recursively, reflecting on them and looking for DependancyProperties.
ErrorProvider, it then calls the ClearError() method on all of the known IErrorDisplayStrategies. This means, it cleans up all errors first.
Path which points to a property, it uses that Path as the argument to the IDataErrorInfo indexer that is implemented on the bound object (our data context, in this case). This returns an error string.
IErrorDisplayStrategies, looking for one that matches the type of framework element it needs. When it finds one, it tells it to display the error. Again, I'd like to stress that this code isn't complete. It's hardly tested, and it does have issues. Here are a few I know of already:
ErrorProvider works the same way.
If anyone comes up with a better way to implement these things, I'd like to see it. I'd probably prefer if you could code up your own solution and blog it, or make a CodeProject article, rather than sending me lots of little code snippets to integrate. If you make an alternative, or improve on the approach, I'll happily add a link in this article.
Thanks for reading, and I hope you found this demo useful!
| You must Sign In to use this message board. | |||||||||||||||
|
|||||||||||||||
|
|||||||||||||||
|
|||||||||||||||
|
|||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 19 Jun 2006 Editor: Chris Maunder |
Copyright 2006 by Paul Stovell Everything else Copyright © CodeProject, 1999-2009 Web20 | Advertise on the Code Project |