Excel VBA – Part 10 VBA for Microsoft Excel – Part 10 Contents The Add method is the usual way to create a name e.g. 1 Names.Add Name:="ExamMarks", RefersTo:="=Exams!$A$2:$D$10" 2 3 4 5 6 7 8 Named Ranges ............................................................................................... 1 1.1 Creating a named range .......................................................................... 1 1.2 Selecting a named range .......................................................................... 1 1.3 Creating Names from a Selection ............................................................ 2 1.4 Redefining a named range after adding data to it ................................... 2 Comparing values in two ranges ................................................................. 3 Application object .......................................................................................... 3 Protecting VBA projects ................................................................................ 4 Protecting Worksheets................................................................................... 4 5.1 Protecting the active sheet ....................................................................... 4 5.2 Protecting all sheets using for each ......................................................... 4 Error handling ................................................................................................ 4 Events .............................................................................................................. 6 7.1 The Worksheet_Change Event ................................................................ 6 Page Setup and Printing ................................................................................ 6 8.1 BeforePrint event..................................................................................... 7 The RefersTo property must be specified in A1 style notation, including dollar signs where appropriate. However, the macro recorder generates this code to create a Name Sub Macro2() ActiveWorkbook.Names.Add Name:="ExamMarks", _ RefersToR1C1:="=Exams!R2C1:R10C4" End Sub The RefersToR1C1 property is used to reference ranges in a different notation, e.g. R1C8 refers to the first row eighth column of the worksheet, ie. cell H1. Therefore it is still an absolute reference. 1.2 Selecting a named range You can select a range with a statement like Range("ExamMarks").Select (assuming the range is on the active sheet.) However, the macro recorder records the line above as 1 Named Ranges Programs can be made easier by using Named Ranges which corresponds to the Names collection in VBA. The following examples are to be used with the Exams worksheet in xlvba10.xls Application.GoTo Reference:= "ExamMarks" 1.1 Application.Goto Reference:="R1C8" which is equivalent to Range("H1").Select Creating a named range There is a simple way if your code preselects a region and then: Selection.Name="Nametext" where Nametext is your choice of name. This alternative way is in fact essential if you want to select a named range that is not on the active sheet. GoTo can also be usde a statement like: One advantage of using GoTo is that it has the capacity to ‘remember’ the address of the range you start from, therefore there is the potential to make Page 1 of 7 Excel VBA – Part 10 a macro return to a given location on a worksheet. The command for this is Application.GoTo The following macro assumes A2:D10 on the worksheet Exams is a range named ExamMarks. What it does is loop through the totals and copies the higher ones and pastes to column H. The totals are the result of a formula and therefore need Paste Special Sub ExtractHighScores() Dim i As Integer i = 1 Worksheets("Exams").Activate Range("ExamMarks").Columns(4).Select Do If ActiveCell.Value >= 100 Then ActiveCell.Copy Application.Goto Reference:="R" & i & "C8" Selection.PasteSpecial Paste:=xlPasteValues Application.CutCopyMode = False i = i + 1 Application.Goto End If ActiveCell.Offset(1, 0).Select Loop Until ActiveCell = "" End Sub If you want to copy and paste both the name and mark to the new location there is only one alteration to make. The line ActiveCell.Copy becomes Union(ActiveCell, ActiveCell.Offset(0, -3)).Copy It’s useful to step through this macro and watch it work. 1.3 Creating Names from a Selection There is a very efficient way to create names from existing worksheet data. Given a selectiont as illustrated below . . . . . . select the Formulas ribbon, click Create from Selection and if you select only the Top Row checkbox, Excel will create names from each of the selected column headings i.e. Date, Open, High and so on. The code for this is would be: Selection.CreateNames Top:=True, Left:=False, _ Bottom:=False, Right:=False 1.4 Redefining a named range after adding data to it There is no method that ‘redefines’ the address of an existing named range, for example, once you have added or deleted data from it. What you do is to use Names.Add to add the range again with the updated dimensions. Rather than struggling to concatenate the update literal range reference an easier way is to make your code select the range, then use a reference to Selection for the RefersTo argument. For example, Names.Add Name:="ExamMarks", RefersTo:= Selection which could be written more tersely Names.Add "ExamMarks", Selection If the range is in a single column you can use Selection.CreateNames as described in the previous section, i.e create the range again with the updated dimensions. Page 2 of 7 Excel VBA – Part 10 Can you determine whether the active cell is in a particular range? Yes, with some difficulty. Assuming you want to test if the active cell is in a named range called ExamMarks then the following expression gives True Union(ActiveCell,Range("ExamMarks")).Address = Range("ExamMarks").Address or to test if the active cell is in the used range: Union(ActiveCell,ActiveSheet.usedrange).Address = ActiveSheet.usedrange.Address 2 Comparing values in two ranges It is possible to process two (or more) ranges in parallel by using the same loop. Using the ftse worksheet, there are two named ranges Close13 and Close12. The following program simply colours the font red if the closing price for each month in 2013 is lower than for the corresponding month in 2012, and green if it is higher. The code takes advantage of the fact that the two ranges are of the same size and can use the count property of one or the other as the upper bound of the For loop: For i = 1 To close13.count but you might have to code a test to see which range is larger. Sub CompareRanges() Dim i As Integer Dim close13 As Range, close12 As Range Set close13 = Range("Close2013") Set close12 = Range("Close2012") For i = 1 To close13.Count If close13(i) < close12(i) Then close13(i).Font.Color = vbRed Else close13(i).Font.Color = vbGreen End If Next i End Sub Note also the use of range variables using Set, which to make the code more compact. 3 Application object Application has useful properties/methods that can improve various aspects of a macro. The first two are Boolean, i.e. set them to True or False. Application.ScreenUpdating Application.ScreenUpdating suppresses the visual display when a macro is running. Note that using Step Into is impossible if ScreenUpdating is set to False. Application.DisplayAlerts Application.DisplayAlerts can be set to False while a macro is running to stop Excel messages interfering with the smooth running of the macro. It is essential to set it back to True before the End Sub, otherwise this setting will persist. Application.InputBox Not to be confused with the VBA Input box. Application.InputBox only accepts data which conforms to a specific data type or there is an error. Type 8 is a range, and the user must select a range on the worksheet. Sub InputBoxForRange() Dim rng1 As Range On Error GoTo ExitPoint Set rng1 = Application.InputBox _ (Prompt:="Select a Range", Type:=8) MsgBox "You chose " & rng1.Address Exit Sub ExitPoint: MsgBox "Object Required" End Sub Page 3 of 7 Excel VBA – Part 10 4 Protecting VBA projects You can protect a VBA project to prevent unauthorised access to your code. 1. 2. Open the Visual Basic Editor Right-click the Project (corresponding to the filename) to be protected … 5 Protecting Worksheets 5.1 Protecting the active sheet The Protect Sheet button is on the Review ribbon. A line of code to protect the current sheet might look like this. 3. 4. and choose VBAProject Properties Choose the Protection tab ActiveSheet.Protect "abcd" True, True, True Protect takes various arguments, including Password, followed by what aspects of the worksheet are being protected – the values are True or False. In the example the password is ‘abcd’ (case sensitive) The corrsesponding line to UnProtect it is ActiveSheet.UnProtect "abcd" True, True, True 5.2 Protecting all sheets using for each A macro can protect all the sheets in the workbook with a For Each loop. 5. Make sure that Lock Project for viewing is checked and type in a password (twice), then choose OK The code associated with this project can still be seen after you have done this. To verify the password protection, close the file, then re-open it. 1. 2. in VBE try to view the password-protected project you must type the password into the box Sub ProtectSheets() Dim wk As Worksheet For Each wk in Worksheets wk.Protect “abcd” Next wk End Sub 6 Error handling An error that arises during the running of a macro is known as a run-time error. If you don't use an On Error statement, any run-time error that occurs is fatal; that is, an error message is displayed and execution stops. Page 4 of 7 Excel VBA – Part 10 The following discussion deliberately contrives an error. The code selects a cell five rows above the current cell and colours it yellow – if there aren’t five rows above the current cell when the macro is run a run-time error occurs. Sub Macro1() ActiveCell.Offset(-5, 0).Select ActiveCell.Interior.Color = vbYellow End Sub It is important to try to anticipate user errors. The usual way is to use On Error GoTo to go to a designated line in the code (known as a label) which is followed by some error handling code. Sub Macro1() On Error GoTo myErrorHandler ActiveCell.Offset(-5, 0).Select ActiveCell.Interior.Color = vbYellow myErrorHandler: Exit Sub this line means ‘if an error occurs go to the line specified by the provided label’ – here myErrorHandler is the label a label is a word followed by a colon End Sub At its most basic the error handling code could merely exit the sub (not strictly necessary here as End Sub follows soon after). Of course this would leave the user unaware that an error took place so you could add a message box; it’s intuitive to think you do this just before the Exit Sub statement myErrorHandler: MsgBox "You must start below Row 5" Exit Sub End Sub but this would cause the message box to appear each time the macro runs, even if the macro is successful. The trick is to put the Exit Sub statement before the error handler and if the macro completes its task successfully it terminates with the Exit Sub statement. Sub Macro1a() On Error GoTo myErrorHandler ActiveCell.Offset(-5, 0).Select ActiveCell.Interior.Color = vbYellow Exit Sub myErrorHandler: MsgBox "You must start below Row 5" End Sub If you want a message box to return the ‘official’ error number and description, use the Err object as follows: MsgBox Err.Number & " " & Err.Description In case you are interested the above macro could be improved as follows: If ActiveCell.Row > 5 Then ActiveCell.Offset(-5, 0).Select As well as a MsgBox, error handling code can be used to restore Application defaults if they have been changed, for example it would be important to include Application.DisplayAlerts = True if your code had switched off Excel’s intereactive capabilities. Resume The keyword Resume is commonly used in error handling code. Resume ‘clears’ the error and makes it possible for the code to continue. You may want a macro to continue regardless in spite of an error. The statement: On Error Resume Next means ‘clear the error and continue with the next line of the macro’. For Page 5 of 7 Excel VBA – Part 10 example you may want to continue calculating down a column even if one calculation fails because of, say, a division by zero error. Page orientation, settings for margins etc. are properties of the PageSetup object, e.g., On Error Resume clears Sub PreviewExams() ' page setup example Worksheets("Exams").Select With ActiveSheet.PageSetup .Orientation = xlLandscape .LeftFooter = ThisWorkbook.FullName .RightFooter = Application.UserName .LeftMargin = Application.InchesToPoints(2.5) .HeaderMargin = Application.InchesToPoints(0.3) .CenterHorizontally = True .Zoom = 150 End With ActiveSheet.PrintPreview End Sub the error and resumes with the current statement, i.e. the statement that caused the error. It’s rare that this is of any use, normally this would create an endless situation of clearing the error only to recreate it. NB: you can only use Resume in the context of an error; it is an error in itself to use Resume outside of an error sitation. 7 Events 7.1 The Worksheet_Change Event This event occurs when the value of a cell on the worksheet changes. You could exploit it if you have volatile data. The code example simulates a worksheet with external links like prices or exchange rates. A change to any value triggers a colour change for every cell on the worksheet. Private Sub Worksheet_Change(ByVal Target As Range) Dim rng As Range For Each rng In Range("prices") If IsNumeric(rng) And Not IsEmpty(rng) Then If rng >= 16 Then rng.Font.Color = vbGreen ElseIf rng < 15 Then rng.Font.Color = vbRed Else rng.Font.Color = vbBlack End If End If Next rng End Sub 8 Page Setup and Printing Every worksheet has its own PageSetup, therefore it follows that the Worksheet object contains the PageSetup object Print Areas By default the whole worksheet is printed. To designate a worksheet area to be printed use the PrintArea property of PageSetup, e.g., ActiveSheet.PageSetup.PrintArea = "$A$1:$C$10" For more flexibility you can use the Address property of Selection ActiveSheet.PageSetup.PrintArea = Selection.Address You can use the comma as the Union operator ActiveSheet.PageSetup.PrintArea = "$A$1:$C$10,$C$2:$C$12" . To clear a Print Area the code is ActiveSheet.PageSetup.PrintArea = "" Page Breaks To add manual Page Breaks use HPageBreaks or VPageBreaks. For example to add page breaks before the active cell: ActiveSheet.HPageBreaks.Add Before:=ActiveCell ActiveSheet.VPageBreaks.Add Before:=ActiveCell Page 6 of 7 Excel VBA – Part 10 To clear Page Breaks use ActiveSheet.ResetAllPageBreaks There are only objects for manual Page Breaks. To control automatic Page Breaks the only way is to change the page margins 8.1 BeforePrint event You can use the BeforePrint event of the workbook make changes to PageSetup every time any worksheet is printed. Private Sub Workbook_BeforePrint(Cancel As Boolean) Dim wk As Worksheet Dim CompanyName As String CompanyName = "City University" For Each wk In Worksheets With wk.PageSetup .LeftFooter = CompanyName .RightFooter = ThisWorkbook.FullName End With Next wk End Sub Page 7 of 7