xlVBAManual

advertisement
KENSINGTON COLLEGE
23 Bloomsbury Square
London WC1A 2PJ
020 7580 1113
Training Manual
Excel VBA
chapter
ch1
ch2
ch3
ch4
ch5
ch6
ch7
ch8
ch9
ch10
ch11
ch12
ch13
extra
Please do not save
any code from the
classes unless asked.
It’s all here!
From experience, this
is a major source of
mistakes - when
you’re not running the
code that you think
you are.
General note. Whenever assigned work is emailed please mark the subject as “Help
Please” if you have query which needs to be answered immediately.
Excellent online course!: http://www.homeandlearn.org/index.html
1
document1
Macros on Excel 2007/2010
To write any code for 2007,10/13 we must first make sure that the Developer Toolbar is on the menu
as follows.
o For 2007 Click here.
o For 2010,13 Click here.
o Options.
o Excel Options.
o For 2007
o Popular.
o Check Show Developer tab in
Ribbon and click OK .
2010,2013:
o Customize Ribbon
o Check the Developer
checkbox and click OK.
2
document1
To place a command button on the worksheet.
o Choose the Developer tab.
o Click on the Insert button. (Don't
confuse this with Insert on the menu.)
Choose Developer Tab
o (Make sure that you
choose the SECOND LOT
of controls. The upper set
belong to Excel 95!).
o Click on the Command Button button.
o
o Draw a button on the sheet and
double-click on it to write some code.
Now follow page 10 of the book.
We can now write our VBA code
save as ….
3
document1
Arranging Windows
Once code has been written, arrange both windows so that they are visible as shown below.
o Make sure that Design Mode is off.
o Now simply modify code in
the code window and then click
the command button to run it.
o (If you lose your code window, it is
probably easiest to right-click the sheet tab
at the bottom of the Excel Window and ..
… choose View Code.
Make sure - if using 2007 or 2010 to save your workbook as a macro enabled file ie
and .xlsm file not .xlsx - otherwise you will lose all your valuable code!
Page 16: The statement x = 2
should be read RIGHT TO LEFT.
ie the 2 is placed into the variable x.
Try this
Let x = 2
This has exactly the same effect. The keyword Let is optional and is rarely used.
It does however convey this RIGHT to LEFTness whereby the value of x takes on the value to the
right of the equal sign. This “right to leftness” is pervasive in all programming languages so get used
to it!
4
document1
Chapter 2 code:
Dim x As Integer
x = 2
Range("A1").Value = x
Dim x As Integer
x = Range("A1").Value
Range("A1").Value = Range("A2").Value
Range("A2").Value = x
'MsgBox "Simple"
Dim x As Integer
x = 2
MsgBox "The value of x is " & CStr(x)
Sales = 2
MsgBox Sales
Dim lg As Long
Dim sg As Single
Dim db As Double
Dim tf As Boolean
Dim vr As Variant
Dim dt As Date
lg = 205
sg = 2.125
db = 2.125
tf = True
vr = 2.6
dt = #1/1/2005#
Range("A1") = lg
Range("A2") = sg
Range("A3") = db
Range("A4") = tf
Range("A5") = vr
Range("A6") = dt
Dim x As Single
x = Range("a1").Value
Range("B1").Value = x * 3 / 4 + 3
'x = 8
'Range("A1").Value = 16 / x - 1
'Range("A2").Value = 18 / (x + 1)
5
document1
Exercises:
1. Here is a formula to calculate the Fahrenheit temperature given the Centigrade.
Do the equivalent in VBA ie write code to take the Centigrade value from A2 and put the equivalent
Fahrenheit temperature into B2. (Alternatively you could MsgBox the result.)
The moral of this? If something
can be done just as well in Excel
rather than VBA use Excel!
2. Find the total amount when 100 is invested at 10% pa for 5 years
3. For finance students only: Find the total amount when 100 is invested at 10% continually
compounding for 5 years.(Hint use Exp())
Course Work Exercise:
We wish to be able to …
o …. type a value into A1…
o …. and then click
the command button.
…. Whereupon, a message box will
appear showing the corresponding
amount of VAT (17.5%)
Improvement:
If you are saving a workbook which
has got some code it must be
saved as an .xlsm not an .xlsx.
Show the original amount and text as well
– as shown here.
Use a variable to hold the value in cellA1.)
6
document1
Website for homework solutions and course code:
http://www.una.co.uk/
To see Chapter by chapter demonstrations see
http://www.una.co.uk/BookSummary.xls
It is usually best to send CW as
text – in email even - but if you
wish to send it as a workbook
make sure it is .xlsm.
MsgBox 100 * Exp(0.1 * 5)
(For financial students: http://finance.bi.no/~bernt/gcc_prog/)
7
document1
Chapter3
Page 28 to 38:
View Locals Window as well:
o
View, Locals Window.
Code:
page 28:
Dim i As Integer
For i = 1 To 3
MsgBox "Simple"
Next i
page 29:
Dim i As Integer
For i = 1 To 5
Cells(i, 1).Value = 10
Next i
'Cells(1, 2).Value = 3
page 32:
Dim i As Integer
For i = 1 To 5
Cells(i, 1).Value = 10
Debug.Print i
Next i
Dim i As Integer
i = 2
If i = 2 Then Cells(1, 1).Value = "yes"
'Cells(1, 2).Value = "of course"
'End If
'Else
'Cells(2, 1) = "sorry"
'End If
page 35:
Private Sub cmdLogical_Click()
Dim i As Integer, j As Integer
i = 1: j = 3
If i = 1 And j = 2 Then Cells(1, 1) = "yes"
'If Not i = 2 Then Cells(1, 1) = "yes"
End Sub
8
document1
page 36:
Private Sub cmdDoLoopUntil_Click()
Dim i As Integer
i = 0
Do
i = i + 1
Cells(i, 1) = 10
Loop Until i = 5
End Sub
Private Sub cmdDoUntilLoop_Click()
Dim i As Integer
i = 0
Do Until i = 5
i = i + 1
Cells(i, 1) = 10
Loop
End Sub
Private Sub cmdDoWhileLoop_Click()
Dim i As Integer
i = 0
Do While i < 5
i = i + 1
Cells(i, 1) = 10
Loop
End Sub
Private Sub cmdDoUntilContd_Click()
Cells(1, 1).Select
Do Until IsEmpty(ActiveCell.Value)
MsgBox ActiveCell.Value
ActiveCell.Offset(1).Select
Loop
End Sub
Private Sub cmdSelectCase_Click()
Dim x As Integer
x = 7
Select Case x
Case 5
MsgBox "Five"
Case 7
MsgBox "Seven"
End Select
End Sub
9
document1
Private Sub cmdPracticalExample_Click()
Dim Sales As Single, comm As Single
Sales = Cells(103, 10).Value
Select Case Sales
Case Is < 1000
comm = 0.05
Case 1000 To 2000
comm = 0.1
Case Is > 2000
comm = 0.15
End Select
Cells(103, 11) = Sales * comm
End Sub
Private Sub cmdProgramDev_Click()
Dim i As Integer, c As Integer
c = 0
For i = 1 To 8
If Cells(i, 1).Value = 12 Then
c = c + 1
End If
Next i
MsgBox Str(c) & " twelves found"
End Sub
Private Sub cmdFlag_Click()
Dim i As Integer, fl As Boolean
fl = False
For i = 1 To 8
If Cells(i, 1).Value = 12 Then fl = True
Next i
If fl = True Then
MsgBox "Found a 12"
Else
MsgBox "12 not found"
End If
End Sub
Private Sub cmdFlagFlexibility_Click()
Dim i As Integer, fl As Boolean, x As Variant
x = Cells(1, 3).Value
fl = False
For i = 1 To 8
If Cells(i, 1) = x Then
fl = True
Exit For
End If
Next i
If fl = True Then
MsgBox "Found a " & x
Else
MsgBox x & " not found"
End If
10
document1
End Sub
Private Sub cmdLoopsInLoops_Click()
Dim i As Integer, j As Integer
For i = 1 To 3
For j = 1 To 4
Cells(i, j).Select
MsgBox Cells(i, j).Value
Next j
Next i
End Sub
Private Sub cmdNumberSearch_Click()
Dim i As Integer, fl As Boolean, _
x As Variant, j As Integer
For j = 1 To 3
fl = False
x = Cells(j, 3).Value
For i = 1 To 8
If Cells(i, 1).Value = x Then
fl = True
Exit For
End If
Next i
If fl = True Then
MsgBox "Found a " & x
Else
MsgBox x & " not found"
End If
Next j
End Sub
Private Sub cmdMoveRight_Click()
Dim i As Integer, c As Integer
c = 0
For i = 1 To 8
If Cells(i, 1) <> "" Then
c = c + 1
Cells(c, 2).Value = Cells(i, 1).Value
End If
Next i
End Sub
Private Sub cmdDeleteBlanks_Click()
Dim i As Integer, c As Integer
c = 0
For i = 1 To 8
If Cells(i, 1).Value <> "" Then
c = c + 1
If c < i Then
Cells(c, 1).Value = Cells(i, 1).Value
Cells(i, 1).Value = ""
End If
End If
Next i
End Sub
11
document1
ElseIf
ElseIf is similar to another If, but if an ElseIf (or If) is executed, the following ElseIfs don’t
get a look in! eg:
Private Sub CommandButton1_Click()
Dim x As Integer
x = 3
o Once this is found to be true, the
If x > 1 Then
other Elseifs are not considered
MsgBox "Greater than 1"
(even though they are true!!!)
ElseIf x > 2 Then
MsgBox " Greater than 2"
ElseIf x > 3 Then
MsgBox " Greater than 3"
End If
End Sub
only!
Select Case
… the same as ElseIf eg :
Private Sub CommandButton1_Click()
Dim x As Integer
x = 0
Select Case x
o Once this is found to be true, the other
Case Is < 1
Case statements are not considered (even
MsgBox "Less than 1"
though they are also true!)
Case Is < 2
MsgBox "Less than 2"
Case Is < 3
MsgBox "Less than 3"
End Select
End Sub
Only gives:
ie after one correct Case is encountered, all the following Case statements are not even considered!
12
document1
Do…Loop
cont’d
Do can be used without a loop counter. ie we can loop around until a certain condition (in this case
finding a blank cell) is satisfied.
Cells(1, 1).Select
Do Until IsEmpty(ActiveCell.Value)
MsgBox ActiveCell
ActiveCell.Offset(1).Select
Loop
ActiveCell.Offset(1)means the cell beneath the active cell.
(OffSet will be considered on p.117 of the book.)
Result:
……..
(Strictly speaking, IsEmpty should really only be used in code to test whether a Variant variable
has not been initialized with a value - but it seems to work in this situation where we are testing to
see whether a cell contains any value (text or number).)
13
document1
Swapping
Exercise:
Preliminaries:
1. We have already written code to swap two values (as below).
Private Sub CommandButton1_Click()
Dim x As Integer
x = Range("A1").Value
Range("A1").Value = Range("A2").Value
Range("A2").Value = x
End Sub
2. Re-write it using Cells notation.
3. Write code to test whether a 2nd one down (in this case the 2) is smaller than the first value.
4. Write some code to swap two such values but only if the 2 nd value is smaller than the first. (ie the
top value should be the smaller as shown below.)
(Combine programs 2. & 3. above, ie enclose the swap code of program 2 in the If statement of
program 3 above.)
14
document1
Exercise:
Find the category:
see page 5 of Soln Manual
AddressesExpt .xlsm boundary sheet
A salesman earns 2750.
He will receive 8% on the first 1000.
12% on the next 1000.
14% on the next 500
and
16% for the excess over 2500. ie 14% of 250.
amt
2750
boundary
0
8%
1000
10%
2000
12%
2500
14%
3000
16%
4000
20%
eg for any amount
between 0 and 1000 the
commission would be 8%.
The essential part of this problem is to find which category the 2750 is in
ie between the 2500 and the 3000. Let’s concentrate on that first:
Ex1
For starters:
CommandButton1
Dim amt As Double, i As Integer, lwr As Double, upr As Double
amt = Cells(1, 1).Value '2750
For i = 1 To 5
lwr = Cells(i,2).Value '2750
upr = Cells(i + 1,2).Value '2000
'etc…..
Next i
Maybe MsgBox lwr & "
" & upr to see its working OK so far.
Ex2:
How would we deal with say 4500? (Change cell A1 to 4500) ie for ANY value above 4000?
It doesn’t have an upper boundary so really we only want the lower limit.
Hint: You might use Exit For once you’ve found the lower limit.
The result could be
The next stage would be to work out the actual commission for eg
2750:
15
document1
Here is the calculation on the s/sheet – just for 2750.
2750
0
8%
1000
80
1000
10%
1000
100
2000
12%
500
60
2500
14%
250
35
3000
16%
4000
20%
2750
275
8% of 1000 etc.
tot comm
This is not as easy as it looks.
The strategy will be:
Loop down as we did previously
For i = 1 To 6
And check it’s the last one or not ie test for i = 6.
If it is …
t=(amt =lwr)*rate
If it’s not there are two cases:
1. Its in the gap
t=(amt =lwr)*rate
2. Its over the border.
t=(upr =lwr)*rate
In all cases do a running total of the commission
comm = comm + t
16
document1
Reconciliation Exercise
Suppose we have cash book entries for cheques received and a bank statement for cheques
hopefully banked.
We wish to delete the entries which exactly correspond, so that, in this case we will have:
As a further exercise, we may wish to place the deleted items in a separate table ie.
It may be better/easier to just COLOUR the entries which are the same rather than delete them.(see
below.)
I may also be easier to just do the Cheque No s for the moment
ie Just use this simplified table first:
(For copying.)
bank
104685
104686
104687
104688
104689
cash
104688
104682
104686
104685
104681
You could choose to perhaps embolden the common elements using Font.Bold = True or
perhaps make then red with Font.ColorIndex = 3.
The result might be:
17
document1
Strings
Page
Private Sub cmdStrings_Click()
Dim st1 As String, st2 As String
st1 = "house": st2 = "boat"
Cells(1, 1) = st1 & st2
Cells(2, 1) = Left(st1, 2)
Cells(3, 1) = Right(st1, 2)
Cells(4, 1) = InStr(st1, "us")
Cells(5, 1) = mid(st1, 3)
Cells(6, 1) = Len(st1)
End Sub
Private Sub cmdReArrangeString_Click()
Dim fullName As String, pos As Integer, _
cn As String, sn As String, newName As String
fullName = "Jack Robinson"
pos = InStr(fullName, " ") 'position of the space
cn = Left(fullName, pos - 1)
'Christian name
sn = mid(fullName, pos + 1)
'surname
newName = sn & "," & cn
Cells(1, 2) = newName
End Sub
Private Sub cmdMoreStrings_Click()
MsgBox LCase("A")
'MsgBox LCase(" Ed ")
MsgBox UCase("a")
MsgBox Asc("a")
MsgBox Chr(97)
MsgBox Val("2")
MsgBox Len(Trim(" ed "))
End Sub
Private Sub cmdXMLExtract_Click()
Dim st As String, pos1 As Integer, pos2 As Integer
Dim L As Integer, txt As String
st = "<Person>ed</Person>"
pos1 = InStr(st, ">") + 1
pos2 = InStr(st, "</")
L = pos2 - pos1
txt = mid(st, pos1, L)
MsgBox txt
End Sub
18
document1
Private Sub cmdParseEd_Click()
Dim stXML As String, data As String
Dim pos1 As Integer, pos2 As Integer
Dim strtTag As String, endTag As String
strtTag = "Person"
endTag = "</" & strtTag & ">"
strtTag = "<" & strtTag & ">"
stXML = "<Table1><Person>joyce</Person></Table1>"
pos1 = InStr(stXML, strtTag) + Len(strtTag)
pos2 = InStr(stXML, endTag)
data = mid(stXML, pos1, pos2 - pos1)
MsgBox Len(data)
End Sub
Exercise
Use a loop (You might start with a For and later use a Do) to find all instances of data eg between
the <Person>tags for this string:
stXML = "<Person>ed</Person><Person>joe</Person>"
Look at these Hints first:
1. Take a look at the Help for InStr
2. What value is returned if nothing is found? Try this
Dim stXML As String, data As String, pos1 As Integer
stXML = "<Person>ed</Person><Person>joe</Person>"
pos1 = InStr(stXML, "al")
MsgBox pos1
Can you use this to exit the Do?
3. What is the purpose of [start]? Try this
Dim stXML As String, data As String, pos1 As Integer
stXML = "<Person>ed</Person><Person>joe</Person>"
pos1 = InStr(1, stXML, "<Person>")
MsgBox pos1
Change the 1 to 2. What is the effect?
4. What is the effect of this in a loop?
pos1 = InStr(pos1, stXML, "<Person>")
19
document1
String Revision Exercises
AddressesExpt.xlsm Chen sheet Solution page 11 Solutions.
We have a set of strings:
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
51009.51701.7412.0.0.0.0.0.0
The objective is to click a button whereupon the strings will be separated as below.
First just do one string:
Dim strToSplit As String, posDot1 As Integer, posDot2 As Integer
Dim stData As String, 'stData will be the actual chunk eg 51009
posDot2 = 0: posDot1 = 0: 'pos1 will be the position of the LH (left-hand)
full-stop.
pos2 will be the position of the RH full-stop.
strToSplit = Cells(1, 1).Value
'eg 51009.51701.7412.0.0.0.0.0.0
We will use a Do Loop to move across the string first finding the dots and then extracting the data
between the dots.
Do
posDot2 = etc 'Look for a dot. Then the next.. eg posDot2 =
6 , 12
etc,
If posDot2 = 0 Then Exit Do ' If no dot found then end of the string.
etc.
Maybe MsgBox out the posDot2’s ?
The tricky bit is that we need to put posDot1 = posDot2 somewhere.
Loop 'Infinite loop beware!
Exercise: Continue down the rows (23 in this case) for all the strings using a For Loop.
Dim off As Integer could be used as horizontal offset to place the next chunk of data to the
right offset by off ?
20
document1
Optional *Revision exercise for stats aficionados
* Revision of basic expression manipulation – not strings
eg temp = y + a3 - a4 / (y + a5 + a6 / (y + a7))
normal dist
In statistics and in particular Financial Mathematics we encounter this curve:
x
The equation of this curve is:
If you want some more information try:
http://www.intmath.com/counting-probability/14-normal-probability-distribution.php
simple normal description
We wish to find the area under this curve
For example:
If x = 1 the area is .8413.
Fortunately we can find a formula for this area.
(The total area is 1.00)
Exercise:
There is some code to be found on the net but one small problem: It is written in Fortran.
http://lib.stat.cmu.edu/apstat/66
Convert it to VBA!
Solution: page 32 Solutions.
Dim area As Double
'* Adapted from http://lib.stat.cmu.edu/apstat/66 ‘Written in Fortran!
Const a0 = 0.5
Const a1 = 0.398942280444
etc
eg for
z = 1
21
document1
Optional Revision Exercise. (Revision of basic expression manipulation – not strings.)
Brownian Motion
statsAll.xlsm brownian sheet
Solution: page 2 Solutions.
We play a game with a coin.
Heads we jump up by 1, tails we jump down by 1.
We play the game 10 times.
At the end of the game below we are -3 for example as shown below.
If we square this number ie (-3)^2 = 9.
Theoretically this number (9) on average is equal to 10 – the number of TRIALS !
Simulate in Excel as shown below:
Now what if we wanted to conduct the above experiment say 100 times (in order to see that the
average distance squared is 10) then we must use VBA.
First write VBA code to simulate one experiment as above.
Hint1. Use the Rnd VBA function which returns a random decimal between 0 and 1.
Hint2. Use an If and test whether Rnd is above or below .5.
Find the SQUARE of the distance.
Average it: It should give 10.
(Note that the average distance away is sqrt(10). )
To see real brownian motion:
soln: StatsAll.xlsm Brownian sheet.
VBA and Matlab:
see page 39 of mtbMatlab and Finance.docx
22
document1
Chapter 4
First do Ch5 Macros
Avoid the Personal workbook!
pages 54-62
Code: (none)
Code:
MsgBox Range("A1:A3").Count
Range("A1:C2").Select
Range("A1").ClearContents
Range("A1").Font.Bold = True
Cells(1, 1).Font.Bold = True
Range("B1").Formula = "= A1 * 2"
Dim x As Variant
x = Range("B1").Formula
MsgBox x
Range("B1:B3").Formula = "= a1 * 2"
Range("B1:B3").Formula = "=$A$1 * 2"
Range("A2").FormulaR1C1 = "= R2C3 * 2 "
MsgBox Selection.Address
Dim st As String
st = Selection.Address
MsgBox st
Range("A1:A3").Font.ColorIndex = 3
MsgBox Selection.Font.ColorIndex
Range("A1").Font.ColorIndex = xlColorIndexAutomatic
Range("A1").Font.Color = 65280
Range("A1").Font.Color = RGB(0, 255, 0)
23
document1
Range("A1").Font.Color = vbGreen
Dim r As Range
Set r = Range("B2:C4")
r.Cells(1, 2).Select
Cells(3).Select
Cells(257).Select
Dim rng As Range
Dim i As Integer
Set rng = Range("A1:A8")
For i = 1 To 8
MsgBox rng.Cells(i).Value
Next i
Dim rng As Range
Dim i As Integer
Set rng = Range("A1:A8")
For i = 1 To 5
MsgBox rng.Cells(i).Font.ColorIndex
Next i
Dim rng As Range, i As Integer, ci As Integer
Dim num As Single
Dim sumRed As Single, sumBlue As Single
sumRed = 0: sumBlue = 0 'set the totals to zero
Set rng = Range("A1:A8")
For i = 1 To 8
ci = rng.Cells(i).Font.ColorIndex
num = rng.Cells(i).Value
Select Case ci
Case 3
sumRed = sumRed + num
Case 5
sumBlue = sumBlue + num
End Select
Next i
MsgBox "The sum of the red values is" & _
Str(sumRed) & vbCrLf & _
"The sum of the blue values is" & Str(sumBlue)
24
document1
Range Object
Represents a cell, a row, a column, a selection of cells containing one or more contiguous blocks of cells, or a 3-D
range.
Remarks
The following properties and methods for returning a Range object are described in the examples section:
Range property
Cells property
Range and Cells
Offset property
Union method
As mentioned - the most confusing aspect of Range is that in code it that is a property which returns a Range
object but in the end it is only these Properties and Methods that we are interested in.
It is confusing because other properties also return a Range object.
Range property!! Range(“A1”).Font.Bold = True
Rows property
Rows(1).Font.Bold = True
Range Object
Cells property
Cells(1).Font.Bold = True
Columns property
Columns(1).Font.Bold = True
Selection property
Selection.Font.Bold = True
See Also
Excel Object Model Reference
Range Object Members
o Take a look at the Properties
and Methods of the Range object.
After all – it is only these that we
are interested in in the end.
25
document1
ColorIndex
For Help ie to these colour codes search for PatternColorIndex!
o Try page 63, 64 Object Variables (ie try the combined code 2/3 way down the page):
o Try this yourself in a new workbook.
After page 65 "Step 1" you should have:
Private Sub CommandButton1_Click()
Dim rng As Range
Dim i As Integer
Set rng = Range("A1:A8")
For i = 1 To 8
MsgBox rng.Cells(i).Font.ColorIndex
Next i
End Sub
o Modify the code above to MsgBox the value in the cell only if a red cell is found in looping around
the Range A1:A8.
o Further modify the code above to make a running total of the red cells and finally – after the loop
is finished to MsgBox this total using sumRed as the counter..
o Now use a respective running total counters sumRed and sumBlue (Integers) to sum the
values of each as per page 65. (Use Select Case.)
Use If and Else (or better: two Ifs rather than Select Case as per the book.
Another Use Of Range
Range(Cells(1, 1), Cells(4, 5)).Select
or
Range(Range("A1"), Range("E4")).Select
26
document1
Course Work:
Exercise:
We wish to sort this column of
numbers according to colour only
(ignore values) into another column.
o Click the button.
The numbers are placed into another
group according to colour.
27
document1
Chapter 6
Pages 80- 85:
See BookSummary.xlsm
Private Sub cmdDeleteSheet_Click()
MsgBox ActiveSheet.Name
ActiveSheet.Delete
End Sub
Ch6 sheet
Private Sub cmdPrintPreview_Click()
ActiveSheet.PrintPreview
'ActiveSheet.PrintOut PreView:=True
End Sub
Private Sub cmdWorkSheetNames_Click()
Dim i As Integer
For i = 1 To 3
MsgBox Worksheets(i).Name
Next i
End Sub
Private Sub cmdWorkSheetCount_Click()
MsgBox Worksheets.Count
End Sub
Private Sub
cmdWorkSheetNamesWithCount_Click()
Dim i As Integer
For i = 1 To Worksheets.Count
MsgBox Worksheets(i).Name
Next i
End Sub
Private Sub cmdWorkSheetsAdd_Click()
Worksheets.Add
End Sub
Private Sub cmdWorkSheetsDelete_Click()
Worksheets(1).Delete
End Sub
Private Sub cmdWorkSheetsCollection_Click()
Dim wks As Worksheet
For Each wks In Worksheets
MsgBox wks.Index
Next wks
End Sub
Private Sub cmdItem_Click()
MsgBox Worksheets.Item(1).Name
End Sub
28
document1
Private Sub cmdSelectCells_Click()
Worksheets("Ch6").Cells.Select
End Sub
Private Sub cmdSelectCells_Click()
Worksheets("Ch6").Cells(1).Select
End Sub
Private Sub cmdSelectColumns_Click()
Worksheets("Ch6").Columns(2).Select
End Sub
Private Sub cmdSelectRows_Click()
Worksheets("Ch6").Rows(2).Select
End Sub
Private Sub cmdColumnsCount_Click()
MsgBox Columns.Count
End Sub
Private Sub cmdRowsCount_Click()
MsgBox Rows.Count
End Sub
Private Sub cmdNameProtect_Click()
Worksheets("Ch6").Protect
End Sub
Private Sub cmdUnprotect_Click()
Worksheets("Ch6").Unprotect
End Sub
Private Sub cmdCodeNameProtect_Click()
myCodeName.Protect
End Sub
29
document1
o Page 86 , 87: Events: Try the book examples on a new workbook.
MsgBox Target.Address
o Before trying page 88, try this first on the new workbook. (Comment out the previous code first.)
Private Sub Worksheet_Change(ByVal Target As Range)
MsgBox Target.Value
End Sub
o The value that is entered is the Target value.
o Don’t try this!:
Private Sub Worksheet_Change(ByVal Target As Range)
Cells(1) = 2
End Sub
Why not?
Page 88:
If Target.Value > 5 Then Target.Font.ColorIndex = 3
Page 89:
Cancel = True
MsgBox "Right Click"
Cancel = True
MsgBox "Double Click"
Page 90:
MsgBox "Recalculated"
o Page 91 Automatic sheet entry. Try this first:
o Make the Invoices sheet as per page 91- top diagram - in a new workbook.
o Write some code which will MsgBox the contents of a cell one to the right of a cell upon which we
double-click. (Use .Offset(0,1). See page 117 for Offset.)
o eg double-click Caldwell and …
… and the corresponding date is displayed.
See the next page for the solution –if you’re not sure.
30
document1
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As
Boolean)
Cancel = True
MsgBox Target.Offset(0, 1).Value
End Sub
o Name two more worksheets in the same Workbook as Caldwell and Crossfire as per page 91
(bottom). These should simply have the Date and Dividend headings as shown on page 91.
Optional:
o Write some code in a Command Button placed on the Invoices sheet which will activate the
Caldwell sheet as follows.
Private Sub CommandButton1_Click()
Worksheets("Caldwell").Activate
End Sub
o Try the code on Page 91 which selects the appropriate sheet. Think how you could also update (ie
transfer the dividend and date values) the Caldwell and Crossfire sheets before trying the code on
page 92.
Exercises:
(Solutions page 14 or vbaClassSolutions.docx)
1. Change the Name of a worksheet to Income and its CodeName to cnIncome.
Change to a different sheet, place a Command Button on the sheet and write code in it to place a
value onto the Income sheet using the Name property. Repeat using the CodeName instead of the
Name property.
2. Write some code which changes the names of all of the worksheets in a workbook to upper case
upon double-clicking on a particular sheet (only works for this sheet).
3. Also write code which changes the names of all of the worksheets to lower case upon right-clicking
on a sheet.
4. Write some code which will move the contents one cell to the right when we double-click on it.
5. We would like to be able to double-click on a fullname whereupon it would be separated into Title,
First Name & Surname like so
(We have previously written tp perform this when we selected the cell and then clicked a button.
Modify that code to use the double-click event.)
31
document1
Chapter 7
Pages 96 to 98:
o Create the 2 new workbooks Tax2004.xlsx and Tax2005.xlsx as per Step 1 on page 96 and leave
them open.
o Click IterateWorkbooks button
Private Sub cmdIterateWorkbooks_Click()
Dim wkb As Workbook
For Each wkb In Workbooks
MsgBox wkb.Name
Next wkb
End Sub
Private Sub cmdPathNameFullName_Click()
MsgBox Workbooks(1).Name
MsgBox Workbooks("Tax2004.xls").Path
MsgBox Workbooks("Tax2004.xls").fullName
End Sub
Private Sub cmdClose_Click()
Workbooks(1).Close
End Sub
Private Sub cmdOpen_Click()
Workbooks.Open ("Tax2005.xls")
End Sub
Workbook Events
Pages 99 to 103
o Start a new blank workbook and try the events on.
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target
As Range)
MsgBox Sh.Name & " " & Target.Address
End Sub
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
MsgBox Sh.Name
End Sub
Private Sub Workbook_BeforePrint(Cancel As Boolean)
ActiveSheet.PageSetup.RightFooter = ThisWorkbook.fullName
End Sub
Private Sub Workbook_Open()
MsgBox "Don't forget the April deadline"
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
MsgBox "Only save the Jan sales figures"
End Sub
Private Sub Workbook_SheetCalculate(ByVal _
Sh As Object)
If Sh.Range("B6").Value > 20000 Then
MsgBox "Sales target achieved by " & Sh.Name
End If
End Sub
32
document1
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
MsgBox Sh.Name
MsgBox Sh.CodeName
End Sub
Pages 104 to 105
Private Sub cmdFontBoldTrue_Click()
Application.Workbooks("Tax2005.xls").Worksheets("Sheet1").Range("A1").Font.
Bold = True
'Workbooks("Tax2004.xls").Worksheets("Sheet2").Range("A1").Value = "Income"
End Sub
Private Sub cmdParent_Click()
MsgBox TypeName(ActiveSheet.Parent)
MsgBox TypeName(ActiveSheet.Parent.Parent)
End Sub
Private Sub cmdTypeName_Click()
Dim i As Integer
MsgBox TypeName(i)
End Sub
o Complete page 106: An automatic alert.
First test the Workbook_SheetCalculate
event. How does this differ from the
WorkSheeet_Calculate event?
Private Sub Workbook_SheetCalculate(ByVal _
Sh As Object)
If Sh.Range("B6").Value > 20000 Then
MsgBox "Sales target achieved by " & Sh.Name
End If
End Sub
o Exercises Chapter 7:
1. Start a new workbook. Write some code to MsgBox the Name* and the CodeName* of a worksheet
that is activated.
*(If neither the Name or the CodeName has been changed, they will be the same eg Sheet1.)
2. Open a few workbooks.
In one of them, write some code to determine whether a particular workbook eg Book1.xls is open or
not. (Iterate thru the WorkBooks collection and check out the Names.)
If it is not open, open it.
3. Write code which will message box out the contents of a cell that you double-click upon but only if
it is from Sheet1 or Sheet3. (Assuming you have a Sheet1 and Sheet3.)
(You will need to use Cancel = True.)
33
document1
Chapter 7 HW
(Courtesy of Erol)
We have 3 workbooks open. Tax2004.xlsx (or.xlsm), Tax2005.xlsx (or.xlsm) and your current
workbook which may be called Book1.xlsm. (Don’t forget that a workbook does not have a name until
you save it.
Tax2004.xlsx
Call the workbooks what you like.
Sheet1
Tax2005.xlsx
Sheet1
Problem:
1. Iterate through the 2 workbooks Tax2004 and Tax2005 and “collect “ the data on Sheet1 cell A1
and concatenate them into a string.
2. Alternatively instead of placing them into a string, place them on the current workbook like so.
34
document1
Chapter 8
Try These
pages 108.
Range("B2:D4").Columns(2).Select
Range("B2:D4").Columns(1).Font.ColorIndex = 5
Range("B2:D4").Columns(1).Font.Bold = True
pages 109
Range("B2:D4").Rows(2).Select
With Range("f2:h4").Rows(3)
.Formula = "=Sum(f2:f3)"
.Font.Bold = True
End With
pages 110
o First, place a Command Button on a new sheet and type 6 values into column B of a new
workbook, define the range as rng and simply display the individual values .
(Iterate using Cells with MsgBox as per page 64 of the text.)
o Think how you could write code to find the maximum value. See p110 & Maximum button on the
web sheet. (ref C25). Single step thru the code.
Again MsgBox the 6 values in column B, but this time use a For Each … Next loop. ( see
Page 83 for the For Each … Next albeit for WorkSheet objects). Note that each individual cell
is a Range object.
Private Sub cmdMaximum_Click()
Dim rng As Range
Dim i As Integer, mx As Variant
'Set rng = Range("B1:B6")
Set rng = Range("b29:b34")
mx = rng.Cells(1).Value
For i = 2 To rng.Count
If rng.Cells(i).Value > mx Then
mx = rng.Cells(i).Value
End If
Next i
MsgBox "Max value is " & mx
End Sub
Private Sub cmdBlue50_Click()
Dim rng As Range, cl As Range
'Set rng = Range("B1:B6")
Set rng = Range("B29:B34")
For Each cl In rng
If cl.Value >= 50 Then
cl.Font.ColorIndex = 5
End If
Next cl
End Sub
35
document1
page 112
Range("D4:E5").Value = Range("A2:B3").Value
Range("D4:E5").Cells.Value = Range("A2:B3").Cells.Value
Exercise: Page 112: Make it work for any selection. ie Use the Selection object.
Hint: Use Range(Cells(…),Cells(…)) to specify a range and
also us something like this
w = sel.Rows(1).Cells.Count
h = sel.Columns(1).Cells.Count
Might need Resize()
page 113
o Note that we can also Copy and Paste in one line of code:
Range("A2:B3").Copy Destination:= Range("D4")
To switch off the “ants” we would use Application.CutCopyMode = False.
Note that there is sometimes an advantage to be gained in using the Cut/Copy paste method over the
Range.,Value = Range.Value method. What is it?
ans: We can copy formatting as well.
Optional Exercise:
Using rngDest.Value = rng.Value, prepare the destination range rngDest to be the same size as the
source range rng.
soln page 19 vbaClassSolutions.doc
page 114
Range("B1").CurrentRegion.Select
In Excel 2007 etc make sure
that the Extend data range..
option is turned off lest you
get the formating extended
down in any case.
page 115
Dim rng As Range, cl As Range
Set rng = Range("e58").CurrentRegion
'Set rng = Range("B1").CurrentRegion
For Each cl In rng
If IsNumeric(cl.Value) Then
'
If Not IsNumeric(cl.Value) Then
cl.Font.ColorIndex = 5
End If
Next cl
36
document1
page 116
ActiveSheet.UsedRange.Select
o Page 116 : UsedRange.
o Try the code from the top of page 116 on a new worksheet. Enter another value outside the
current used range (eg in cell E11 – but remember which one!). Make it bold. Try the code. Of
course it will now be included in the used range.
o Unbold the cell and delete the contents by pressing the delete button (remember which cell it
was). Try the code – that cell is still included in the used range!
o To properly exclude it, use Edit, Clear, Formats. It will then be no longer part of the used
range.
Conclusion: UsedRange is unreliable.
Range("C2").End(xlDown).Select
o To get help on End(xlDown) etc, type End in the VBE Help box and choose the End Property.
o See the following link for a discussion of: Finding the Last Cell in a Range.
http://www.ozgrid.com/VBA/ExcelRanges.htm
eg Range(”A65536”).End(xlUp).Select
page 117
These are written for Excel 2003. How would
you generalize it for an unknown number of
rows (eg it could be Excel 2007.) Take a look
at Last Row button on Ch8 sheet cell ref N77.
Dim
Set
Set
Set
rng
rng
rng
rng
As Range
= Range("B2:D4").Offset(4, 1)
= Range("B2:D4").Offset(4)
= Range("B2:D4").Offset(0, 4)
page 118
(Try below as an exercise first.)
Dim rng As Range, code As Variant
Dim i As Integer
code = "dfg"
Set rng = Range("f78:f81")
'Set rng = Range("C3:C6")
For i = 1 To rng.Count
If rng.Cells(i).Value = code Then
MsgBox rng.Cells(i).Offset(0, -1).Value
End If
Next i
o Put this into a UDF. See page 156 for UDF
o Exercise: Emulate the “True” feature in the VLookup formula.
(Soln page 24 of vbaClassSolutions.doc)
37
document1
page 119
Dim rng As Range
Set rng = Range("B2:D2").Resize(2, 2)
Set rng = Range("B2:D4").Resize(1)
'Set rng = Range("B2:D4").Resize(, 1)
rng.Select
Note that you can resize to make the range bigger. Try it.
page 120-1
Private Sub cmdIncreaseSelectionByOne_Click()
Dim rng As Range
Set rng = Selection.CurrentRegion
Set rng = rng.Resize(rng.Rows.Count + 1)
rng.Select
End Sub
Private Sub cmdSelectBottomRow_Click()
Dim rng As Range
Set rng = Selection.CurrentRegion
Set rng = rng.Resize(rng.Rows.Count + 1)
rng.Rows(rng.Rows.Count).Select
End Sub
page 122
Intersect(Range("B2:D4"), Range("D3:F5")).Interior.ColorIndex = 33
page 123
Union(Range("B2:D4"), Range("D3:F5")).Interior.ColorIndex = 33
page 124
Dim rng As Range
Set rng = Range("B2:D4")
If Intersect(rng, Target) Is Nothing Then
MsgBox "Please select a cell in the range " & rng.Address
ElseIf Target.Cells.Count <> 1 Then
MsgBox "Please select a single cell
Else
MsgBox "OK"
End If
page 125
Private Sub cmdSelectAreas_Click()
Range("B136:B137,D136:E137").Select
'Range("B2:B3,D2:E3").Select
End Sub
Private Sub cmdAreas_Click()
Dim rng As Range, i As Integer
Set rng = Range("B136:B137,D136:E137")
'Set rng = Range("B2:B3,D2:E3")
For i = 1 To rng.Areas.Count
MsgBox rng.Areas(i).Address
38
document1
Next i
End Sub
page 126
Private Sub cmdExactlyTwo_Click()
If Selection.Areas.Count <> 2 Then
MsgBox "You must make exactly 2 selections"
ElseIf Selection.Areas(1).Cells.Count <> 1 Or
Selection.Areas(2).Cells.Count <> 1 Then
MsgBox "You must select single cells"
Else
MsgBox "OK"
End If
End Sub
39
document1
To Build a Worksheet Formula using a String
o Write this code to sum A1:A2:
Private Sub CommandButton1_Click()
Range("A3").Formula = "=SUM(A1:A2)"
End Sub
This should of course result in this formula being placed into cell A3.
Break the string up as shown below: (It should do exactly the same as above.) We could also omit the
Formula property as shown below.
Private Sub CommandButton1_Click()
Dim st As String
st = "A1:A2"
Range("A3") = "=SUM(" & st & ")"
End Sub
o Change the code as shown.
Private Sub CommandButton1_Click()
Dim st As String
st = Selection.Address
Range("A3") = "=SUM(" & st & ")"
End Sub
o
Make sure that you make the selection first
as shown before clicking the command button.
40
document1
The formula will be placed in cell A3 as before.
Note that the Address property
has provided the address as an
absolute reference. (It makes no
difference, the SUM still works.)
Recall that if we wished to make the Address property return a relative address, we could use:
(When using the Address property, it may be expedient to copy the definition from the Address
Property in the VBA Help:
expression.Address(RowAbsolute, ColumnAbsolute, ReferenceStyle, External, RelativeTo)
to your code and modify it.)
… in which case, a relative
address will now be placed in A3
when the button is clicked.
Recall how we can copy a formula across a range of cells: (see Formula page 59.)
o Try this.
Private Sub CommandButton1_Click()
Dim rng As Range
Set rng = Range("A3:C3")
rng.Formula = "=SUM(A1:A2)"
End Sub
The formula is copied across to
each of the 3 cells in the range.
41
document1
Exercise:
1. “Colour the Surname”
We wish to be able to click on the Surname….
…. and hold down the ctrl-key and then click on the actual surnames so that we have the selection of
5 cells as shown above.
We then need to write some code which upon clicking the Colour button above will colour the 4 actual
addresses the same colour as the “Surname” cell (L3).
Hint: Use Areas or use Cells(1) of the selected range.
2.
Write code and place it in a new ClearColour button which will remove the colouring
of any range that we will select (It could be a multiple selection).
42
document1
Exercise
Courtesy of Chen
Data is to be dumped here. Its size (ie no of rows) varies.
Write code to split the data about the full stops and place it like so.
Suggestions: To define the range of the data (in order to iterate thru it) use:
1. Current Region
2. Name the range.
3. Select it with the mouse beforehand.
Use a For or preferably a Do to loop thru the individual strings eg 51009.51701.7412.0.0.0.0.0.0
using Instr to find each full-stop. (See previous code to find data in an XML string – remember :
pos+1 for the next start position.)
43
document1
Private Sub cmdSplitStops_Click()
'Chen's exercise
Dim rngToSplit As Range, strToSplit As String, posDot1 As Integer, posDot2
As Integer
Dim stData As String, rngCell As Range, off As Integer, i As Integer
Set rngToSplit = Range("A1").CurrentRegion 'This is the imported data
For i = 1 To rngToSplit.Rows.Count 'Work down the rows. eg 23 rows in
total.
posDot2 = 0: posDot1 = 0: off = 2 'pos1 is the position of the LH
(left-hand) full-stop.
'pos2 is the position of the RH full-stop. off is the offset across to
where we want to place the data.
Set rngCell = rngToSplit.Cells(i) 'Do each row as we go down.
strToSplit = rngCell.Value 'The string value in ech row. eg
51009.51701.7412.0.0.0.0.0.0
Do
posDot2 = InStr(posDot2 + 1, strToSplit, ".") 'Look for the first
dot. Then the next.. eg posDot2 = 6 , 12 etc,
If posDot2 = 0 Then Exit Do ' If no dot found then get out.
stData = Mid(strToSplit, posDot1 + 1, posDot2 - posDot1 - 1) 'The
actual chunk eg 51701
rngCell.Offset(, off).Value = stData 'Place it to the right
starting at offset 2.
off = off + 1 'Get ready for the next placement of data one cell to
the right.
posDot1 = posDot2 ' THe LH dot becomes the old RH dot - leap-frog!
Loop 'Infinite loop beware!
Next i 'Do the next string down
End Sub
44
document1
Chapter 9
1. Go thru book on a new workbook ‘till end of p140.
code:
cmdEnabled.Caption = "Enabled"
Selection.Font.ColorIndex = 3
Selection.ClearFormats
txtSales.Text = "Sales Results are good"
Cells(1, 1).Value = txtSales.Text
Private Sub SpinButton1_SpinUp()
Cells(2, 2).Value = Cells(2, 2).Value + 1
End Sub
Private Sub SpinButton1_SpinDown()
Cells(2, 2).Value = Cells(2, 2).Value - 1
End Sub
Private Sub SpinButton2_Change()
SpinButton2.Max = 200
SpinButton2.Min = 100
SpinButton2.SmallChange = 5
Cells(2, 2).Value = SpinButton1.Value
End Sub
Range("D3").Value = CheckBox1.Value
Private Sub CheckBox2_Change()
Range("D6").Value = CheckBox2.Value
End Sub
Private Sub OptionButton1_Change()
Range("D3").Value = OptionButton1.Value
End Sub
Private Sub OptionButton2_Change()
Range("D6").Value = OptionButton2.Value
End Sub
Note that on page 132: Before we click the Remove Formatting button, it is not necessary to re-select
the range so long as it is on the same sheet and so long as we have not clicked elsewhere. Why not?
Because there is only one selection object at any given time.
But – if the selection spans more than one sheet then in order to make a CHANGE on another sheet
we must ACTIVATE that sheet first eg Sheet2.Activate.
Place 3 Option Buttons and 2 Check Boxes (page 137, 138 without any code behind them to see the
different effect of clicking on them. Due to the associative nature of Option buttons, the change of one
necessarily sets off the change event in the other.
45
document1
Grouping Option Buttons
o Place 5 option buttons on a sheet.
At present, they all act as one group. i.e. checking one unchecks the other
To place a controls on a s/sheet you
could also double-click the (Option
Button in this case) control.
o In Design Mode, rightclick on the Option Button
and choose Properties.
We wish to make them act as 2 groups.
o Change the GroupName property of each
of these 2 option buttons individually to
saySheet1x as shown. (Note that the default
GroupName was Sheet1).
o Now change out of Design view and you
should find that the left 3 option buttons act
independently of the right 2 option buttons.
The GroupName property can be read and written to using e.g.
OptionButton1.GroupName = "Sheet1x".
This principle is the same for a UserForm.
Exercise: Write code to determine which option button is true and which group it is from.
.
46
document1
o Finish book till last part: p.141 : Using Option buttons. Leave both workbooks open. (See demo
first.) (Of course the extensions will be .xlsm and not .xls for 2007/10.)
o On the Analysis.xlsm on the Import sheet in the cmdImportSheet_Click()event, write code to
MsgBox the state of the option buttons and then code to MsgBox the caption of only the one that is
checked as follows:
Private Sub cmdImportSheet_Click()
If OptionButton1.Value = True Then
MsgBox OptionButton1.Caption
ElseIf OptionButton2.Value = True Then
MsgBox OptionButton2.Caption
End If
End Sub
o Comment out the above code inside the command button.
o Write code in the same cmdImportSheet button (comment the previous code) to display the
Name of the current workbook using a message box as follows:
MsgBox ThisWorkbook.Name
(This should produce “Analysis.xlsm”,)
o Now comment out the above code inside the command button and write code in this same
Command Button to copy the 2004 worksheet from the Company.xlsm workbook as follows:
Workbooks("Company.xlsm").Worksheets("2004").Copy
When you run this code you may be surprised to see that the
sheet 2004 is copied from Company.xls to a new! Workbook.
o Delete this new workbook.
If we wish to ensure that the worksheet is copied into the current Workbook, we must specify where
amongst the current sheets it is to be placed.
o Modify the code as shown and try it.
Workbooks("Company.xlsm").Worksheets("2004").Copy After:=Worksheets(1)
The sheet is now placed in the
current workbook Analysis.xlsm.
o then finish p. 142 : ie write code in the analysis.xls book cmdImportSheeet button to import either
the 2004 sheet or the 2005 sheet depending on which option button is selected.
code page 142:
Private Sub cmdImportSheet_Click()
Dim shName As String
If OptionButton11.Value = True Then
shName = OptionButton11.Caption
ElseIf OptionButton12.Value = True Then
shName = OptionButton12.Caption
End If
Workbooks("Company.xls").Worksheets(shName).Copy _
After:=ThisWorkbook.Worksheets(Worksheets.Count)
End Sub
47
document1
Exercise:
The Option Buttons on p.138 are both "unselected" upon the very first start up (ie run-time).
1. Show how a default value could be ascribed at Design Time. (ie use the Properties box.)
1. Write some code to set a default value as above but this time at Run Time. (Use the Value
property.)
2. How would you write code to determine which option button is true? Im afraid that the only
way is to loop thru the collection of controls, check if it an option button and then check if it’s
true or not.
This exercise is not as easy as it looks. Try Googling and seek help on OLEObject.
Or look at the solution below!
Private Sub CommandButton1_Click()
Dim wks As Worksheet
Dim OLEObj As OLEObject
Set wks = Worksheets("Sheet1")
’ or whatever
For Each OLEObj In wks.OLEObjects
If TypeOf OLEObj.Object Is MSForms.OptionButton Then
If OLEObj.Object.Value = True Then
MsgBox OLEObj.Name
End If
End If
Next OLEObj
End Sub
or use Shapes
looping through controls on a UserForm (see ch 11) is easier
see http://www.ozgrid.com/VBA/control-loop.htm
Private Sub CommandButton1_Click()
Dim cCont As Control
For Each cCont In Me.Controls
'DO STUFF HERE
Next cCont
End Sub
Note that Me means the current form!
48
document1
Exercise
We wish to make a userform on which we can select a company
and click the button whereupon the chart for that
company will appear as shown.
The respective company data is located on the spreadsheet.
A “problem” is that we cannot place a chart directly onto a userform. We must make a chart on the
spreadsheet and export our chart as an image and then load that image into an Image Frame on the
userform.
First we will practise just Making a chart on the spreadsheet:
It is useful to first name the range D2:D13 (the months) as rngDataRef. We can then refer to IBM’s
data as Range("rngDataRef").Offset(, 1) etc.)
o Place this code in a button on the sheet and run it to produce the chart shown above but on the
spreadsheet.
Private Sub cmdTest_Click()
Dim chrt As Chart
Set chrt = ActiveSheet.Shapes.AddChart(XlChartType:=xlLine).Chart
chrt.SeriesCollection.NewSeries
chrt.SeriesCollection(1).Name = "IBM"
chrt.SeriesCollection(1).XValues = Range("rngDataRef")
chrt.SeriesCollection(1).Values = Range("rngDataRef").Offset(, 1)
End Sub
49
document1
Now make the userform , name it frmSelect and place a button on the spreadsheet to open this
userform.
Combo Box
Image Box.
frmSelect.Show
Next:
You might like to first practise loading up a given .gif image onto the userform.
eg logo.gif:
You will need to know where
your image is.
Dim imgName As String
imgName = "C:/temp/logo.gif"
Place this code in the command
button on the userform.
frmSelect.Image1.Picture = LoadPicture(imgName)
The userform was named frmSelect. I kept the name of my image box as Image1.
Place the code in a test command button on the form ( We will place it in the
Userform_Initialize() event. later.)
The image should appear in the image box on the userform.
Next:
Loading up the combo box with the company names.
(Name this range
as rngCompany.)
It would make things simple if this range could have been referred to from ListFillRange or
RowSource directly from code but this seems to present difficulties (It would not accept a row as a
source) so I had to go the long way round with code as below:
We could place this code in a button.
Dim ListRange As Range, cl As Range
Set ListRange = Range("rngCompany")
For Each cl In ListRange
cboSelect.AddItem cl.Value ' Insert the values from ListRange
Next cl
(I named my combo box as cboSelect.)
50
document1
Next : The ListIndex is the index of the item chosen in the combo box- starting at zero.
The Text property is what has been chosen/displayed.
Text is IBM
ListIndex is 0.
In the code on the userform:
Use Text to display the Name of the chosen data series ie.
Dim chrt As Chart, chrtName As String
chrtName = cboSelect.Text
chrt.SeriesCollection(1).Name = chrtName
Use ListIndex as the offset from the range rngDataRef on the spreadsheet.
Converting the chart to an image
Use the Export method:
chrt.Export Filename:=imgName
See stock sheet for the userform and the code.
51
document1
Chapter 10 Procedures
page 144
o Subs page 144-146.
(Note that Square is not a reserved word.) Try all of these on a new book.
To see the point of a sub or function.
Here is code to find the data between the <Person> tags. Try it.
Private Sub CommandButton1_Click()
Dim stXML As String, pos1 As Integer, pos2 As Integer
Dim stSearch As String, stTag1 As String, stTag2 As String
Dim stData As String
stXML = "<Person>ed</Person><Balance>10</Balance>"
pos1 = 0: pos2 = 0
stSearch = "Person"
stTag1 = "<" & stSearch & ">"
stTag2 = "</" & stSearch & ">"
pos1 = InStr(stXML, stTag1) + Len(stTag1)
pos2 = InStr(stXML, stTag2)
stData = Mid(stXML, pos1, pos2 - pos1)
MsgBox stData
End Sub
What if we now wanted to also find the data between the <Balance> tags?
Rewrite the whole thing for <Balance> ? No way.
Solution: Put the code into a sub and PASS the value to be searched for (<Person> or <Balance>)
Do it.
Code for Page 144 to 147:
Private Sub cmdSimpleSub_Click()
Square
End Sub
Sub Square()
Dim x As Double
x = 3
x = x ^ 2
MsgBox x
End Sub
52
document1
Private Sub cmdPassValueSub_Click()
Square 3
End Sub
Sub Square(x As Double)
x = x ^ 2
MsgBox x
End Sub
Private Sub cmdPassValueFunction_Click()
Dim y As Double
y = Square(3)
MsgBox y
End Sub
Function Square(x As Double) As Double
x = x ^ 2
Square = x
End Function
Private Sub cmdPassValueFunctionBy2_Click()
MsgBox 2 * Square(3)
End Sub
Exercises Write a sub (or a function) which passes two values to the sub .
Sub Square(x As Double)
x = x ^ 2
MsgBox x
End Sub
Page 148 Code:
Private Sub cmdCallColorCells_Click()
ColorCells 2, Range("A1:A6")
ColorCells 4, Range("C1:D4")
End Sub
Sub ColorCells(x As Variant, rng As Range)
Dim cl As Range
For Each cl In rng
If cl.Value = x Then cl.Font.ColorIndex = 3
Next cl
End Sub
Page 149 Code:
Private Sub cmdFind2_Click()
Dim num As Integer, fnd As Variant
fnd = 2
num = FindNum(fnd, Range("A1:A6"))
MsgBox "The number of " & fnd & _
" 's found is" & Str(num)
End Sub
Function FindNum(x As Variant, rng As Range) _
As Integer
Dim c As Integer, cl As Range
c = 0
53
document1
For Each cl In rng
If cl.Value = x Then c = c + 1
Next cl
FindNum = c
End Function
Page 150 Code:
Private Sub cmdSubScope_Click()
sub1
sub2
End Sub
Sub
Dim
i =
End
sub1()
i As Integer
1
Sub
Sub sub2()
i = 2
End Sub
Page 151 Code:
Dim i As Integer
etc
Page 152 Code:
Private Sub cmdFindMin_Click()
Dim i As Integer, mn As Single
Dim c As Integer
Set rng = Selection
mn = rng.Cells(1).Value
c = 1
For i = 2 To rng.Count
If rng.Cells(i).Value < mn Then
mn = rng.Cells(i).Value
c = i
End If
Next i
rng.Cells(c).Font.ColorIndex = 5
End Sub
Private Sub cmdClearFormats_Click()
rng.Cells.ClearFormats
End Sub
Page 153 Code:
Private Sub cmdStatic_Click()
Dim i As Integer
MsgBox i
i = i + 1
End Sub
Page 154 Code:
Private Sub cmdMsgBoxCommasAll_Click()
Dim x As Integer
x = MsgBox("Click", vbOKOnly, "Test")
54
document1
MsgBox x
End Sub
Page 155 Code:
Private Sub cmdMsgBoxCommas_Click()
Dim x As Integer
x = MsgBox("Click", , "Test")
MsgBox x
End Sub
Private Sub cmdMsgBoxNamedPars_Click()
Dim x As Integer
x = MsgBox(Prompt:="Click", Title:="Test")
MsgBox x
End Sub
Private Sub cmdMsgBoxNamedParsResponse_Click()
Dim x As Integer
x = MsgBox(Prompt:="Delete the cell contents?", _
Buttons:=vbYesNo)
If x = vbYes Then
Selection.ClearContents
End If
'otherwise do nothing
End Sub
Page 156 Code: UDF
Function doll(st As Single) As Single
Dim rate As Single
rate = GetUSDtoGBPRateUsingIE()
doll = st / rate
End Function
55
document1
Public Variables
Public variables are known throughout the whole program – ie on every code sheet throughout the
whole workbook.
For a Public variable we must Dim our variable in a code module. Public is used rather than Dim.
o
In a new workbook, from the VBE menu choose Insert, Module.
Declare the variable.
Public means that the variable will be known in each module throughout the program
i.e. the variable i will have program-level scope.) (Using Dim would mean that this
variable would only be known in this module i.e it would only have module-level scope.)
(The value if i could be also be defined in this module (we would need to place it in a sub) - or simply
defined in one of the Command Buttons as below.)
o
First place a Command Button on Sheet1 in a new workbook with the code shown.
o
o
Define i = 2.
o Try it now if you wish. You should
get a message box as below.
Place a second Command Button on Sheet2 with the code shown.
o
Exit Design Mode, click the first Command
Button on Sheet1 and then click the other Command
Button on Sheet2. (In that order.)
In both cases, i will be known – to both sheets and no
error will occur. The value of i is initialized in the code
of CommandButton1 and then output in the code of
CommandButton2.
What would happen if you clicked the buttons in the reverse order? Will you have to start a new
session? Yes if you want to reset i.
56
document1
Exercise:
1. Write a function to find and return the mean of two numbers. Call it mean.
The parameters should be Double.
Extra: Write a function to find and return the
Call the function like this
mean of any number of parameters (Do this
after arrays) numbers.
Private Sub CommandButton1_Click()
Use ParamArray.
MsgBox mean(2, 4)
http://stackoverflow.com/questions/20783170
End Sub
/pass-array-to-paramarray
2. Write a sub to swap two numbers. Call it swap.
We wish them to be permanently swapped so pass them By….
call the sub like this:
Private Sub CommandButton1_Click()
Dim a As Double, b As Double
a = 2: b = 4
MsgBox a & ", " & b
swap a, b
MsgBox a & ", " & b
End Sub
Result:
before swap:
.
after swap:
57
document1
Chapter 11
p.158, 159, 160: Write this code in a new workbook. Use a breakpoint to watch the array elements
being concatenated.
Dim Company(1 To 3, 1 To 2) As String
Company(1) = "Business Systems"
Company(2) = "Best Image"
Company(3) = "Analytical Systems"
MsgBox Company(3)
Dim i As Integer
For i = 1 To 3
Company(i) = Company(i) & " Ltd"
Next i
Dim i As Integer
For i = 1 To 3
MsgBox Company(i)
Next i
End Sub
Add a Watch to view the contents of Company
page 160:
Option Base 1
Dim Company(1 To 3) As String
Dim Company(1 To 3, 1 To 2) As String
Dim rw As Integer, cl As Integer
For rw = 1 To 3
For cl = 1 To 2
Company(rw, cl) = Cells(rw, cl).Value
Next cl
Next rw
o Rewrite the code on page 160 to using LBound and Ubound instead of
and
For rw = 1 To 3
For cl = 1 To 2.
LBound and Ubound are useful when
we dimension our arrays dynamically.
UBound(Company, 1)
UBound(Company, 2)
58
document1
ReDim can be used just to dynamically dimension an array eg
Dim Company() As String
Dim i As Integer
i = 3
ReDim Company(i) As String see InputBox example below.
ReDim Preserve can only we used on the last dimension as we will now see.
o
Read 1st half of page 161 then try this.
Private Sub CommandButton1_Click()
Dim Company() As String
ReDim Company(3)
Company(0) = 1
Preserve is
ReDim Company(5)
not used – yet.
MsgBox Company(0)
End Sub
Any contents
are lost!
o Replace ReDim Company(5) with ReDim Preserve Company(5). The contents are
retained.
o See note on bottom left of page 161. Confirm that only the very last dimension can be changed
with ReDim Preserve - as demonstrated below:
Dim sales()As Single
ReDim sales(2, 2)
sales(2, 2) = 33.2
ReDim Preserve sales(3, 2)
Can’t change this
dimension if using
Preserve.
o First try
ReDim Preserve sales(2, 3)
This should work OK.
Running this will produce the error
message “Subscript out of range”.
Rule:
We can ReDim as much as we like – but the contents will be lost.
If we wish to use Preserve as well then we can only ReDim the last dimension.
Code for page 161: Dynamic Dimming:
Dim Sales() As String
Private Sub cmdPreserveSales_Click()
Static i As Integer
i = i + 1
ReDim Sales(i)
Sales(i) = InputBox("Sales Figures?")
End Sub
59
document1
Page 162 Variant Array
Dim Company As Variant
Company = Range("A1:B3").Value
MsgBox Company(1, 1)
Use the Locals Window this time to
view the Contents of Company.
See the note bottom left page 162 – that we must always use 2 indices with a Variant Array when
assigning it to an Range, even when assigning the array to a single column as follows….
Dim Titles As Variant
Titles = Range("A1:A3")
MsgBox Titles(2, 1)
Similarly,
(2,1) refers to the 2nd row down, in the 1st column.
You can’t just have
Titles(1) etc.
… or a single row:
Dim Titles As Variant
Titles = Range("A1:B1")
MsgBox Titles(1, 2)
(1,2) refers to the 1st row in the 2nd column.
The Array Function Page 162
Dim Company As Variant, Sales As Variant
Company = Array("Business Systems", "Best Image", _
"Analytical Systems")
Sales = Array(2.34, 3.42, 5.62)
MsgBox Company(0)
MsgBox Sales(0)
End Sub
60
document1
The UserForm Page 164
Dim Company As Variant, Sales As Variant
Company = Array("Business Systems", "Best Image", _
"Analytical Systems")
UserForm1.ListBox1.List = Company
UserForm1.Show
After page 166, try this exercise:
Exercise 1:
The UserForm uses the array
Company = Array("Business Systems", "Best Image", "Analytical Systems")
o Define (and declare) the array:
Sales = Array(2.34, 3.42, 5.62)
The objective is to click on the company name e.g. …
… whereupon we will get the message:
Hint: Use ListBox1.ListIndex as the index of the Arrays Company and Sales.
soln page 27 vbaClassSolutions.doc
61
document1
Run-Time Errors
On Error Resume Next
MsgBox 10 / Cells(1, 1).Value
If Err.Number = 11 Then GoTo divZero
Exit Sub
divZero: MsgBox "A1 must contain a non-zero number"
o Omit the Exit Sub code and note the effect.
page 169
Dim Sales(2) As Variant
On Error GoTo overRange
Static i As Integer
i = i + 1
Sales(i) = InputBox("Enter Sales")
Exit Sub
overRange: If Err.Number = 9 Then _
MsgBox "Only 2 entries allowed"
page 170
Private Sub cmdVariantArrays_Click()
Dim Company As Variant
Company = Range("A1:B3").Value
MsgBox Company(1, 1)
MsgBox Names("myName").RefersTo
Private Sub CommandButton2_Click()
MsgBox [myName]
HW:
62
document1
Chapter 12
Date and Time
Microsoft’s Date System. It’s wrong.
The intention was to make day 1 on the 1st Jan 1900 (00:am). What then about day 0?
This would mean we would need to account for day zero as 31st December 1899.
Place 0 in cell A2. Ctrl-drag down to
about 100. Copy this column across to
column B and format column B as Date.)
You may need to repair cell B2 and
change it from 00/01/1900 to 31/12/1899.
But someone made a mistake! Take a look further down.
Somebody thought that
1900 was a leap year!
(M/soft blames Lotus 123 for this – or put differently there
were so many s/sheets in existence with the wrong date
that they didn’t want to change it. What do you think?)
If we do a manual count of the actual days since 31st Dec 1899, 01/03/1900 is in fact the 60th day not
the 61st as shown here.
If we wish it to be the 61st day as erroneously shown here, move the goalposts!
ie make the start date one day earlier: 30th Dec 1899.
Now 01/03/1900 will be the 61st day since the 30th Dec 1899 – which it is.
So we finally have it. The official M/soft start date is 30th Dec 1899 – day zero - 00:am on that day.
Day 1 will be the day after: 31st Dec 1899. (00:am is the start of that day.)
All days from the 61st day will now be correct (with the bogus leap year still erroneously included), but
there is still a problem: days before this bogus value will be one day out - and indeed this is the case
today. Dates before the bogus leap day will be wrong insofar as the number of days since 30th Dec
1899 is concerned – so keep this in mind!
Whereas this may seem at first sight a rather drastic error, it is not as bad as it may seem. We are
usually concerned with date differences and usually after this bogus leap day. Differences will of
course be correct provided that the dates don’t straddle the bogus leap day.
63
document1
p.172
MsgBox Now()
MsgBox CDbl(Now())
p.173
MsgBox Year(Date)
MsgBox Second(Time)
MsgBox DatePart("y", Date)
MsgBox DateValue(Now())
p.174
MsgBox #30/10/46#
o Type this line but don’t press
Enter, because when you do….
Private Sub CommandButton1_Click()
MsgBox #10/30/1946#
End Sub
… it thinks that this is a mistake
and converts it to American format!
Hence always use DateValue instead of date delimiters. eg:
Private Sub CommandButton1_Click()
MsgBox DateValue("30/10/1946")
End Sub
Dates are measured from midnight. eg
March 3 to March 4 means from 00 hrs on
March 3 to 00 hrs March 4 ie 1 full day.
o On the web, click the MsgBoxDates (ref E4 on sheet) button on Ch12 sheet. (Note that the
function on the BookSummary s/sheet is selected each time that a MsgBox appears.)
64
document1
p.175
MsgBox DateValue(Now() + 1)
MsgBox DateAdd("ww", 1, Date)
DateDiff:
Dim strtDate As Date, fnshDate As Date
Dim n As Integer
strtDate = DateValue("Dec 1,2004")
fnshDate = DateValue("Mar 1,2005")
n = DateDiff("d", strtDate, fnshDate)
n = DateDiff("m", strtDate, fnshDate)
'n = fnshDate - strtDate
MsgBox n
MsgBox DateDiff("m", strtDate, fnshDate)
o Note that n = fnshDate – strtDate could be used
instead of n = DateDiff("d", strtDate, fnshDate). Why?
p.176 WeekDay:
MsgBox Weekday(Date)
Dim strtDate As Date, fnshDate As Date
Dim dt As Integer, i As Long
Dim numWorkDays As Long
strtDate = DateValue("Dec 1,2004")
March 1 2005 is now included since we are using a
fnshDate = DateValue("Mar 1,2005")
numWorkDays = 0
loop to consider every date in the interval.
For i = strtDate To fnshDate
dt = Weekday(i)
If (dt <> vbSaturday And dt <> vbSunday) Then
numWorkDays = numWorkDays + 1
End If
Next i
MsgBox numWorkDays
Note that there seems to be some “internal compensation” for the bogus leap day. When the following
procedure is run, the numbers of days between these dates is given correctly.
Private Sub CommandButton1_Click()
Dim d1 As Date, d2 As Date
d1 = DateValue("28 feb, 1900")
d2 = DateValue("1 march, 1900")
MsgBox d2 - d1
End Sub
The two dates straddle the bogus leap day.
DateValue is of the form
DateValue(day month, year).
65
document1
p177: preliminary:
True And False
True has the arithmetic value of -1 and False has the arithmetic of 0.
o Try these message boxes and write in the answers
MsgBox 1 < 2
MsgBox 1 > 2
MsgBox (True And True)
MsgBox (True Or True)
MsgBox Not(True) etc.
MsgBox CInt(True)
MsgBox (3 + True)
MsgBox (3 * True)
MsgBox (3 * False)
MsgBox (True And False Or False) etc.
MsgBox 1=2
Mod
MsgBox 13 Mod 4
MsgBox 1900 Mod 100
MsgBox 1900 Mod 4
MsgBox (1900 Mod 4 = 0)
MsgBox (1900 Mod 100 = 0)
MsgBox (1900 Mod 100 <> 0)
MsgBox (1900 Mod 400 = 0)
Page 177.
MsgBox (yr Mod 4 = 0) And (yr Mod 100 <> 0) Or (yr Mod 400 = 0)
66
document1
p. 178 Anniversaries
o 1. Type this code in a command button – using your date of birth for dob.
Dim dob As Date
dob = DateValue("Dec 30, 1966")
MsgBox Year(Date)
MsgBox Month(dob)
MsgBox Day(dob)
MsgBox DateSerial(Year(Date), Month(dob), Day(dob))
o 2 Put the above together using DateSerial to construct the date of your birthday this year
and assign it to a variable of Date type named bday and then:
and then MsgBox bday.
code:
Dim dob As Date
dob = DateValue("Dec 30, 1966")
MsgBox Month(dob)
MsgBox Day(dob)
MsgBox Year(Date)
MsgBox DateSerial(Year(Date), Month(dob), Day(dob))
o 3. Find your age by subtracting the Year of your dob from the current year as follows.
MsgBox Year(Date) - Year(dob)
Is it correct? Maybe. Maybe not. One year older? Why not?
o 4. Try this. (Use your birthday.)
MsgBox (Date < bday)
The result will be True if today’s Date is less than the date of your birthday this year ie if you have not
had your birthday yet this year.
Use CInt to convert the True or False to Integer value as follows.
MsgBox CInt(Date < bday)
Recall however that CInt is not really necessary (but it is good practice include it) if an implicit
conversion is performed
67
document1
o Now try p 178.
Dim age As Integer
Dim dob As Date, bday As Date
dob = DateValue("Oct 30, 1966")
bday = DateSerial(Year(Date), Month(dob), Day(dob))
MsgBox bday
age = Year(Date) - Year(dob) + (Date < bday)
MsgBox age & " years old"
HomeWork Exercises : 1.You are given a date. Find the month of that date.
eg for 17/7/2005:
MsgBox Month(Date)
2. Use DateSerial to find the date of the first day of that month of that year.
Express this as a day of the week:
o 3. More difficult: Find the date of the last day of a month.
There is no simple VBA function. You will need to construct one.
4. The code on page 176 of the book finds the number of weekdays (working days) between
Dec 1,2004 and Mar 1,2005.
Modify the book code to exclude these public holidays:
“Dec 25,2004", "Jan 1,2005", "Jan 26 ,2005".
Your result should be:
(Note that “Dec 25,2004" and "Jan 1,2005"
are both Saturdays.)
Hint: Put the holidays into an array: Something like this:
stHols = Array("Dec 25,2004", "Jan 1,2005", "Jan 26 ,2005")
and iterate through the array for a match for each day i.
You may wish to also use a Boolean variable isHol and set it equal to True if a match is found.
68
document1
solutions ch12
Private Sub cmdFirstDayOfMonth_Click()
Dim dt As Date, stDay As String, dt1 As Date
dt = CDate("October 30 1946")
dt1 = DateSerial(Year(dt), Month(dt), 1)
MsgBox dt1
stDay = Format(dt1, "ddd")
MsgBox stDay
End Sub
Private Sub cmdLastDayOfMonth_Click()
Dim dt As Date, stDay As String
dt = CDate("October 30 1946")
stDay = Format(DateSerial(Year(dt), Month(dt) + 1, 0), "ddd")
MsgBox stDay
End Sub
Private Sub CommandButton1_Click()
Dim dt As Date, m As Integer
m = Month(Date)
'MsgBox m
dt = DateSerial(Year(Date), Month(Date), 1)
'MsgBox dt
MsgBox Format(dt, "ddd")
End Sub
No 4. Holidays:
Private Sub CommandButton1_Click()
Dim strtDate As Date, fnshDate As Date
Dim dt As Long, i As Long, n As Integer
Dim numWorkDays As Long
Dim stHols As Variant, Hols(3) As Long
Dim isHol As Boolean
stHols = Array("Dec 25,2004", "Jan 1,2005", "Jan 26 ,2005")
For n = 1 To UBound(stHols, 1) ' Convert strings to Dates and then to
Longs.
Hols(n) = CLng(DateValue(stHols(n)))
Next n
strtDate = DateValue("Dec 1,2004")
fnshDate = DateValue("Mar 1,2005")
numWorkDays = 0
For i = strtDate To fnshDate ' For all days between these dates.
'First test to see if this i is a holiday
isHol = False 'Set iHol back to false for each new date.
For n = 1 To UBound(Hols, 1) ' Check to see if this date i is a
holiday. isHol = True if it is.
If i = Hols(n) Then
isHol = True 'eg if i = 38346 Then
Exit For
End If
Next n
dt = CLng(Weekday(i))
If (dt <> vbSaturday And dt <> vbSunday And isHol = False) Then 'ie a
weekday and not a holiday
numWorkDays = numWorkDays + 1
End If
Next i
MsgBox numWorkDays
End Sub
69
document1
Chapter13
page 180.
Application.SheetsInNewWorkbook = 5
Obviousloy SheetsInNewWorkbook is a property of the Application object.
All of this may be confirmed
Try :
by recording a macro.
ActiveWindow.DisplayGridlines = False
The Window object is itself a child of the Application
object, so
Application.ActiveWindow.DisplayGridlines = False
would achieve the same thing.

