Sunday, October 9, 2011

Validation in the Mortgage Report Page

In the last post we covered data context and data binding in the MortgageReport Page.

Validation with Data Binding

In a previous post we discussed LINQ to SQL and explored our Database, data model and the DataContext generated by SQL Metal.

Now that we have a solid foundation of how binding works lets try to extend it's functionality.

<TextBox Name="principalT" Grid.Row="1" Text="{Binding Path=Principal, ValidatesOnDataErrors=True,  Converter={StaticResource currencyConverter}}" />
We can see that this TextBox is bound to the Principal property located inside the Mortgage object. The Text attribute also contains two other interesting properties.

ValidatesOnDataError lets the GUI component know that it has to check the Model object bound to the control for errors.

This is our validation logic on the Model:

partial void OnPrincipalChanging(decimal value)
{
    if(value <= 0)
        AddError("Principal", "Principal must be greater than 0.");
    else
        RemoveError("Principal", "Principal must be greater than 0.");
}
In our case we have a special setting for all TextBox's that modifies the look if errors have been encountered. We've placed it in the App.xaml file.

<Style TargetType="{x:Type TextBox}">
    <Setter Property="Validation.ErrorTemplate">
        <Setter.Value>
            <ControlTemplate>
                <DockPanel LastChildFill="True">
                    <TextBlock DockPanel.Dock="Top" FontSize="12pt">
                        *
                    </TextBlock>
                    <Border BorderBrush="Red" BorderThickness="1">
                        <AdornedElementPlaceholder />
                    </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>
Validation.ErrorTemplate specifies that this template should be applied when errors are encountered. In this case, we show a red border and an asterisk. We also change the ToolTip to show the error message returned from the Model validation.

Lets take a look at the result when invalid data is entered.









Converters

Converter={StaticResource currencyConverter} specifies how to convert from the data stored in the model to a string displayed in the TextBox and back.

However, We have a class called "CurrencyConverter," but not "currencyConverter." "currencyConverter" is specified at the top of the xaml file in the Page.Resources section.

<Page.Resources>
    <l:CurrencyConverter x:Key="currencyConverter" />
    <l:PercentageConverter x:Key="percentageConverter" />
    <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
    <Style x:Key="vStyle" TargetType="{x:Type DataGridCell}">
        <Setter Property="Visibility" Value="{Binding 
            YourObjectVisibilityProperty}"/>
    </Style>
</Page.Resources>
One more step is required. We need to make sure there is a reference to the namespace where the converters lie. "l" reffers to the the xml namespace attribute in the Page tag at the top of the file.
<Page x:Class="MortCalc.MortgageReportPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:MortCalc.Converters"
        xmlns:j="clr-namespace:MortCalc.AmortizeLogic"
        Title="Mortgage Calculator" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
Let's take a look at our currency converter:
namespace MortCalc.Converters
{
    [ValueConversion(typeof(decimal), typeof(string))]
    public class CurrencyConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, 
            object parameter, CultureInfo culture)
        {
            if (value == null)
                return 0;
            else
                return ((decimal)value).ToString("C2", culture);
        }

        public object ConvertBack(object value, Type targetType, 
            object parameter, CultureInfo culture)
        {
            if (value.ToString().Trim() == "")
                return 0;

            decimal result;
            decimal.TryParse(value.ToString(), NumberStyles.Currency, 
                culture, out result);
            return result;
        }
    }
}
Convert takes the decimal value and uses the ToString method, passing in the "C2" format, to retrieve a currency formatted in the passed in culture with an after decimal precision of 2. ConvertBack does the opposite using the TryParse method.

Using the converter for currency changes 20.3 to $20.30.

Likewise, I wanted the percentage rate to be human readable but stored in a format immediately usable in mortgage calculations. Something obtuse like .0675 gets converted to 6.75.

In the next post we will dive back into the MortgageReport Page xmal and take a look at the DataGrid.


Download the Code Here

No comments:

Post a Comment