Showing posts with label Converter. Show all posts
Showing posts with label Converter. Show all posts

Sunday, October 9, 2011

Mortgage Report XMAL Stored State and DataGrid

In the last post we covered validation with data binding and converters.

Overview of Goals for Usability

When I first started working on this project in WPF I had a short list of GUI guidelines I wanted to adhere to:
  1. Better overall use of space
  2. Uniform spacing and orientation of user input fields
  3. Ability to hide most optional fields
  4. Ability to hide optional columns in the Amortization Schedule
With WPF and a small amount of research you practically fall into the first 2 objectives.

Ability to Hide Most Optional Fields

WPF was well thought out. Somewhere along the line someone working on WPF realized that binding should not stop just at values but could be extended to attributes and states in GUI components.

I wanted the Expander components in my MortgageReport Page to be open or closed based upon their previous state when the last save of the Mortgage objects occurred.

In a previous post I showed the Mortgage table in our database. It contained a bunch of expand bit typed columns. These would save whether or not an Expander was expanded.

Lets take a look at one of the Expanders in xaml:
<Expander Header="Home Value" HorizontalAlignment="Left" 
    Name="homeValueE" VerticalAlignment="Top" Width="225" 
    Grid.Row="3" IsExpanded="{Binding Path=HomeValueExpand}">
...
</Expander>
IsExpanded="{Binding Path=HomeValueExpand}" tells Expander to bind to the HomeValueExpand property in the Mortgage object. Dead simple.

Ability to hide optional columns in the Amortization Schedule

Hiding columns in a grid was a little more difficult although not much.

WPF has Visual Trees and Logical Trees to represent the UI. The Logical Tree contains all UI components specified in xaml or programmatically generated. The Visual Tree contains only components that need to be rendered and inherit from the Visual or Visual3D classes.

<DataGrid Grid.Column="0" Grid.Row="0" Name="amortizeGrid" 
    CanUserResizeRows="False" CanUserSortColumns="False" 
    CanUserReorderColumns="False" AutoGenerateColumns="False" 
    SelectionMode="Single" AlternatingRowBackground="Beige" Margin="10,0" 
    BorderThickness="1" FontWeight="Normal" IsReadOnly="True">
    <DataGrid.ColumnHeaderStyle>
        <Style TargetType="Control">
            <Setter Property="FontWeight" Value="Bold"/>
        </Style>
    </DataGrid.ColumnHeaderStyle>
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding PaymentNum}" Header="Payment #" />
        ...
        <DataGridTextColumn Binding="{Binding CashFlow}" Header="Cash Flow" />
    </DataGrid.Columns>
</DataGrid>
While DataGrid is in the Visual Tree, DataGridColumns (a base class for DataGridTextColumns) is not. Therefore, binding to a property that modifies the visual appearence does not work.

The way I got around this was to hide or show columns in the DataGrid programmatically.

private void SetColumnsVisible()
{
    BooleanToVisibilityConverter btvc = new BooleanToVisibilityConverter();

    //we hate magic numbers
    amortizeGrid.Columns[GridConstants.AMORTIZE_GRID_TAX_COL].Visibility = 
        (System.Windows.Visibility)btvc.Convert(m.PropertyTaxShowDef, 
        null, null, null);
...
    amortizeGrid.Columns[GridConstants.AMORTIZE_GRID_CASHFLOW_COL].Visibility = 
        (System.Windows.Visibility)btvc.Convert(m.RentShowDef, 
        null, null, null);
}
Because Visibility is not a boolean property but an enum of Collapsed, Hidden or Visible, we need something to convert our bit/boolean values to Hidden or Visible. BooleanToVisibilityConverter is a class found Windows.Controls that does the conversion for you.

I hate magic numbers with the fury of a thousand suns. Nothing is more annoying then trying to keep straight what numbers represent when you have 50 other things to think about. What's more, what happens when the columns now represent something else? Using constants solves both of these problems, hence the GridConstants.

Loading Data into the Grid and Summary Data

Since binding is an amazingly effective tool in WPF I wanted to use it again for displaying the results of the Amortization logic. After the calculations are complete we receive a list of MonthIteration objects (we will cover its contents more in the next post). The MonthIteration class contains all the data that needs to be displayed in the Grid.

MonthIteration has public string properties that represent the underlying data. This allows for formatting of the values and feeding the DataGridTextColumns strings instead of something it doesn't know how to process.

private int _PaymentNum;
public String PaymentNum
{
    get
    {
        return _PaymentNum.ToString();
    }
}
...
private decimal _CashFlow;
public String CashFlow
{
    get
    {
        return _CashFlow.ToString("C2");
    }
}
If we look at our DataGrid code again we notice that the individual columns are bound.

<DataGrid Grid.Column="0" Grid.Row="0" Name="amortizeGrid" 
    CanUserResizeRows="False" CanUserSortColumns="False" 
    CanUserReorderColumns="False" AutoGenerateColumns="False" 
    SelectionMode="Single" AlternatingRowBackground="Beige" Margin="10,0" 
    BorderThickness="1" FontWeight="Normal" IsReadOnly="True">
    <DataGrid.ColumnHeaderStyle>
        <Style TargetType="Control">
            <Setter Property="FontWeight" Value="Bold"/>
        </Style>
    </DataGrid.ColumnHeaderStyle>
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding PaymentNum}" Header="Payment #" />
        ...
        <DataGridTextColumn Binding="{Binding CashFlow}" Header="Cash Flow" />
    </DataGrid.Columns>
</DataGrid>
The code that binds the list of MonthIteration to the DataGrid is shown below.
private void SetGridData()
{
    amortizeGrid.ItemsSource = theMI.ReturnAllIterations();
    totalInterestPaidL.Text = theMI.TotalInterestPaid;
    totalPaidL.Text = theMI.TotalInterestPrincipalPaid;
    int numOfMonths = theMI.NumOfPayments;
    int numOfYears = numOfMonths / 12;
    numOfMonths = numOfMonths % 12;
    loanTermL.Text = numOfYears.ToString() + " years " + 
        numOfMonths.ToString() + " month(s)";
    totalCostL.Text = theMI.Payment;
}
In a lot of Mortgage applications a summary of interesting facts about the mortgage is displayed to the user. The rest of the SetGridData method is just a summary of details I think might be helpful to the user.













13 years 4 months is a rather odd length for a mortgage. This is due to the implementation of my Extra Payment field shortening the length of the loan.















Each month the user stipulated paying an extra $321.00. This cut the mortgage time down by more then half. Having the summary information at the top is a nice way around scrolling to the bottom of the amortization and dividing by 12.

In our next post we will go more in depth with the amortization calculations.


Download the Code Here

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