Exercise: Write code to toggle the gridlines.
p. 180 to181: Try the others.
Application.StatusBar = "Calculating..."
Application.DisplayAlerts = False
ActiveWorkbook.Close
Application.DisplayAlerts = True
Contrary to what Microsoft says, the
file is not saved if Display Alerts is
turned off and the file is closed.
Try page 182 Worksheet Sum formula
Cells(4, 1).Value = WorksheetFunction.Sum(Range("A1:A3"))Confirm that the formula
itself is not inserted – only the result!
o Try page 183 PMT.
A
amount
rate
nper
pmt
B
100000
0.05
12
Dim rate As Double, nper As Integer
Dim paymnt As Double
rate = [B2].Value / 12: nper = 20 * 12
paymnt = WorksheetFunction.Pmt(rate, nper, [B1].Value)
[B4].Value = Format(Abs(paymnt), ".##")
[B4].Value = Abs(paymnt)
[B4].NumberFormat = ".##"
What is the “overall” interest rate?
o Try page 184 OnTime
70
document1
Application.OnTime Now() + TimeSerial(0, 0, 3), "mBox"
Sub mBox()
MsgBox "Sub
called"
End Sub
Try page 184 OnKey
Application.OnKey "{TAB}", "mBox"
[a1].Select
'Application.OnKey "{TAB}"
o As mentioned in the text, it won’t work unless you click on the spreadsheet first after clicking the
Command Button (before pressing the Tab key)
A better solution is to change the TakeFocusOnClick property from its default of True to False (see
below). This effectively removes the focus from the Command Button after it is clicked.
o Right-click on the command
button in Design View and
choose Properties.
Or select a cell:
Private Sub cmdOnKey_Click()
Application.OnKey "{TAB}",
"mBox"
[a1].Select
End Sub
o Change TakeFocusOnClick to False.
71
document1
Page 185. Undo:
In a new workbook, try this first:
o Type a value eg 43 in Cell A1.
o Type and try the code below in the Worksheet_Change event (The code may be found on
BookSummary Ch13 sheet ref B35.)
Private Sub Worksheet_Change(ByVal Target As Range)
MsgBox Target.Value
End Sub
o Change the 43 to 44. The 44 is what we see in the message box ie the new value entered is the
target value.
o Modify the code as follows:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
MsgBox Target.Value
Application.Undo
MsgBox Target.Value
Application.EnableEvents = True
End Sub
(Take care that you don’t make a
syntax error and stall your code after
Application.EnableEvents =
False.
You would then need to explicitly
make
Application.EnableEvents =
True
in perhaps a separate Command
Button procedure.)
o Make sure that the Design Mode is off and overtype another value eg 45 in Cell A1.
o Immediately that the 45 is entered…
Undo causes Excel to effectively
type in the old value - so that this
value becomes the Target.
.. the Target Value - the value that
has been placed in A1 - is displayed.
Undo cause the previous value to be returned…
…and this Target Value which has effectively
been entered by Excel is displayed.
o
The code on p. 185 simply saves the original Target Value in a variable x and places it into
cell A1. Try it.
Application.SheetsInNewWorkbook = 5
Application.StatusBar = "Calculating..."
Application.DisplayAlerts = False
ActiveWorkbook.Close
Application.DisplayAlerts = True
72
document1
SendKeys
o page 185: On a new worksheet, place a Command Button and try: SendKeys "%h ".
o Try inserting a new worksheet using these following 2 methods.
1. The Alt+Shift+F1 key combination (where the Alt key, the Shift key and the F1 key are all held
down together).
2. The Alt+I combination followed by W.
From VBA Help:
To specify that any combination of SHIFT, CTRL, and ALT should be held down while several other keys are pressed,
enclose the code for those keys in parentheses.
For example, to specify holding down SHIFT while E and C are pressed, use "+(EC)".
To specify holding down SHIFT while E is pressed, followed by C without SHIFT, use "+EC".
For example:
To insert a new worksheet:
The following is equivalent to pressing the Alt+I combination followed by W.
SendKeys "%IW"
o Try it.
The following is equivalent to the Alt+Shift+F1 key combination. Holding the Alt key, the Shift key and
the F1 key down together can be specified by enclosing them in parentheses aa shown here.
SendKeys "(%+{F1})"
o Try it.
Using either of these will result in a new worksheet being inserted into the workbook.
73
document1
Volatile Functions
page 186.
Try this first.
To show that NOW is a volatile function
As per page 186 – a volatile function is one
which updates automatically when a
recalculation takes place.
o Type = NOW() into a cell and then format it so that
the seconds is visible (Format, Cells…, Time) and then
choose a Type which has seconds displayed.
o
Independently, place a formula into a cell and a value upon which it relies.
o A formula has been placed here.
o Change the value upon which the formula depends.
The time is updated - showing that Now() is a Volatile function.
To show that Rand is a volatile function
o Type =RAND() into a cell.
A random number appears.
o Recalculate the sheet by changing the value upon which the above formula depends – as above.
A new random number should appear.
o Try Page 186 in a new workbook.
Function GetWorksheetName() As
Variant
Application.Volatile
GetWorksheetName =
ActiveSheet.Name
End Function
74
document1
To use a Class in another Workbook
First Export the class that you previously made as follows:
o In the VBE,
highlight the
class that you
wish to export…
o ..then click File
and then click
Export File….
o Save the class sheet to the desktop with the name clsEmp.cls
o Close the current workbook.
o Open a new workbook (before closing, copy the command button code for later use, copy the
General Declarations code as well.) and place two command buttons on the sheet. Double-click to
take you to the VBE.
o From the VBE of the new workbook:
o Click File and then
click Import File….
o Choose clsEmp.cls from the desktop and click Open. In Project Explorer, you will see that it has
been added to your project.
o Write some code in the command button which creates a new object and utilizes its properties
and methods as before as follows:
You will need to Dim an object (instance) as we did previously with code similar to:
(You may wish to paste the code we used in the previous book.)
Dim emp1 As clsEmp
Set emp1 = New clsEmp
o Take a look and the properties and methods of the
class by using the Object Browser (press F2 from the
VBE and choose VBAProject or <All Libraries>).
o Now write (or copy) the command button code to utilize the properties and methods of this object
as before.
So far we have exported and imported class source code from one application to another. This is just
scratching the surface. We are also able to utilize a class in Excel that has been compiled. (When
code is compiled, it is converted to 1’s and 0’s which only the microprocessor can understand.) That’s
right. Executable code can be re-used! )
But here is the bad news. Excel is not capable of compiling a class - only utilizing it. We have to use
Visual Basic or C++ to do that. If we wish to utilize a compiled class in Excel, we need to start again
and make a class in Visual Basic - and compile it – see: To Make a .DLL using Visual Basic see
http://www.una.co.uk/ To Make a DLL Containing a Class.doc ) (pages 2-6)
We will now see how to utilize this compiled class in an Excel project.
75
document1
Interacting with other Office Applications
Using Excel to Place Some Text into a Word Document:
o Preliminary: Open Microsoft Word and make a macro to insert the word mat at the position of the
cursor as shown below.
o
Edit the macro to view it's code
Automation
What if we wished to do the same thing but this time we wished to do this from Excel.
First - is it possible? Yes because Excel and Word both support Automation.
Since we know that Word supports Automation, we know that we can get access to Word’s objects
from Excel. We say that Word exposes its objects to Excel (to other applications such as Access as
well.)
Note that we do not even need to run Word to use its objects. This could be done completely from
Excel without opening Word but for the exercise we would like to watch the Word document to see
that it works.
Before we start using Word's objects, we need to tell Excel that we intend to use them by setting a
reference to the Microsoft Word Object library.
o …click Tools, References….
o
In Excel, place a Command Button on a sheet and in the VBE …
o Select Microsoft
Word 11.0 (Or later
version) Object Library
and then click OK.
76
document1
o
Place the following code in a Command Button procedure and run it.
Every application which supports Automation provides an Application object. This is usually our
starting point.
Private Sub Cmdstartword_Click()
Dim Appword As Word.Application
Set Appword = New Word.Application
Appword.Visible = True
Appword.Documents.Add
Appword.Selection.Typetext "Mat"
Set Appword = Nothing
End Sub
o Appword can now be used to
access all of Word’s VBA!
We are now programming in Word VBA.
Note also that when we are typing, we get the IntelliSense assisting us – even for Word Keywords!
(This is also an indication that the reference to the Word objects library has been successfully set –
one of the benefits of “Early Binding”).
When we click our Command Button on our Excel sheet to run the code, you should find that Word
opens with a new document and the text “mat” is placed at the start of the document.
Late Binding is slower, we don’t get the IntelliSense feature when editing and is really only
included for backward compatibility.
We do not have to set a reference (Tools, References etc) when we chose to use Late Binding.
We would then use
Set appWord = GetObject(,"Word.Application") and CreateObject etc instead of
Dim appWord As Word.Appliction
Set appWord = New Word.Application.) etc.
77
document1
API (Application Programming Interface)
Many Windows functions can be accessed from Excel VBA. (There are over a 1000 of them!)
We can get and change system information eg
 the Current Directory.
 the User Name
 the Computer Name
 the keyboard repeat rate
 the amount of RAM available etc
