In Getting Started with Visual Basic for Applications in Microsoft Excel, you learned to record, run, and update macros to automate routine tasks. However, the VB editor (VBE)—which provides access to all the bells and whistles of the language—was not explored in depth. This guide will explain how to manipulate a data set using features unique to VBA and other programming concepts you are probably familiar with: conditional statements and loops. You will also explore how to invoke and modify the behavior of macros using forms—all inside of Microsoft Excel.
To follow along with this guide, be sure to first enable the developer as described in the guide noted above. If you wish, you can download the spreadsheet used in the examples below by clicking on this link.
To insert a new form, launch the VBE and right-click on the VBAProject, where you will see the current file name. Next, choose Insert and then UserForm:
This will add a new form in the design area and display the toolbox where you can select controls for it. In this example, insert a placeholder for an image, five buttons, and two drop-down lists as shown below. To do this, drag and drop those controls from the toolbox into the user form.
Don't worry about the styling, the form caption, or the other labels at this point. We'll take care of them shortly.
To select one of the controls that you just added in the form, click on it. When you do, you'll find its Properties in the left-hand area. There, modify the following fields in each control:
In UserForm1, change:
The Properties section should look similar to the following image:
Next, update the attributes in the other controls as follows:
Control | Property | New Value |
CommandButton1 | (Name) | btnViewOrdersByCountry |
CommandButton1 | Caption | Choose Country |
CommandButton2 | (Name) | btnViewOrdersByEmployee |
CommandButton2 | Caption | Choose Employee |
CommandButton3 | (Name) | btnViewPendingOrders |
CommandButton3 | Caption | View Pending Orders |
CommandButton4 | (Name) | btnFormatTable |
CommandButton4 | Caption | Format Table |
CommandButton5 | (Name) | btnClearFormats |
CommandButton5 | Caption | Clear Formats |
ComboBox1 | (Name) | cbListCountries |
ComboBox2 | (Name) | cbListEmployees |
Image1 | (Name) | imgLogo |
Image1 | BorderStyle | 0 - frmBorderStyleNone |
Image1 | Picture | Select a logo image of your choosing |
At this point, the form should look as follows:
As you can see, the background colors of the buttons and the checkbox were changed to light green and yellow, respectively. We are now ready to add actions to the controls.
The cbListCountries and cbListEmployees drop-down lists should be populated when the form first loads. Said another way, when the form is initialized a macro will add items (without duplicates) to each combo-box control.
To accomplish this goal, right-click on the form control inside the project explorer and select View Code:
On the right, enter the following code blocks. The first, UserForm_Initialize()
, is private to the form and triggers when it loads. On the other hand, Populate(startCell As String, cbList As comboBox)
is the custom macro that fills the controls when called from the first:
1Private Sub UserForm_Initialize()
2 Populate "G2", cbListCountries
3 Populate "H2", cbListEmployees
4End Sub
5
6Sub Populate(startCell As String, cbList As comboBox)
7 Dim itmCollection As New Collection
8 Dim cell As Range
9 On Error Resume Next
10 With cbList
11 .Clear
12 For Each cell In Range(startCell, Range(startCell).End(xlDown))
13 If Len(cell) <> 0 Then
14 Err.Clear
15 itmCollection.Add cell.value, cell.value
16 If Err.Number = 0 Then .AddItem cell.value
17 End If
18 Next cell
19 End With
20 cbList.ListIndex = 0
21End Sub
Let's examine each of these procedures in more detail:
Sub
blocks, which stands for sub-procedure.UserForm_Initialize()
, the Populate
macro is called twice: once for each combo-box control. The latter takes two arguments that indicate 1) where to start looking for values to populate each list, and 2) which control should be filled in each case. Dim itmCollection As New Collection
declares and creates a collection of items that can be handled as a unit. This type of object is more appropriate than others for what we will be doing later with the items.Range
object can be a single cell, a row, a column, or a bigger block. With Dim cell as Range
, you are defining a placeholder variable called cell
of type Range
.On Error Resume Next
means that if an error occurs during runtime while accessing objects, the macro will not crash. Instead, it will continue with the next statement. You can read more about the On Error
statement in the VBA online reference here.With
block, when a word is preceded by a dot it refers to the object that is being accessed. Thus, .Clear
and .AddItem
are combo-box methods from cbList
. Keep in mind that this variable represents the second argument passed to Populate
.For Each
loop is used to iterate over a Range
object that consists of all the cells between the one specified by startCell
down to the last non-empty location in the same column. Here, Range(startCell).End(xlDown)
produces the same effect as placing the cursor over startCell
manually and pressing Ctrl + Shift + the down arrow. The Next
keyword tells the loop to proceed with the next cell after each iteration.Collection
object is that its items can be accessed either by index (the position in the collection) or key (a unique value). In itmCollection.Add cell.value, cell.value
, the first cell.value
is the actual item that is being added whereas the second represents the key. This approach allows us to check for errors in each step and add an item only if Err.Number
equals 0. This condition is met only when the current item does not exist in the collection already.If
block spans two or more lines, it needs to close with the corresponding End If
statement. Otherwise, we can omit the latter.cbList.ListIndex = 0
indicates that the first item in the collection should be selected by default. Feel free to change this to another value if you wish. However, no item will be selected if the number is larger than the size of the collection.When you run the form by pressing F5 from the editor, you will notice that France and Steve Buchanan appear as the default values of the drop-down lists. Those are precisely the first values in the G and H columns.
The expectation when you click on Choose Country or Choose Employee is to see the list of orders shipped to that place or associated with that person, respectively. As you double click on these controls in the editor, an empty private sub-procedure will be created for you to start typing code.
Since we want to add new functionality (filtering the spreadsheet on a column based on a given value), we will create a macro for that. To do so, insert the following code below Populate
:
1Sub Filter(col As String, argValue As String)
2 With Worksheets(ActiveSheet.Name)
3 .AutoFilterMode = False
4 .Range(col).AutoFilter Field:=1, Criteria1:=argValue
5 End With
6End Sub
Worksheets
is another example of a collection in VBA. Since it contains all the sheets in the current workbook, we use ActiveSheet.Name
to stipulate the macro be applied to whatever sheet is active at a given time. This strategy helps us write more flexible code than if we utilized the name of the sheet, Orders.
If used without arguments, the .AutoFilterMode = False
method will remove any filters that have been applied to the sheet. The following line, .Range(col).AutoFilter Field:=1, Criteria1:=cbList.value
, applies a filter to the column defined by col
. We can pass the arguments as 1, cbList.value
, but doing it as Filter:=1
and Criteria1:=cbList.value
makes clearer what is being done. The VBA online reference has more information about the parameters accepted by .AutoFilter
here.
Field
is the column number inside the selected range—not the entire worksheet. In this case, Field:=1
represents the entire column given by col
.Finally, be sure to call the Filter
macro from the Click
event of each button:
Press F5 to run the report and choose a country or employee to see the associated orders.
In this step, you will use the Click
event of btnViewPendingOrder
to view only those orders that still have not been shipped. Since it is the same filter functionality implemented earlier, we will reuse the Filter
sub-procedure after a slight change.
In Filter
, change the type of the second argument to String
as follows:
1Sub Filter(col As String, argValue As String)
2 With Worksheets(ActiveSheet.Name)
3 .AutoFilterMode = False
4 .Range(col).AutoFilter Field:=1, Criteria1:=argValue
5 End With
6End Sub
In the Click
event of btnViewPendingOrders
and the other previous two buttons, do:
1Private Sub btnViewOrdersByCountry_Click()
2 Filter "G:G", cbListCountries.value
3End Sub
4
5Private Sub btnViewOrdersByEmployee_Click()
6 Filter "H:H", cbListEmployees.value
7End Sub
8
9Private Sub btnViewPendingOrders_Click()
10 Filter "E:E", ""
11End Sub
As you can see, the value by which the table will be filtered will come from the calling event instead of setting it up in Filter
as before. This is another step towards writing more efficient, maintainable code.
The last two buttons will serve the purpose of 1) calling the FormatTable
macro from the previous guide, and 2) removing all the formats from the active sheet. To achieve this, insert the following code blocks inside the corresponding Click
events of each button:
1Private Sub btnFormatTable_Click()
2 Module1.FormatTable
3End Sub
4
5Private Sub btnClearFormats_Click()
6 With Worksheets(ActiveSheet.Name)
7 .AutoFilterMode = False
8 .Cells.ClearFormats
9 .Cells.Borders.LineStyle = xlLineStyleNone
10 End With
11End Sub
In the first Click
event above, the FormatTable
sub-procedure is invoked by preceding it with the name of the module where it resides. In the second, after identifying the active sheet, all formats, borders, and filters are removed.
Once all the form code has been put in place, open the Module1 module and insert the following macro at the top:
1Sub OpenForm()
2 frmHistoricalPurchaseOrders.Show
3End Sub
The sub-procedure above will open the form when it is run. By putting it in Module1, it will become available in the list of macros that we can access from the spreadsheet menus. If you now go to Developer and click Macros, you will notice that OpenForm has been added to the list. Click Run to run the macro and launch the form:
Take a couple of minutes to play around and test the form and its features.
In this guide, we have learned how to go beyond macro recording to leverage the power of Visual Basic for Applications. Also, you have learned how to create forms, add controls to them, and link them to sub-procedures. Although covering the ins and outs of VBA would take dozens of articles, you now have a better idea of the possibilities it offers.