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