We may wish to:





change the caption of a form
make it flash
bring one form(window) to the top
find which form has got the focus
change the colour of a command button etc
How do we find out about API's? – the best source is the net.
Whole books on API's exist. (Fortunately the code we use is identical whether we are using Excel
VBA or Visual Basic itself.
Fortunately we don’t have to type in the declarations by hand- we can copy then from a file called
win32API.txt.
o Do a Find File to try to locate this file. (It is also included with various book CDs) – or try from the
school site using this URL in Internet Explorer : http://www.una.co.uk and clicking Win32API.txt.
We can now cut and paste it from this text file.
78
document1
To Retrieve the User Name from Excel
o
Type (or copy) the following into a new Module.
Copy this code from www.una.co.uk/Win32API.txt
Declare
means that
the function
is external to
Excel.
LpBuffer is where the user name string will eventually be stored.
This is a rather unusual way to return the string - implicitly in a "buffer".
nSize will be the size
of this string buffer.
Where you see ByVal in the Declaration above read ByRef!!
This anomaly comes about because these API functions are written in C language and the default is
ByVal rather than ByRef which it is in VB.
o
Place a command button on a form and write the following code.
Private Sub CommandButton1_Click()
Dim strUserName As String
Dim di As Long
strUserName = String(255, 0)
di = GetUserName(strUserName, 255)
MsgBox strUserName
End Sub
di will be 0 for failure or
non-zero upon success.
This creates a dummy fixed length
string of size 255 (255 zeros). (API's
can't cope with variable length strings.)
Note that the string is actually returned here "inside" the function! ie by reference
o
Run the code.
You should get a message box with your user name (assuming that you have got one).
79
document1
Saving Data To Disc
We know well how to save our particular spread sheet and it's associated data and code, but what if
we wish to save some data onto disc independently.
For example we might save some lines of text or perhaps a set of numbers or even a picture (bitmap).
You will come across the words output and input.
Output means from the computer (memory) to the disc.
ie writing to the disc
Input means to the computer from the disc.
ie reading from the disc
Sequential file
A Sequential file is a file which has variable length records.
If we save strings for example, then the strings could be placed contiguously.
The actual file on the disc may look something like this:
red.blue.yellow.green
This can cause a bit of a problem when we try to retrieve a particular record since we need to know
where each record starts.
Random Files
Random Files have fixed length records
(There is nothing “random” about them at all except that we are able to choose a record at random!)
The actual file on the disc may look something like this:
red.….blue….yellow..green…
- where in this case each record is 8 bytes long – the rest of the record is padded out with spaces for
example. It is a lot easier to retrieve say the 3rd record since we can calculate its start position (3 x 8
= 24)
80
document1
Creating a Sequential File and Writing to it
You may wish to work from
BookSummary ref E20 on
the Lesson14 sheet.
o Place a command button on the spreadsheet.
o In order to change the Name and Caption, select the button
and choose Properties.
o Change the
Name.
o Change the
Caption.
 Type the following code.
c: is the location of the file ie where it is to be saved. At the moment we have specified the
root directory. (If you don’t specify a directory, ie only the file name is specified, then the file
will be saved into the directory in which you are currently working on your project.)
Private Sub cmdCreateSerial_Click()
Open "c:\Saver" For Output As 1
Write #1, "ed", "jo", "al"
Close #1
End Sub
At the moment we are sending
data to the disk so it is Output.
(As we shall see later, this could
also be Input or Append.)
This file number (1) can be any number (provided this
number isn’t being currently used as the number of
another file.) It must be consistent throughout.
If you make a mistake in typing your code, the code of course may stall when run.
The file may have been opened and not closed. If we repair the code and try to run
it, we will probably get an error saying that the file is open. To avoid this, make
another button with the following code which will close all files :
Private Sub CommandButton1_Click()
Close #0
End Sub
81
document1
We now wish
To Read Back The File
 Place a second command button on the spreadsheet with the Name and Caption as below. Type
the following code.
Private Sub cmdReadSerial_Click()
Dim a As String, b As String, c As String
Open "c:\Saver" For Input As 1
Input #1, a, b, c
Close #1
Cells(1) = a: Cells(2) = b: Cells(3) = c
End Sub

Click on the command button. The records are printed onto the sheet as shown below.
Note that the commas and the inverted commas (these are called field delimiters) are not retrieved as
well.
82
document1
We can b=view the actual bytes in memory using a hex editor.: eg
http://mh-nexus.de/en/hxd/
The contents of your file can be dumped (hence the d) into memory so that you can view it.
22 is the ascii for an
inverted comma.
2C is the ascii for comma. (This is a comma
delimited file.) 22 is the ascii for space.
OD OA is Ascii for carriage return,
line feed, ie the end of your file.
(Note that the other “junk” bytes are remnants of what was there
previously. - you may later discover that these junk bytes may be the
remnants of your previous file which may cause some confusion)
(Each of these entries - “ed” etc is referred to as record.)
83
document1
Where to go from here:
Consolidation:
Wrox Press Excel VBA 2003 is an excellent reference.
Internet Sites. There are many sites offering code snippets and general instruction on Excel VBA. In
particular:
http://www.cpearson.com/excel.htm
also:
http://www.vba-programmer.com/
Forums: Mr Excel and also:
http://www.ozgrid.com/forum/
- very helpful and turnaround is quick. (Try and help answer questions yourself- sometimes it’s a race
to do so!)
Progress:
We have seen how to make a class. Visual Basic.Net and C++ etc. rely heavily upon the use of
classes.
Even placing a Command Button on a form – the very first thing that we did is indeed creating an
instance (object) of the Command Button class on our form. Each object thus created object has a
Caption property for example. Such an object is an ActiveX control. As well as being associated with
a class a control also has an accompanying graphical object. Creating a Command Button object is –
as you have seen – simply a matter of dragging such an object onto the sheet!
E Robinson
To learn VBScript, HTML, ASP (server-side scripting) etc see www.w3schools.com
To learn Access VBA, C++ see Mr R!
84
document1
Exercise:
To display the formula in the cell to the right.
Place an apostrophe in front of the actual formula.
Drag it across to the next cell to the right.
Remove the apostrophe in front of the original formula.
Write a macro to do this when double-clicking on the original formula.
Won’t always work if ends in a number eg
85
document1
Private Sub cmdBS_Click()
MsgBox BSfra("v", "C", 110, 100, 10, 2, 4, 0.1)
End Sub
Function BSfra(retval, CP, Price, strike, Volatility, Tinn, yf, DiscFactor)
'Black 76 for utregning av opsjon på FRA
Dim T As Double
Dim vega, pris, delta As Double
s = Price
x = strike
v = Volatility / 100
d = DiscFactor
T = Tinn * 360 / 365
d1 = (Log(s / x) + (v ^ 2 / 2) * T) / (v * Sqr(T))
d2 = d1 - v * Sqr(T)
nposd1 = Application.NormSDist(d1)
Nposd2 = Application.NormSDist(d2)
If CP = "C" Or CP = "c" Then
pris = yf * d * (s * nposd1 - x * Nposd2)
delta = nposd1
Else
pris = yf * d * (x * (1 - Nposd2) - s * (1 - nposd1))
delta = nposd1 - 1
End If
nder1 = Exp(-(d1 ^ 2 / 2)) / Sqr(2 * Application.Pi)
'
N'(d1)
vega = s * Sqr(T) * nder1 * d * yf
If retval = "v" Or retval = "V" Then
BSfra = vega
Else
If retval = "f" Or retval = "F" Then
BSfra = pris
Else
BSfra = delta
End If
End If
End Function
VBA only exists as an embedded "macro language" inside a host application like Excel. At one time
you could even buy the SDK to embed VBA in your own applications, but there was never any "stand
alone VBA."
86
document1
pseudo random numbers:
Addresses.xlsm MonteBakke sheet.
are stored permanently! in the computer memory.
0.237866 0.263761 0.801471 0.887471 0.279494 0.960849
0.751148 0.603176 0.084221 0.764313 0.730556 0.88049
0.576213 0.759915 0.824393 0.063505 0.737212 0.544134
They are called pseudo because…..
Soln see
AddressExpt or
DNB.xlsm.
MonteBakke
sheet.
Rnd() produces pseudo random numbers.
First produce a column of them.
For i = 1 To 100
We can use a seed to
ensure that we get a
different set of random
numbers. A seed of -1
ie Rnd(-1)will give the
SAME random
numbers.
Cells(i, 2) = Rnd() ' uniform distn
Next i
They are uniformly distributed:
Frequency
15
10
Frequency
5
1
0.8
0.6
0.4
0
0.2
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1
More
Frequency
0
9
14
8
9
14
8
8
12
7
11
0
0
Bin
To produce this frequency table we need to install:
then Data, Data Analysis, Histogram.
Then draw an ordinary bar chart.
87
document1
Likewise we could produce a set of normally distributed (rather that uniformly distributed ) random
numbers using the Analysis ToolPack.
To produce normally distributed (rather that uniformly distributed ) random numbers without this
Toolpack we could generate ourselves them using Box-Muller:
http://mathworld.wolfram.com/Box-MullerTransformation.html
Exercise: Employ this function to produce a column of 100 normally distributed random numbers.
Function gasdev(Seed As Long)
' Gaussian random numbers using Box-Muller method with rejection
Static iset As Integer
Static gset As Double
Dim fac As Double, rsq As Double, v1 As Double, v2 As Double
If (iset = 0) Then
Do
v1 = 2# * Rnd(Seed) - 1#
v2 = 2# * Rnd(Seed) - 1#
rsq = v1 * v1 + v2 * v2
Loop While (rsq >= 1# Or rsq = 0#)
fac = Sqr(-2# * Log(rsq) / rsq)
gset = v1 * fac
iset = 1
gasdev = v2 * fac
Else
iset = 0
gasdev = gset
End If
End Function
Soln see
AddressExpt or
DNB.xlsm.
MonteBakke
sheet.
and plot them on a histogram
Bin
-3
-2.5
-2
-1.5
-1
-0.5
0
0.5
1
1.5
2
2.5
3
More
Frequency
0
1
0
6
9
13
21
11
17
11
7
3
1
0
25
20
15
10
5
0
-3
-2
-1
0
1
2
3
88
document1
Bivariate Normal distribution
Rather like aiming at a bullseye – a 2-dimensionsional normal distribution:
(Plot courtesy Matlab:)
mu = [0 0];
var = .75
Sigma = [var 0; 0 var];
x1 = -3:.2:3; x2 = -3:.2:3;
[X1,X2] = meshgrid(x1,x2);
F = mvnpdf([X1(:) X2(:)],mu,Sigma);
F = reshape(F,length(x2),length(x1));
surf(x1,x2,F);
caxis([min(F(:))-.5*range(F(:)),max(F(:))]);
a=3;
axis([-a a -a a 0 .3])
xlabel('z1'); ylabel('z2'); zlabel('Probability Density');
The above formula is a simplification when the correlation between x & y (on the horizontal axes) is
zero. If it is not we must use:
f
eg for any value of correlation
greater than zero (and less than 1)
the plot will look more like:
This might be used to solve problems like this:
(We are here assuming tall husbands choose tall wives (and vice versa!)
Or for height and weights: The corr’n between heights and weights is .6
Mean height s and weights are… Find the prob of someone being over … in ht and … in weight.
.
89
document1
In solving such problems we need to find the volume! under the plot – in 3-D. This is not tractable but
fortunately approximations exist:
http://www-2.rotman.utoronto.ca/~hull/technicalnotes/TechnicalNote5.pdf
(or see handout)
Exercise Use the function below to show that the area (actually volume) from – ∞ to 0 for x (ie a = 0)
and – ∞ to 0 for y (ie b = 0) is .25 ie ¼ of the plot – the correlation (rho) could actually be any value ,
it will still be ¼ of the plot but we could put rho equal to 0 just to get the value.)
VBA code to find approximate bivariate area under the normal bivariate curve:
(Note that it is a recursive function ie calls itself!.
Single-step this:
A recursive function:
Private Sub CommandButton1_Click()
MsgBox recur
End Sub
Function recur()
Static x As Integer
x = x + 1
If x = 5 Then Exit Function
recur ' This calls itself!!
recur = x 'Usual function return. (Nothing to do with recursion.)
End Function
Note also that you will need to include the auxiliary functions f and Norm (See end of this code).
'Bivariate Cumulative Normal Distribution Approximation (As given by Hull,
etc)
Function BivN(a As Double, b As Double, rho As Double) As Double
Dim
Dim
Dim
Dim
Dim
Dim
aarray, barray
rho1 As Double, rho2 As Double, delta As Double
ap As Double, bp As Double
Sum As Double
i As Integer, j As Integer, Pi As Double
denum As Double
If (rho = 1) Then
BivN = Norm(a)
If (b < a) Then
BivN = Norm(b)
End If
GoTo EXIT_NOW
End If
90
document1
aarray = Array(0.24840615, 0.39233107, 0.21141819, 0.03324666,
0.00082485334)
barray = Array(0.10024215, 0.48281397, 1.0609498, 1.7797294, 2.6697604)
Pi = 3.14159265358979
ap = a / Sqr(2 * (1 - rho ^ 2))
bp = b / Sqr(2 * (1 - rho ^ 2))
If a <= 0 And b <= 0 And rho <= 0 Then
Sum = 0
For i = 1 To 5
For j = 1 To 5
Sum = Sum + aarray(i - 1) * aarray(j - 1) * f(barray(i 1), barray(j - 1), ap, bp, rho)
Next
Next
BivN = Sum * Sqr(1 - rho ^ 2) / Pi
ElseIf a <= 0 And b >= 0 And rho >= 0 Then
BivN = Norm(a) - BivN(a, -b, -rho)
ElseIf a >= 0 And b <= 0 And rho >= 0 Then
BivN = Norm(b) - BivN(-a, b, -rho)
ElseIf a >= 0 And b >= 0 And rho <= 0 Then
BivN = Norm(a) + Norm(b) - 1 + BivN(-a, -b, rho)
ElseIf a * b * rho >= 0 Then
denum = Sqr(a ^ 2 - 2 * rho * a * b + b ^ 2)
rho1 = (rho * a - b) * Sgn(a) / denum
rho2 = (rho * b - a) * Sgn(b) / denum
delta = (1 - Sgn(a) * Sgn(b)) / 4
BivN = BivN(a, 0, rho1) + BivN(b, 0, rho2) - delta
End If
EXIT_NOW:
End Function
Public Function f(X, Y, ap, bp, rho)
Dim r
r = ap * (2 * X - ap) + bp * (2 * Y - bp) + 2 * rho * (X - ap) * (Y - bp)
f = Exp(r)
End Function
Public Function Norm(X As Double) As Double
Norm = Application.NormSDist(X)
End Function
91
document1
Private Sub cmdMonte_Click()
Cells(1, 1).Value = BivN(0, 0, 0)
Cells(2, 1).Value = gasdev(1)
End Sub
'Bivariate Cumulative Normal Distribution Approximation (As given by Hull,
etc)
Function BivN(a As Double, b As Double, rho As Double) As Double
Dim
Dim
Dim
Dim
Dim
Dim
aarray, barray
rho1 As Double, rho2 As Double, delta As Double
ap As Double, bp As Double
Sum As Double
i As Integer, j As Integer, Pi As Double
denum As Double
If (rho = 1) Then
BivN = Norm(a)
If (b < a) Then
BivN = Norm(b)
End If
GoTo EXIT_NOW
End If
aarray = Array(0.24840615, 0.39233107, 0.21141819, 0.03324666,
0.00082485334)
barray = Array(0.10024215, 0.48281397, 1.0609498, 1.7797294, 2.6697604)
Pi = 3.14159265358979
ap = a / Sqr(2 * (1 - rho ^ 2))
bp = b / Sqr(2 * (1 - rho ^ 2))
If a <= 0 And b <= 0 And rho <= 0 Then
Sum = 0
For i = 1 To 5
For j = 1 To 5
Sum = Sum + aarray(i - 1) * aarray(j - 1) * f(barray(i 1), barray(j - 1), ap, bp, rho)
Next
Next
BivN = Sum * Sqr(1 - rho ^ 2) / Pi
ElseIf a <= 0 And b >= 0 And rho >= 0 Then
BivN = Norm(a) - BivN(a, -b, -rho)
ElseIf a >= 0 And b <= 0 And rho >= 0 Then
BivN = Norm(b) - BivN(-a, b, -rho)
ElseIf a >= 0 And b >= 0 And rho <= 0 Then
BivN = Norm(a) + Norm(b) - 1 + BivN(-a, -b, rho)
ElseIf a * b * rho >= 0 Then
denum = Sqr(a ^ 2 - 2 * rho * a * b + b ^ 2)
rho1 = (rho * a - b) * Sgn(a) / denum
rho2 = (rho * b - a) * Sgn(b) / denum
delta = (1 - Sgn(a) * Sgn(b)) / 4
BivN = BivN(a, 0, rho1) + BivN(b, 0, rho2) - delta
End If
EXIT_NOW:
End Function
92
document1
Public Function f(X, Y, ap, bp, rho)
Dim r
r = ap * (2 * X - ap) + bp * (2 * Y - bp) + 2 * rho * (X - ap) * (Y f = Exp(r)
End Function
bp)
Public Function Norm(X As Double) As Double
Norm = Application.NormSDist(X)
End Function
Sub MonteCarloRandomExposures()
Dim s As Long, i As Integer, j As Integer, Thresholds() As Double,
Simulations As Long
Dim V As Double, Vi As Double, AssetReturn As Double, U As Double,
Correlation As Double
Dim rho1 As Double, rho2 As Double, MeanExposures() As Double,
StdDevExposures() As Double
Dim Loss1 As Double, Loss2 As Double, Loss3 As Double, Seed As Long,
TotalExposure As Double
Dim NumNames As Integer, Exposure As Double
NumNames = Range("NumNames")
Correlation = Range("Correlation")
Simulations = Range("MCSims")
Seed = Range("Seed")
ReDim
ReDim
ReDim
ReDim
Thresholds(1 To NumNames)
MeanExposures(1 To NumNames)
StdDevExposures(1 To NumNames)
EPE(1 To NumNames)
rho1 = Sqr(Correlation)
rho2 = Sqr(1 - Correlation)
Rnd (-Seed)
Randomize (Seed)
For i = 1 To NumNames
Thresholds(i) = Range("Thresholds").Offset(i - 1)
EPE(i) = Range("EPEs").Offset(i - 1)
MeanExposures(i) = Range("Exposures").Offset(i - 1)
TotalExposure = TotalExposure + EPE(i)
StdDevExposures(i) = Range("StdExposures").Offset(i - 1)
Next i
'
'
For s = 1 To Simulations
If (s Mod (Simulations / 20) = 0) Then
Range("Progress") = s / Simulations
End If
Loss1 = 0
Loss2 = 0
Loss3 = 0
U = Rnd()
V = Application.WorksheetFunction.NormSInv(U)
V = gasdev(Seed)
'
If V < -2.5 Then
V = V
End If
For i = 1 To NumNames
U = Rnd()
93
document1
'
Vi = Application.WorksheetFunction.NormSInv(U)
Vi = gasdev(Seed)
AssetReturn = rho1 * V + rho2 * Vi
' default ?
If (AssetReturn < Thresholds(i)) Then
U = Rnd()
V = Application.WorksheetFunction.NormSInv(U)
Vi = gasdev(Seed)
Exposure = MeanExposures(i) - Vi * StdDevExposures(i)
If (Exposure < 0) Then
Exposure = 0
End If
Loss1 = Loss1 + MeanExposures(i)
Loss2 = Loss2 + EPE(i)
Loss3 = Loss3 + Exposure
'
'
End If
Next i
Range("MCOutput").Offset(s) = Loss1 / TotalExposure
Range("MCOutput").Offset(s, 0) = Loss2 '/ TotalExposure
Range("MCOutput").Offset(s, 1) = Loss3 '/ TotalExposure
'
Next s
Range("Progress") = ""
ActiveSheet.Calculate
End Sub
94
document1
This needs allocating
Exercise:
Finding the total commission
eg find the total commission on £2750
(For example the commission is 5% for sales between 0000 and 1000.)
amt
2750
boundary
0000
1000
2000
2500
3000
4000
comm%
5%
10%
12%
4%
3%
2%
amt/each comm
1000
1000
500
250
Boundaries.
The problem is to find
the total commission due
on 2750. (ans : 220). It is
calculated as follows:
The 1st 1000 is at 5%.
The 2nd 1000 is at 10%.
The 3rd is obtained by
subtracting 2500 from
2750 and multiplying this
difference by 12%.
As distinct from a similar problem we are told that the amount (amt) is definitely IN one of these
intervals ie noting bigger that 4000.
1000 * 5% + (2000-1000) * 10% + (2500-2000) * 12% + (2750-2500) * 4% = 220.
50
+
100
+
60
+
10
This problem is interesting and quite representative because there is ONE ESSENTIAL THING that
we must do. What is it?
Ans: The problem comes down to determining which INTERVAL (ans: 2500-3000) that the 2750 is in!
So let’s do this first – step-by-step
Exercise 1
Start off by simply message boxing the boundary values.
Private Sub CommandButton1_Click()
(I have here named the
range B1:B6 as bounds,)
Dim amt As Single, rng As Range, i As Integer
Set rng = Range("bounds")
For i = 1 To rng.Count
amt = rng.Cells(i).Value
MsgBox amt
etc
Next i
End Sub
Now find which boundary that the 2750 is in.
Using
Dim lwr As Single, upr As Single
95
document1
we must first find these boundaries. In our case lwr = 2500 and upr = 3000.
Solution:
Private Sub CommandButton1_Click()
Dim amt As Single, i As Integer, lwr As Single, upr As Single
Dim rng As Range
Set rng = Range("bounds")
amt = Cells(1, 1).Value '2750
For i = 1 To 5
If amt > rng.Cells(i).Value And amt < rng.Cells(i + 1).Value Then
lwr = rng.Cells(i).Value
upr = rng.Cells(i + 1).Value
End If
Next i
MsgBox "Lower boundary is " & lwr
MsgBox "Upper boundary is " & upr
End Sub
Next: Calculate the commission for this interval only.
ie Subtract our value 2750 from the lower boundary and multiply by the 4% in this case but keep in
mind that it must work for any amt.
Hint: You might use
rng.Cells(i).Offset(, 1).Value
to get the corresponding commission.
96
document1
solution
Private Sub CommandButton1_Click()
Dim amt As Single, i As Integer, lwr As Single, upr As Single
Dim rng As Range, tot As Single
Set rng = Range("bounds")
amt = Cells(1, 1).Value '2750
tot = 0
For i = 1 To 5
If amt > rng.Cells(i).Value And amt < rng.Cells(i + 1).Value Then ' ie
its in this interval
lwr = rng.Cells(i).Value
upr = rng.Cells(i + 1).Value
tot = (amt - lwr) * rng.Cells(i).Offset(,
1).Value
End If
Next i
MsgBox tot
End Sub
Next we need to “accumulate” the “previous” commissions
ie in our example
1000 x 5%
1000 * 10% and
500 * 4%
Hints: Move the
lwr = rng.Cells(i).Value
upr = rng.Cells(i + 1).Value immediately after the
For i = 1 To 5
and use an Else.
Also use :
tot = tot + (upr - lwr) * rng.Cells(i).Offset(, 1).Value
97
document1
solution:
Private Sub CommandButton1_Click()
Dim amt As Single, i As Integer, lwr As Single, upr As Single
Dim rng As Range, tot As Single
Set rng = Range("bounds")
amt = Cells(1, 1).Value '2750
tot = 0
For i = 1 To 5
lwr = rng.Cells(i).Value
upr = rng.Cells(i + 1).Value
If amt > lwr And amt < upr Then ' ie its in this interval
tot = tot + (amt - lwr) * rng.Cells(i).Offset(, 1).Value
Else
tot = tot + (upr - lwr) * rng.Cells(i).Offset(, 1).Value
End If
Next i
MsgBox tot
End Sub
98
document1
DNB Notes
1. DNB.xlsm Output sheet (VBA Project.xlsx)
2. BiVariate
Helper fns
Recursive
see techNoteBivar sheet DNB.xlsm
3. Bloomberg
http://www.codeproject.com/Tips/455253/Gathering-Bloomberg-Data-withSynchronous-VBA-Call
Simple code to retrieve Bloomberg data.
First add a Reference to the ‘Bloomberg Data Type Library’.
Public Sub BloombergExample()
Dim bbgDataControl As New BLP_DATA_CTRLLib.BlpData
Dim price As Variant
price = bbgDataControl.BLPSubscribe("IBM US Equity", "PX LAST")
Debug.Print price(0, 0)
End Sub
To find out more about the Bloomberg API, go to <WAPI> on your Bloomberg Terminal.
http://remington-qa.blogspot.co.uk/2011/02/vba-bloomberg-api-download.html
To put a Bloomberg formula in the sheet from code
Cells(2, "K").Formula = "=BDP(""IBM Equity"",""PX_LAST"")"
then try MsgBox Cells(2, "K").value
see Bloomberg sheet of DNB.xlsm SimulateBDP button
99
document1
4.
Ch 7 HW
From Page 34
Reproduced here
Chapter 7 HW
We have 3 workbooks open. Tax2004.xlsx (or.xlsm), Tax2005.xlsx (or.xlsm) and your current
workbook which may be called Book1.xlsm. (Don’t forget that a workbook does not have a name until
you save it.
Tax2004.xlsx
Call the workbooks what you like.
Sheet1
Tax2005.xlsx
Sheet1
Problem:
3. Iterate through the 2 workbooks Tax2004 and Tax2005 and “collect “ the data on Sheet1 cell A1
and concatenate them into a string.
4. Alternatively instead of placing them into a string, place them on the current workbook like so.
100
document1
5.
Chapter 8 Page Page 35 of this manual
vlookup exercise ch 8 page 37 this manual
soln page 24 of solutions
page 118
(Try below as an exercise first.)
Dim rng As Range, code As Variant
Dim i As Integer
code = "dfg"
Set rng = Range("f78:f81")
'Set rng = Range("C3:C6")
For i = 1 To rng.Count
If rng.Cells(i).Value = code Then
MsgBox rng.Cells(i).Offset(0, -1).Value
End If
Next i
o Put this into a UDF. See page 156 for UDF
o Exercise: Emulate the “True” feature in the VLookup formula.
Be warned that this exercise can be difficult in that your errors may not always be reported – it just
won’t work sometimes! For example if we use rng = rng.Columns(2) rather than Set rng =
rng.Columns(2) it just wont work! (Soln page 26 of vbaClassSolutions.doc)
The Meaning of the True and False in the Excel Vlookup Formula
For example try looking for 35 using VLookup. An #NA message will result because an exact match
could not be found as shown below.
What if we are happy to find a value corresponding to the value which is just below (in value) the one
we are looking for?
We must do two things: (do this)
o 1. Sort the data.
o 2. Change the False to True (or omit it – it’s the default)
30 is smaller than 35 and hence 85 is returned.
101
document1
102
document1
6, HTML ?
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<h1>This is a Heading</h1>
<p>This is a new paragraph.</p>
</body>
</html>
Save to notepad as “myPage.html”
Open from Internet Explorer or Firefox etc
w3schools
http://www.w3schools.com/
o Choose
Microsoft Internet
Controls and click OK.
o Write this code in a command button click event (replace the previous code).
Private Sub CommandButton1_Click()
Dim oIE As SHDocVw.InternetExplorer
Dim sPage As String
'Create a new (hidden) instance of IE
Set oIE = New SHDocVw.InternetExplorer
'Open the web page
oIE.Navigate "http://www.x-rates.com/"
'Wait for the page to complete loading
Do Until oIE.ReadyState = READYSTATE_COMPLETE
DoEvents
Loop
'Retrieve the text of the web page into a variable
sPage = oIE.Document.body.InnerHTML
Debug.Print sPage
End Sub
o Run it and take a look at the Immediate Window (Ctrl-G):
103
document1
Monte Carlo
To produce a large number of normally distributed numbers which can be used in monte carlo
formulas we really need VBA.
After Page 436 Benniga:
Private Sub CommandButton1_Click()
normalSimulation
Place a command button on a
sheet and place this code in the
code module behind as well.
End Sub
Sub normalSimulation()
Dim rand1, rand2, S, i, X1, N
N = 100
For i = 1 To N
start:
Page 234 Odegaard
http://finance.bi.no/~bernt/
http://finance.bi.no/~bernt/gcc_prog/
for this algorithm. Benninga uses a
slightly different algorithm.
rand1 = 2 * Rnd - 1
rand2 = 2 * Rnd - 1
S = rand1 ^ 2 + rand2 ^ 2
If S > 1 Then GoTo start 'ie too big start again
X1 = rand1 * Sqr(-2 * Log(S) / S) 'ln
Cells(i, "D").Value = X1
Next i
End Sub
104
document1
Lognormal Distribution
The curve of this function has 2 components.
1. an exponential upward! trending part (assuming the asset price is continuing up!)and
superimposed 2. a normally random fluctuation up or down from this exponential curve.
This equation gives the NEXT value based upon the PREVIOUS Value.
Hence we need a computer program to simulate it – by calculating a value and then calculating the
next value from that etc.
See page 289 Benninga for the simulation code and page 291 for the graphs which are the
simulated paths for a stock which has a mean return of .1, a volatility of .2 with an initial stock price of
30 over a period of 125 days.
105
document1
Call Price Option and the max Function
Say an asset has a strike price of 30 and the current price is 20. Clearly we would not exercise an
option to buy. The return would be zero.
If the current price were 40, then we would exercise our option and the return would be 10.
Exercise:
Use the Excel worksheet function max to max an expression for this.
Private Sub cmdFindReturn_Click()
Dim X As Single, S As Single
X = 30: S = 20
MsgBox Application.WorksheetFunction.Max(0, S - X)
End Sub
Result:
If S – X is negative
then the max
would be 0.
o Change this to: X = 30: S = 40
Result:
106
document1
Simulate a Single Lognormal Share Price After 1 Day:
Function random_normal() As Double
Dim rand1, rand2, S, i, X1
start:
rand1 = 2 * Rnd - 1
rand2 = 2 * Rnd - 1
S = rand1 ^ 2 + rand2 ^ 2
If S > 1 Then GoTo start 'ie too big start again
X1 = rand1 * Sqr(-2 * Log(S) / S) 'ln
random_normal = X1
End Function
Private Sub cmdMonte_Click()
Dim R As Double
'interest rate
Dim sigma As Double 'volatitily
Dim t As Double
'time to final date
Dim SD As Double, S As Double
S = 100: R = 0.1: sigma = 0.25: t = 1
R = (R - 0.5 * sigma ^ 2) * time
SD = sigma * Sqr(time)
S = S * Exp(R + SD * random_normal)
MsgBox S
End Sub
We will now do 3 things:
1. Repeat the simulation 200 times ie calculate 200 random stock prices after 1 day.
2. Calculate the call return for each simulation using our max function if the strike price X is 95.
3. Find the average (ie the expected payoff)
Private Sub cmdOption_Click()
Dim
Dim
Dim
Dim
Dim
Dim
R As Double
'interest rate
sigma As Double
'volatitily
t As Double
'time to final date
SD As Double, S As Double
i As Integer, N As Integer, X As Double
sumPayoff As Double, ST As Double, Payoff As Double
S = 100: R = 0.1: sigma = 0.25: t = 1: N = 200: X = 95
For i = 1 To 200
R = (R - 0.5 * sigma ^ 2) * time
SD = sigma * Sqr(time)
ST = S * Exp(R + SD * random_normal)
sumPayoff = sumPayoff + WorksheetFunction.Max(0, ST - X)
Next i
Payoff = sumPayoff / N
MsgBox Payoff
End Sub
107
document1
Compare this to the Black Scholes price.
See DNB.xlsm for BS code. Ch 10 sheet
Function BSfra(retval, CP, Price, strike, Volatility, Tinn, yf, DiscFactor)
More correctly:
Msgbox Exp(-r*t)* Payoff
108
document1
Download