Showing posts with label DataGridTextColumn. Show all posts
Showing posts with label DataGridTextColumn. Show all posts

Monday, October 10, 2011

Threading and Saving Data with LINQ to SQL

In our last post we looked at the actual calculations of the amortization.

Threading

Depending on the machine and the values the user has specified the calculation and displaying of the mortgage could lag. 100-200 milliseconds is the threshold before a user notices lag. We want the transition from MortgageHome Page to MortgageReport Page to be as seamless as possible while still immediately amortizing the mortgage. In order to accomplish this we need to run the calculations in their own worker thread, outside the UI thread.

In the MortgageReport page we set up the following threading.
//start a worker thread so it does not bog down the gui
private void Amortize()
{
    BackgroundWorker worker = new BackgroundWorker();

    worker.DoWork += delegate(object s, DoWorkEventArgs args) 
    {
        args.Result = RunAmortize(); 
    }; 
    worker.RunWorkerCompleted += delegate(object s, 
        RunWorkerCompletedEventArgs args) 
    {
        theMI = (MonthIteration)args.Result;
        SetGridData();
    }; 
    worker.RunWorkerAsync();
}

private MonthIteration RunAmortize()
{
    MonthIteration thisMI = new MonthIteration(m);
    return thisMI;
}
In the last post we saw how the MonthIteration class constructor ran all of our calculations for us. As soon as the first line in RunAmortize is complete so are our calculations.

I choose a BackgroundWorker due to it's straight forward approach of assignment to clearly spelled out events. In this case we are creating 2 anonymous delegates; one to start the thread and do the work and one to notify the thread that the work is complete. There are other events for updating progress and when dispose on the component is called. This could have easily just been pulled into its own class. However, since it's relatively uninvolved I kept it in the MortgageReport Page.

Saving Data with LINQ to SQL

The last piece of the puzzle is to save the data to the database for use later on. Because we put the legwork in for LINQ to SQL in a previous post, all we need to do now is use that code to save the data.
private bool IsValid(ref String em, bool checkForName)
{
    List<String> errors = new List<string>();

    if (checkForName && m.Name == null)
        errors.Add("You must specify a Name.");
    else if (checkForName && m.Name.Trim() == String.Empty)
        errors.Add("You must specify a Name.");
    if (m.Principal <= 0)
        errors.Add("You must specify Principal Amount greater than $0.00.");
    if (m.Term <= 0)
        errors.Add("You must specify a Loan Term greater than 0.");
    if (m.InterestRate <= 0)
        errors.Add("You must specify an Interest Rate.");
    em = String.Join(Environment.NewLine, errors);

    if (errors.Count > 0)
        return false;
    else
        return true;
}

Before we can even attempt to save we need to validate all the necessary values. In this case we pass in a string by ref to ensure the calling method can show the user the problems encountered.
private void SaveB_Click(object sender, RoutedEventArgs e)
{
    String em = "";
    if (!IsValid(ref em, true))
    {
        MessageBoxResult result = MessageBox.Show((Window)this.Parent, 
            em, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
    else
    {
        m.Date = DateTime.Now;

        if (isNew)
        {
            if (DBApi.DB.mortDB.Mortgage.Count() > 0)
            {
                m.Key = (from thisMort in DBApi.DB.mortDB.Mortgage
                            select thisMort.Key).Max() + 1; 
            }
            else
                m.Key = 0;
            DBApi.DB.mortDB.Mortgage.InsertOnSubmit(m);
        }

        DBApi.DB.mortDB.SubmitChanges();
        isNew = false;
    }
}

If the values are invalid we give the user immediate feedback in the form a error MessageBox.

One problem that crept up on me when I was working on this is the defaulting of the Key to 0. Since Key 0 already exists in the database, we are now no longer allowed to add new records. To get around this I simply did a quick LINQ to SQL query to find the Max Key and incremented it by 1.

InsertOnSubmit is the same as the insert command in T-SQL. SubmitChanges looks for updates, inserts or deletions and sends them to the database.

That's it! We now have a fully functional Mortgage Calculator that looks pleasant, is somewhat dynamic to the users preferences, stores UI state and gives the user a lot of useful information.

Future Improvements

When coding it's almost impossible to not notice improvements. The following are some features I would like to see in the Mortgage Calculator in the future.
  1. An installer package
  2. In-grid adjustments
  3. Better color schema
  4. More detailed stats on values such as
    1. Total insurance paid
    2. Total taxes paid
  5. Home page selection memory
  6. Years and months term input fields instead of just months
  7. PMI
  8. Date tracking by listing the start date
  9. Monthly payment date to adjust interest and principal to the day payment was received
  10. Application Icon
  11. MortgageHome showing more details about each Mortgage
  12. Start amortizing in the middle of the year and figure costs accordingly like Tax increasing at the end of the year, which might be 4 months into the loan.
  13. Graphs and charts
  14. User accounts
  15. User account summaries of the user's entire portfolio

Download the Code Here

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