Excel Print Macros
Have you ever recorded the print settings for a macro to print a set of reports? You can see an example recording below. At first glance the process of making the print settings and sending the report to the printer look unbelievably complicated. Do not despair, read on…
Sub Macro1()
Range("C3:E8").Select
ActiveSheet.PageSetup.PrintArea = "$C$3:$E$8"
With ActiveSheet.PageSetup
.PrintHeadings = False
.PrintGridlines = False
.PrintComments = xlPrintNoComments
.PrintQuality = 600
.CenterHorizontally = False
.CenterVertically = False
.Orientation = xlLandscape
.Draft = False
.PaperSize = xlPaperLetter
.FirstPageNumber = xlAutomatic
.Order = xlDownThenOver
.BlackAndWhite = False
.Zoom = 100
End With
ActiveWindow.SelectedSheets.PrintOut Copies:=1, Collate:=True
End Sub
The Macro Recorder is not selective; it has recorded the state of every control in the Page Setup dialog box. You just need to delete the unnecessary statements and you will see that the code the code for printing macros can be quite dramatically simplified and reduced.
The PrintOut method is all you need for the actual printing part of your macro. As usual the command is “Object.Method”. The object is what you want to print, for example to print a range of cells you give the cell references. There is no need to select the range or set it as the print range first.
This is all you need for printing:
Sub ConcisePrintMacro()
'Print a range of cells.
Range("A1:G250").PrintOut
'Print the used range of the active worksheet.
ActiveSheet.PrintOut
End Sub
The page setup settings refer to the worksheet where the range of cells is located. Here, the object is the worksheet’s page setup settings. For example to set the report to landscape orientation, the instruction is:
ActiveSheet.PageSetup.Orientation=xlLandscape
You have to repeat the object reference for each print setting that you need to make; orientation, headers, footers, print titles etc. Or you can use the “With” keyword to make the reference once and then start each instruction with a dot, as in the example below:
Sub PageSetupSettings()
With ActiveSheet.PageSetup
.CenterFooter = "My Report"
.RightFooter = "by Anon E. Mouse"
.Orientation = xlLandscape
.FitToPagesWide = 1
.FitToPagesTall = 1
End With
Range("A1:G250").PrintOut
End Sub
Good luck with your printing.
How Easy is Copying?
Copying data from one location to another is one of the most common activities in Excel macros but the Copy method has a secret that is not always appreciated.
This is an example of a recorded macro. Select a range of cells, copy it to the clipboard, select a destination range and paste from the clipboard. Finally, press the ESC key to clear the clipboard.
Sub RecordedCopyAndPaste()
Range("C4:E11").Select
Selection.Copy
Sheets("Sheet2").Select
Range("D7").Select
ActiveSheet.Paste
Application.CutCopyMode = False
End Sub
As usual with recordings, every single action is recorded step-by-step. You can simplify your code quite easily and remove the unnecessary instructions. For example, it is not necessary to make a selection first before the cell data is copied or cut, just refer directly to the cell range:
Range("C4:E11").Copy
Range("C4:E11").Cut
Watch out when pasting, the cell does not have a Paste method, the following instruction will fail:
Range("C4:E11").Paste
Use Paste Special instead, like this:
Range("C4:E11").PasteSpecial.
But the reason why there is no Paste method for a cell is very simple, you do not need one. The Copy method has an optional argument, “Destination” and you can copy and paste all in one instruction. In fact, you copy directly from one cell to another and avoid using the clipboard entirely. Make sure that you get the instruction entered correctly, there is a space after the “y” of “Copy”:
Range("C4:E11").Copy Destination:= Range("G10")
You enter the line without the argument descriptor, like this:
Range("C4:E11").Copy Range("G10")
Use can use square brackets to make the range references if you prefer:
[C4:E11].Copy [G10]
Copy from one worksheet to another, either use full object references or square brackets:
Sheets("Sheets1").Range("C4:E11").Copy Sheets("Sheet2").Range("G10")
or
[Sheet1].[C4:E11].Copy [Sheet2].[G10]
As an alternative to the copy method, you can always use an equals sign to copy information from one cell to another but there is a subtle difference. For example the following statement copies the value that is displayed in cell A1 to cell B1:
[B1] = [A1]
Whereas this statement copies the formula:
[A1].Copy [B1]
Measuring areas and lists in Excel macros
In many Excel macros we need to select or measure an area of cells whose dimensions can not be predicted at design time, when you write the code. You have to allow the macro to detect the extent of the data. Here’s a few examples:
Measure the dimensions of the current block of consecutive data:
x = ActiveCell.CurrentRegion.Rows.Count
x = ActiveCell.CurrentRegion.Columns.Count
Identify the cell coordinates of this range:
x = ActiveCell.CurrentRegion.Address
Measure the dimensions of the area containing data on a worksheet:
x = ActiveSheet.UsedRange.Rows.Count
x = ActiveSheet.UsedRange.Columns.Count
Identify the first used row of the worksheet:
x = ActiveSheet.UsedRange.Row
Identify the last used row of the worksheet:
x = Cells.SpecialCells(xlCellTypeLastCell).Row
Identify the next free row starting from A1:
x = Range("A1").End(xlDown).Row + 1
Identify the first row and column in the block containing the active cell:
x = ActiveCell.CurrentRegion.Row
x = ActiveCell.CurrentRegion.Column
To select the block of cells containing the active cell:
ActiveCell.CurrentRegion.Select
Select from cell C3 to the top of the current region:
Range("C3").End(xlUp).Select
Select from cell C3 to the last cell on the right in the current region:
Range("C3").End(xlToRight).Select
Starting a New Line in Excel Macros
You either want to start a new line in your code or you want force a new line in the prompt text in an input box or a message box.
Line Continuation in Macros
Some statements are rather lengthy and difficult to read on one line. Do not press enter to wrap the text; this just produces a syntax error. To continue the same logical line onto the next physical line, introduce a line continuation character into your code. Use the following sequence of keystrokes for a line continuation character; Spacebar, Underscore, Enter. It is a sequence, not a key combination. You can have as many line continuations as you require. Second and subsequent lines can be tabbed.
Statements like this can be rather difficult to read:
ActiveWorksheet.Cells.SpecialCells(xlCellTypeVisible).Select
Statements are much easier to read with line continuation characters:
ActiveWorksheet.Cells. _
SpecialCells(xlCellTypeVisible). _
Select
Starting a New Line in a Message Box or an Input Box
The prompt text in the message does not wrap onto a new line in the box until the character count reaches 160; meanwhile the box just gets wider with the text on one line. Use any one of the following constant values to force a new line:
Chr(10),Chr(13), vbCrLf, vbCr, vbLf
Forcing new lines in the prompt:
MsgBox "Hello Charlie," & vbCrLf & "have a nice day."
The VBA Format function
If you have not yet discovered the VBA Format function then it is time that you did and then you can ruefully shake your head and mutter, "If only I had known about that six months ago…"
The Format Function will transform any numeric value. Although they differ in detail the fundamental number format codes for Excel and VBA are identical. To find the relevant code values look up Custom Number Formats in Excel Help or the Format function in VBA Help.
Format(Expression, "Format Code")
Here’s a few examples:
To format -5000 to (5,000.00):
Format(-5000, "#,##0.00_);(#,##0.00)")
To format 5,000,000 to 5.0 million:
Format(5000000,"0,,.0 million")
To return the current month number as two digits, i.e. June = 06:
Format(Month(Date), "00")
To return the name of the current month from the current system date:
Format(Month(Date), "MMMM")
To return the day of the week from the current system date:
Format(Date, "DDDD")
To express one number as a percentage of another:
Format(34 / 5000,"0%")
Using Excel Worksheet Functions in Macros
You can use VBA functions in your Excel macros and you can use Excel worksheet functions but you must call them from Excel if the function is not recognised in the VBA language. It is very easy to determine where you need to call the function from Excel.
For example, we wish to use the LEN function to measure the character length of an expression and assign the value to the x variable. LEN is both an Excel worksheet function and a VBA function. As you type the statement into the module you will see that if the function is recognised then it’s syntax diagram will appear, like this:
x=Len(Expression
However, if no prompt appears then you know that the call will fail:
X=Sum(
If the function is exclusively an Excel function then you need to include the Application object reference.
The shortcut is to just access the Application object:
x = Application.Sum( 50, 650, 100)
Excel functions are members of the WorksheetFunction Collection. The full reference will display a list of all the Excel worksheet functions after you type-in the dot operator:
x = Application.WorksheetFunction.Average( y, z)
Use Excel’s Input Box!
You can use either the generic VBA Input Box function or the Excel Application object's Input Box Method in your Excel macros. The InputBox Method allows for some entry validation using its optional Type argument and is the only one where you can point out of the box to select a range of cells on a worksheet. Invalid data entry into Excel’s Input Box is handled by the Excel application and as most of the work involved in coding Input Boxes is in the validation of the received input it is usually the best choice.
VBA Input Box Function
The generic VBA function does not have any facility for validating the user's input, this has to be done in your code. The result of the function can be directly assigned to a cell but it is usually better assigned to a variable so that it can be effectively evaluated.
Range("A1") = InputBox("Please enter the date.", _
Title:="Current Period", _
Default:=Date)
Excel Input Box Method
You will notice the difference between the two when you enter an invalid input. So long as you have completed the Type argument, Excel will handle any invalid input and you only have to test for the Cancel button in your code. The Cancel button for the Input Box function returns a zero length string whereas the Cancel button for the Input Box method returns FALSE.
Range("A1") = Application.InputBox("Please enter the date.", _
Title:="Current Period", _
Default:=Date, _
Type:=1)
The Type argument specifies the return data type. It can be one or a sum of the values shown in the list below:
0 - Formula
1 - Number
2 - Text
4 - True or False
8 - Cell reference
16 - Error value
64 - An array of values
Only the Excel InputBox Method allows you to point out of the box to return a range reference. In the example, return data type 8 is specified and the input box will accept a range reference either by typing or dragging through the cells.
Sub ExcelInputBoxMethod()
Set MyRange = Application.InputBox( _
Prompt:="Please select a range.", _
Title:="Colour me Red", _
Type:=8)
MyRange.Interior.ColorIndex = 3
End Sub
When you assign a variable value using an Input Box never set the data type before the input has been received and validated. To avoid Type Mismatch errors, declare the variable as Type Variant and then use Type conversion functions after the input has been captured and validated.
In the following example the USD variable has to be of Type Currency. Had the initial declaration been As Currency then the code would produce a Type Mismatch error when the Input Box received invalid data and before the input could be evaluated in the loop:
Sub MisMatchErrors()
Dim USD As Variant
Do
USD = InputBox("Enter the USD rate:")
Loop Until IsNumeric(USD) = True
USD = CCur(USD)
End Sub
Dynamic Arrays in VBA Macros
A dimensioned array has to be declared using a constant value; however this constant value maybe unknown at the point of declaration. Use the ReDim statement instead of Dim to create a dynamic array; one that can be re-sized at run time.
The following example creates an array of worksheet names:
Sub ArrayofSheetNames()
Dim iNumShts As Integer
Dim i As Integer
'Calculate the number of sheets.
iNumShts = Sheets.Count
'Size the array.
ReDim sSheetNames(1 To iNumShts) As String
'Populate the array.
For i = LBound(sSheetNames) To UBound(sSheetNames)
sSheetNames(i) = Sheets(i).Name
Next
'Add another sheet.
Sheets.Add
'Resize the array.
ReDim sSheetNames(1 To Sheets.Count) As String
'Repopulate the array.
For i = LBound(sSheetNames) To UBound(sSheetNames)
sSheetNames(i) = Sheets(i).Name
Next
End Sub
In the previous example you will have noticed that we had to repopulate the array after having resized it. ReDim resizes the array but clears the data already stored. Use ReDim Preserve when you want to resize an array but retain the data previously stored. ReDim Preserve is particularly useful when you want to gather some information and store it in an array but do not know the extent of the data. In the following example a range of cells is being searched and we want to store the cell references of the cells containing a certain value.
As the data is found, it is stored in the array and then an extra element is added to the array ready for the next item of data. When the search is completed the array has one element too many; this is then removed.
Note the use of ReDim at the start of the procedure to initialise the array variable; this has to be done so that the UBound function can calculate the size of the array when the first element of data is stored.
Sub DynamicArrayOnTheFly()
Dim vList As Variant
Dim oCell As Range
'Initialise the variable so that we can
'use UBound later on.
ReDim vList(0)
'Loop through the cells.
For Each oCell In Range("A1:D50")
'Test for a value of 5.
If oCell.Value = 5 Then
'Store cell reference in array.
vList(UBound(vList)) = oCell.Address
'Add element to array ready for next item.
ReDim Preserve vList(UBound(vList) + 1)
End If
Next
'Remove empty element from array.
ReDim Preserve vList(UBound(vList) - 1)
End Sub
How to make Pivot Tables Refresh Automatically
Excel Pivot Tables do not automatically update when the source data is changed, they have to be refreshed. To make your Pivot Tables refresh automatically you need a macro to do the refreshing and an Event to trigger the macro. This seems quite an involved process the first time that you do it but it gets easier, it is mainly just copying from one workspace to another.
Recording the Macro
You have to record a macro and then copy the recorded code from the macro into one of the worksheet’s events. Firstly, the recording: Select a cell in your Pivot Table and then choose Tools, Macro, Record New Macro from the menu. Just make sure that the Store macro in: setting is This Workbook and click the OK button.
Now you have to record your refreshing the Pivot Table, so choose the Data menu and select Refresh Data. The recording is completed and you can turn off the Macro Recorder by choosing Tools, Macro, Stop Recording. The next job is to find the recorded code and copy the instruction that refreshes the Pivot Table. Choose Tools, Macro, Macros and select the recorded macro from the list and then click the Edit button on the right hand side. You will now see the Visual Basic Editor displayed and you can view the recorded procedure, which should look something like this:
Sub Macro1()
' Macro1 Macro
' Macro recorded by Anon E. Mus
ActiveSheet.PivotTables("PivotTable2").PivotCache.Refresh
End Sub
Copy the line of code that refreshes the table, it should be obvious which one you need. Do not copy anything else. Now, return to the Excel worksheet workspace by clicking an Excel icon or by pressing ALT-F11.
Using an Event to trigger the Macro
Next, you need to find the Event code shells for the worksheet where your Pivot table is located. Right-click the worksheet tab for the relevant worksheet and choose View Code from the shortcut menu. You are returned to the Visual Basic Editor once again. Looking at the active window you will see two drop-down lists at the top of the window.
The drop-down list on the left hand side is the Object list, the one on the right hand side is the Procedure list. Drop the Object list and select the Worksheet object, then choose a suitable event from the Procedure list. The Activate event is probably the most suitable for our purposes. Paste your recording into the procedure so that it looks like this:
Private Sub Worksheet_Activate()
ActiveSheet.PivotTables("PivotTable1").PivotCache.Refresh
End Sub
The Activate event is triggered whenever you make the worksheet active, so in order to test that the procedure is working correctly you need to perform the event. Return to the Excel worksheet workspace by clicking an Excel icon or by pressing ALT-F11, select another worksheet in the workbook and then select the original worksheet where the Pivot Tables are stored. This triggers the event and you will see that the tables are automatically refreshed. Of course, you do not have to make the initial macro recording every time that you need to do this, all you need is the code for the Refresh method so you can type it in or paste it from a previous recording. But, be careful, each Pivot Table needs to be correctly described using its Name Property; this is the text value inside the brackets. In the example below the name of the Pivot Table is "PivotTable1"
ActiveSheet.PivotTables("PivotTable1").PivotCache.Refresh
To find out the name of a specific Pivot Table, right-click one of its cells and choose Table Options from the short cut menu. The name is shown in the top left corner of the dialog. It is quite in order to list the Pivot Tables if you have more than one which needs refreshing in which case the procedure would be like this:
Private Sub Worksheet_Activate()
ActiveSheet.PivotTables("PivotTable1").PivotCache.Refresh
ActiveSheet.PivotTables("PivotTable2").PivotCache.Refresh
End Sub
Although it would be easier to have a looping procedure so that you can avoid having to look up the name of each individual Pivot Table:
Private Sub Worksheet_Activate()
Dim pt As PivotTable
For Each pt In ActiveSheet.PivotTables
pt.PivotCache.Refresh
Next
End Sub
Pivot Tables are contained by each individual worksheet so if you want to have a procedure which will refresh every Pivot Table on every worksheet in the entire workbook then you will need an outer loop for the worksheets and an inner loop to refresh the pivot tables. The code would be as follows:
Private Sub Worksheet_Activate()
Dim ws As Worksheet
Dim pt As PivotTable
For Each ws In Worksheets
For Each pt In ws.PivotTables
pt.PivotCache.Refresh
Next pt
Next ws
End Sub
World wide invite to download our Excel Training materials and workbooks