Jul 14, 2011

WPF Validation on calculated fields

I'm working on a WPF application that uses a WCF service. The service communicates the GUI changes to a radar that runs on a separate computer. The WCF service also runs the parameters through a radar equation. The system parameters are such that at extreme values for all the fields the system can generate an invalid state. Typically the sampling rate becomes faster than the allowed. Since it's unclear which parameter the user will want to back-off, a good solution is to just show the calculated values and indicate when one of them is in error. Of course I also disable the Run button so there's no chance they actually try to command the system into an invalid state.

WPF Validation via IDataErrorInfo

There are many ways to achieve validation using WPF, including custom properties, XAML that checks for exceptions and the method I ended up using; implementing the IDataErrorInfo interface. This interface only has two members and only the indexer property this[string] needs an actual implementation, since you don't have to use the Error property. Given that the property bound to the displayed sampling rate widget is called SamplingRate here's a simple validator:

public string this[string columnName]
{
    get
    {
        switch (columnName)
        {
            case "SamplingRate":
                if (_samplingRate > MAX_SAMPLING_RATE_MHZ)
                {
                    return String.Format("Sampling Rate may not exceed {0} MHz.",MAX_SAMPLING_RATE_MHZ);
                }
                break;
        }
        return "";

    }
}

Returning a non-empty string for any property that matches columnName indicates a validation failure returning an empty string indicates the validation passed.

Now in my xaml  I just turn on the validation by adding the ValidatesOnDataErrors like this:

Text="{Binding SamplingRate, Mode=OneWay, ValidatesOnDataErrors=True}" 

Now when the validation is violated a red border appears around the text box with this attribute. This is the default handling for validation errors. Unfortunately, the nice message I returned is nowhere to be seen.

Showing your Validation Message

I'm not sure why the default behavior is to not show your message. It seems quite reasonable to have the message show up as a ToolTip by default. My solution was to to create a Style that retrieved the message and put it in a tool tip as shown:

<Style x:Key="ValidationHandler" 
        <Style.Triggers>
        <Trigger Property="Validation.HasError"
                 Value="True">
            <Setter Property="TextBox.ToolTip"
                    Value="{Binding RelativeSource={RelativeSource Self}, 
Path=(Validation.Errors)[0].ErrorContent}" /> </Trigger> </Style.Triggers> </Style>

 

The Problem with IsEnabled

Only one problem, it didn't work. When showing calculated fields I typically set the IsEnabled flag to false. The user not only can't make changes but they get a pretty decent visual indicator that changes aren't allowed.  Turns out that when a TextBox (and maybe all controls) are disabled the tool tips don't show up.  I'm not sure why and there may be a better work-around but since I already had the style, I did my best to fake the disabled behavior. Here's the new xaml for the control:

<TextBox Style="{StaticResource ValidationHandler}"
          IsReadOnly="True"
         Text="{Binding SamplingRate, Mode=OneWay, ValidatesOnDataErrors=True}"  />

I've set the IsReadOnly flag and removed the IsEnabled flag in the xaml for the control itself. You can also see how the ValidationHandler is hooked in. The new Style will take care of the IsEnabled flag. When no error is found IsEnabled will be false. If an error is found it will set IsEnabled to true and set the background gray (IsReadOnly is left set to True in both cases). The downside is the  user will still be able to click in the read-only box and highlight the value, but the TextBox will look like the disabled version and the user still can't make any changes.

<Style x:Key="ValidationHandler" BasedOn="{StaticResource nudSpacing}">
    <Setter Property="Control.IsEnabled"
            Value="False" />
    <Setter Property="Control.Background"
            Value="#F3F3F3"/>
        <Style.Triggers>
        <Trigger Property="Validation.HasError"
                 Value="True">
            <Setter Property="TextBox.ToolTip"
                    Value="{Binding RelativeSource={RelativeSource Self}, 
Path=(Validation.Errors)[0].ErrorContent}" /> <Setter Property="TextBox.IsEnabled" Value="True"></Setter> </Trigger> </Style.Triggers> </Style>

Now when the user violates the validation and puts the cursor over the field they see:SNAGHTML46718a5

About Me

My photo
Tod Gentille (@todgentille) is now a Curriculum Director for Pluralsight. He's been programming professionally since well before you were born and was a software consultant for most of his career. He's also a father, husband, drummer, and windsurfer. He wants to be a guitar player but he just hasn't got the chops for it.