Lab 7: Performing Calculations in Excel Solutions
Lori Turner
Microsoft Corporation
March 2004
Applies to:
Microsoft® Visual Studio® Tools for the Microsoft Office System
Microsoft Office Professional Edition 2003
Microsoft Visual Basic® .NET 2003
Summary: Demonstrates how to create an Excel solution using an existing workbook so you can take advantage of the calculation and formatting capabilities available in Excel. (11 printed pages)
Contents
Introduction
Exercise 1: Managing Data Entry and Formatting in a Worksheet Using the Change Event
Exercise 2: Calling Back from Excel into Your Assembly
Next Steps
Download the VSTOLabs.exe sample file.
Introduction
The objective of this lab is to show how you can use the calculation and formatting capabilities in Microsoft® Office Excel to manage data entry in a workbook that calculates loan payments and computes an amortization schedule. The lab also demonstrates how you can call back from Excel into your assembly so you can use the return value of a function in the assembly as a worksheet function (or a user-defined function). You create your project using Microsoft Visual Studio® Tools for the Microsoft Office System.
Estimated time to complete:
- Exercise 1: Managing Data Entry and Formatting in a Worksheet Using the Change Event - 20 minutes
- Exercise 2: Making a Callback from Excel into Your Assembly - 10 minutes
For a link to all labs in this series, see Visual Studio Tools for the Microsoft Office System Training.
Note The labs in this series are designed to be completed in order.
Exercise 1: Managing Data Entry and Formatting in a Worksheet Using the Change Event
The objective of this exercise is to illustrate how you can apply a managed code solution to an existing workbook to use the calculations and formatting in the workbook. In particular, you handle the Change event of the worksheet to manage data entry and formatting; the event is wired and un-wired on the fly.
This exercise includes the following tasks:
- Create a new Excel Workbook project for an existing workbook.
- Create an amortization schedule using Excel formatting and calculations.
Task 1: Create a New Excel Workbook Project for an Existing Workbook
Before creating the new project, examine the existing workbook.
Copy Amortization.xls from C:\Labs\Files to C:\Labs\Lab7.
Open C:\Labs\Lab7\Amortization.xls in Excel to examine the workbook.
Note the following:
- All cell formulas needed to calculate a monthly payment and to create an amortization schedule are already in place.
- The ranges that you examine or change at run time are predefined with range names. On the Insert menu, click Names, and then click Define to observe the names defined for various ranges on the worksheet.
- Only three pieces of data are needed to compute the monthly payment and schedule: Loan Amount, Interest Rate, and Years for the loan.
- There is an additional area that is used to compute a "what-if" scenario for paying off a loan earlier than the load period. You use the Excel Solver Add-in at run time to solve the "what-if" scenario.
- Ranges in the workbook where the user enters data are set up with data validation to ensure that the data entered by the user matches certain criteria. To examine the data validation for a cell, select the cell and click Data Validation on the Data menu.
Close Amortization.xls without saving your changes and quit Excel.
To create a new project for an existing workbook
Start Visual Studio .NET.
On the File menu, point to New, and click Project.
The New Project dialog box appears.
Select Visual Basic Projects under Microsoft Office System Projects.
Select the Excel Workbook template.
Type Amortization for the name of your new project and C:\Labs\Lab7 for the location.
Click OK.
In the Microsoft Office Project Wizard, select Existing Document, locate C:\Labs\Lab7\Amortization.xls, and then click Finish.
Task 2: Create an Amortization Schedule Using Excel Formatting and Calculations
As noted in the previous section, the Excel workbook is already completely structured to calculate a loan payment and compute an amortization schedule. Now, you can use code to manage data entry in the worksheet and react to user input to format the workbook so that the results displayed are appropriate for the user's entries.
To manage formatting and data entry in the worksheet, you handle the worksheet Change event. To wire up the Change event for the worksheet, you do not use WithEvents. Instead, you use AddHandler to wire the Change event delegate to a specific function at run time. You use AddHandler so that you can in turn use RemoveHandler to un-wire the Change event delegate from the handler at points in the code where you are programmatically changing the worksheet and do not want the Change event handler to execute.
To create an amortization schedule
Add the following class-level variables to the OfficeCodeBehind class:
Dim sheet As Excel.Worksheet Dim rngLoanAmt As Excel.Range Dim rngIntRate As Excel.Range Dim rngYears As Excel.Range Dim rngWhatIfYears As Excel.Range Dim rngWhatIfArea As Excel.Range Dim rngAddlPmt As Excel.Range Dim rngMonthlyPmt As Excel.Range Dim nScheduleMonths As Int32
The "WhatIf_Area" range on the worksheet should not be available to the user until all the data required to calculate a loan payment and schedule has been entered. The WhatIfAvailable function enables or disables the cells of the "WhatIf_area" based on a Boolean parameter.
Add WhatIfAvailable to the OfficeCodeBehind class:
Private Sub WhatIfAvailable(ByVal bAvailable As Boolean) ' Reset the formula for WhatIf_Years = Years and reset ' the Additional_Payment = $0. rngWhatIfYears.Formula = "=Years" rngAddlPmt.Value = 0 ' Lock/unlock the WhatIf_Years range. rngWhatIfYears.Locked = Not (bAvailable) ' Format the WhatIf_Area to give the appearance that the range ' is either enabled or disabled. If bAvailable Then rngWhatIfArea.Font.ColorIndex = Excel.Constants.xlAutomatic rngWhatIfYears.Interior.ColorIndex = Excel.Constants.xlAutomatic Else rngWhatIfArea.Font.ColorIndex = 48 'Gray rngWhatIfYears.Interior.ColorIndex = 19 'Yellow End If End Sub
The ShowSchedule function formats the Schedule range on the workbook so that the schedule displays the correct number of payments based on the user's most current entries for the loan calculation. ShowSchedule also calls the WhatIfAvailable to enable or disable the "What-if" area on the worksheet; ShowSchedule un-wires the Change event handler while modifying the worksheet and then re-wires the Change event when those modifications are complete.
Add ShowSchedule to the OfficeCodeBehind class:
Private Sub ShowSchedule(ByVal WhatIfReset As Boolean) Try ' Unprotect the sheet and remove the handler for the Change ' event so that your programmatic changes are not caught ' by the Change event handler. sheet.Unprotect() RemoveHandler sheet.Change, AddressOf LoanChange Dim rngSchedule As Excel.Range = sheet.Range("Schedule") ' If there is data in the loan_amount, interest_rate, ' and months defined ranges, then go ahead and create ' the amortization schedule on open, otherwise set the ' status bar text. If rngLoanAmt.Value Is Nothing Or _ rngIntRate.Value Is Nothing Or _ rngYears.Value Is Nothing Then ' Set the status bar with instructional text. ThisApplication.StatusBar = "Please provide a " & _ "Loan Amount, Interest Rate, and Number of Months." ' Disable the WhatIf Area of the worksheet. WhatIfAvailable(False) ' Hide the Amortization schedule. rngSchedule.Rows.Hidden = True Else ' Show the number of rows in the Schedule defined range ' based on the number of months in the schedule. rngSchedule.Rows.Hidden = True rngSchedule = rngSchedule.Resize(nScheduleMonths + 1, _ rngSchedule.Columns.Count) ThisWorkbook.Names.Add("Schedule", rngSchedule) rngSchedule.Rows.Hidden = False CType(rngSchedule.Item(2, 1), Excel.Range).Select() ' Set the status bar to the default. ThisApplication.StatusBar = False ' Enable the WhatIf Area on the worksheet. If WhatIfReset Then WhatIfAvailable(True) End If ' Re-protect the sheet and wire up the Change event handler ' for the sheet. AddHandler sheet.Change, AddressOf LoanChange sheet.Protect() sheet.EnableSelection = _ Excel.XlEnableSelection.xlUnlockedCells Catch ex As Exception MessageBox.Show("Error creating amortization schedule: " & _ ex.Message, "Amortization", MessageBoxButtons.OK, _ MessageBoxIcon.Error) End Try End Sub
The LoanChange function serves as the handler for the worksheet Change event delegate. You use range intersection and union to determine if the user has entered data in the Loan_Amount, Interest_Rate, or Years defined ranges and, if so, call the ShowSchedule function.
Add the LoanChange function to the OfficeCodeBehind class:
Private Sub LoanChange(ByVal Target As Microsoft.Office.Interop.Excel.Range) Dim rng As Excel.Range Try ' Check to see if the changed cell is for Interest_Rate, ' Loan_Amt, or Years, and if so, compute the number ' of months and show the Amortization schedule. rng = ThisApplication.Intersect(Target, _ ThisApplication.Union(rngIntRate, rngLoanAmt, rngYears)) If Not (rng Is Nothing) Then nScheduleMonths = Convert.ToInt32(rngYears.Value) * 12 ShowSchedule(True) Return End If Catch ex As Exception MessageBox.Show("Error creating amortization schedule: " & _ ex.Message, "Amortization", MessageBoxButtons.OK, _ MessageBoxIcon.Error) End Try End Sub
Now you add the functionality needed to calculate the user's "What-If" scenario. The Change event handler for the worksheet detects when the user changes the number of years for their "What-If" scenario. When the number of years for the "What-If" scenario changes, you use the Excel GoalSeek function to compute the additional payment required to result in a zero balance for the number of years entered by the user.
To calculate the "What-If" scenario
Append the following code to the LoanChange function, which serves as the event handler for the worksheet Change event:
Try ' Check to see if the changed cell is WhatIf_Years, and if ' so, use the Goal Seek to compute the additional payment ' required to reach a zero balance in the specified number ' of years and show the Amortization Schedule. rng = ThisApplication.Intersect(Target, rngWhatIfYears) If Not (rng Is Nothing) Then sheet.Unprotect() RemoveHandler sheet.Change, AddressOf LoanChange Dim rngToZero As Excel.Range = CType( _ sheet.Range("$G$" & (10 + Convert.ToInt32( _ rngWhatIfYears.Value) * 12)), _ Excel.Range) rngToZero.GoalSeek(0, rngAddlPmt) AddHandler sheet.Change, AddressOf LoanChange nScheduleMonths = Convert.ToInt32( _ rngWhatIfYears.Value) * 12 ShowSchedule(False) sheet.Protect() sheet.EnableSelection = _ Excel.XlEnableSelection.xlUnlockedCells End If Catch ex As Exception MessageBox.Show("Error creating amortization schedule: " & _ ex.Message, "Amortization", MessageBoxButtons.OK, _ MessageBoxIcon.Error) End Try
When the workbook opens, you set up references to the defined ranges on the worksheet, wire the worksheet's event delegate to the LoanChange handler function, and call ShowSchedule to display (or hide) the amortization schedule.
Add the following code to the workbook Open event:
Private Sub ThisWorkbook_Open() Handles ThisWorkbook.Open ThisApplication.ScreenUpdating = False ' Set up references for the worksheet and the defined ' ranges on the worksheet. sheet = CType(ThisWorkbook.Worksheets(1), Excel.Worksheet) rngLoanAmt = sheet.Range("Loan_Amount") rngIntRate = sheet.Range("Interest_Rate") rngYears = sheet.Range("Years") rngWhatIfYears = sheet.Range("WhatIf_Years") rngWhatIfArea = sheet.Range("WhatIf_Area") rngAddlPmt = sheet.Range("Additional_Payment") rngMonthlyPmt = sheet.Range("Monthly_Payment") AddHandler sheet.Change, AddressOf LoanChange ' Create the Amortization schedule. ShowSchedule(True) ThisApplication.ScreenUpdating = True End Sub
Testing the Application
Try it out:
Press F5 to build and run the project.
Enter the required data into the worksheet:
Loan Amount: 175000
Interest Rate: 7.5%
Years: 30
The monthly payment is calculated, and the amortization schedule appears. For the loan example above, the monthly payment would be $1223.63.
Change the number of years in the "What-If" scenario to 25.
The additional payment required to reach a zero balance in 25 years is computed, and the amortization schedule is updated as illustrated in Figure 1 below.
Figure 1. Updated amortization schedule
Exercise 2: Calling Back from Excel into Your Assembly
The objective of this exercise is to illustrate how you can call back from Excel into your assembly. One reason you might wish to make a callback is to use the return value of a function in the assembly as a worksheet function (or a user-defined function).
There is no way to call back into the assembly without using Visual Basic for Applications (VBA) code. All of the projects that you have created thus far have run under High macro security mode because managed code extensions are governed by Microsoft .NET Framework security and are not affected by the Excel VBA macro security rules. By injecting VBA code into the workbook to make a callback, the Amortization solution is subject to your VBA macro setting.
To call back from Excel into your assembly
Start Excel.
On the Tools menu, click Macro, and then click Security.
Change your security to Medium and click OK.
On the File menu, click Open.
Browse to select C:\Labs\Lab7\Amortization.xls.
Hold down the SHIFT key to suppress your code from running, and click Open.
**Note **To prevent your code from executing, you must open the workbook using the File menu. The code is not suppressed if you hold the SHIFT key and open the workbook from the Getting Started task pane.
Select cell G2.
On the Format menu, click Cells.
Select the Protection tab, clear the Locked check box, and click OK.
Press ALT+F11 to start the Visual Basic Editor.
On the Insert menu in the Visual Basic Editor, click Module.
Add the following VBA code to the module:
Public objManaged As Object Public Sub RegisterCallback(o As Object) Set objManaged = o End Sub Public Function UserName() As Variant UserName = objManaged.UserName End Function
Close the Visual Basic Editor.
Save your changes to Amortization.xls and quit Excel.
In your Visual Studio .NET Amortization solution, append the following line of code to the Open event handler of the workbook:
ThisApplication.Run("RegisterCallback", Me)
Add the UserName function to the OfficeCodeBehind class:
Public Function UserName() As Object Try Return System.Security.Principal.WindowsIdentity. _ GetCurrent.Name.ToString Catch ex As Exception Return "Error!" End Try End Function
Testing the Application
Try it out:
Press F5 to build and run the project.
In cell G2 of Amortization.xls, type the following formula and press ENTER:
=UserName()
The value returned by the UserName function in the managed code extension is returned to the cell.
**Note **If your VBA macro security is set to High, the managed code extension would continue to work, but the VBA wrapper function for UserName would not work unless the workbook is signed.
Next Steps
For more information, see Visual Studio Tools for the Microsoft Office System or visit the Office Developer Center.
For more information, as well as links to other labs in this series, see Visual Studio Tools for the Microsoft Office System Training.