After playing around with this for a while I did figure out that as of at least the latest version of WPF in .Net 4, you can get built in red boxes around controls when errors exist in the middle tier. You have to add a small piece of code to the binding expression in your xaml. Lets say you have a binding expression that looks like this:
<TextBox Text="{Binding MyText}" />
If you want to show the red border on errors you would add this:
<TextBox Text="{Binding MyText, ValidatesOnDataErrors=True}" />
Well, what if the TextBox is bound to an integer and the user enters an ‘A’ in the textbox. In that case, you can add
<TextBox Text="{Binding MyText, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" />
*note that you don't need the PropertyChanged trigger, but that will validate when the user types instead of then the control loses focus.
The good news is that you can still use the style at the end of this article to make the error display any way you want it to. I personally do not like the red box, and I feel like it still needs the tooltip trigger in the style. You can also use the tricks below to subclass your own textbox. It might even be handy to do. You could add features to it that way and give those features to every textbox in your application at once…. but that would be another article.
Cheers!
[Here is the older article for reference]
- I wrote this while in a crappy mood. Good info, but I will be bitching a lot along the way. You’ve been warned.
Does WPF Suck?
In a nutshell yes, most everything sucks. I think it’s more of a question of relative suckness when compared to other things that also suck. Since technology often sucks a lot, even a moderate amount of suck can still work and even be fun. I have found a few issues with WPF and some of them are really WEAK! Microsoft seems content to leave a shit product alone, so this one is no exception. The ComboBox control has issues showing data values instead of display values to the user (yes I know you can create a template to fix it, but it should work out of the box as it does in every other technology). The RadioButton control loses its data binding when you select another radio in the same group. So if you select one button and it’s value is now true (selected) then click to another radio button in the same group, that new button goes true, but the original one not only stays true, but loses it’s binding altogether. Don’t worry about scrambling for the hotfix on this one, there isn’t one.
This leads me to IDataErrorInfo support
IDataErrorInfo
Coming from a world of windows programming using WinForms, and then moving into Silverlight I think I got spoiled. See at work we use CSLA as our middle tier of choice. It’s a great free business logic framework created by Rocky Lhotka. You can get it at http://lhotka.net/. Anyway, all business objects in Csla implement IDataErrorInfo. So if you create business rules (like the object is invalid if the Date of Birth is greater than today), and that rule gets violated, the business object is in an invalid state. It’s not throwing exceptions, nothing is blowing up, it’s just not valid. Csla reports these invalid objects via IDataErrorInfo. Too bad WPF doesn’t pick this up and show us a nice red border like Silverlight has built in. There are a couple of things you can do about it and many links on google about how to handle it all, but I decided to inherit a textbox and other controls and make my own textbox that works like the silverlight one …. well kind of….
ValidationError
So for WPF validation the team came up with the ValidationError object. Basically, you give this thing a BindingExpression and an error message in the form of a ValidationError object and then tell the Validation engine to mark it as “Invalid”, or not. Not a bad system, but why it wouldn’t accept IDataErrorInfo like everything else does is beyond me.
So we need to keep the Validation system happy while utilizing a hybrid engine of our own. It’s job will be to convert IDataErrorInfo stuff into WPF Validation stuff. Here’s how it goes.
ValidationBase
Validation base gets created by passing a FrameworkElement (like a TextBox or other control) and a DependencyProperty. The DependencyProperty is the property that has a binding to a middle tier object property. In the case of a TextBox, the “.Text” property is the property that would be invalid. Typing the wrong thing into the Text property would cause the business object to go invalid. Lucky for us there is a static DependencyProperty on the TextBox (TextBox.TextProperty). So we can create our ValidationBase object like this:
_validationBase = new ValidationBase(this, TextProperty);
Here is the full code of my custom TextBox control using ValidationBase:
#region
using System.ComponentModel;
using System.Windows.Controls;
#endregion
namespace WPF.Controls
{
[DesignTimeVisible(true)]
[ToolboxItem(true)]
public class ValidationTextBox : TextBox
{
private ValidationBase _validationBase = null;
public ValidationTextBox()
{
_validationBase = new ValidationBase(this, TextProperty);
}
}
}
So above I just pass in “this” which is the whole textbox control and the TextProperty which is really equivalent to this.TextProperty which a TextBox exposes.
I Tried And It Didn’t Work!!
Lucky for us there is more work to do to get functionality that should come out of the box. We need to give this new ValidationTextBox a style for it’s error condition. I created a xaml file in the same project called CustomControlStyles.xaml. It looks like this:
<Style TargetType=”{x:Type inputControls:ValidationTextBox}”>
<Setter Property=”Validation.ErrorTemplate”>
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill=”True”>
<!–<TextBlock DockPanel.Dock=”Right”
Foreground=”Orange”
Margin=”5″
FontSize=”12pt”
Text=”{Binding ElementName=MyAdorner,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}”>
</TextBlock>–>
<Border BorderBrush=”Red” BorderThickness=”3″>
<AdornedElementPlaceholder Name=”MyAdorner” />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<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>
It’s easier than it looks trust me. The first line just says this is a style for ALL ValidationTextBox controls. it does this because it does NOT define a x:Key. Remember that Validation framework WPF has I told you a little about earlier, well this next part defines a template to use if the ValidationTextBox in question is in an error condition. It can have any look and feel you want. I have left commented code I got from the web so you have an example of how to get to the error message itself, but I don’t like orange text you can’t read in my forms, so I commented it out. The cool part about this is the <AdornedElementPlaceholder /> Notice it has a red border around it. What happens at error time, is this style grabs the control (ValidationTextBox) that is in a error state, and replaces the <AdornedElementPlaceholder/> with the control in error. So if you have a textbox with an invalid state, it takes that textbox and drops it into that template (it will have the red border around it).
The last part is just a trigger that hooks the HasError property on the Validation framework and when that value is true, it sets the tooltip to the error message. I’m sure you or your designer can come up with something nice instead of this ugly template, but you can plug in whatever you want. This was simple to explain. That’s why I used the one from the web instead of my own.
You will have to reference this new style you created in your Resources or App.xaml so that it can be seen. Inside a ResourceDictionary it looks like this:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source=”/WPF.Controls;component/CustomControlStyles.xaml”/>
</ResourceDictionary.MergedDictionaries>
One Last Gotcha
One last thing, make sure when you use your new textbox that you use an x:Name and not a Name. It wont compile because the control code exists in your current solution.
Hope that helps you get past some of the suck of WPF. I’m sure we’ll all run into lots more.
Till next time.
-Jeff Noble