Learn Excel® VBA in 24 Hours - A quick reference for beginners Copyright © Liaw Hock Sang 2016 All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording, scanning, or otherwise, without prior written permission. For permission requests, please write to:
[email protected]
Trademarks and Copyrighted Content Every effort has been made to appropriately capitalize all terms that are known to be trademarks or service marks mentioned in this book. The author cannot attest to the accuracy of this information. Use of a term in this book should not be regarded as affecting the validity of any trademark or service mark. Should there be any violations in this respect, the author apologies and shall stop the selling of the book Learn Excel® VBA in 24 Hours - A quick reference for beginners within the control of the author. Microsoft, Office, Excel, Word, Access, Outlook, Visual Basic, Internet Explorer, and Windows are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries. All other trademarks and trade names are the property of their respective owners.
Warning and Disclaimer Every effort has been made to make this book as accurate as possible. However, no warranty or fitness is implied. The author and the publisher shall have neither responsibility nor liability to any person or entity with respect to any losses or damages arising from the information contained in this book.
Thank you Thank you for purchasing this book. This book is for your personal use only. It may not be resold or given away to other people. If you are reading this book and did not purchase it, please destroy it. Thank you for your support and respecting the hard work of the author.
A supplementary Excel file to share The author would like to share a supplementary Excel file. It contains almost all code listings and code statements stated in the book. Sub procedures are ready to be executed either by clicking buttons on worksheets or by accessing the Marco dialog box. Functions are ready to be tested in worksheet formulas. The file definitely facilitates you to master the content of the book. Please download the file from here. If you cannot download the file, kindly notify the author to update and republish the link.
Table of Contents Table of Contents Introduction A supplementary Excel file to share Chapter 1: Knowing your tools Developer tab Visual Basic Editor Customizing VBE Immediate window Exercise 1-1: Executing a comparison statement in the Immediate window Exercise 1-2: Entering a value into a cell from the Immediate window Object Browser Exercise 1-3: Exploring the properties of an object Exercise 1-4: Exploring the properties and methods of an object Macro recorder Exercise 1-5: Recording a macro VBA Help system Exercise 1-6: Checking the syntax for a structure Exercise 1-7: Exploring the VBA Help system in Excel 2007 and 2010 Chapter 2: Knowing the places to store your VBA code Types of VBA modules Sample procedures in a standard VBA module A simple Sub procedure: Color banding Exercise 2-1: Executing a Sub procedure by using the Macro dialog box Exercise 2-2: Executing a Sub procedure by using a shortcut key combination Exercise 2-3: Making a Sub procedure available to all opened workbooks A simple Sub procedure: Hiding the cells' contents Exercise 2-4: Executing a Sub procedure by using the Macro dialog box A simple Function procedure: A power-of-three function Exercise 2-5: Using a custom function in a worksheet formula Exercise 2-6: Calling a custom function from the Immediate window Exercise 2-7: Calling a custom function by a procedure in other module A sample procedure in a workbook module A simple event handler: Workbook_Open
Sample procedures in a sheet module A simple event handler: Worksheet_SelectionChange A simple event handler: CommandButton1_Click() A sample procedure in a UserForm module A simple UserForm: Collecting information to build a multiplication table Exporting and importing a VBA module Chapter 3: Programming fundamentals in VBA Data types Constants and variables Declaring constants and variables Scope of constants and variables Exercise 3-1: Testing a static variable Syntax for decision structures If-Then structure If-Then-Else structure Select Case structure Syntax for loop structures For-Next loop For Each-Next loop Do loop Working with strings Two types of string variables Comparing strings Comparison operators The Like operator The StrComp function VBA built-in string functions Working with arrays One dimensional arrays Multidimensional arrays Dynamic arrays Function and Sub procedures Declaring procedures Scope of procedures A function with no argument
Exercise 3-2: Using a custom function as a worksheet function Exercise 3-3: Calling a custom function by a procedure from other module A function with one argument Functions with an optional argument Exercise 3-4: Executing a custom function from the Immediate window Exercise 3-5: Using a custom function (with a built-in constant) as a worksheet function A function with two optional arguments A function with an array argument A function with an arbitrary number of arguments Exercise 3-6: Using SimpleSum1 and SimpleSum2 as worksheet functions Exercise 3-7: Using SimpleSum3 as a worksheet function A function that returns an array of values Exercise 3-8: Executing the MySplit function from the Immediate window Exercise 3-9: Using MySplit as a worksheet function Exercise 3-10: Another way of using MySplit as a worksheet function Exercise 3-11: Returning a two-dimensional array into a range of cells Debugging Locals window Watch window Call stack A debugging example from scratch The MsgBox function Error handling A sample procedure with no error handler Sample procedures with an error handler Sample procedures with two error handlers On-Error-Resume-Next and On-Error-GoTo-line statements Two On-Error-GoTo-line statements Handling errors in error-handling routines A fatal error in an error-handling routine Overcoming fatal errors with the Resume statement Overcoming fatal errors with a called Sub procedure Chapter 4: Some other tips on VBA programming Writing more efficient VBA code Working with arrays instead of a range of cells
Disabling screen updating, alert displays, events, and automatic calculations Reducing the size of a working range Commenting out a block of VBA code Removing all comments in VBA modules in one click Getting a list of VBA built-in functions in the code window Dealing with the situation when Auto List Members does not work Calling a private procedure from other module Hiding public Sub procedures from the Marco dialog box Some good programming practices
Introduction Visual Basic for Applications (VBA) is the programming language built into Microsoft Office applications (such as Microsoft Excel, Microsoft Word, Microsoft Access, and Microsoft Outlook) to automate various tasks in their own environment and to work with other applications. This book focuses on VBA in Microsoft Excel for Windows. The discussions in the book are applicable to Excel 2007, Excel 2010, Excel 2013, and Excel 2016, unless otherwise stated. It starts with Chapter 1, which is to get you familiarize with some essential tools that help you to write your VBA programs. Chapter 2 covers the places where you should store your VBA code. Chapter 3 is the fundamentals of programming in VBA. It provides you a quick reference in writing syntax error-free VBA code, in debugging, and in handling errors. Chapter 4 offers some other tips on VBA programming. This book is for readers that are totally new to Excel VBA, but should have a basic understanding of programming language, such as C or Python. The reader should be an average Excel user who knows, for example, what an array formula is. If you are new to Excel, please teach yourself Excel before exploring the chapters. You may refer to another book written by me entitled Learn Microsoft ® Excel® 2010-2016 for Windows ® in 24 Hours - A jumpstart to be an intermediate user, which was written for those who are new to Excel. This book does not serve as a comprehensive reference for intermediate users. Nevertheless, it is a jumpstart for beginners to learn Excel VBA. It is also served as a foundation for my future books and other references that are beyond the reach of beginners. I hope this book will shorten your time to teach yourself Excel VBA and serve as a quick reference in writing error-free and working VBA programs in realizing your goal. Let Excel VBA work for you.
A supplementary Excel file to share The author would like to share a supplementary Excel file. It contains almost all code listings and code statements stated in this book. Sub procedures are ready to be executed either by clicking buttons on worksheets or by accessing the Marco dialog box. Functions are ready to be tested in worksheet formulas. The file definitely facilitates you to master the content of the book. Please download the file from here. If you cannot download the file, kindly notify the author to update and republish the link.
Chapter 1: Knowing your tools Developer tab By default, the Developer tab in the Excel Ribbon is hidden. Most often when writing programs, you need to access some of the commands on this tab. Hence, it is essential to turn it on by executing the steps in the following table: Version
Steps 1.
Excel 2010, Excel 2013, or Excel 2016
2. 3. 4. 1.
Excel 2007
2. 3. 4.
Click the File tab and choose Options to display the Excel Options dialog box. Click the Customize Ribbon tab. Under Customize the Ribbon (and with Main Tabs selected), tick the Developer check box. Click OK to close the dialog box. Click the Microsoft Office button and click Excel Options to display the Excel Options dialog box. Click the Popular tab. Under the title Top options for working with Excel, tick the check box labelled Show Developer tab in the Ribbon. Click OK to close the dialog box.
Note: Later in the book, when accessing a command on the Ribbon or on a menu bar, I will just state choose File | Options, for example.
Visual Basic Editor Visual Basic Editor (VBE) is the place where you develop your VBA programs. To activate VBE, choose Developer | Code | Visual Basic or press Alt+F11. VBE contains a menu bar, several toolbars, and a number of windows such as the Project Explorer window, the Properties window, the code window, the Immediate window, the Object Browser window, the Locals window, and the Watch window. See Figure 1-1. To identify these windows one by one, click View on the menu bar and select appropriate items from the drop-down menu of View.
Figure 1-1: Components in Visual Basic Editor (VBE).
As a beginner, you need not to know the purpose of every component in VBE, but it is sufficient to know some commonly used features in the editor, which are to be discussed in this chapter, and appreciate more their purposes as you read and test the VBA code in this book and as you start to write some programs.
Customizing VBE In VBE, choose Tools | Options from the menu bar to display the Options dialog box. In the Editor tab, make sure all check boxes are ticked. After sometimes writing VBA code and knowing the programming syntax, you may want to unselect the Auto Syntax Check option. The Auto List Members and Auto Quick Info options are particular useful. While you type the name of an object followed by a dot (also known as a period) in the code window and in the Immediate window, Auto List Members allows a list of members for that object to be displayed automatically. You can then use the up- and down-arrow keys and the Tab key to select an item from the list. If you type the name of a function, property, or method followed by an opening parenthesis, Auto Quick Info allows the information about the arguments available for the function, property, or method to be displayed automatically. In the Editor Format tab, you can see the default colors for text in the code window. The table below shows some of them. Text in window
the
code
Default color
Normal code
Black
Keywords
Blue
Comments
Green
Syntax errors
Red
All other settings in VBE are just right by default.
Immediate window The Immediate window is most useful to execute a single code statement and get the result immediately, without having to create a procedure. I discuss procedures later in Chapter 3.
Exercise 1-1: Executing a comparison statement in the Immediate window Type the following statement, for example, in the Immediate window and press enter to get the immediate result for a logical comparison: ? 2>3
It returns False.
Exercise 1-2: Entering a value into a cell from the Immediate window Type the following statement, for example, in the Immediate window and press enter: ActiveCell = "Hello" The statement above enters the word Hello into an active cell in a worksheet of a workbook. At any particular time, only one workbook, one worksheet of the workbook, and one cell in the worksheet can be active. Press Alt-F11 to switch to Excel and see the result in the worksheet.
Object Browser The Object Browser window allows you to browse a complete list of classes, the properties, methods, and events in the classes, and VBA built-in constants in any object libraries. When you are working with other applications (such as Microsoft Word and Internet Explorer), the libraries associated with those applications need to be included in your VBA project by setting references. I discuss events and the way of setting references later in Exercise 3-2 of Chapter 3. Working with other applications is only discussed in my future book for intermediate users. In addition, Object Browser also shows the procedures written by you.
Exercise 1-3: Exploring the properties of an object To browse the properties of an object, for example the Font object, execute the following steps: 1. 2.
In VBE, choose View | Object Browser from the menu bar or press F2 to display Object Browser. In the Search box of Object Browser, type Font and press Enter. See Figure 1-1. The Search Results window displays any items that match the search text, in this case, Font.
3.
In the list box of the Search Results window, you can see that Font is a class under the Class header, and Font is also a member of other classes under the Member header. If you select any Font under the Member header, its class is then displayed in the Classes window. Select the Font class in the Search Results window.
4.
In the Classes window, Font is selected. Under the Members of ‘Font’ header, the members of the Font class are displayed. In the case of the Font class, all members are properties. For other class, such as the Worksheet class, it can have properties, methods, and events as its members.
Select Underline. In the bottom pane, it shows more information about the selected item, in this case, Underline. 5.
Press F1 to access a help page about this member. If to access a help page about the class itself, press F1 when the class is selected either in the Classes window or in the Search Results window. Help pages are in the VBA Help system, which will be discussed later in this chapter.
Exercise 1-4: Exploring the properties and methods of an object To browse the properties and methods of an object, for example the Range object, execute the following steps: 1. 2.
In VBE, choose View | Object Browser from the menu bar or press F2 to display Object Browser. In the Search box of Object Browser, type Range and press Enter. The Search Results window displays any items that match the search text, in this case, Range.
3.
In the Classes window, the classes are arranged alphabetically. Look for the Range class and click on it.
4.
Under the Members of ‘Range’ header, you can see the Font property is one of its members. There are many other properties and methods. Clear is one of the methods.
5.
Press Alt+F11 to activate Excel.
6.
Enter some text into cell B5 and do some formatting to the cell.
7.
Press Alt+F11 to activate VBE.
8.
In the Immediate window, type the following statement and press Enter to see how the Clear method clears everything in cell B5. Range("B5").Clear The statement above shows a typical way of referencing a member of an object by using a dot (also known as a period).
Macro recorder The macro recorder converts your Excel actions into VBA code. Although it does not always generate the most efficient code, but it is an extremely useful tool that provides you clues on how to proceed your coding and it allows you to discover the VBA built-in constants, the properties and methods of objects that you are not familiar with.
Exercise 1-5: Recording a macro Execute the following steps to record your Excel actions in changing the font of a cell. 1.
Start a blank workbook.
2.
Choose Developer | Code | Record Macro to display the Record Macro dialog box.
3.
In the box labelled Store macro in, choose This Workbook.
4.
Click OK to accept other default settings. By clicking OK, a VBA module is created in VBE. It is named Module1, by default.
5.
Press Alt+F11 to activate VBE.
6.
In the Project Explorer window, double-click the newly created module Module1 to activate its code window.
7.
Resize and place both Excel and VBE windows side by side, so that you can watch later how Excel actions in Steps 8 – 10 below are recorded in VBE lively.
8.
Select cell B5 or any cell in a worksheet in the Excel window.
9.
Choose Home | Font | Bold and Underline, and select the Red font color.
10. Type the word Hello into the selected cell and press Enter. 11. Choose Developer | Code | Stop Recording or alternatively click the square icon on the status bar to stop the recording. The Excel actions in Steps 8 – 10 were converted into VBA code by the macro recorder and appeared in the code window of Module1. The following listing is the VBA code. Sub Macro1() ' ' Macro1 Macro ' '
Range("B5").Select Selection.Font.Bold = True Selection.Font.Underline = xlUnderlineStyleSingle With Selection.Font .Color = -16776961 .TintAndShade = 0 End With ActiveCell.FormulaR1C1 = "Hello" Range("B6").Select End Sub The generated lines of code are known as a macro, which is a Sub procedure. Even though it is not the most efficient code, but it does provide clues on how a program in changing the font of a cell can possibly be written and allows you to know the Range and Font objects, the Selection, Font, Bold, Underline, Color, TintAndShade, ActiveCell, and FormulaR1C1 properties, the Select method, and the xlUnderlineStyleSingle built-in constant. The macro can be improved by removing the unnecessary actions of selecting cells and the default value of the TintAndShade property: Range("B5").Font.Bold = True Range("B5").Font.Underline = xlUnderlineStyleSingle Range("B5").Font.Color = -16776961 Range("B5").FormulaR1C1 = "Hello" Its efficiency can be further improved by using the With-End With construct. The construct reduces the number of dot references that VBA needs to process. With Range("B5").Font .Bold = True .Underline = xlUnderlineStyleSingle .Color = -16776961 End With Range("B5").FormulaR1C1 = "Hello"
VBA Help system The VBA Help system contains valuable and detailed information about objects and topics on VBA programming. The system provides context-sensitive help. When you are writing a VBA code statement in the code window or in the Immediate window, you can get specific help about a particular object, property, method, constant, keyword, or syntax by just typing the word and pressing F1. In Excel 2013 and 2016, you must be connected to the Internet to access the help system. If you are not connected to the Internet, you can still access a small part of the help system by downloading a help file from Microsoft Download Center. The latest version that I downloaded was a help file named Excel 2013 Developer Documentation.chm. However, the downloaded file is not linked to VBE to provide context-sensitive help. In Excel 2007 and 2010, the VBA Help system is stored in your local drive. It provides context sensitive help, without a need to connect to the Internet.
Exercise 1-6: Checking the syntax for a structure For example, to check the syntax for the Select Case structure, execute the following steps: 1.
Type Select Case in the code window.
2.
Press F1 to get more information related to Select Case. You can find the syntax, an example on how to use the Select Case structure, and other information on the help page.
Exercise 1-7: Exploring the VBA Help system in Excel 2007 and 2010 You can find lots of information in the VBA Help system by exploring the table of contents and by searching related words using the search box. For example, to learn more about operators in Excel VBA, execute the following steps: 1.
In VBE, press Alt+H twice to display the Help system.
2.
In the Search box, type Operator and press Enter.
3.
In the Search results pane, click Operator Precedence.
4. 5.
In the left pane, which is a table of contents, a list of topics about operators is under a book icon named Operators. Click each of these topics for more information.
Chapter 2: Knowing the places to store your VBA code Types of VBA modules A VBA module is a container for storing your VBA code. It can be one of the following types: ·
A standard VBA module: It holds VBA code for macros created by the macro recorder and procedures written by you. The procedures in the module can be called by procedures in other modules (including the other four types of module that to be discussed below) if they are declared as public by using the Public keyword. I discuss procedures later in Chapter 3.
·
A workbook module: By default, it is named ThisWorkbook. One workbook has only one workbook module. It holds VBA code for workbook-level events. An event is an action initiated either by users or by other VBA code which triggers VBA to execute an event Sub procedure (also known as event-handler procedure or simply event handler). An event handler is written by you. Opening a workbook, before closing a workbook, changing a selection on a worksheet, and clicking a command button are examples of events. A workbook module can also hold VBA code for other procedures written by you.
·
A sheet module: It holds VBA code for event handlers that response to the events associated with a particular sheet in a workbook. Each sheet (either a worksheet or a chart sheet) has its own sheet module. A sheet module can also hold VBA code for other procedures.
·
A UserForm module: A UserForm is a custom dialog box that you build to collect information from a user. A UserForm module contains a UserForm and event handlers that response to the events related to the UserForm. It can also hold VBA code for other procedures. In a workbook, it can have more than one UserForm module, with one UserForm in each of these modules.
·
A class module: It is used to create your own new class – a template to create an instance of an object at runtime. The VBA code used to define properties, methods, and events for a new class is stored in this module. I consider class module is an advanced topic and it is beyond the scope of this book.
In this chapter, you are going to see some sample procedures in each of these modules (except the class module). They are only simple procedures and of course many more can be created in those modules. Nevertheless, the samples do provide you ideas to place your VBA code in the right modules, and provide you the reasons why the code listings in this book are stored in certain modules. If you study some sample code listings from other references, such as books and the Internet,
you should find no exception in where VBA code should be stored.
Sample procedures in a standard VBA module A simple Sub procedure: Color banding Suppose that you want to apply alternate color banding to a selected range of cells. Execute the following steps: 1.
Start a blank workbook.
2.
Press Alt+F11 to activate VBE.
3.
Add a new standard VBA module to the project of the workbook by executing the following steps: a. b.
In the Project Explorer window, right-click the project’s name (by default, it is named VBAProject). Choose Insert | Module from the shortcut menu. By default, it is named Module1. You can change its name in the Properties window. If the Properties window is not visible, press F4.
4.
In the Project Explorer window, double-click the newly added module’s name to activate its code window.
5.
Enter the following Sub procedure in the code window: Sub CreateAlternateColorBanding() 'To alternately color rows within a selection Dim rw As Range For Each rw In Selection.Rows If rw.Row Mod 2 0 Then _ rw.Interior.ColorIndex = 15 Next rw End Sub Note: An underscore character (preceded by a space) is to separate a single long code statement that does not fix on a single line in this book. Separating a single long statement into a few lines also makes the code easier to read, without a need to scroll horizontally in the code window.
Exercise 2-1: Executing a Sub procedure by using the Macro dialog box After storing the CreateAlternateColorBanding procedure in a standard VBA module, execute the following steps to run the sample procedure: 1.
Press Alt+F11 to activate Excel.
2.
Select a range of cells of at least 2 rows by 2 columns.
3.
Press Alt+F8 to display the Macro dialog box.
4.
Select the macro named CreateAlternateColorBanding, and click Run.
Exercise 2-2: Executing a Sub procedure by using a shortcut key combination A faster way to run a Sub procedure is to assign it a shortcut key combination or to create a custom control on the Ribbon. I discuss the latter in my future book for intermediate users. The former can be used easily done by executing the following steps: 1.
In Excel, press Alt+F8 to display the Macro dialog box.
2.
Select the macro CreateAlternateColorBanding, and click the Options button to display the Macro Options dialog box.
3.
Assign your shortcut key combination by pressing Shift+B, for example, and click OK to close the Macro Options dialog box. The shortcut key combination is then Ctrl+Shift+B.
4. 5.
Click Cancel to close the Macro dialog box. Select a range of cells and test the procedure again by pressing the newly assigned shortcut key combination.
Exercise 2-3: Making a Sub procedure available to all opened workbooks If to make a Sub procedure available for other workbooks and not just for the workbook that stores the VBA code for that procedure, you may store the code in your Personal Macro Workbook. This workbook is named Personal.xlsb, as you can see in the Project Explorer window. If Personal.xlsb does not exist, execute the following steps to create the workbook. 1.
In Excel, choose Developer | Code | Record Macro to display the Record Macro dialog box.
2.
In the box labelled Store macro in, choose Personal Macro Workbook and click OK to accept other default settings. By clicking OK, the Personal Macro Workbook is created in VBE.
3. 4. 5.
Choose Developer | Code | Stop Recording or alternatively click the square icon on the status bar to stop recording a dummy macro in the newly created Personal Macro Workbook. Press Alt+F11 to activate VBE. In the code window, delete the dummy macro, but retain its VBA module in the Project
Explorer window for later use. To make CreateAlternateColorBanding available for all opened workbooks, copy the Sub procedure and paste it into the code window of the VBA module in the Personal Macro Workbook. You can also assign it a shortcut key combination, as discussed in Exercise 2-2 above.
A simple Sub procedure: Hiding the cells' contents Suppose that you want to store some information in certain cells, but the information is not relevant to users. Hence, you want to hide it. Enter the following Sub procedure in an existing or a new standard VBA module: Sub HideCellContent() Selection.NumberFormat = ";;;" End Sub
Exercise 2-4: Executing a Sub procedure by using the Macro dialog box After storing the HideCellContent procedure in a standard VBA module, execute the following steps to run the sample procedure: 1.
In a worksheet, enter something (text, numbers, or formulas) into a few cells.
2.
Select the cells with something entered in Step 1.
3.
Press Alt+F8 to display the Macro dialog box.
4.
Select the macro named HideCellContent, and click Run to hide the contents. The contents of the selected cells are not actually totally hidden. By selecting one of the cells, you can still view its contents in the formula bar.
A simple Function procedure: A power-of-three function A standard VBA module not only can store Sub procedures, but also Function procedures. They are known as user-defined or custom functions. The simple function below is to demonstrate that a custom function in a standard VBA module can be used as a worksheet function in a worksheet, just like other Excel worksheet functions, and can be used in other modules, just like other VBA built-in functions.
Enter the following Function procedure in an existing or new standard VBA module: Function Power3(x As Double) As Double Power3 = 3 ^ x End Function
Exercise 2-5: Using a custom function in a worksheet formula To use the preceding sample custom function in a worksheet formula, execute the following steps: 1. 2.
Press Alt+F11 to activate Excel. Select any cell in a worksheet and enter the following formula, for example: =Power3(4) It returns 81, which is 3 to the power of 4.
Exercise 2-6: Calling a custom function from the Immediate window To call the function from the Immediate window, enter the following statement, for example: ? Power3(2) It returns 9, which is 3 to the power of 2.
Exercise 2-7: Calling a custom function by a procedure in other module A custom function (or a procedure, in general) stored in a standard VBA module can be called not only by procedures in its own module, but also by procedures in other modules if it is declared as a public function (or public procedure, in general). These other modules can be other standard VBA modules, workbook modules, sheet modules, UserForm modules, and class modules. To call the custom function Power3 (which is stored in a standard VBA module) from other module, execute the following steps: 1.
Add a standard VBA module to the project of the workbook that stores the custom function. Note: Calling a custom function by procedures in other workbooks will be discussed in Exercise 3-2.
2.
In the code window of the newly added module (or in the code window of the workbook module or a sheet module), enter the following Sub procedure: Sub TestPower3() Debug.Print Power3(4)
End Sub The Debug.Print method executes the expression Power3(4) and sends the output of the execution to the Immediate window. If to call the custom function from the workbook module and a sheet module, copy and paste the TestPower3 procedure into those modules. 3.
Press F5 or click the triangle icon on the Standard toolbar to execute the TestPower3 procedure. In this case, it returns 81 in the Immediate window.
A sample procedure in a workbook module By default, the workbook module in a workbook is named ThisWorkbook. Each Excel file has only one workbook module.
A simple event handler: Workbook_Open Suppose that you want to limit the scroll area in a particular worksheet of a workbook when the workbook is opened. Execute the following steps: 1.
Start a blank workbook, or open an existing workbook.
2.
Press Alt+F11 to activate VBE.
3.
In the Project Explorer window, double-click ThisWorkbook to activate the code window of the workbook module.
4.
In the code window, select Workbook from the Object drop-down list, which is located on the top-left corner of the code window. See Figure 1-1.
5.
By default, the Open event is automatically selected from the Procedure drop-down list, which is located on the top-right corner of the code window. Try to select other events from the Procedure drop-down list. Once a new item is selected, an empty event handler of the selected event is inserted into the code window.
6.
To set the scroll area for a particular worksheet (say, the first worksheet) of the workbook to the range A1:N20, enter the following VBA code into the code window: Private Sub Workbook_Open() Worksheets(1).ScrollArea = "A1:N20" End Sub
7.
Save the workbook as a macro-enabled XLSM file in Excel 2010 – 2016 or XLM file in Excel 2007.
8. 9.
Close and reopen the workbook. If you are prompted with a security warning about active or macro content, you need to enable the content. Otherwise, the Workbook_Open event-handler procedure and any other procedures to be written by you will not work.
To clear the scrolling limit, execute the following statement in the Immediate window: Worksheets(1).ScrollArea = "" An alternative to set and clear the scrolling limit for a particular worksheet without using VBA code
is by changing the ScrollArea property in the Properties window of the worksheet. In VBE, press F4 (or in Excel, choose Developer | Controls | Properties) to display the Properties window.
Sample procedures in a sheet module Each sheet (either a worksheet or a chart sheet) has its own sheet module. The event handlers in a sheet module only handle events associated with that particular sheet.
A simple event handler: Worksheet_SelectionChange Suppose you are entering data into a worksheet. You want to keep the row of an active cell (the cell where data is to be entered) to be the second row from the top of the worksheet. To accomplish this task, execute the following steps: 1.
Start a blank workbook, or open an existing workbook.
2.
Press Alt+F11 to activate VBE.
3.
Under a folder named Microsoft Excel Objects in the Project Explorer window, double-click the name of a worksheet that you want to enter data into. This will activate the code window of the sheet module of the worksheet.
4.
In the code window, select Worksheet from the Object drop-down list, which is located on the top-left corner of the code window.
5.
By default, the SelectionChange event is automatically selected from the Procedure dropdown list, which is located on the top-right corner of the code window. Try to select other events from the Procedure drop-down list. Once a new item is selected, an empty event handler of the selected event is inserted into the code window.
6.
To keep the row of an active cell to be the second row from the top of the worksheet, enter the following VBA code into the code window: Private Sub Worksheet_SelectionChange( _ ByVal Target As Range) If Target.Row > 1 Then _ ActiveWindow.ScrollRow = Target.Row - 1 End Sub
7.
Press Alt+F11 to active Excel.
8.
Click any cell in that worksheet and start entering data into a few cells to see the effect.
A simple event handler: CommandButton1_Click()
Suppose that you want to create a button on a worksheet and by clicking the button, a Sub procedure is executed. To accomplish this task, execute the following steps: 1.
Start a blank workbook, or open an existing workbook.
2.
In Excel, select the worksheet that you want to place the button.
3.
Choose Developer | Controls | Insert and select Command Button (ActiveX Control).
4.
Draw the button on the worksheet. Command buttons have default names: CommandButton1, CommandButton2 …
5.
Double-click the command button to activate the code window of the sheet module. Observe the item selected from the Procedure drop-down list, which is located on the top-right corner of the code window. By default, the Click event is selected.
6.
7.
Enter the following VBA code into the code window: Private Sub CommandButton1_Click() MsgBox "Hello world!" End Sub An event of clicking the command button will CommandButton1_Click event handler.
trigger
the
execution of the
Press Alt+F11 to activate Excel and choose Developer | Controls | Design Mode to exit design mode. Or in VBE, choose Run | Exit Design Mode from the menu bar.
8.
Click the command button on the worksheet to call the event handler. You may replace the MsgBox statement with a statement of calling a procedure, for example: Call CreateAlternateColorBanding Or Application.Run "CreateAlternateColorBanding" CreateAlternateColorBanding is the procedure that was discussed right before Exercise 2-1.
A sample procedure in a UserForm module A UserForm is a custom dialog box with necessary controls (label, text box, combo box, list box, check box, option button, toggle button, frame, command button, scroll bar, spin button, image, multipage, and some other less commonly used controls) placed on it to collect information from users.
A simple UserForm: Collecting information to build a multiplication table Suppose that you want to create a multiplication table on a worksheet based on a number selected by a user. Figure 2-1 shows an example of a UserForm to collect information from the user.
Figure 2-1: A Userform to collect information from a user.
To create the UserForm, execute the following steps: 1.
Start a blank workbook, or open an existing workbook.
2.
Press Alt+F11 to activate VBE.
3.
Add a UserForm module to the project of the workbook by executing the following steps: a.
In the Project Explorer window, right-click the project’s name of the workbook.
b.
Choose Insert | UserForm from the shortcut menu. By default, it is named UserForm1. You can change its name in the Properties window. If the Properties window is not visible, press F4.
A blank UserForm is then created with a floating Toolbox window. If the Toolbox window is
not visible, choose View | Toolbox from the menu bar. 4.
In the Properties window of the UserForm, a.
Change the Caption property to Create Multiplication Table.
b.
Change the Name property to MTableUF.
If the Properties window is not visible, press F4. 5.
Add a frame, three option buttons, and a command button to the UserForm by choosing the controls from Toolbox and drawing the controls on the UserForm. See Figure 2-1. You may use the buttons on the UserForm toolbar to align and distribute the controls. If the toolbar is not visible, choose View | Toolbars | UserForm from the menu bar.
6.
Set the following properties of the controls, as listed in the table below: Control/Property Name
Accelerator
Frame
NA
Option button 1 Option button 2 Option button 3 Command button
Caption Select a Frame1 number Option3 3 Option4 4 Option5 5 CreateButton Create
3 4 5 C
The Accelerator property emulates an action of clicking a control by using a key combination. For example, a user can press Alt+3 as if the option button Option3 is clicked. 7.
To set one of the option buttons as a default selected button when the Userform is shown, set its Value property to True.
8.
To set the command button as the default command button that receives the keystroke of Enter, set its Default property to True.
To create an event handler for an event of clicking the command button on the UserForm and to create a Sub procedure for generating a multiplication table, execute the following steps: 1.
Double-click the command button on the UserForm to insert an empty event handler for clicking the command button into the code window of the UserForm module. You may use F7 and Shift+F7 to switch between the code window and the UserForm.
2.
Enter the following code for the event handler and Sub procedure in the code window: Private Sub CreateButton_Click() Dim n As Long If Me.Option3 Then n = 3 If Me.Option4 Then n = 4 If Me.Option5 Then n = 5 Call CreateMTable(n) Unload Me
End Sub Note: Me refers to the UserForm object. Private Sub CreateMTable(n As Long) 'To create a multiplication table of n 'on an active worksheet Dim i As Long With ActiveSheet For i = 1 To 12 .Cells(i, 1) = _ i &"x"&n&"="&i *n Next i .Columns("A:A").AutoFit End With End Sub To create a command button on a worksheet and an event handler for an event of clicking the command button, execute the following steps: 1.
Press Alt+F11 to activate Excel, and select a worksheet.
2.
Choose Developer | Controls | Insert and select Command Button (ActiveX Control).
3.
Draw the button on the worksheet.
4.
Double-click the command button to activate the code window of the sheet module. By default, the Click event is selected from the Procedure drop-down list, which is located on the top-right corner of the code window.
5.
Enter the following event handler into the code window: Private Sub CommandButton1_Click() MTableUF.Show End Sub
6.
Press Alt+F11 to activate Excel and choose Developer | Controls | Design Mode to exit design mode. Or in VBE, choose Run | Exit Design Mode from the menu bar.
7. 8.
Click the command button on the worksheet to show the UserForm. Select an option and click the Create button on the UserForm to generate a multiplication table.
Exporting and importing a VBA module To export a VBA module from a workbook, execute the following steps: 1.
In the Project Explorer window of the workbook, right-click the module that to be exported.
2.
Choose Export File from the shortcut menu.
3.
Locate the folder to save the file, type a filename, and click Save. The original module actually remains in the project, and only a copy of it is exported.
To import an exported VBA module, which was saved in a file, into a workbook, execute the following steps: 1.
In the Project Explorer window of the workbook, right-click the project’s name.
2.
Choose Import File from the shortcut menu.
3.
Locate the file containing the VBA module, and click Open.
Another way of exporting and importing a VBA module from one workbook to another is just simply by opening both workbooks. In the Project Explorer window, drag the module that to be exported and drop it into the project of the other workbook. The original module actually remains in the project, and only a copy of it is added to the project of the other workbook.
Chapter 3: Programming fundamentals in VBA As mentioned in Introduction, this book assumes that you should have a basics understanding of programming language, such as C or Python. This chapter recaps the fundamentals of programming in the environment of Excel VBA.
Data types A data type, such Integer or Boolean, is a classification of data. It determines the space the data occupies in memory, the values the data can store, and the operations can be done on the data. The following table lists the supported data types in Excel VBA with their storage sizes and value ranges. Data type
Storage size
Range of values
Byte
1 byte
0 to 255
Boolean
2 bytes
True or False
Integer
2 bytes
-32,768 to 32,767
Long
4 bytes
-2,147,483,648 to 2,147,483,647
4 bytes
-3.402823E38 to -1.401298E-45 for negative values; 1.401298E-45 to 3.402823E38 for positive values
8 bytes
-1.79769313486231E308 to -4.94065645841247E-324 for negative values; 4.94065645841247E-324 to 1.79769313486232E308 for positive values
8 bytes
-922,337,203,685,477.5808 to 922,337,203,685,477.5807
Decimal
14 bytes
+/-79,228,162,514,264,337,593,543,950,335 with no decimal point; +/-7.9228162514264337593543950335 with 28 places to the right of the decimal
Date
8 bytes
January 1, 100 to December 31, 9999
Object
4 bytes
Any object reference
String (variablelength)
10 bytes + string length
0 to approximately 2 billion characters
String (fixed-length)
Length of string
1 to approximately 65,400 characters
Variant (with numbers)
16 bytes
Any numeric value up to the range of a Double data type
Variant (with characters)
22 bytes + string length
Same range as for variable-length String
User-defined (using Type)
Bytes required by elements
The range of each element is the same as the range of its data type.
Single
Double
Currency
A Variant is a special data type. If a variable is declared without explicitly stating its data type, it is then, by default, of data type Variant. A variant variable can hold any kind of data, including special values such as Empty, Error, Nothing, and Null. The data type of a variant variable changes when needed, depending on what manipulation is done on the variable. The following listing illustrates the point by checking the data type of a variant variable. Sub CheckVariantDataType() Dim myvar As Variant
myvar = Null Debug.Print TypeName(myvar) 'returns Null myvar = 3 Debug.Print TypeName(myvar) 'returns Integer myvar = myvar & 1 Debug.Print TypeName(myvar) 'returns String myvar = myvar * 2 Debug.Print TypeName(myvar) 'returns Double Debug.Print myvar 'returns 62 End Sub The TypeName function is used to determine the data type of a variable. VBA also allows you to define custom (also known as user-defined) data types. Often, custom data types are to group several related variables. The following definition of a custom data type named uStaffInfo illustrates the point: Type uStaffInfo StaffName As String * 30 Addr(1 To 2) As String Age As Byte End Type The definition of a custom data type must be placed at the top of a VBA module before any procedures.
Constants and variables A constant is an identifier or a unique name with a value that cannot be altered during an execution of a program. Constants are preferable to hard-coded values. For example, a constant named VatRate is better than a hard-coded value, say, of 6 in a program. It makes the code in the program easier for you to understand and easier to maintain if a need to change the VatRate constant arises. On other hand, a variable is an identifier with a value that can be changed during an execution of a program.
Declaring constants and variables A declaration is to name a constant or variable and to specify its characteristics, such as the data type and the scope of visibility. VBA allows you to use a variable without declaring it. However, it can lead to bugs with no compilation error, but with unexpected or, even worse, unnoticed wrong results. Suppose you use an undeclared variable named temp. At some point in your code, you misspell it as tmp. When the compiler encounters a name that it does not recognize as an existing variable, it creates a new variable using that name. It does not flag that as an error. Instead, it will create a new variable named tmp. This can lead to bugs that they are very difficult to identify. You may use the Option Explicit statement at the top of a module to force explicit declarations of all variables in a module. To automatically insert the Option Explicit statement in a new VBA module, you may enable the Require Variable Declaration option in the Editor tab of the VBE Options dialog box. (Choose Tools | Options from the menu bar to display the Options dialog box.) The following two tables, respectively, show some examples of how constants and variables are declared. Examples of declaring constants To declare an integer constant: Const RateCol As Integer = 2 To declare a constant without a data type: Const RateCol = 2 Without explicitly stating a data type, VBA determines the data type that is most appropriate to the constant. To declare multiple constants on a single line:
Const Rate = 6.15, Term As Integer = 48 To declare a string constant: Const AppName As String = "Housing Loan" Examples of declaring variables To declare a variable of data type Boolean: Dim FileOpened As Boolean To declare an integer variable: Dim Counter As Integer To declare a variable of data type Long: Dim NextRow As Long To declare a variable of data type Date: Dim Timer1 As Date To declare a variable of data type String: Dim FileName As String To declare a string variable with a specified number of characters: Dim FileName As String * 20 To declare a variable of data type Object: Dim objElements As Object To declare a variable without explicitly stating a data type: Dim Temp The variable is then, by default, of data type Variant. To declare multiple variables on a single line: Dim i As Long, j As Long The variables i and j are of data type Long. To declare multiple variables on a single line: Dim i, j As Long Only j is of data type Long. Since i is in fact declared without explicitly stating a data type, it is of data type Variant, by default. To declare an object variable that can, but not yet, contain a reference to a worksheet object: Dim ws As Worksheet The variable ws holds the special value Nothing until it is assigned an object reference using the Set statement, for
example: Set ws = ActiveSheet To declare an object variable: Dim coln As Collection To create an object (using the New keyword) and assign its reference to an object variable: Set coln = New Collection The object reference (to the newly created Collection object) is assigned to the variable coln using the Set statement. A shortcut to declare an object variable, to create an object, and to assign the reference of the object to the object variable: Dim coln As New Collection To declare a two-dimensional array of integers: Dim iMatrix(1 To 3, 1 To 4) As Integer To declare a dynamic array of custom data types: Dim uCACommittee() As uStaffInfo To declare a dynamic array of variants: Dim vMyArray() I discuss arrays later in this chapter.
Scope of constants and variables A declared constant or variable can be used either in all modules, in a particular module, or just in a particular procedure. Its scope of visibility (that is, in where it can be used) depends on where and how (by using either the Public, Private, Dim, or Static keyword) it is declared. The following two tables, respectively, show the scopes of declared constants and variables. Location where a constant is declared Before the first procedure in a module
Example of how a constant is declared
Scope of visibility
Public Const Dept As String = "DXC"
All modules
Private Const Dept As String = "DXC" Before the first procedure in a
All procedures Const Dept As String = in that
module
"DXC"
Within a procedure (after the Sub or Function statement)
Const Dept As String = Only in that "DXC" procedure
Location where a variable is declared
Example of how a variable is declared
Scope of visibility
Before the first procedure in a module
Public myStr As String
All modules
Before the first procedure in a module
Private myStr As String All procedures in that Dim myStr As String module
Within a procedure (after the Sub or Function statement)
Dim myStr As String Static myStr As String
module
Only in that procedure
The Public and Private keywords can only be used before the first procedure in a module. All public constants and variables declared in a VBA module with the Public keyword can be used in all modules. If they are declared in a non-standard VBA module (such as a sheet or workbook module), to use them from other modules you need to precede their names with the module name. For example, a public variable named myStr declared in a worksheet module named Sheet1 can be accessed with the following expression: Sheet1.myStr A module-level constant, a constant that is declared in a module before the first procedure, without using the Private keyword is private to the module by default. A module-level variable either declared with the Private or Dim keyword is private to the module. All variables and constants declared within a procedure can only be accessed within the procedure. A procedure-level variable declared with the Static keyword (the keyword that can only be used within a procedure to declare variables) retains its value between procedure calls.
Exercise 3-1: Testing a static variable To understand more about a static variable, execute the following steps:
1.
Enter the following Function procedure into the code window of either a new or an existing standard VBA module: Function Counter() As Long 'To return the number of times it is called 'The variable x preserves its value 'between calls Static x As Long x=x+1 Counter = x End Function
2.
Enter the following Sub procedure either into the code window of the module where the Counter function is stored in or into the code window of other module: Sub TestCounter() Dim i As Long For i = 1 To 4 Debug.Print Counter() Next i End Sub
3.
Choose Run | Run Sub/UserForm or press F5 to execute the Sub procedure. Observe the values displayed in the Immediate window. The static variable x (in the Counter function) retains its value.
4.
In the Immediate window, enter the following statement to call the function another time: ? Counter() It returns 5. That is, the fifth time the function is called.
5.
Press Alt+F11 to activate Excel, and enter the following formula into any cell: =Counter() It returns 6. That is, the sixth time the function is called.
Syntax for decision structures VBA supports three decision structures: If-Then, If-Then-Else, and Select Case. This section lists the syntax followed by one or a few examples for each structure.
If-Then structure The following are two variations of an If-Then structure followed by one or two simple examples for each variation. Syntax for an If-Then structure If condition Then statement(s) Example 1 If A > 3 Then Cnt = Cnt + 1 Example 2 If A > 3 Then Cnt = Cnt + 1 : Sum = Sum + A Note: A colon is to place two statements on a single line.
Syntax for an If-Then structure If condition Then [statement(s)] End If Note: A pair of square brackets indicates an optional part of the structure. Example If A > 3 Then Cnt = Cnt + 1 Sum = Sum + A End If
If-Then-Else structure The following are two variations of an If-Then-Else structure followed by a simple example for each. Syntax for an If-Then-Else structure If condition Then
[statement(s)] Else [statement(s)] End If Example If A > 3 Then Cnt = Cnt + 1 Else B=B+1 End if Syntax for an If-Then-Else structure If condition1 Then [statement(s)] ElseIf condition2 Then [statement(s)] Else [statement(s)] End If Example If Score >= 8 Then MsgBox "A" ElseIf Score > 3 And Score < 8 Then MsgBox "B" Else MsgBox "C" End If
Alternatively, the preceding structure that uses ElseIf can be replaced with the following structure, which is more intuitive and easier to understand. Syntax for an If-Then-Else structure If condition1 Then [statement(s)] Else If condition2 Then [statement(s)] Else [statement(s)] End If End If
Example If Score >= 8 Then MsgBox "A" Else If Score > 3 And Score < 8 Then MsgBox "B" Else MsgBox "C" End If End If
Select Case structure When the conditions in an If-Then-Else structure are referring to the same test expression, a Select Case structure is a better alternative. It offers much clearer code. Syntax for a Select Case structure Select Case TestExpression Case Expression1 [statement(s)] [Case Expression2 [statement(s)]] [Case else [statement(s)]] End Select Example 1 Select Case Score Case Is >= 8 MsgBox "A" Case 4 To 7 MsgBox "B" Case Else MsgBox "C" End Select Example 2 Select Case Round(Rate, 2) Case Is < 0.50: MsgBox "Low" Case 0.50 To 2.49: MsgBox "OK" Case 2.50 To 2.99: MsgBox "KIV" Case Else: MsgBox "High" End Select Example 3 Select Case TypeName(myVar)
Case "Empty": MsgBox "Empty" Case "Integer": MsgBox "Integer" Case "String": MsgBox "String" Case "Double": MsgBox "Double" End Select
TestExpression can be a numeric expression or string expression. VBA allows multiple expressions (separated by commas) and ranges in each Case clause, for example: Case 0 To 90, 125, 180 To 270, 325, Is > 360 I f TestExpression matches more than one Case expression, only the statements following the first match are executed. Hence, the order of the Case expressions does matter. The following listing illustrates the concept. Sub CatchAlphanumeric() Dim myChar As Variant myChar = InputBox("Enter a character: ") If Len(myChar) 1 Then MsgBox "More than a character was entered." Exit Sub End If Select Case UCase(myChar) Case 0 To 9 MsgBox "A digit." Case "A", "E", "I", "O", "U" MsgBox "A vowel." Case "A" To "Z" 'Vowels were tackled in previous Case. 'Hence, it does not matter to have 'them again. MsgBox "A consonant." Case Else MsgBox "Not a digit nor a letter." End Select End Sub If the case of vowels (A, E, I, O, U) is placed after the case of alphabet (A to Z), then the vowel case will never be matched.
Syntax for loop structures VBA supports three loop structures: For-Next, For Each-Next, and Do-Loop. This section lists the syntax followed by one or a few examples for each loop structure.
For-Next loop A For-Next loop executes a group of statements for a specified number of times. Syntax for a For-Next loop For counter = start To end [Step step] [statement(s)] [Exit For] [statement(s)] Next [counter] Example 1 Dim i As Long For i = 2 To 10 Step 2 Debug.Print i, 'returns 2 4 6 8 10 Next Example 2 Dim i as Long For i = 9 To 1 Step -2 Debug.Print i, 'returns 9 7 5 3 1 Next i
The optional Exit For statement in a For-Next loop is often used after evaluating of some condition, for example in an If-Then structure, and control is then transferred to the statement immediately after the For-Next loop.
For Each-Next loop A For Each-Next loop executes a group of statements for each element in a collection. Syntax for a For Each-Next loop For Each element In group [statement(s)] [Exit For]
[statement(s)] Next [element] Example Dim ws As Worksheet For Each ws In Worksheets Debug.Print ws.Name Next
Do loop A Do loop is either a Do-While loop or a Do-Until loop. Unlike For-Next and For Each-Next loops that execute for a specified number of times, a Do loop executes as long as the While condition in the loop is True or the Until condition in the loop becomes True. Syntax for a Do-While loop Do [While condition] [statement(s)] [Exit Do] [statement(s)] Loop Example Dim i As Long: i = 2 Do While i 10
The optional Exit Do statement in a Do loop is often used after evaluating of some condition, for example in an If-Then structure, and control is then transferred to the statement immediately after the Do loop.
Working with strings Two types of string variables There are two types of string variables in VBA: ·
A fixed-length string: A string variable that you declare with a specified number of characters, for example: Dim strName As String * 30 If you assign the strName variable with characters less than 30, VBA fills the extra positions with spaces. If you assign strName with characters greater than 30, only the first 30 characters are assigned to the variable, and the rest are ignored. The maximum length of a fixed-length string is 65,535 characters.
·
A dynamic string: A string variable that you declare without stating any specified number of characters, for example: Dim strName As String It can store up to 2 billion characters.
Comparing strings There are three ways to compare a pair of strings: comparison operators (=, =, and ), the Like operator, and the StrComp function. The Option Compare statement appeared at the top of a VBA module before any procedures specifies the string comparison method (Binary – for case-sensitive comparisons, Text – for case-insensitive comparisons, or Database – only be used within Microsoft Access) in the module. If a module does not have the Option Compare statement, the default comparison method is Binary.
Comparison operators Type the following statements into the Immediate window and press Enter after each. ? "Amy" < "Ann" ? "Amy" "Ann" ? "Amy" >= "Ann" ? "Amy" "Ann"
'returns True 'returns True 'returns False 'returns False 'returns False 'returns True
? "Amy" < " Ann" 'returns False ? "Amy" < "Amy Bond" 'returns True VBA performs a character-by-character comparison between two strings.
The Like operator The Like operator allows you to use wildcards, character lists, and character ranges in comparing strings, not just fixed characters. The allowed wildcard characters are "?", "*", and "#". A character list is a list of characters in a pair of square brackets. A character range is specified by a stating character, a hyphen character ("-"), and an ending character. The following two tables clarify the explanation. Character
What it matches in a string
?
Any single character
*
Zero or more characters
#
Any single digit (0–9)
[CharacterList]
Any single character in CharacterList
[!CharacterList]
Any single character not in CharacterList
Example statement using the Like operator
The statement returns true if …
MyStr Like "[A-Za-z0-9]????#"
MyStr is six-characters long, starts with an alphanumeric character and ends with a digit.
"W 48a" Like "[A-Z] ##[a-z]"
The first character is a capital letter, the second is a space, the next two are digits, and the fifth is a lowercase letter.
MyStr Like "[AEIOUaeiuo]*[*]"
MyStr starts with a vowel and ends with an asterisk character.
MyStr Like "[!AEIOUaeiuo]*"
The first character in MyStr is not a vowel.
The StrComp function
The syntax for the StrComp function is as below: StrComp(string1, string2[, compare]) Similar to comparison operators, the StrComp function performs a character-by-character comparison between two strings. The third argument in the function provides an option to ignore the Option Compare statement in a module. The return values of the StrComp function are as shown in the following table. Condition string1 < string2 string1 = string2 string1 > string2 string1 or string2 is Null
Return value -1 0 1 Null
VBA built-in string functions VBA has a lot of useful built-in functions when dealing with strings. Knowing their functionalities, you can then incorporate the functions in your VBA code. After you type the name of a function (followed by an opening parenthesis) in the code and Immediate windows, the information about its arguments is automatically displayed. If VBE does not display the information, enable the Auto Quick Info option in the Editor tab of the VBE Options dialog box. (Choose Tools | Options from the menu bar to display the Options dialog box.) The next table shows some commonly used VBA string functions. The table lists the syntax for each function, the common functionality of the function, and a simple example. Consult the VBA Help system for a complete description of the syntax. Function
Syntax | What it returns | Example
Returning the length of a string Len(string | varname) Len The number of characters in a string. Len("This is an example") returns 18. Returning the position of a substring in a string InStr([start, ]string1, string2[, compare]) The start position of the first occurrence of a InStr substring in a string. InStr("This is an example", "is") returns 3. InstrRev(stringcheck, stringmatch [, start[, compare]]) The position of the first occurrence of a InStrRev
substring in a string, starting from the right side of the string. InStrRev("This is an example", "is") returns 6. Returning a manipulated string
Left
Right
Mid
Left(string, length) A string containing a specified number of characters from the left side of a string. Left("This is an example", 5) returns "This ". Right (string, length) A string containing a specified number of characters from the right side of a string. Right("This is an example", 5) returns "ample". Mid(string, start[, length]) A string containing a specified number of characters from a string. Mid ("This is an example", 5, 3) returns " is". Note: If to extract a substring starting in the middle of a string, until the end of the string, omit the third argument.
LTrim,
RTrim
LTrim(string) A string with no leading spaces. LTrim(" 2s and 3 ") returns "2s and 3 ". RTrim(string) A string with no trailing spaces. RTrim(" 2s and 3 ") returns " 2s and 3". Trim(string) A string with no leading and trailing spaces. Trim(" 2s and 3 ") returns "2s and 3".
Trim Note: To remove all spaces except for single spaces, use Application.WorksheetFunction.Trim(). LCase
UCase
StrConv
LCase(string) A string converted to lowercase. LCase("She is Ann") returns "she is ann". UCase(string) A string converted to uppercase. UCase("She is Ann") returns "SHE IS ANN". StrConv(string, conversion, LCID) A string converted to a specified form. StrConv("SHE IS ANN", vbProperCase) returns "She Is Ann". Space(number)
Space
A string with a specified number of spaces. "Hi" & Space(3) & "!" returns "Hi !". Note: The ampersand ("&") characters are to join multiple strings into a single string.
String
String(number, character) A string with a repeating character. String(3, "*") returns "***".
StrReverse(expression) StrReverse A string in reversed order. StrReverse("AbcDe") returns "eDcbA". Replace(expression, find, replace [, start[, count[, compare]]]) A string in which a specified substring has been replaced with another substring. Replace("This is an example", "s", "S") returns "ThiS iS an example". Replace
CStr
Note: The Replace function has six arguments and other functionalities. For example, if to replace only the first occurrence of a specified substring in a string, set the fifth argument to 1, as shown in an example below: Replace("a12012b", "12", "B", , 1)returns "aB012b". CStr(expression) Convert an expression to a string. CStr(12.3) returns "12.3". Note: Check the Help system for other typeconversion functions.
Format
Format(expression[,format [,firstdayofweek [,firstweekofyear]]]) A string formatted according to a format string expression. Format("9/8/2016", "dd mmm yy") returns"08 Sep 16".
Returning and joining an array of strings Split(expression[, delimiter[, limit[, compare]]]) A zero-based, one-dimensional array containing a number of substrings. Split("This is an example", " ") returns an array
Split
containing four strings: "This", "is", "an", and "example". Note: If the second argument is omitted, a space character (" ") is assumed to be the delimiter.
Join
Filter
Join(sourcearray[, delimiter]) A string created by joining a number of substrings contained in an array. Join(Split("This is an example"), "|") returns "This|is|an|example". Filter(sourcesrray, match[, include[, compare]]) A zero-based, one-dimensional array containing a subset of a string array based on specified filter criteria. Join(Filter(Split("This is an example"),"a"),"|") returns "an|example". The Filter function in the example above returns an array of two elements: "an" and "example", in which both have the character "a".
Returning a character Chr(charcode) A character associated with a character code. Chr(65) returns "A". Chr Note: To return a character code associated with a character, use Asc(). For example: Asc("A") returns 65.
Working with arrays An array is a group of elements that are of the same data type and have a common array name. Each element in an array can be referred by using the array name and an index (for a one-dimensional array) or several indexes (for a multidimensional array).
One dimensional arrays There are two ways of declaring an array with a specific number of elements in the array: one with both lower and upper indexes, and the other with only the upper index. Dim strWeekDays(1 To 7) As String Dim strWeekDays(6) As String The two arrays above consist 7 elements each. Without stating the lower index, by default, VBA assumes it to be zero. If to make VBA to assume that 1 is the lower index, include the Option Base 1 statement at the top of a VBA module before any procedures. To avoid any confusion, just include both lower and upper indexes when declaring arrays. In fact, the lower index needs not to be 0 or 1. It can be other positive and negative integers. The following listing shows an example of how to declare a one-dimensional array, to assign a value to an array element in the array, and to retrieve the value. Sub OneDArrayDemo1() 'Declare an integer array of 7 elements Dim intScales(-2 To 4) As Integer 'Assign an integer 50 to 2nd element in the array 'by using the index that refers to the element intScales(-1) = 50 'Check the assigned value Debug.Print intScales(-1) 'returns 50 End Sub The next listing shows an example of a one-dimensional array of custom data types. 'Define a custom data type Type StaffInfo StaffName As String * 30 Addr(1 To 2) As String Age As Integer
End Type
Sub OneDArrayDemo2() 'Declare a 50-elements array of custom data types Dim cusStaff(1 To 50) As StaffInfo 'Assign values to an element in the array With cusStaff(1) .StaffName = "Richard" .Addr(1) = "1, Wall Street" .Addr(2) = "New York" .Age = 35 End With 'Check the assigned values Debug.Print cusStaff(1).StaffName 'returns Richard Debug.Print cusStaff(1).Addr(2) 'returns New York End Sub
Multidimensional arrays A multidimensional array can be two, three, or more dimensions. The way to access each element in a multi-dimensional array is by using the array name and the indexes of its respective dimensions. To check the lower and upper indexes for each dimension of an array, the LBound and UBound functions are used. The following two listings show examples of how to check the indexes for two and three dimensional arrays, respectively: Sub MultiDimArrayDemo1() 'Declaring a 4-by-5 two-dimensional 'array of integers Dim a(3, 4) As Integer 'Checking the upper and lower indexes 'for 1st dimension of a() Debug.Print LBound(a, 1), 'returns 0 Debug.Print UBound(a, 1) 'returns 3 'for 2nd dimension of a() Debug.Print LBound(a, 2), 'returns 0 Debug.Print UBound(a, 2) 'returns 4
End Sub
Sub MultiDimArrayDemo2() 'Declaring a 10-by-4-by-8 three'dimensional array of variants Dim b(1 To 10, 0 To 3, -3 To 4) 'Checking the upper and lower indexes 'for 1st dimension of b() Debug.Print LBound(b, 1), 'returns 1 Debug.Print UBound(b, 1) 'returns 10 'for 2nd dimension of b() Debug.Print LBound(b, 2), 'returns 0 Debug.Print UBound(b, 2) 'returns 3 'for 3rd dimension of b() Debug.Print LBound(b, 3), 'returns -3 Debug.Print UBound(b, 3) 'returns 4 End Sub
Dynamic arrays If the number of elements in an array that you want to declare is only known at runtime, you may declare it as a dynamic array without stating the upper index (and the lower index), for example: Dim a() As Integer Dim b() As Integer You can later use the ReDim keyword to tell VBA the number of elements in the array, for example: ReDim a(2 To x) ReDim b(1 To x, -3 To y) where x and y are the values that are only known at runtime. ReDim can be used for any number of times to change the number of elements in an array. When it is used, the existing values in the array are destroyed. If to preserve the values, you can use ReDim Preserve, such as: ReDim Preserve a(2 To x + 5) ReDim Preserve b(1 To x, -3 To 8)
However, you can resize only the last dimension of a dynamic array and can change only its upper index. The following listing is an example of how a dynamic array is resized in a For-Next structure while preserving the values in the array. Sub DynamicArrayDemo1() Dim dynArry() As Long, i As Long For i = 1 To 5 ReDim Preserve dynArry(1 To i) dynArry(i) = i * 2 Next 'Display the values in the array For i = LBound(dynArry) To UBound(dynArry) Debug.Print dynArry(i), 'returns 2 4 6 8 10 Next i End Sub It is not always a must that one needs to use the ReDim statement before a declared dynamic array can store values, as shown in the following example: Sub DynamicArrayDemo2() 'Store values without ReDim Dim Words() As String Words() = Split("This is an example") Debug.Print Join(Words, "|") End Sub After execution of the statement Words() = Split("This is an example"), the Words array becomes a zero-based, one-dimensional array containing four elements: "This", "is", "an", and "example". The Join(Words, "|") statement returns "This|is|an|example".
Function and Sub procedures A procedure is either a Function procedure or a Sub procedure (also known as subroutine or macro). It contains lines of code to accomplish a specific task. A Function procedure written by you is a custom function. It must return a single value or an array of values. It can possibly be used as a worksheet function and as a function in other procedures. A Sub procedure, on the other hand, does not return any value. Nevertheless, procedures can emulate return values if their arguments (also known as parameters) are passed by reference (ByRef).
Declaring procedures Declaring a procedure is to name the procedure and to specify its characteristics, such as the data type and the scope of accessibility. The syntax for a Function procedure: Function Name ([ArgumentsList]) [As Type] [statement(s)] [Name = expression] [Exit Function] [statement(s)] [Name = expression] End Function
The syntax for a Sub procedure: Sub Name ([ArgumentsList]) [statement(s)] [Exit Sub] [statement(s)] End Sub Name is the name of a procedure. The rules for naming procedures are the same as the ones in naming arguments in the procedures, constants, and variables. A valid name must begin with a letter. It can contain digits and underscore characters ("_"), but cannot contain spaces, arithmetic operator
symbols, punctuation marks, other special characters such as ~, `, @, #, $, %, ^, &, =, |, \, . ArgumentsList is a list of arguments that is passed to a procedure when it is called. Arguments are separated by commas. By default, arguments are passed by reference (ByRef). To pass an argument by value, precede the argument with the ByVal keyword. However, a custom data type can only be passed by reference. Type is the data type of the return value by a Function procedure. It can be Byte, Boolean, Integer, Long, Currency, Single, Double, Date, String, Object, Variant, or a custom data type.
Scope of procedures A procedure can be called by procedures in any modules or only by procedures in the module that stores the procedure. Its scope of accessibility (that is, from where it can be called) depends on how (by using either the Public or Private keyword) it is declared, as shown in the following table. An example of how a procedure is declared Public Sub UpdateImage()
Can be called by …
Sub UpdateImage() Procedures in Public Function Power3(x As Double) As any modules Double Function Power3(x As Double) As Double Private Sub UpdateImage()
Procedures in the module that Private Function Power3(x As Double) As stores the procedure Double
A procedure declared without explicitly using the Public keyword is public by default. If a public procedure is stored in a standard VBA module, it can be called by procedures in any modules using the Call keyword, for example Call UpdateImage. If it is a Function procedure, it can also be used as a worksheet function, just like other Excel built-in worksheet functions. A public Sub procedure in a standard VBA module can be executed in one of the following ways: ·
Use the Macro dialog box. See Exercise 2-1.
·
Press the shortcut key combination that is assigned to the procedure. See Exercise 2-2.
· ·
In VBE, press F5 to run it. Call it from the Immediate window. For example, to execute a Sub procedure named CreateAlternateColorBanding, enter the following statement in the Immediate window: Call CreateAlternateColorBanding Or Application.Run "CreateAlternateColorBanding"
·
Call it by a procedure in other module. The way to call is the same as the one from the Immediate window. For example, Call CreateAlternateColorBanding Or Application.Run "CreateAlternateColorBanding"
·
Click a command button. If the command button is a form control, the Assign Macro dialog box is automatically displayed after you draw the button on a worksheet. In the dialog box, you can assign a Sub procedure to the button. If the command button is an ActiveX control, please consult the topic "A simple event handler: CommandButton1_Click()" in Chapter 2.
A public Function procedure in a standard VBA module can be executed in one of the following ways: ·
Use it as a worksheet function. See Exercise 2-5.
·
Call it from the Immediate window. See Exercise 2-6.
·
Call it by a procedure in other module, as if it is a VBA built-in functions. See Exercise 2-7.
If a public procedure is in a non-standard VBA module (such as a sheet or workbook module), it can be called by procedures in any modules using the Call keyword and the name of the module that stores the procedure, for example Call Sheet1.UpdateImage. If it is a Function procedure, it cannot be used as a worksheet function. A public Sub procedure either in a standard or non-standard VBA module can be executed in the same ways, with one exception. In a statement calling a procedure in non-standard module, the procedure name must be preceded by the module name, for example: Call Sheet1.CreateAlternateColorBanding Or Application.Run "Sheet1.CreateAlternateColorBanding" Often, when you want a procedure to be only accessible by the procedures in the module storing the
procedure, you declare it as a private procedure. However, VBA provides a way to call a private procedure from other module. Since the way distracts the main purpose of declaring a procedure private, it is only discussed later in Chapter 4 under the topic "Calling a private procedure from other module". Literally there are countless of Function and Sub procedures. In the following discussion, you will see some sample functions: from a function with no argument to a function with an arbitrary number of arguments, from a function that returns a single value to a function that returns an array of values, and functions with optional arguments. The sample functions provide you the programming syntax on how to write those functions. The discussion on the sample Function procedures applies also to Sub procedures, with the only difference that Sub procedures do not return a value or an array of values.
A function with no argument A function can take no argument and its return data type depends on the purpose the function is created. For example, the following ThisFilePath function takes no argument and returns the complete path of an active workbook: Function ThisFilePath() As String ThisFilePath = ActiveWorkbook.Path End Function
Exercise 3-2: Using a custom function as a worksheet function As mentioned earlier, to use a custom function as a worksheet function, the function must be stored in a standard VBA module and declared as public. To use the custom function ThisFilePath as a worksheet function, execute the following steps: 1.
Start a blank workbook, or open an existing workbook.
2.
Press Alt+F11 to activate VBE.
3.
Insert a new or use an existing standard VBA module.
4.
Enter the VBA code for the custom function in the code window of the module.
5.
Press Alt+F11 to activate Excel.
6.
Save the workbook as a macro-enabled XLSM file in Excel 2010 – 2016 or XLM file in
Excel 2007. 7.
Select any cell in a worksheet of the newly saved workbook and enter the following formula into the cell: =ThisFilePath() The formula returns the path where the workbook is saved.
8.
To use the function as a worksheet function in other workbook, there are two ways: a.
Include the complete path before the function name: ='C:\VBA\Book1.xlsm'!ThisFilePath() Book1.xlsm is the Excel file that stores the custom function. Before the function can be used in other workbook, the workbook that stores the function must first be opened.
b.
Set a reference i. To avoid name conflicts with other VBA projects, change the project's name of the custom function from VBAProject to LearnVBAProject or other unique name in the Properties window of the project. If the Properties window is not visible, press F4. ii. In the Project Explorer window, select the project’s name of the workbook that will use the function by clicking. iii. Choose Tools | References from the menu bar to display the References dialog box. iv. Click Browse to display the Add Reference dialog box. v. Choose Microsoft Excel Files from the file type box. vi. Locate the workbook file storing the function, in this case Book1.xlsm. vii. Click Open and then OK to close the References dialog box. When a reference is successfully established, there is a reference node in the Project Explorer window. To use the function, you can now just enter the following formula into any cell without a need to include the complete path: =ThisFilePath() Similarly, when you use Excel VBA to work with other applications such as Microsoft Word and Internet Explorer, you need to set references to the libraries associated with those applications.
Exercise 3-3: Calling a custom function by a procedure from other module
To call the function ThisFilePath from other modules in the same workbook that stores the function, execute the following steps: 1.
Open the workbook containing the custom function. See Steps 1 - 4 in Exercise 3-2 on how to store the function in a standard VBA module.
2.
In VBE, insert a new standard VBA module, or use an existing sheet module or the workbook module.
3.
In the code window of the module, enter the following Sub procedure: Sub TestThisFilePath() Debug.Print ActiveWorkbook.Name & _ " is stored in " & ThisFilePath End Sub
4.
To execute the Sub procedure, choose Run | Run Sub/UserForm or press F5.
5.
Observe the output in the Immediate window. If the Immediate window is not visible, press Ctrl+G.
If to execute a custom function from other workbook, you need to set a reference to the workbook that stores the function, as discussed in Step 8b, Exercise 3-2.
A function with one argument The following is an example of a function with one argument. The function takes a filename with (or without) the complete path as its argument and returns a filename without the path. Function FileNameOnly(FullName As String) As String 'e.g.: "C:\temp\BookA.xlsx" becomes "BookA.xlsx" FileNameOnly = Mid(FullName, _ InStrRev(FullName, "\") + 1) End Function This function can be used as a worksheet function and can be called by a procedure in any module. To test the function, consult Exercises 3-2 and 3-3 above.
Functions with an optional argument
A function can have one or more optional arguments. If an optional argument is not provided when a function is called, the VBA code defining the function uses a default value. The default value is either implicitly stated in the VBA code for the function or explicitly stated in the argument list. Optional argument(s) can only be placed after any required argument(s). The following function shExist takes two arguments: one required and one optional. It returns True if a sheet exists in a workbook. A sheet can be a worksheet, chart sheet, macro sheet, or dialog sheet. The default value for the optional argument is ActiveWorkbook, which is implicitly stated in the VBA code for the function. Function shExist(shName As String, _ Optional wb As Workbook) As Boolean Dim Dummy As String If wb Is Nothing Then Set wb = ActiveWorkbook On Error Resume Next Dummy = wb.Sheets(shName).Name On Error GoTo 0 If Dummy "" Then shExist = True Else shExist = False End If End Function This function can be called by a procedure in any module. To use the function as a worksheet function, leave the second argument blank. To test the function, consult Exercises 3-2 and 3-3 above. The following is another example of a function with one optional argument. The CountRepeat function returns the number of occurrences of a substring within a search string and provides an option to a user whether the search is case-sensitive or case-insensitive. The default value for the optional argument is vbTextCompare, which is explicitly stated in the argument list of the function. Function CountRepeat(MyString As String, _ MySubStr As String, Optional _ Compare As VbCompareMethod _ = vbTextCompare) As Long If Len(MySubStr) > 0 Then _ CountRepeat = _ (Len(MyString) - Len(Replace( _ MyString, MySubStr, "", , , Compare))) _
/ Len(MySubStr) End Function This function can be used as a worksheet function and be called by a procedure in any module. To test the function, consult Exercises 3-4 and 3-5 below.
Exercise 3-4: Executing a custom function from the Immediate window After storing the CountRepeat function in a standard VBA module, execute the following steps to call the function from the Immediate window: 1. 2.
In VBE, press Crtl+G to display the Immediate window. In the Immediate window, type the following fragment of a statement: ? CountRepeat( The information about the arguments for the function is automatically displayed. If VBE does not display the information, enable the Auto Quick Info option in the Editor tab of the VBE Options dialog box. (Choose Tools | Options from the menu bar to display the Options dialog box.)
3.
Type the first two arguments, for example: ? CountRepeat("abc Bb","b", A list of choices (vbBinaryCompare, vbDatabaseCompare, and vbTextCompare) for the third argument is automatically displayed. If VBE does not display the list, enable the Auto List Members option in the Editor tab of the VBE Options dialog box.
4.
Use the up- and down-arrow keys and the Tab key to select an item in the list. For example, the following statement returns 2: ? CountRepeat("abc Bb","b",vbBinaryCompare)
Exercise 3-5: Using a custom function (with a built-in constant) as a worksheet function After storing the CountRepeat function in a standard VBA module, execute the following steps to use the function in a worksheet formula: 1.
Enter the following formula into any cell: =CountRepeat("abc Bb","b") It returns 3 correctly since the search of "b" in "abc Bb" is case-insensitive.
2.
Enter the following formula into any cell: =CountRepeat("abc Bb","b",vbBinaryCompare)
It returns an error because Excel does not recognize the VBA built-in constant: vbBinaryCompare. 3.
Use Object Browser to check the value of the built-in constant, or simply type the following statement in the Immediate window to find out its value. ? vbBinaryCompare It returns 0.
4.
Enter the following formula into any cell: =CountRepeat("abc Bb","b",0) It then returns 2 correctly since the search of "b" in "abc Bb" is case-sensitive.
5.
If cell A1 contains the text "abc Bb", and cell A2 contains the text "b", you may test the function by entering the following formula into any other cells: =CountRepeat(A1,A2,0) It returns 2 correctly.
When a custom function has several arguments and it is used as a worksheet function, to remember what to enter for each of these arguments is not easy. You may then press Ctrl+Shift+A after typing an equal sign and the function's name into a cell (for example, =CountRepeat) to display the names of the arguments.
A function with two optional arguments The following shows a fictitious function with two optional arguments. Even though it is fictitious, but at least it shows how an argument list of a function with any number of optional arguments looks like. Function AddAllDemo(arg1 As Long, _ Optional arg2 As Long, _ Optional arg3 As Long = 10) As Long AddAllDemo = arg1 + arg2 + arg3 End Function Arguments in a procedure can be passed in several different ways. Table below shows a few ways in passing the arguments of the AddAll function when it is called from the Immediate window (or equivalently from a VBA module) and when it is used as a worksheet function. The default values for the first and second optional arguments are implicitly zero and explicitly ten, respectively. Way of passing arguments In the Immediate window, enter the
In a worksheet, enter the following
Return
following statement …
formula into a cell …
value
All 3 arguments supplied ? AddAllDemo(5,6,7) =AddAllDemo(5,6,7) 18 First two arguments supplied ? AddAllDemo(5,6)
=AddAllDemo(5,6)
21
Second argument omitted ? AddAllDemo(5,,7)
=AddAllDemo(5,,7)
Using named arguments ? AddAllDemo(arg1:=5, Not applicable arg3:=7)
12
12
A function with an array argument A function can take not only single values, but also an array of values and arrays of values as its arguments. Arrays are always passed by reference (ByRef). They cannot be passed by value (ByVal). Consider a function that takes only an array of values as its argument. In declaring the function, there is no way to state the dimension of the array argument, for example: Function MyArrayFunction(Arry() As Double) ... End Function However, you can restrict the dimension of the array when working with the VBA code in the function. The table below shows how to restrict the dimension of an array argument to one and two, and how to allow the array argument to accept an array of any dimensions. A function with a one-dimensional array argument Function SumAllArray1D(Arry() As Double) As Double Dim i As Long SumAllArray1D = 0 For i = LBound(Arry) To UBound(Arry) SumAllArray1D = SumAllArray1D + Arry(i) Next i End Function A function with a two-dimensional array argument Function SumAllArray2D(Arry() As Double) As Double Dim i As Long, j As Long
SumAllArray2D = 0 For i = LBound(Arry) To UBound(Arry) For j = LBound(Arry, 2) To UBound(Arry, 2) SumAllArray2D = SumAllArray2D + Arry(i, j) Next j Next i End Function A function with an array argument of arbitrary dimensions Function SumAllArrayMD(Arry() As Double) As Double Dim Item As Variant For Each Item In Arry SumAllArrayMD = SumAllArrayMD + Item Next Item End Function Alternatively, you may define the argument as Variant data type and let VBA decide the most appropriate data type, such as below: Function SumAllVariant(Arry As Variant) As Double Dim Item As Variant For Each Item In Arry SumAllVariant = SumAllVariant + Item Next Item End Function The SumAllVariant function can also take a range of cells as its array argument.
A function with an arbitrary number of arguments A function can literally take an infinite number of arguments, as shown in the following fictitious function: Function ParamArrayFunction(arg1 As Long, ByVal arg2 As Long, ParamArray ArgList() As Variant) As Long ... End Function The first two arguments in the procedure's argument list are required arguments: one is passed by reference, and the other is passed by value.
The third argument with the ParamArray keyword is the one that enables the function to take literally an array of an infinite number of arguments. It must be defined as an array of variants and always be the last argument in the procedure's argument list. It cannot be used with the ByVal, ByRef, and Optional keywords. The following SimpleSum1 function literally can take an infinite list of numbers in its argument list. Function SimpleSum1(ParamArray ArgList()) As Double Dim i As Long For i = 0 To UBound(ArgList) SimpleSum1 = SimpleSum1 + ArgList(i) Next End Function Note: LBound of ArgList() is always 0, regardless of the Option Base statement. Alternatively, SimpleSum1 can be rewritten as below: Function SimpleSum2(ParamArray ArgList()) As Double Dim Item For Each Item In ArgList SimpleSum2 = SimpleSum2 + item Next End Function An argument that passed to the SimpleSum1 and SimpleSum2 functions can be a number, a number in a cell, or a number returned by a function.
Exercise 3-6: Using SimpleSum1 and SimpleSum2 as worksheet functions After storing the SimpleSum1 and SimpleSum2 functions in a standard VBA module, execute the following steps to use the functions as worksheet functions: 1.
Enter the following formulas separately into any two cells: =SimpleSum1(1,2,3) =SimpleSum2(1,2,3) Both return 6.
2.
Enter the following formulas separately into any two cells: =SimpleSum1(1,2,3,SQRT(4)) =SimpleSum2(1,2,3,SQRT(4)) Both return 8.
3.
Enter a number into cell A5 and enter the following formulas separately into any other two cells: =SimpleSum1(1,2,3,SQRT(4),A5) =SimpleSum2(1,2,3,SQRT(4),A5) Both return the sum of 1, 2, 3, 2, and the numeric value in cell A5.
SimpleSum1 and SimpleSum2 consider only the case if the ParamArray argument is an array of numbers. If to write a more versatile function that can accept not only an array of numbers, but also arrays of numbers, such as ranges of cells, you need to tackle the two cases in the VBA code for the function. Note: A ParamArray argument can literally take an array of an infinite number of arguments. An argument in the array can be a number, or a range of cells (which is an array of cells containing numbers) – among many other possibilities of arguments. A range of cells (such B1:B3) is just one of the arguments, not a list of arguments. A list of cells (separated by commas – for example, B1, B2, B3) is a list of arguments. Ranges of cells (separated by commas – for example, B1:B3, B5:D8, C3:D5) are a list of arguments. The following SimpleSum3 function considers the cases that the arguments in the array of an infinite number of arguments are numbers and ranges of cells. Function SimpleSum3(ParamArray ArgList()) As Double Dim item, cel As Range For Each item In ArgList Select Case TypeName(item) Case "Range" For Each cel In item SimpleSum3 = SimpleSum3 + cel Next cel Case Else SimpleSum3 = SimpleSum3 + item End Select Next item End Function The TypeName function is used to determine the data type of the argument in the argument list of the SimpleSum3 function.
Exercise 3-7: Using SimpleSum3 as a worksheet function After storing the SimpleSum3 function in a standard VBA module, execute the following steps to use
the function in a worksheet formula: 1.
Enter a few numbers separately into a few ranges of cells, for example the ranges B1:B3 and B5:B7.
2.
In any other cell, enter the following formula: =SimpleSum3(3,SQRT(4),B1:B3,B5:D8) It returns the sum of 3, 2, and the numeric values in the ranges B1:B3 and B5:D8.
A function that returns an array of values So far, the sample functions discussed above return only single values. A function can possibly return an array of values. The following MySplit function accepts a string of characters as its argument and returns a zerobased, one-dimensional array containing each character in the string. Function MySplit(MyString As String) As String() Dim tmp As String tmp = StrConv(MyString, vbUnicode) tmp = Left(tmp, Len(tmp) - 1) MySplit = Split(tmp, Chr(0)) End Function
Exercise 3-8: Executing the MySplit function from the Immediate window After storing the MySplit function in a standard VBA module, enter the following statement in the Immediate window to test the function: ? Join(mysplit("Hello World!"),"|") It returns H|e|l|l|o| |W|o|r|l|d|!
Exercise 3-9: Using MySplit as a worksheet function Since the MySplit function returns an array of string, to use the function in a worksheet formula, you need to enter the formula as an array formula. After storing the function in a standard VBA module, execute the following steps to test the function:
1. 2.
Select a range of cells, for example the range A21:L21. Type the following array formula into the formula bar and press Ctrl+Shift+Enter (not just Enter): =MySplit("Hello World!") It returns individual characters of the string "Hello World!" into the range A21:L21.
3.
To return individual characters into a vertical range, select a vertical range of cells, for example the range M21:M32, and enter the following array formula into the formula bar by pressing Ctrl+Shift+Enter (not just Enter): =TRANSPOSE(MySplit("Hello World!"))
If you need to edit an array formula, execute the following steps: 1.
Select any cell in the array formula range.
2.
Press F2 or click the formula bar and make changes to the formula.
3.
Press Ctrl+Shift+Enter to accept the changes.
To delete an array formula, select any cell in the array formula range, press Ctrl+/ to select all the cells in the range and press Delete.
Exercise 3-10: Another way of using MySplit as a worksheet function You can also enter an array formula into a range of cells by using VBA code. This can be done by the FormulaArray property of a Range object. For example, after storing the MySplit function in a standard VBA module, execute the following steps: 1. 2.
Enter the following statement in the Immediate window Range("A22:L22").FormulaArray = "=MySplit(""Hello World!"")" Press Alt+F11 to activate Excel and see the result.
The next function ReturnArryDemo1 returns a 3-by-4 two-dimensional array: Function ReturnArryDemo1() As Long() Dim L() As Long Dim R As Long, C As Long ReDim L(1 To 3, 1 To 4) For R = 1 To 3
For C = 1 To 4 L(R, C) = R + C Next C Next R ReturnArryDemo1 = L End Function
Exercise 3-11: Returning a two-dimensional array into a range of cells After storing the ReturnArryDemo1 function (which returns a two-dimensional array of numbers) in a standard VBA module, execute the following steps to use the function in a worksheet formula: 1. 2.
Select a range of 3-by-4 cells, for example the range A25:D27. Type the following array formula into the formula bar and press Ctrl+Shift+Enter (not just Enter): =ReturnArryDemo1() The function returns an array of numbers into the selected cells.
3. 4.
Select another range that is larger than 3-by-4 cells, for example the range E25:I29. Type the following array formula into the formula bar and press Ctrl+Shift+Enter (not just Enter): =ReturnArryDemo1() The function not only returns numbers into the selected cells, but also fills out the unused cells with #N/A errors.
You can then modify ReturnArryDemo1 to fill out the unused cells either with empty characters or numbers. The following ReturnArryDemo2 function discusses the latter: to fill out with numbers. Function ReturnArryDemo2() As Long() Dim L() As Long Dim R As Long, C As Long Dim nR As Long, nC As Long 'Determine the size of a selected range With nR = .Rows.Count nC = .Columns.Count End With ReDim L(1 To nR, 1 To nC)
For R = 1 To nR For C = 1 To nC L(R, C) = R + C Next C Next R ReturnArryDemo2 = L End Function The VBA code in the ReturnArryDemo2 function first determines the size of a selected range. Then, the dimensions of the array (that to be returned) is resized according to the size of the selected range.
Debugging Debugging is a process of finding and fixing errors in program code. The following is a typical way of using the debugger to step through the code and identify errors: 1. 2.
Add breakpoints at particular lines of code, where you suspect errors, by clicking at the grey left margin of the code window. Press F5 to execute the VBA code. VBE executes the code until the first breakpoint is met, putting the code into break mode. The particular line of code at the breakpoint is not executed yet.
3.
Choose one of the three operations at the breakpoint: Operation
Shortcut Action of the debugger key
Continue
F5
It continues executing until the next breakpoint.
F8
If the current point beak does not have a procedure (a Function or Sub procedure) to be called, it executes the line of code. Otherwise, it goes into the procedure. To step out of a procedure, press Ctrl+Shift+F8.
Shift+F8
If the current point beak has a procedure to be called, it treats the procedure as a single instruction, instead of going into the procedure.
Step Into
Step Over
The operations listed in the table above can alternatively be accessed from the Debug menu on the menu bar. 4.
As you step through the code to identify possible errors, you can hover the mouse pointer over any variable to examine its current value. If VBE does not display the value, enable the Auto Data Tips option in the Editor tab of the VBE Options dialog box. (Choose Tools | Options from the menu bar to display the Options dialog box.)
Alternatively, you may use the Immediate window to check the current value of a variable when the code is in break mode. For example, enter the following statement in the Immediate window: ? myVariable In addition, you may use the Locals window and the Watch window (which are the topics to be discussed next) to monitor the values of variables when debugging your VBA code.
Locals window The Locals window allows you to observe the current values of all variables in a procedure that is currently being executed. In VBE, choose View | Locals Window from the menu bar to display the Locals window. Only when the code is in break mode, the current values of all variables are displayed. Otherwise, the Locals window is empty.
Watch window The Watch window allows you to put your VBA code into break mode when certain condition is met. The condition is based on the value of an expression or a variable that you set in the Watch window. To add a variable as a watch to the Watch window, execute the following steps: 1. 2.
Right-click any occurrence of the variable, for example a variable named Sum, in the code window. Choose Add Watch from the shortcut menu to display the Add Watch dialog box. The Expression box automatically inserts the name of the variable: Sum, in this case.
3.
4.
In the Watch Type group, select one of the three watch types: ·
Watch Expression: The Watch window simply watches the expression without stopping code execution. The value of the expression is displayed in the Watch window only when VBE is in break mode.
·
Break When Value Is True: Your VBA code is put into break mode when the value of the watched expression is True. If necessary, you may then change the watched expression and its watch type.
·
Break When Value Changes: Your VBA code is put into break mode when the value of the watched expression changes. If necessary, you may then change the watched expression and its watch type.
Click OK to accept other default settings.
Only when VBE is in break mode, the current values of all watched expressions are displayed. Otherwise, the values are out of context. To edit (or delete) a watch, right-click the watch in the Watch window and choose Edit Watch (or Delete Watch). If the Watch window is not visible, choose View | Watch Window from the menu bar
to display the window.
Call stack The call stack lists all procedures that are currently running, with the most current procedure at the top of the list. When VBE is in break mode, choose View | Call Stack from the menu bar or press Ctrl+L to display the Call Stack dialog box. The dialog box can only be displayed when there is at least one procedure in break mode.
A debugging example from scratch 1.
Start a blank workbook.
2.
Press Alt+F11 to activate VBE.
3.
In the Project Explorer window, right-click the project’s name (by default it is named VBAProject) and choose Insert | Module from the shortcut menu to insert a standard VBA module.
4.
In the Project Explorer window, double-click the newly created module’s name (by default, it is named Module1) to activate its code window.
5.
Enter the following code into the code window: Option Explicit Sub DebugDemo() Dim k As Long For k = 1 To 3 Call Sub1a Next k End Sub Sub Sub1a() Dim i As Long, j As Long, Sum As Long For i = 1 To 2 For j = 1 To 4 Sum = Sum + i * j Debug.Print Sum
Debug.Assert Sum 10 Next j Next i End Sub 6.
Press Ctrl+G to display the Immediate window.
7.
Choose View | Locals Window from the menu bar to display the Locals window.
8.
Execute the following steps to add two watches: k changes and Sum = 3 is True. a)
Right-click any occurrence of the k variable in the code window.
b) Choose Add Watch from the shortcut menu to display the Add Watch dialog box. c)
Type k into the Expression box.
d) In the Watch Type group, select the option Break When Value Changes. e)
Click OK to accept other default settings.
f)
Right-click any occurrence of the Sum variable in the code window.
g) Choose Add Watch from the shortcut menu to display the Add Watch dialog box. h) Type Sum = 3 into the Expression box.
9.
i)
In the Watch Type group, select the option Break When Value Is True.
j)
Click OK to accept other default settings.
Resize the Immediate, Locals, Watch, and code windows, if needed, so that you can see all of them.
10. Click anywhere within the DebugDemo procedure to activate it and press F5 to run it. The VBA code is put into its first break mode when k changes from 0 to 1. Observe the changes in the Locals and Watch windows. 11. Press F5 to continue the code execution. The code is then in its second break mode when the expression Sum = 3 is True. Observe the changes in the Locals and Watch windows. The Debug.Print Sum statement of displaying the value of Sum (in this case, 3) in Immediate window has not been executed yet. Hence, it is not shown in the Immediate window. 12. Press F5 to continue the code execution. VBE enters into break mode again when the condition (Sum 10) in the Debug.Assert statement is False. Observe the value of Sum in the Locals window, which is 10. An alternative to the Debug.Assert Sum 10 statement is to add the Sum variable as a watch to the Watch window: with the expression in the Expression box entered as Sum = 10 and with its watch type selected Break When Value Is True.
13. Press Ctrl+L to the display the Call Stack dialog box. Choose one of the two procedures in the list and click the Show button to jump to the point of execution in the chosen procedure. 14. Press F5 a few more times to continue the code execution while observing the changes in the Locals, Watch, and Immediate windows until the code execution ends. You can see that the VBA code is put into break mode whenever the k variable changes, the expression Sum = 3 is True, or the value of the variable Sum is 10. In a nutshell, you have seen four debugging tools to put VBE into break mode: ·
Stepping through the VBA code by using the debugger
·
Setting breakpoints in the VBA code
·
Adding watches to the Watch window
· Inserting the Debug.Assert statements in the VBA code When VBE is in break mode, the current values of variables can then be examined.
The MsgBox function The MsgBox function can be used to monitor the value of a specific variable while your VBA code is executing, by displaying the value on a message box. The execution is paused until the message box is dismissed. You place the functions at strategic locations in the code to check whether the variable is holding expected values at those locations. For example, you are expecting a variable named Sum to have at least reached a value of 10 during the code execution. Hence, you place the following statement at a strategic location in the code to make sure it really reaches that value: If Sum = 10 then MsgBox "Sum is " & Sum & "." The statement above simply displays a message box with an OK button when the value of the variable is, expectedly, 10. If no message box is displayed throughout the execution, then something and somewhere in the code must be wrong. To debug further, you may possibly place more MsgBox functions at other locations, or use the four debugging tools (that were discussed earlier) to find and fix the error in the code. Other than as a debugging tool, the MsgBox function is also useful to get a decisive response from a user. For example, if to let a user decide whether to continue the code execution, you may display Yes and No buttons on the message box, and subsequently handle the decision made by the user. The
following block of statements shows a fictitious example, where Sum is a variable: If Sum = 10 Then _ If MsgBox("Sum has reached a value of " _ & Sum & "." & Chr(10) & _ Chr(10) & "Yes to continue." & _ Chr(10) & "No to stop.", vbYesNo) = vbNo _ Then Exit Sub End If The MsgBox function can also be used to display meaningful information to users, especially when a runtime error occurs, which is a topic to be discussed next: Error handling.
Error handling Without error handlers, any runtime error that occurs is fatal; that is, an error message is displayed and execution stops abruptly. Rather than letting users helplessly facing error messages, it is a good practice to trap errors and take appropriate actions, such as displaying a more helpful message, in your error-handling code. You can enable an error handler by using either the On Error Resume Next statement or the On Error GoTo line statement. An error handler is enabled whenever the On Error statement of the error handler is executed. To disable error handling and let VBA handle errors, the On Error GoTo 0 statement is executed. Table below briefly explains the role of these On Error statements: On Error statement On Error Resume Next
On Error GoTo line
On Error GoTo 0
Description To ignore any runtime errors that occur and to continue with the next statement. However, you might then use the Err object to determine the next cause of action. If a runtime error occurs in a procedure, execution jumps to the line of code labeled line in the procedure. The section of code marked b y line is called an error-handling routine. The routine must be in the same procedure as the On Error statement. Disables any error handling and let VBA handle errors.
To allow error handlers in your VBA code to work, make sure that you have enabled the Break on Unhandled Errors option in the General tab of the VBE Options dialog box. (Choose Tools | Options from the menu bar to display the Options dialog box.) To understand the discussion on error handlers later in this section, execute the following steps: 1.
Enter the Sub procedures listed in this section into a VBA module.
2.
Press F8 to step through the code in those procedures.
3.
Observe the output in the Immediate window and follow the flow of control, especially when runtime errors occur.
A sample procedure with no error handler The following Sub procedure shows that without error handlers in a procedure, any runtime error is fatal.
Sub Sample0() 'Without any error handler Debug.Print 1 / 0 'fatal error Debug.Print "other code" 'not executed End Sub
Sample procedures with an error handler If there is no error handler in a called procedure, when a runtime error occurs, control is passed to the error handler in the calling procedure. The following calling procedures Sample1a and Sample1b (with the error handlers On Error Resume Next and On Error GoTo err1, respectively) and the called procedure NoErrHandlerSub (without any error handler) show that when the runtime error division-by-zero occurs in the called procedure, control is immediately passed to the calling procedures. Sub Sample1a() 'Illustrating what happens when an error 'occurs in a called procedure with 'no error handler On Error Resume Next Debug.Print 1 / 0 Call NoErrHandlerSub Debug.Print 1 'executed On Error GoTo 0 'optional End Sub Sub Sample1b() 'Illustrating what happens when an error 'occurs in a called procedure with 'no error handler On Error GoTo err1 Call NoErrHandlerSub Debug.Print "other code" 'not executed Exit Sub err1: Debug.Print "1 in err1" 'executed End Sub
Private Sub NoErrHandlerSub() Debug.Print 1 / 0 Debug.Print "other code" 'not executed End Sub A general practice is to end an On Error Resume Next statement with an On Error GoTo 0 statement. Even without the On Error GoTo 0 statement, all error handlers are automatically disabled when a procedure is exited.
Sample procedures with two error handlers If there is more than one error handler in a procedure, only the last enabled error handler is activated when an error occurs. An active error handler is the one currently in the process of handling a particular error.
On-Error-Resume-Next and On-Error-GoTo-line statements In the following Sample2a procedure, at first On Error Resume Next is enabled, but later it is disabled after On Error GoTo 0 is executed. On Error GoTo err1 is then the only enabled error handler and to be activated when the division-by-zero error occurs. Sub Sample2a() 'The last enabled error handler 'is activated when an error occurs On Error Resume Next Debug.Print 1 / 0 Debug.Print 1 'executed On Error GoTo 0 On Error GoTo err1 Debug.Print 1 / 0 Debug.Print "Other code" 'not executed Exit Sub err1: Debug.Print "1 in err1" 'executed End Sub In the following Sample2bFail procedure, On Error GoTo err1 does not properly trap the fatal error
division-by-zero because both error handlers have been disabled after the execution of On Error GoTo 0. To allow On Error GoTo err1 to properly trap the fatal error, simply restate On Error GoTo err1 after On Error GoTo 0, as shown in Sample2bOK. Sub Sample2bFail() 'On Error Goto O disables all enabled 'error handlers in a procedure On Error GoTo err1 '… Other code (without errors) goes here … On Error Resume Next Debug.Print 1 / 0 Debug.Print 1 'executed On Error GoTo 0 Debug.Print 1 / 0 'fatal error Exit Sub err1: Debug.Print "1 in err1" 'not executed End Sub
Sub Sample2bOK() 'Overcoming by re-enabling the error handler On Error GoTo err1 '… Other code (without errors) goes here … On Error Resume Next Debug.Print 1 / 0 Debug.Print 1 'executed On Error GoTo 0 On Error GoTo err1 'restate Debug.Print 1 / 0 Exit Sub err1: Debug.Print "1 in err1" 'executed End Sub
Two On-Error-GoTo-line statements The following Sample2c procedure shows that the last enabled error handler, On Error GoTo err2, is properly activated when the division-by-zero error occurs. Sub Sample2c() 'The last enabled error handler 'is activated when an error occurs On Error GoTo err1 '… Other code (without errors) goes here … On Error GoTo err2 Debug.Print 1 / 0 Debug.Print "Other code" 'not executed Exit Sub err1: Debug.Print "1 in err1" 'not executed Exit Sub err2: Debug.Print "1 in err2" 'executed End Sub
Handling errors in error-handling routines A fatal error in an error-handling routine When an error handler of On Error GoTo line in a particular procedure is activated (that is, in the process of handling an error in its error-handling routine), any On Error statements of error handlers encountered in the routine are executed and hence enabled, but they cannot be activated even when another error occurs. In other words, when an On-Error-GoTo-line error handler is active, any runtime error in the error-handling routine is fatal. See the Sample3Fail procedure below. Sub Sample3Fail() 'Any error that occurs in an error'handling routine is fatal On Error GoTo err1 Debug.Print 1 / 0 Exit Sub 'not executed
err1: Debug.Print "1 in err1" 'executed On Error GoTo err2 'not working 'On Error Resume Next 'not working Debug.Print 1 / 0 'fatal error Exit Sub 'not executed err2: Debug.Print "1 in err2" 'not executed End Sub
Overcoming fatal errors with the Resume statement An active error handler in a procedure can be deactivated (but it still remains enabled) by a Resume statement. This statement can only be used in an error-handling routine and an error must have been occurred. Otherwise, an execution of the statement generates a runtime error with an error message of "Resume without error". When a Resume statement is executed, VBE resumes execution at certain line of code in the procedure, at which it depends on one of the following forms of the Resume statement. Statement
Description
Resume
Execution resumes at the same line of statement that caused the error.
Resume Next
Execution resumes with the statement immediately following the statement that caused the error.
Resume line
Execution resumes at the line of code labeled line in the procedure.
When a Resume statement is executed, the current error handler is deactivated (but not disabled). If a runtime error then occurs, the last enabled handler will be activated. In the Sample4aInfiniteLoop procedure below, when the division-by-zero error occurs, execution jumps to the line of code labeled err1. When the Resume statement is executed, it resumes execution at the division-by-zero statement. Hence, the division-by-zero error occurs again, On Error Goto err1 is activated again, and execution jumps to the line labeled err1. Hence, the Sub procedure loops forever. The discussion here is for the understanding of the flow of control when using the Resume statement. Please consult the VBA Help system (by searching the word Resume) for a working example on how the statement can possibly be used to rectify the cause of an expected runtime error. Sub Sample4aInfiniteLoop() 'Resuming execution at the line of code
'that caused an error (without rectifying 'the cause in the error-handling routine) 'forms an infinite loop On Error GoTo err1 Debug.Print 1 / 0 Debug.Print "other code" 'not executed Exit Sub err1: Debug.Print "1 in err1" 'executed Resume End Sub The following Sample4b procedure replaces Resume with Resume Next to avoid an infinite loop. When the division-by-zero error occurs, execution jumps to err1. When the Resume Next statement is executed, it resumes execution with the statement immediately following the division-by-zero statement. Sub Sample4b() 'Resuming execution with the statement 'immediately following the statement 'that caused the error On Error GoTo err1 Debug.Print 1 / 0 Debug.Print "other code" 'executed Exit Sub 'executed err1: Debug.Print "1 in err1" 'executed Resume Next End Sub The third possible way of using the Resume statement is shown in the following Sample4c and Sample4d procedures. In each of the procedures, it shows that how the active error handler On Error GoTo err1 is deactivated by the Resume statement and control is diverted to a specified line of code to enable a new error handler. The newly enabled error handler is then the last enabled error handler. It is activated when an error occurs. Hence, the fatal error in Sample3Fail can be avoided and handled gracefully. Sub Sample4c() 'Deactivating an active error handler and 'diverting control to a specified 'line to enable a new error handler
On Error GoTo err1 Debug.Print 1 / 0 Exit Sub 'not executed err1: Debug.Print "1 in err1" 'executed Resume Continue Exit Sub 'not executed Continue: On Error GoTo err2 Debug.Print 1 / 0 Exit Sub 'not executed err2: Debug.Print "1 in err2" 'executed End Sub
Sub Sample4d() 'Deactivating an active error handler and 'diverting control to a specified 'line to enable a new error handler On Error GoTo err1 Debug.Print 1 / 0 Exit Sub 'not executed err1: Debug.Print "1 in err1" 'executed Resume Continue Exit Sub 'not executed Continue: On Error Resume Next Debug.Print 1 / 0 Debug.Print "2" 'executed On Error GoTo 0 End Sub
The following Sample4dAlternative procedure is an alternative to Sample4d. It also shows that the last enabled error handler, On Error Resume Next, is properly activated when the division-by-zero error occurs. Try to comment the On Error Resume Next statement and step through the code to see what happens. It forms an infinite loop. Sub Sample4dAlternative() 'Deactivating an active error handler and 'diverting control to a new line 'to enable a new error handler On Error GoTo err1 Debug.Print 1 / 0 Exit Sub 'not executed err1: Debug.Print "1 in err1" 'executed On Error Resume Next Resume Continue Exit Sub 'not executed Continue: Debug.Print 1 / 0 Debug.Print "2" End Sub
'executed
Overcoming fatal errors with a called Sub procedure An alternative to handling errors gracefully in an error-handling routine, without using the Resume statement, is to use a called procedure in the routine because any On Error statement in a calling procedure will become inactive when another procedure is called. The following SampleAsIn4c and SampleAsIn4d procedures show the alternatives to the Sample4c and Sample4d procedures, respectively. Sub SampleAsIn4c() 'An alternative to Sample4c by using 'a called procedure On Error GoTo err1 Debug.Print 1 / 0 Exit Sub 'not executed err1: Debug.Print "1 in err1" 'executed Call GotoErr2Sub
End Sub Private Sub GotoErr2Sub() On Error GoTo err2 Debug.Print 1 / 0 Exit Sub 'not executed err2: Debug.Print "1 in Err2" 'executed End Sub
Sub SampleAsIn4d() 'An alternative to Sample4d by using 'a called procedure On Error GoTo err1 Debug.Print 1 / 0 Exit Sub 'not executed err1: Debug.Print "1 in err1" 'executed Call ResumeNextSub Debug.Print "2 in err1" 'executed End Sub Private Sub ResumeNextSub() On Error Resume Next Debug.Print 1 / 0 Debug.Print "2" 'executed On Error GoTo 0 End Sub
Chapter 4: Some other tips on VBA programming Other than the tools discussed in Chapter 1 and some tips discussed along the way in previous chapters, below are some other tips on VBA programming for beginners.
Writing more efficient VBA code Working with arrays instead of a range of cells VBA and Excel are two different entities. VBA code that repeatedly switches between the two will greatly increase the execution time. A better approach is to copy the entire range once into an array, to work with that array, and to write back the result to the worksheet. This will greatly reduce the number of times that the VBA code needs to switch between these two entities. Let's compare the execution times between these two approaches. Suppose you want to count the number of numbers that are divisible by four in a range of 50,000 cells. The code that works with an array is generally faster than the one switches between VBA and Excel. The following table shows the execution times (in seconds) for two different approaches in counting the number of numbers that are divisible by four in a range of 50,000 cells: Working with a range of Working cells array 0.20
with
an
0.031
The listing below is an example of VBA code that works with a range of cells. Sub WorkingWithRange() 'To count the number of numbers that are 'divisible by 4 in a range of 50,000 cells. Dim r As Long, c As Long Dim rN As Long, cN As Long Dim cnt As Long Dim StartTimer As Date, EndTimer As Date StartTimer = Timer rN = 500: cN = 100: cnt = 0 For r = 1 To rN For c = 1 To cN If Sheets(1).Cells(r, c) Mod 4 = 0 _ Then cnt = cnt + 1 Next c Next r EndTimer = Timer Debug.Print cnt Debug.Print EndTimer - StartTimer End Sub
The next listing is an example of much faster VBA code that works with an array. Sub WorkingWithAnArray() 'To count the number of numbers that are 'divisible by 4 in a range of 50,000 cells. Dim x() As Variant Dim r As Long, c As Long Dim rN As Long, cN As Long Dim cnt As Long Dim StartTimer As Date, EndTimer As Date StartTimer = Timer rN = 500: cN = 100: cnt = 0 ReDim x(rN, cN) Sheets(1).Activate x = Range("A1").Resize(rN, cN) For r = 1 To rN For c = 1 To cN If x(r, c) Mod 4 = 0 Then _ cnt = cnt + 1 Next c Next r EndTimer = Timer Debug.Print cnt Debug.Print EndTimer - StartTimer End Sub
Disabling screen updating, alert displays, events, and automatic calculations If they are not necessary during the execution of your VBA code, disable them before the execution and restore them to their initial settings right before the execution ends. Restoring the settings rather than turning them all on is a good practice since some users may have different settings, as shown in the following listing. 'Declaration Dim ScrnU As Boolean, DispA As Boolean, _ Evnt As Boolean, Calc As Long
'Save the settings With Application ScrnU = .ScreenUpdating DispA = .DisplayAlerts Evnt = .EnableEvents Calc = .Calculation End With 'Disable them before the execution begins With Application .ScreenUpdating = False .DisplayAlerts = False .EnableEvents = False .Calculation = xlCalculationManual End With
'Here runs your code
'Restore the initial settings 'before the execution ends With Application .ScreenUpdating = ScrnU .DisplayAlerts = DispA .EnableEvents = Evnt .Calculation = Calc End With
Reducing the size of a working range Reduce the size of a working range whenever possible. Let's take a fictitious situation to discuss this. Suppose that you have written the following SumSelectedRange1 procedure to sum up all numbers in a selected range of cells. Sub SumSelectedRange1() 'Sum numbers in a selected range Dim cel As Range, sum As Double
If TypeName(Selection) "Range" Then MsgBox "Please select a range." Exit Sub End If For Each cel In Selection If VBA.IsNumeric(cel) Then _ sum = sum + cel Next cel MsgBox "The sum of all numbers in " & _ "the selected range is " & Sum End Sub If an entire sheet of cells is selected by a user, the above procedure will take a very long time to complete its execution because it is actually processing 17,179,869,184 cells. The above procedure can be improved by considering only used range of cells in the worksheet, as shown in the following listing. Sub SumSelectedRange2() 'Sum numbers in a selected range 'In case a user selected an entire row, 'column, or sheet Dim cel As Range, sum As Double Dim WorkRng As Range If TypeName(Selection) "Range" Then MsgBox "Please select a range." Exit Sub End If Set WorkRng = Intersect(Selection, _ ActiveSheet.UsedRange) For Each cel In WorkRng If VBA.IsNumeric(cel) Then _ sum = sum + cel Next cel MsgBox "The sum of all numbers in " & _ "the selected range is " & Sum End Sub
The ActiveSheet.UsedRange property returns the range of cells that are used in the active worksheet. Hence, the SumSelectedRange2 procedure has greatly reduced the number of cells to be processed. However, the UsedRange property of the Worksheet object does not actually determine the cells that have really been used. To understand what I meant, execute the following steps: 1.
Start a blank workbook.
2.
Enter something (a number or text) into cell A1 in a worksheet.
3.
Press Ctrl+↓ (the down-arrow key) to reach the last cell in column A, that is, cell A1048576.
4.
Press Ctrl+→ (the right-arrow key) to reach the rightmost bottom cell in the worksheet, that is, cell XFD1048576.
5.
Enter something (a number or text) into this cell XFD1048576.
6.
Press Alt+F11 to activate VBE.
7.
In the Immediate window, enter the following statement: ? ActiveSheet.UsedRange.Address It returns $1:$1048576, that is, the address of the entire cells in the worksheet.
If the SumSelectedRange2 procedure is executed for a worksheet where cell A1 and cell XFD1048576 are used, then there is no difference in term of execution between SumSelectedRange1 and SumSelectedRange2 (if the entire worksheet selected by a user before execution). Nevertheless, in very rare cases, only cell A1 (or any cells near cell A1) and cell XFD1048576 (or any cells near the last column XFD and the last row 1048576) are used, but in between these cells mostly unused. Another way to improve the execution time of SumSelectedRange1 is to deal with only those cells with numbers. VBA provides a quick way to identify those cells: the SpecialCells method, as shown in the SumSelectedRange3 procedure below. Sub SumSelectedRange3() 'Sum numbers in a selected range 'Only working with numbers Dim cel As Range, sum As Double Dim ConstantNumbers As Range, _ FormulaNumbers As Range If TypeName(Selection) "Range" Then MsgBox "Please select a range." Exit Sub End If On Error Resume Next 'if no cells were found Set ConstantNumbers = Selection. _
SpecialCells(xlConstants, 1) Set FormulaNumbers = Selection. _ SpecialCells(xlFormulas, 1) On Error GoTo 0 'Sum constant numbers If Not ConstantNumbers Is Nothing Then For Each cel In ConstantNumbers sum = sum + cel Next cel End If 'Sum numbers in formula cells If Not FormulaNumbers Is Nothing Then For Each cel In FormulaNumbers sum = sum + cel Next cel End If MsgBox "The sum of all numbers in " & _ "the selected range is " & Sum End Sub
Commenting out a block of VBA code 1.
In VBE, choose View | Toolbars | Edit to display the Edit toolbar. See Figure 1-1.
2.
Select the lines of code that you want to comment out.
3.
Hover the mouse pointer over the buttons on the Edit toolbar to identify the Comment Block button and click on it.
To uncomment lines of comments, use the Uncomment Block button on the Edit toolbar.
Removing all comments in VBA modules in one click 1.
In the code window of any module, choose Edit | Replace or press Ctrl+H to display the Replace dialog box.
2.
In the box labeled Find What, type the following: '*
3.
In the box labeled Replace With, keep it empty.
4.
In the Search frame, select the option whether you want all comments in the current procedure, in the current module, or in the current project to be removed.
5.
Tick only the check box labeled Use Pattern Matching.
6.
Click the Replace All button.
Caution: If you use single apostrophe characters not only for comments, but also in executable lines of code containing strings (for example, a statement with the string "Time's up."), they will be removed too.
Getting a list of VBA built-in functions in the code window Sometimes when you are writing a code statement, you need to type a particular VBA built-in function, but you are not sure about the exact spelling. You may then use the VBA Help system to check the spelling. An alternative is to display a list of VBA functions and choose the right one in the list. To accomplish this task, execute the following steps: 1.
In the code window or the Immediate window, type VBA followed by a dot character ("."): VBA. A list of items is automatically displayed. If VBE does not display the list, enable the Auto List Members option in the Editor tab of the VBE Options dialog box.
2.
Use the up- and down-arrow keys and the Tab key to choose the right item in the list.
Similarly, you can type the name of any object followed by a dot to get a list of members of the object. For example, typing Range. displays a list of members of the Range object.
Dealing with the situation when Auto List Members does not work In certain circumstances, even you have enabled the Auto List Members option in the Editor tab of the VBE Options dialog box, typing the name of an object followed by a dot does not display a list of members of the object. The following code demonstrates one of those circumstances: Worksheets(1). A list of members of the Worksheet object is not displayed after the dot. Note: In the above code, Worksheets is a container (also known as a collection) object. It contains a collection of objects of the same type. A container object is usually the plural form of objects it contains. An object in the container can be accessed either by an index or the name of the object. To display a list of member of an object, define an object variable for that object and then type the object variable followed by a dot. The following code demonstrates the idea for the Worksheet object, and in general it can be any object: Sub WithAutoListDemo() Dim ws as Worksheet ws. End Sub A list of members of the Worksheet object is displayed after the dot.
Calling a private procedure from other module Often you declare a procedure in a VBA module private, so that it can only be called by procedures in that module. However, VBA provides a way to call a private procedure from other module by using the Application.Run method. Suppose the following two private procedures are stored in a standard VBA module. Private Sub DemoSub() Debug.Print "Hello from DemoSub" End Sub Private Function AddBoth( _ x As Long, y As Long) As Long AddBoth = x + y End Function The two procedures can be called from other module with the following statements, respectively: Application.Run "DemoSub" Application.Run("AddBoth", 2, 4) To get immediate results of calling the procedures, you might type the following statements into the Immediate window and press Enter after each: Application.Run "DemoSub" ? Application.Run("AddBoth", 2, 4) 'returns 6 If the Sub procedure is stored in a non-standard VBA module, precede the procedure name with the module name. For example, if the procedure is in the workbook module, enter the following statement to call the procedure: Application.Run "ThisWorkbook.DemoSub" However, if the Function procedure is stored in a non-standard VBA module, preceding the procedure name with the module name does not return a value, as shown in the following example of statement in the Immediate window: ? Application.Run("ThisWorkbook.AddBoth", 2, 4)
Hiding public Sub procedures from the Marco dialog box A public Sub procedure in a VBA module can be called by procedures in other module. If you don’t want the procedure to be shown in the Macro dialog box while keeping it to be remained accessible by procedures in other module, you can add a dummy optional argument to the procedure. You might want to walk through the following example to get what I meant by executing following steps: 1.
2.
Enter the following public procedure into a new or an existing standard or non-standard VBA module: Sub Main() Debug.Print "Hello from Main" 'Other code ... End Sub In VBE, choose Tools | Macros to display the Macro dialog box. The Sub procedure named Main is there in the dialog box.
3.
4.
Modify the procedure by adding a dummy optional argument, as shown below: Sub Main(Optional dummy As Byte) Debug.Print "Hello from Main" 'Other code ... End Sub In VBE, choose Tools | Macros to display the Macro dialog box again. The Sub procedure named Main is no longer visible in the dialog box.
5.
In the Immediate window, enter either of the following statements to call the procedure: Call Main Application.Run "Main" If the procedure is stored in a non-standard VBA module, precede the procedure name with the module name. For example, if the procedure is in the workbook module, enter either of the following statements to call the procedure: Call ThisWorkbook.Main Application.Run "ThisWorkbook.Main"
Some good programming practices You may try to google the Internet for some programming practices. Among the practices are: ·
Create several small procedures rather than one single large procedure: ideally one procedure for one specific task. Small procedures are clearer and easier to maintain, modify, and reuse. · Add a comment to describe what a particular block or line of code does. Without comments, you will spend much more time to figure out what it does if you need to revisit the code months later. · Use the Tab key for consistent indentation. It clarifies the structure of your code and makes the code easier to read. · Add blank lines between blocks or lines of code that do different tasks. They make the code easier to read.