Advanced Excel Notes
Advanced Excel Notes
With Excel
A User’s CookBook
Harish Gopalkrishnan
Copyright © 2016 by Harishkumar Gopalkrishnan
All rights reserved. No part of this work may be reproduced or transmitted in any form or by any means,
electronic or mechanical, including photocopying, recording, or by any information storage or retrieval
system, without the prior written permission of the copyright owner.
Trademarks: Microsoft Excel, Word, PowerPoint and Outlook are registered trademarks of Microsoft
Corporation in the United States and/or other countries. All other trademarks are the property of their
respective owners. The Author is not associated with any product or vendor mentioned in this book.
MySQL is a registered trademark of MySQL AB A Company.
Trademarked names may appear in this book. Rather than use a trademark symbol with every occurrence of
a trademarked name, we use the names only in an editorial fashion and to the benefit of the trademark
owner, with no intention of infringement of the trademark.
LIMIT OF LIABILITY/DISCLAIMER OF WARRANTY: THE AUTHOR MAKES NO
REPRESENTATIONS
OR WARRANTIES WITH RESPECT TO THE ACCURACY OR COMPLETENESS OF THE
CONTENTS OF THIS WORK AND SPECIFICALLY DISCLAIM ALL WARRANTIES, INCLUDING
WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE. NO
WARRANTY MAY BE CREATED OR EXTENDED BY SALES OR PROMOTIONAL MATERIALS.
THE INFORMATION IN THIS BOOK IS DISTRIBUTED ON AN “AS IS” BASIS, WITHOUT
WARRANTY. ALTHOUGH EVERY PRECAUTION HAS BEEN TAKEN IN THE PREPARATION OF
THIS WORK, NEITHER THE AUTHOR(S) NOR THE PUBLISHER SHALL HAVE ANY LIABILITY
TO ANY PERSON OR ENTITY WITH RESPECT TO ANY LOSS OR DAMAGE CAUSED OR
ALLEGED TO BE CAUSED DIRECTLY OR INDIRECTLY BY THE INFORMATION CONTAINED IN
THIS WORK. THE ADVICE AND STRATEGIES CONTAINED HEREIN MAY NOT BE SUITABLE
FOR EVERY SITUATION. IF PROFESSIONAL ASSISTANCE IS REQUIRED IN A SPECIFIC FIELD,
THE SERVICES OF A COMPETENT PROFESSIONAL PERSON SHOULD BE SOUGHT. THE
AUTHOR SHALL NOT BE LIABLE FOR DAMAGES ARISING HEREFROM. THE FACT THAT AN
ORGANIZATION OR WEBSITE IS REFERRED TO IN THIS WORK AS A CITATION AND/OR A
POTENTIAL SOURCE OF FURTHER INFORMATION DOES NOT MEAN THAT THE AUTHOR
ENDORSES THE INFORMATION THE ORGANIZATION OR WEBSITE MAY PROVIDE OR
RECOMMENDATIONS IT MAY MAKE. FURTHER, READERS SHOULD BE AWARE THAT
INTERNET WEBSITES LISTED IN THIS WORK MAY HAVE CHANGED OR DISAPPEARED
BETWEEN WHEN THIS WORK WAS WRITTEN AND WHEN IT IS READ.
Table of contents
CHAPTER 1 GETTING STARTED
1.1 FOR AN ANALYST/MANAGER NOT A PROGRAMMER
1.2 NEVER WRITTEN A MACRO? –PLEASE SEE THE APPENDIX
1.3 FLEXIBILITY, MAINTAINABILITY, USER-FRIENDLINESS
1.4 NO SELECTION, ACTIVEWORKSHEETS OR ACTIVATE …
1.5 USING THIS BOOK – VIEWING CODE EXAMPLES
1.6 WHAT YOU SHOULD ALREADY KNOW
1.7 WHAT IS NOT COVERED HERE
B) Maintainability
Whenever we add a new functionality, we should try and make use of whatever
functionality we have already built into our workbook.
This also means that when we create a component of a solution, like a summary
cell or a Named range (Chapter 10, Dynamic Named Ranges), we should try and
ensure that it is flexible enough to be used by any other feature that would come
up in the near future.
C) User-Friendliness
Common User Understanding
As far as possible we should use features that are commonly used and intuitively
understood, rather than things that may look esoteric but not well understood by
general users. For example:
Writing formulas usingcell addresses written as A1, B2 and so on; which is more
intuitively understood by users of Excel, rather than the R1C1, R2C2 style which
becomes slightly difficult to interpret.
Using commonly used features ensures that when we passon our solution to our
team mates who will use and maintain the spreadsheet, they will easily
understand the working of the solution and will be able to make small tweaks as
required.
Manageable File Size
When people are working on tight schedules, we cannot send an email to our
team mates with a bulky workbook with lots of sheets and formulas and ask
them to patiently download a file (usually, as it happens, just a few minutes
before a meeting) from their email or from a server and wait few more minutes
for it to open.
We also cannot ask users to change one single cell and expect them to wait till
all the big formulas get calculated.
For this reason, it’s always prudent to keep our data and processing logic
(macros and most of our formulas) in one file, compile the reporting sheets and
dashboards (with minimal dependence on the raw data) and send only those
sheets to our team members.
1.4 No Selection, ActiveWorksheets or Activate …
Usually, when we record a macro, the resulting code would contain words like
those listed below:
1. Selection: representing the cells we select when the macro is getting
recorded.
2. Activesheet: The worksheet currently visible on the screen.
3. ActiveWorkbook: The workbook currently visible on the screen.
And quite a few more…..
However, in this book, our aim is to develop methods to work with multiple
workbooks with a single macro transferring data from one workbook to another.
As such, we will not be worried about which workbook is visible on the screen.
Hence we will not be using the objects like ActiveWorkbook or ActiveSheet.
Our macro won’t even be selecting any cells, instead the cells will be directly
controlled from the program irrespective of which cell the user may have
selected before running our macro.
1.5 Using this book – viewing code examples
This book is written in a way that allows the reader jump directly to the section
of interest. However, many of the sections build on concepts in earlier sections.
If you are starting out, it’s better to read chapter 3 first that deals with
Rangeconstruction.
· Code is written in Courier New font and Comments are written in Times New
Roman font.
· However, these may not stand out on all ebook readers.
· Large code blocks are enclosed in a box to make them stand out from
surrounding text
· The comment lines begin with a ( '//** ) and end with a with a ( **// ). This is
done to especially differentiate them from code lines. The standard Visual
Basic comment line just starts with an apostrophe ( ‘ )
· Whenever code continues to the next line the previous line ends with an
underscore ( _ ). This is standard Visual Basic line continuation. While
writing code readers can write the entire code in a single line without ( _ ).
· On some ebook readers a long line of code and comments may overflow to
the next line. In such cases, the reader may have to either decrease the font
size or turn the device in landscape mode to have better readability.
· Same is the case with slightly broad tables. Please change device orientation
to landscape for better readability.
Best effort has been made to keep all the code on a single page but this was not
possible in all cases and many examples have code split across multiple pages.
Please bear with this when reading the code.
1.6 What you should already know
This book is not written for someone completely new to Excel. Readers are
expected to know the basics like feeding data, copying, pasting, basic
formatting, creating charts and pivot tables and inserting simple formulas.
If you are completely new to Excel, you might want to read some of the basic
literature or consult with your friends and co-workers. Alternatively, a large
amount of literature is easily available that would teach the most basic
operations of Excel.
1.7 What is not covered here
We will not cover ways to format data.
Why? Because formatting usually becomes important in the presentation stage.
This is when the summarized results of processing a large amount of data are to
be presented to senior managers or a larger audience.
Note the following points which are usually true in normal course of work:
1. The boundary of the presentation is fixed. This means we always present a
fixed number of cells (usually shown as a table) or a fixed size of chart.
2. The variables to be reported in the chart or group of cell are fixed. Only the
values of the variables need to be worked out based on data processing.
For example, we might have a chart that shows sales numbers of different
products of our company. We made a report last month and we will make a
similar report this month.
The products and regions don’t change. It’s only the values of the sales figures
that get updated. Major change to our report would occur only if a new region or
product gets added.
In line with these observations, we can always manually preformat a group of
cells or a chart before doing any automation. Then, we can create macros or
advanced formulas to update the values in those cells using the methods we will
learn in this book. The related chart or PivotTable will get updated
automatically.
Chapter 2
The Basics
2.1 Important notes on organizing data
For proper functioning of any dashboard or report or macro, the input data has to
be structured in a certain way. That means:
1. Certain columns should be at particular location all the time so that in the
macros we can code in their positions with numeric values.
2. The data in a given column has to be of the same type (all text, or all
numbers or all dates) and should not contain certain characters like extra
spaces, special symbols or error values (#N/A, #Ref, #Value).
3. There should not be any row that is blank in all the columns.
Listed below are some important steps we should take to organize our data
before we start any type of processing. Although these seem to be based on
common sense they are often neglected and lead to malfunctioning of macros
and formulas.
1. Data should always be organized in tabular form (rows and columns).
2. First row of the table should always have column headers.
3. Whenever possible, first row of the table should be the first row of the
worksheet. Avoid empty rows on top that show company logo or
department/author name. These can be included in a separate sheet that
displays summaries.
4. At least one column should have values in all rows. Ideally this should also be
the first column in the table. Remove any row that is completely blank.
5. The top (header) row should not haveblank cells in any column.
6. Whenever possible, the first column of any table should be the first column of
the worksheet. Avoid empty cells in any rowin the leftmost column.
7. Summaries should be created on a separate sheet as far as possible.
2.2 Select, Delete, Insert Entire Row/Column
For all the code in this section, we create a workbook object and set it to
“Book1.xlsx’. If the macro is in the same work book that is being edited, then we
can use the ‘Thisworkbook’ object.Also, ‘r’ and ‘c’ refer to row and column
Numbers, of a given cell.
Example, Cell B5 has r = 5, c = 2
Dim wbk as Workbook
Set wbk = Workbooks(“Book1.xlsx”)
Select Entire Row/Column
Selecting entire row
Keystrokes: VBA:
Select any cell in the row
Shift + Spacebar Wbk.Worksheets(“Sheet1”).Rows(r).Select
Selecting entire column
Keystrokes: VBA:
Select any cell in the
column
Ctrl + Spacebar Wbk.Worksheets(“Sheet1”).Columns(c).Select
We can use the column letter if we want. For example, in our case for column B
we can use the code:
Wbk.Worksheets(“Sheet1”).Columns(“B”).Select
Delete Entire Row/Column (shift neighboring cells)
Deleting entire row (Data below the row, moves up one row)
Keystrokes: VBA:
Select any cell in the row
Shift + Spacebar
Ctrl + ‘-‘ Wbk.Worksheets(“Sheet1”).Rows(r).Delete
Deleting entire column(Data to the right of the column, moves to the left)
Keystrokes: VBA:
Select any cell in the
column
Ctrl + Spacebar
Ctrl + ‘-‘
Wbk.Worksheets(“Sheet1”).Columns(c).Delete
Delete only contents for Row/Columns
Clear entire row (Data below the row, moves up one row)
Keystrokes: VBA:
Select any cell in the row
Press: Shift + Spacebar
Press: ‘Delete’ Wbk.Worksheets(“Sheet1”).Rows(r).ClearContents
Clear entire column(Data to the right of the column, moves to the left)
Keystrokes: VBA:
Select any cell in the
column
Press: Shift + Spacebar Wbk.Worksheets(“Sheet1”).Columns(r).ClearContents
Press: ‘Delete’
Insert New Blank Row/Column
Inserting a row
Keystrokes: VBA:
Select the row where new
blank row is required
Press: Ctrl + ‘+’ Wbk.Worksheets(“Sheet1”).Rows(r).Insert
Inserting a column
(New column is inserted in place of the selected column. Existing data moves to
the right)
Keystrokes: VBA:
Select the column where
new blank row is required
Press: Ctrl + ‘+’ Wbk.Worksheets(“Sheet1”).Columns(c).Insert
Insert Copied Row/Column
Insert Copied Row
Keystrokes: VBA:
Select row to be copied
Press: Ctrl + ‘c’ Worksheets("sheet2").Rows(r).Copy
Worksheets("sheet2").Rows(r1).Insert
Select row where copied Application.CutCopyMode = False
row has to be inserted
Press: Ctrl + ‘+’
Press: Escape
Insert Copied Column
Keystrokes: VBA:
Select row to be copied
Press: Ctrl + ‘c’ Worksheets("sheet2").Columns(c).Copy
Worksheets("sheet2").Columns(c1).Insert
Select column where copied row Application.CutCopyMode = False
has to be inserted
Press: Ctrl + ‘+’
Press: Escape
If we are not careful, our macro may give erroneous results. This happens
especially when we try to specify a date as string and our macro has to process
that input. Examples of such situations would be:
1. When we try to find a particular date in a range of cells.
2. When we write a macro to process emails received after a particular date.
3. When we want to find or filter rows on a worksheet based on dates.
4. We share our workbook with team members working in a different country
where different date format is followed for example dd/mm/yy instead of
mm/dd/yy.
Along with the issue of month and day ambiguity, there is also the issue of date
separators.
In such situations, Excel (on the worksheets) resolves the date based on the short
date format specified in the regional settings also known as the System Locale.In
Windows, these can be found in “Control panel -> Clock, Language, Region” -
>“Region”.
However, when dealing with dates in our macros, we can run into problems.
There are two situations when care has to be taken when specifying dates:
A) When we are dealing only with dates
To avoid any problems, it is always better to use the built in functions of Visual
Basic when just dates have to be specified. The functions are:
Date: This function provides us the current date in the appropriate format. The
function returns a value of Date datatype. Based on this value we can calculate
past dates. So now if we want to process only those emails that were received
within the past 5 days we would have some code like
If(receivedDate >= (Date - 5)) Then ..
Now, to find the row having the date 1- Oct-2015 in column B of Sheet1 the
code would be as follows
Worksheets(“sheet1”).Columns(2).Find(Dateserial(2015,10,1)).Row
CurrentRegion
Usually when co-workers send us input files, the data is presented in some fixed
format. In these cases:
1. We know the top-left cell of the data
2. There is no row or column that is completely blank in the entire table of data.
Meaning, that every row has data in atleast one column and, any blank column
has atleast the column header that is non blank.
In this situation we can get the entire Range of the data using a single line of
code.
Suppose the top left cell is “C5”, in worksheet “InputData”, in a workbook that
is set to the workbook object ‘wbk’
Set rngData = wbk.worksheets(“InputData”).Range(“C5”).CurrentRegion
Fig 3.4.2
We use the fact that the vertical bounds of the table are known (columns 2 to 7).
Notice the use of WorksheetFunction property.
3.5 Putting formulas in a worksheet with VBA
In VBA, every cell object that represents a cell on a worksheet has a Formula
property.
The ‘Formula’ property is a string that can be written as the formula that we
enter into a cell on the worksheet.
We just set the ‘Formula’ property of the cell to the required string. For
example,suppose we run a macro with the following code
Thisworkbook.Worksheets(“sheet1”).Cells(5,2).Formula = “=Sum(A1:A10)”
When we go to Sheet1 we will find that cell B5, contains the sum of cells A1 to
A10 and clicking in cell B5 shows the formula “=Sum(A1:A10)” in the formula
bar.
Most of the times, we can just type the required formula in the formula bar, copy
it and paste it in the code of our macro. However, care needs to be taken when
the formula uses text strings, or when it refers to another worksheet or workbook
In the second formula, please note that the workbook “Issues v2.xlsx” may be
closed when we are inserting the formula in the current workbook. But in that
case we need to specify the full path of the workbook that the formula will refer
to.
Notice also that the names of workbooks and worksheets can contain blank
spaces and other characters permitted by the operating system. In such cases we
need to be extra careful when creating our formula string because the code can
become difficult to read and maintain.
Solution:
As mentioned earlier, the best way is to enter the formula manually in one cell
and copy its string from the formula bar and use that in our macro. But let us
now look at a way to handle this if we absolutely have to do this in our macro.
We create a formula in a separate string as follows: (Take special note of the
additional characters that are used)
1. Name of the workbook is placed between square brackets ([ ]). If the
workbook is closed at the time of entering the formula, we need to provide
the full path of the workbook.
The path of the workbook is separated from the name of the workbook with a
path separator (“\” in case of Windows).
2. Then follows the name of the worksheet.
3. Combination of 1 and 2 is then placed between single quotes ( ‘ ‘ ).
4. Then we add an exclamation mark ( ! ).
5. Then follows the address of the range on which we want to do the
calculation.
The string formed in steps 1 to 5 is then combined with equal to sign ( = ) and
the name of the function we want to apply and normal curved bracket ‘ ( ) ‘.
Let us now apply the steps to create the second formula in the problem
description. Assume that the file “Issues.xlsx” is currently closed.
Notice the following:
1. We have used the concatenation operator (&) quite extensively.
2. Many of the steps can be combined, like the path, name and square brackets
can be added in one single line of code. But these have been shown separately
to highlight the concepts.
Dim strFormula As String
‘//** Notice the path separator (\) at the end **//
strFormula = "C:\Input\"
‘//** Add the workbook name and square brackets **//
strFormula = strFormula & "[Issues V2.xlsx]"
‘//** Add the worksheet name **//
strFormula = strFormula & "Pending List"
‘//** Add the single quotes, exclamation mark and the range address **//
strFormula = "’" & strFormula & "’" & "!" & "$C2:$C25"
‘//** Combine with the worksheet function **//
strFormula = "=CountA("& strFormula &")"
‘//** Insert the formula in cell B5 of Summary sheet in current workbook. **//
ThisWorkbook.Worksheets("Summary").Range("B5").Formula = strFormula
3. The Discount column will be added in column G and the Discount percentage
will be based on the type of SaleMedium. The table below shows the discount
criteria.
Discount Criteria Discount(%)
(SaleMedium)
Online 15
Direct 20
Retail 5
Formula in cell G2 will be:
=IF(B2="Online",15,IF(B2="Retail",5,IF(B2="Direct",20,0)))
The formula chosen shows:
1. How we can put nested functions(function within function) on aworksheet.
2. How the double quotes required in the formula are created using four quotes
(""""). This is a string which can be joined to the other parts of the formula to
form the complete string.
Here is the VBA code for inserting the formula in column G to all the rows of
the table. Notice how we have used the range of cells without using a range
object
Wks : is the worksheet object in which the formula has to be placed.
lLastRow : is the last row for the table of data.
'//** Put the formula in one cell in the second row-notice the four double quotes **//
wks.Range("G2").Formula =_
"=IF(B2=" & """" & "Online" & """" & _
",15,IF(B2=" & """" & "Retail" & """" & _
",5,IF(B2=" & """" & "Direct" & """" & ",20,0)))"
'//** Copy the formula **//
wks.Range("G2").Copy
'//** Using PasteSpecial method, apply the formula in all the rows of the table in column G **//
wks.Range("G2:G" &cstr(lLastRow)) _
.PasteSpecial(xlPasteFormulas)
‘//** Turn off the copy mode **//
Application .CutCopyMode=False
Fig 4.1.1
Ø Press Alt + ‘d’.
Ø Now press ‘f ‘ two times. And we have Auto filter applied to the entire table
Ø Now filter rows based on values in different columns and work with rows.
Ø Any time you want to ‘Show all data’ click Press Alt + ‘d’, press ‘f’, press ‘s’.
Ø To remove the autofilter, Press Alt + ‘d’, now two times press ‘f’
4.1.2 Autofilter with VBA
In macros, we can use the Autofilter method. Note that Autofilter is a method of
the Range object. So for applying Autofilter we first need a range. Once it is set,
this range can be obtained back with the Range property of the Autofilter object.
The required Range can be:
Ø A custom range constructed using one of the methods described
Ø Full worksheet, in which case, the VBA code would be
Set rng = wbk.Worksheets(“Sheet1”).Cells
Excel automatically selects the appropriate columns that bounds the table of
data.
What if the column positions are different in each of the input files?Or, What
if the users have inserted extra columns in between?
We deal with that situation in the next section.
We want to make a report which requires a list of:
All Laptops or Tablets sold in second quarter of 2015, in the North and East
region where the SaleQty is between above 10 and up to 100
The criteria for filtering would be:
1. ProductID: Column 1, has either ‘TAB’ or ‘LTP’ in it
2. SellerRegion: Column 6, is either ‘North’ or ‘East’
3. SaleQty: Column 5, is above 10 and less than or equal100
4. SaleDate: Column 4, is between and includes 1-Apr-2015 and 30-June-
2015
The code for applying Autofilter looks like this:
Rng.AutoFilter Field:=1, Criteria1:="ctr1", _
Operator:=xlAnd, Criteria2:="ctr2"
Ø Rng : is the range on which filter is applied
Ø Field : is the position of the column relative to the first column in the range.
In our example, we need to repeat this code 4 times. Rng will be the columns A
to F.
Let’s see the full code for the macro. Notice how we have used the string
concatenation operator, ‘For’ loop and the ‘Open’ method of workbooks
collection.
Code Example – Autofilter in VBA – Column positions known
Sub FilterRows()
‘//** Declarations **//
Dim wbk as Workbook
Dim strFilePath as String
Dim rng, rngVisible as Range
‘//** string holding the path C:\Sales\Reports\WorkingFiles **//
strFilePath = Thisworkbook.Path
‘//**Partial Path of files **//
strFilePath = strFilePath & “\Temp\SalesInput_”
‘//** Work with one workbook at a time **//
For i = 1 to 3 step 1
‘//** Open the workbook SalesInput_i.xlsx **//
Set wbk = Workbooks.Open(“strFilePath” &i& “.xlsx”)
‘//** Set the range to refer to columns A to F on SalesData worksheet **//
Set rng = wbk.Worksheets(“SalesData”).Columns(“A:F”)
‘//** Filter for Product ID **//
rng.AutoFilterField:=1,Criteria1:="=*TAB*", _
Operator:= xlOr,Criteria2:="*LTP*"
‘//** Filter for SaleDate **//
rng.AutoFilter Field:=4, Criteria1:= ">=4/1/2015", _
Operator:= xlAnd, Criteria2:= "<=6/30/2015"
‘//** Filter for SaleQty **//
rng.AutoFilter Field:=5, Criteria1:= ">=10", _
Operator:= xlAnd, Criteria2:= "<=100”
‘//** Filter for SellerRegion **//
rng.AutoFilter Field:=6,Criteria1:= "North", _
Operator:= xlOr,Criteria2:= "East”
‘//**-------------------------------------------
‘//** Work with the rows here
‘//** Refer section 2.5 for code can be placed here
‘//** -------------------------------------------
‘//** Remove all filters **//
rng.Autofilter
‘//** Close the workbook (here we are not saving any changes) **//
‘//**and move to the next workbook **//
wbk.Close(False)
Next i
End Sub
4.2 Working with filtered cells – SpecialCells
Excel does not provide a straight forward way to work with filtered rows.
However, there are workarounds that would make our life easy. Let’s take a look
at few of the operations we can perform once we have filtered a set of rows.
We will see the use of the SpecialCells method of the Range object. For more
information, please see section B.2.6 in Appendix B.
Fig 5.1.1 Setting a name for a PivotTable
Let us look at that most important methods and properties of the PivotTable
object. The syntax can be seen in the next two sections when we mention code
examples.
Properties
1. Name: Returns a string holding the name of the PivotTable. We can set the
value of this property in a macro. However, it is always a good practice to set
the name manually and just read it in macros.
2. SourceData: Returns a string holding the sheet name and address of the range
on which the PivotTable is based.
If we are setting a value for this property in our macro, we would have to
append the name of the sheet containing the source data. Section 5.2,
“Updating bounds of a PivotTable in VBA”, will demonstrate how to do this.
Methods
1. RefreshTable: Refreshes the PivotTable (data and calculations) from its
source data.
2. GetPivotData: This method returns a range from within a PivotTable that
contains data from the pivot table. The range returned depends on the criteria
we specify as arguments.
5.2 Updating bounds of a Pivot Table in VBA
Problem:
We have a reporting sheet that has a pivot table report that refers to a range of
data. We compiled data on our worksheet and number of rows has increased.
Sadly, the original pivot table was not based on a Table. We need to update the
range of data in the pivot table.
Solution:
We update the SourceData property of the PivotTable.
For the code in this section assume that we have a PivotTable named
‘PivotReport1’ on a worksheet named ‘Report’.
For the code example below, assume that rngPivot is a range for the data created
using one of the methods described in Chapter 3. Here is the code for updating
the PivotTable:
Set pvt = ThisWorkbook.Worksheets("Report") _
.PivotTables("PivotReport1")
'//** Set the source data of pivot table to new range. **//
pvt.SourceData = _
rngPivot.Worksheet.name &"!"& rngPivot.Address
'//** Refresh the pivot table **//
Pvt.RefreshTable
Fig 5.3a Part of the demand data that we will use in this section
Fig 5.3d Remaining columns of the pivot table shown in Fig 5.3c
The table that follows shows a number of different ways thet GetPivotData can
be used to extract information from a PivotTable. Study each example and
compare it with the table in figures 5.3c and 5.3d.
6.3 Sorting a range – Column position unknown
Problem:
We have received a number of input files from our team mates. However, each
one of them has either inserted additional columns or rearranged columns for
his/her own analysis.
We need to sort the range on certain sheets before we begin working on the data.
Solution:
First create the range for the data using techniques described in chapter 3.
Now, use the ‘Find’ technique we saw in section 3.2 on the first row of the range,
to locate the text of column names in row 1. Use that column number for
working out the Key argument.
Assumption:
Our colleagues have not changed the names of the columns and have not deleted
any of the columns.
Important note:
When specifying the Key range for SortField, we need to find the column’s
position relative to the first cell in the range that has to be sorted. The Find
method used to locate the column gives the column relative to column A in the
worksheet. Hence the relative column position is found using the code:
(rngKey.Column) -rngToSort.Cells(1,1).Column + 1
3. After typing, hold down the Ctrl and Shift keys together and hit Enter.
What do you see?
We see that Excel puts curly braces around the entire formula, including the ‘=’
sign. Also that the numbers are now distributed one in each of the selected cells.
Now, do the following, in the exact order as stated here
1. First, on the same worksheet, select the cells A3 to E3 (5 cells).
2. Click in the Formula bar and type (Don’t forget the curly braces)
=IF(A1:E1>3,"Yes","No")
3. After typing, hold down the Ctrl and Shift keys together and hit Enter.
What do you see?
We see that Excel puts curly braces around the entire formula, including the ‘=’
sign.
Also, each number in the cells A1 to E1 is compared independently with the
number 3 and, then if it is greater than 3, a “Yes” is placed in the corresponding
cell in row 3, else a “No” is placed there.
Meaning, it works like this:
“Is the value in Cell A1 (the First cell in input range) > 3 – Yes – Place “Yes” in
the First cell in the selected range where the formula was entered”
“Is the value in Cell B1 (the Second cell in input range) > 3 – Yes – Place “Yes”
in the Second cell in the selected range where the formula was entered”
And so on…
“Is the value in Cell E1 (the Fifth cell in input range) > 3 – No – Place “No” in
the Fifth cell in the selected range where the formula was entered”
Say Hello to Array Formulas. A.K.A “Ctrl-Shift-Enter” or “CSE” formulas.
Notes on the figure 7.1a:
· The cells that were selected for entering the array formulas are given a
thick dark border
· Column G shows the formula inserted in the respective rows. If we click
in any cell inside the blue outline, we should see that formula in the
formula bar.
Array Formulas are written just like regular Excel formulas, using some of the
same Excel worksheet functions. But, they differ in three important ways:
1. Where regular formulas take single cell values or a range as input for
calculation, these formulas always take a range.
2. Regular formulas produce a single result in a single cell. CSE formulas
produce an array of results. Length of the array being same as length of array
supplied as input.
3. Regular formulas are completed by hitting the ‘Enter’ key. CSE formulas
require “Ctrl + Shift + Enter”
Take note that these are not called Array formulas because they take an Array or
Range as input. It is because they produce an Array as their result.
The nice thing about them is that the resultant array can be fed into any regular
formula that takes an array for input.
For example, continuing with the example we used above, on the same
worksheet, select cell A5 and enter the following in the formula bar:
=SUM(IF(A1:E1>3,1,0))
After typing, hold down the Ctrl and Shift keys together and hit Enter.
The ‘IF’ part of the formula is similar to our earlier example, except that instead
of returning strings “Yes” and “No” , now it returns numeric values 0 and 1. This
portion returns an array.
The resulting array is numeric. Hence it can be fed as input to any regular
formula or function that takes an array as input. ‘Sum’ is one such function. It
takes an array of numbers and adds all of them up.
The IF function in our array formula produces an array of 0 and 1 and then Sum
takes up that array and adds all the elements. The result is a singlevalue showing
how many numbers in cells A1 to E1 are greater than 3.
v What if we only press ‘Enter’?
For the formulas to work, we had to tell Excel to treat the input ranges as arrays.
Hence Excel applies the function on each element one at a time. We
communicate this need to excel by pressing “Ctrl + Shift + Enter”.
If we just press “Enter”, Excel would just work with the first element of the
array and would produce a result for a single cell.
And there’s more:
Now let’s look at the figure again (sorry to make you turn pages) and focus on
row 7 and below. In column G we see formulas with more than one array.
Cells A7 to B9 have some numbers. This is how the Excel works when we select
cells D7:D9 and enter the formula shown in G7.
“Take the first value (the value in A7), in the array A7:A9 add this to the first
value in the array B7:B9 (the value in B7), this gives the first value in our result
Array”
“Take the second value in the array A7:A9 (the value in A8), add this to the
second value in the array B7:B9 (the value in B8), this gives the second value in
our result Array”
“Take the third value in the array A7:A9 (the value in A9), add this to the third
value in the array B7:B9 (the value in B8), this gives the third value in our result
Array”
“Cells D7:D9 were selected for entering the formula, so paste the result array in
this range, one value per cell”
In this example we have added the array values, but yes you are right, we can
subtract, multiply and divide as well (provided none of the values used as
denominators are zero).
As mentioned earlier, we can feed the resultant array into a function that accepts
an array as input. That is exactly what is done in cell D11. Please note that we
can use more than 2 arrays as well, only requirement is that they should be of the
same size (in our example, both arrays have 3 cells each).
Orientation matters:
The placement or display of the results of an array formula in the cells of a
worksheet, the result array, depends on the input array and the selection made at
the time of entering the formula. The following points should be borne in mind:
Arrays entered directly in the formula bar are always returned as horizontal row.
So if we select cells A1:A5 and then type
={3.01,5.5,4,1, 2.3}
We will get only the value “3.01” in the cell A1. Now consider the Figure 7.1b
Fig. 7.1b Array formulas with two arrays as arguments
Like before, the cells that were selected at the time of entering the array formulas
are marked with a thick dark border.
Column F shows the array formulas entered in the selection and the lightly
colored cells are simply numeric values.
In each of the formulas, one input array is vertical (A4:A6) the other is
horizontal (B1:D1).We observe the following:
1. When a row is selected for entering the formula, the horizontal array is given
importance.
The first cell from the vertical array is added to each cell of the horizontal array.
The resulting array is horizontal.
2. When a column is selected for entering the formula, the vertical array is given
importance.
The first cell from the horizontal array is added to each cell of the vertical array.
The resulting array is vertical.
3. If we select a range of ‘h’ rows by ‘w’ columns (where h is the number of
cells in the vertical array and w is the number of cells in the horizontal array),
then every combination of row and column cells is calculated.
The first cell of vertical array, is added to every cell of horizontal array and
becomes the first row of the resulting array.
Similarly, the second cell of vertical array, is added to every cell of horizontal
array and becomes the second row of the resulting array.
In our example ‘h’ and ‘w’ both have the value 3 giving a 3x3 range of cells.
The good news:
In most practical applications we have to deal with arrays that are fed into other
Excel functions that would then return a single value. Hence, it would not matter
whether the resulting array is vertical or horizontal.
7.2 …And why we use them only sparingly
Here are some reasons why array formulas are not preferred in spite of their
versatility and low memory consumption:
Formulas taking fixed length arrays as inputs
Ø In figures 7.1a and 7.1b, all the arrays that were used as input to array
formulas were of fixed length. This means whenever a new row or column of
data is added, we have to manually update each and every formula. We would
have to extend the range in the input as well as output.
The solution to this problem would be to use a variable named range that
would expand and contract as new data gets added.
Formulas returning fixed length arrays as output
Ø In figures 7.1a and 7.1b, all the array formulas returned arrays of fixed length.
These arrays are similar to array constants. All we can do with them is to paste
them across a range of cells or use them as inputs to some other functions.
Ø We cannot use these arrays for purposes like creating a validation list, or as a
chart series.
The solution to these problems is to use formulas that return a range of cells
rather than an array. Unlike arrays, variable length ranges can be created using
formulas and they can be named and stored in the workbook. We can use
ranges very easily for creating cell validation and chart series.
We will learn how to create variable length named ranges in Chapter 10
“Dynamic Named Ranges”. But before that we will see some of the functions
that take numbers or strings as input and return a range.
7.3 Index
This function provides us with either a single cell or a group of cells (an array)
that we can use further in functions.
The most common form of this formula is:
=INDEX(reference, row_num, [column_num], [area_num])
Arguments:
1. reference:This is address of one or more ranges (mainly rectangular).
If multiple disjoint ranges are specified, they are included in a comma
separated list. In that case, each range is called an area and can be selected by
the area_numargument.
Examples:
· Single range
=INDEX($A$1:$G$25, [row_num], [col_num], [area_num])
· Multi-range
=INDEX((A1:G25, I5:K45, A26:G51),[row_num],[col_num],[area_num])
We can see that the ranges specified for multi-range format, don’t have to be
of the same size. Meaning they don’t need to have the same number of
rows/columns.
2. row_num: This is the number of a single row from the specified range (or
selected area, in case of multi-range formula) from which the value has to be
taken.
If the column number is omitted, then specifying this argument becomes
mandatory. In that case, the formula returns the full row from the range.
Width of the row (number of cells in it) is equal to the number of columns in
the input range.
3. col_num: This is the number of column from the specified range (or selected
area, in case of multi-range formula) from which the value has to be taken.
If the row number is omitted, then specifying this argument becomes
mandatory. In that case, the formula returns the full column from the range.
Height of the column (number of cells in it) is equal to the number of rows in
the input range.
If both, ‘row_num’ & ‘col_num’ are specified, Index returns a single cell at
intersection of the specified row and column. Let us consider few examples.
4. area_num: This argument is applicable only in multi-range scenarios. It is the
number of the range from which the value will be taken.
The ranges are specified in the referenceargument in the form of a comma
separated list enclosed in brackets (). Each such range is called an area. The
first area is Area 1, second is Area 2 and so on.
If only a single range is specified, the area_num argument becomes
unnecessary.
Arguments:
1. lookup_value:This is the value that we are trying to find in the array. It could
be a constant value that we type in, or address of a cell from which to refer
the value.
2. lookup_array: This is the array in which we want to find the value. The array
can be a range on a spreadsheet or an array that we type in by hand. It has to
be one dimensional-single row or column of cells. We can’t specify a
rectangular group of cells.
3. match_type: This parameter specifies how Excel will match the value inside
the lookup array. Its value can be one of the 3 values listed in the table below.
The default value is 0.
Match Excel does the following
Type
Most preferred value used in order to avoid logical errors.
Returns the position of a value in the lookup array that exactly
0
matches the lookup value. Lookup array need not be sorted in
any manner.
Returns the position of the largest value that is less than or equal
to the lookup value.
1
The lookup array should be in sorted in ascending order for this
parameter to work.
Returns the position of the smallest value that is greater than or
equal to the lookup value.
-1
The lookup array should be in sorted in descending order for
this parameter to work.
Return Value:
Match function returns the position of a value in the array. It does not return the
value itself.
If a correct matching value is not found, the function returns “#N/A”
Let’s see a few examples that illustrate different match types. We will consider a
spreadsheet setup shown in the Fig 7.4. the value ‘c3’ in the cell E2 is for use in
the ‘Indirect’ function that we describe in the next section.
In this case, Excel starts with cell A3 and goes towards cell E3 looking at each
value one at a time. Notice that the values are arranged in descending order.
Value 2.9 in C3, is lower than the lookup value 2.95. The only value that is
higher than 2.9 but still lower than the values seen so far is 3.1 in cell B3.
Although 4.5 is also greater than the lookup value, there is a value in the array
that is lower than 4.5. Hence that value, the one in B3, is taken as the match
value.
This value is in the second position in the array. So the return value is 2.
· =MATCH(2.95,A1:E1,1)
Arguments:
rngReference: This is the range in which we want to count the non-empty cells.
The best use of this function is to find out the dimensions of a range of cells.
Example 1:
Consider the weekly table of Revenues and Expenses shown in Fig 7.5a.
Every week we enter a new column to the right. If at any time, we wish to find
the number of weeks entered, we just use the following formula
=CountA(1:1)
So the total number of columns in the table is given by the above formula. If we
want to include one empty cell A1, the formula would be:
‘//** To include the one empty cell in row 1 **//
=CountA(1:1) + 1
This gives us the dimension of a range that can be used in other functions.
For this formula to serve its intended purpose, the requirements are :
1. There should not be any blank cell in between two non-empty cells, at least in
the first row.
2. There should not be any content after the extreme right column otherwise that
too would get counted.
We can do something similar when the data is arranged in columns. This is
shown in the next example
Example 2:
Consider the weekly table of Revenues and Expenses shown in Fig 7.5b.
Every week we can enter a new row at the bottom.
This function will play a much more important role when we create self-
expanding named ranges in Chapter 10
7.6 Indirect
The Indirect function takes a string value representing a cell address and returns
a reference to that address.
If entered into a cell, this function returns the value contained in the cell
represented by the string. The real power of this function is seen when we want
to select the range on the fly based on values selected by the user.
Syntax is as follows: = INDIRECT(strAddr, [a1])
Arguments:
1. strAddr: This is the string representing the cell or range address. It should
include the sheet name if the cell is on a sheet different than the one where the
formula is entered.
2. a1: This is an optional Boolean value that specifies if the reference mentioned
is in A1 format or R1C1 format.
The default value for this argument is ‘True’, meaning the string represents
A1 style reference. We will mostly deal with this case as it is more intuitive
when using a worksheet.
Consider the table shown in figure 7.6. Let’s see some of the formulas and their
effects.
‘//** returns 2.8 **//
· =INDIRECT("B1")
‘//** returns 2.9 **//
· =INDIRECT(E2)
In this case the cell E2 contains string ”C3” that acts as the input string. The cell
C3 contains the value 2.9. The first formula below has a string in R1C1 format.
‘//** returns 3.1 **//
· =INDIRECT("R3C2",FALSE)
‘//** returns 5 **//
· =INDIRECT("’Range Values’!B5")
We use the function to fetch a value from a cell on nother worksheet named
“Range Values”.Notice that since the sheet name has a space, we need to enclose
the sheet name in single quotes (‘)
· =Average(INDIRECT(A5&"!$B$5:$B$505"))
The formula above takes the name of the sheet from the value of cell A5, then it
fetches the range “B5:B505” from that sheet and averages the values
therein.Notice that the sheet name can be entered by the user in cell A5.
7.7 Text functions – Trim, Clean, Text
Trim
The Trim function is used to remove extra blank spaces from the beginning and
end of a string. Consider the example in figure 7.7
The most important use of this function is when creating input for functions like
Vlookup where extra spaces can cause a value to appear as a mismatch. An
example can be found in the section for “Text” function.
Clean
The Clean function is used to remove some non-printable characters from text.
This mostly happens when text data is imported from some other program into
Excel.
Most of the times this should be used in conjunction with Trim. An example is
given in the section for “Text” function.
Important note:
Note that the Clean function can only remove around 32 non printable
characters. (Technically speaking, those with ASCII values from 0 to 31).
It cannot remove non-printable characters generated by programs like text
processors that support multiple languages. An example would be Data copied
from Word files having Chinese or Japanese characters.
Another annoying situation is when data is copied from websites. Often to
separate text, websites use a special character denoted by “ ” (called a
‘Non-breaking space’) in the web page’s html code.
When we copy data from the website either manually or using another software,
this character may creep in along with the other character data. Unfortunately,
this character cannot be removed using Trim or Clean
Text
The TEXT function converts a numeric value to text and lets you specify the
display formatting by using special format strings.
Major applications of this function deal with display of data on a worksheet.
Since we are more interested in analyzing and summarizing data, we will cover
only one application that helps with looking up data in a range of cells.
If we have a reporting worksheet where we need to format specific cells we can
use the formatting options available on the “Home” tab.
We begin with a description of a problem and then, use the TEXT function as a
solution to that problem.
Problem:
Suppose that our organization uses 5-character alphanumeric product codes.
Unfortunately, not all codes are a combination of numbers and letters. Some of
them are just numbers. To make things worse, some of these numeric codes have
leading zeros. For example, “00063”.
We have a workbook (Product Master) that has a listing of all the products. In
this workbook, the codes are listed in the correct format (“00063”) in column A.
The Product Names are listed in column B in this workbook.
However, we have a sales database from which we get a daily report extracted in
plain text format. In this file, the codes are not correctly extracted. The code
“00063” gets listed as “63”. The file has two columns, product code in column A
and units sold in column B.
Output from these two sources is shown in the following figures.
2. Copy this formula in all rows of column C. This will bring in the proper
product names from the other file. Of course the original values in column
A remain unaffected.
7.8 Conditional Math - COUNTIFS, SUMIFS,
AVERAGEIFS
These functions calculate the count, sum and average of cells in one range, but
only if those cells meet certain criteria specified in a set of other ranges.
For our examples we will consider the table shown in figure7.8
We will see the use of formulas by specifying two criteria, but Excel allows as
many as 127 different criteria to be specified.
For each function we will consider some scenario. The criteria to be met will be
specified in cells B2 and B3, and the result of the formula will be shown in cell
G2.
We will use the first function (COUNTIFS) to gain a deeper understanding of the
different ways available for specifying the criteria.
COUNTIFS
This function counts the number of times the specified criteria are
simultaneously met across different ranges. The syntax for the formula is as
follows:
=COUNTIFS(criteria_range1, criteria1, _
[criteria_range2, criteria2]…)
“criteria_range1, criteria_range2…” These are the ranges that contain values that are to be
checked against the criteria.
“criteria1, criteria2…” These are the actual criteria that would be checked.
Cells B2 and B3 can contain the values 1300 and 900 respectively. The result
would be 3. There are 3 weeks where production was above 1300 AND when
demand was below 900.
For easy reference, the formula entered in cell G2 is shown expanded in the cell
below it.
Method 2:
Another method is shown in figure 7.8b. Notice how we have provided the cell
reference, B2 and B3, in place of criteria and notice the content of those cells.
The result, obviously is the same.
Obviously, if we provide a cell reference and the cell does not have an operator,
then, only those cells are counted that exactly match the value in the reference
cell.
We have seen the operators Equal to (=), Less than (<), and Greater than (>)
being used in the function. We can also use the operators Less Than or Equal to
(<=), Greater Than or Equal to (>=), Not Equal to (<>). For example, for the
table in figure 7.8b, the following formula will give a value of 4
=COUNTIFS(D10:K10,"<=2190",D8:K8,"<900",D10:K10,"<>900")
It gives a count of all the columns(weeks) which satisfy the following criteria
1. Production (row 10) is less than or equal to 2190 but also, not exactly equal to
900
2. Demand (row 8) is less than 900
The result is 3, because only rows 3, 4, 5 and 9 have a Measure of “nos.” and out
of these only row 5 has the word “bolt” in the ProductName.
Note how we have used the wildcard character “*” in the formula. The formula
will consider all entries in column B that contain the word ‘bolt’ with any
number of characters before or after that word. These characters can include
blank spaces. Also, there may not be any character before or after “bolt”.
Meaning the entries beginning and ending with “bolt” are also considered when
counting.
If we had used “*bolt”, Excel considers only the entries that end with the word
“bolt”. Similarly, “bolt*” would consider only the entries that begin with the
word “bolt”.
Now let us look at a case of using “?” wildcard. We will also specify criteria for
a mix of text and numeric columns.
Suppose we want to find:
How many rows have a value above 100 in the ‘Units Sold’ column (col. D)
where the enntry in the ProductName column (col. B) ends with “wir” and just
one more character?
The formula to use would be:
=COUNTIFS(D1:D9,">100",B1:B9,"=*wir?")
The result is 2, because only rows 6, 7 and 9 have values above 100 in column D
and characters “wir” in column B. And, out of these only rows 6 and 7 have just
one character after “wir”.
AVERAGEIFS
This function calculates the average of numerical values in one range based on
whether specified criteria are simultaneously met across different ranges. The
syntax for the formula is as follows:
=AVERAGEIFS(average_range, criteria_range1, _
criteria1, [criteria_range2, criteria2]…)
“average_range” This is the range that contains the numeric values that are to be
averaged
“criteria_range1, criteria_range2…” These are the ranges that contain values that are to be
checked against the criteria.
“criteria1, criteria2…” These are the actual criteria that would be checked
The methods for specifying the criteria_range and criteria are same as those for
COUNTIFS function.
SUMIFS
This function calculates the sum of numerical values in one range based on
whether specified criteria are simultaneously met across different ranges. The
syntax for the formula is as follows:
=SUMIFS(sum_range, criteria_range1, criteria1, _
[criteria_range2, criteria2]…)
“sum_range” This is the range that contains the numeric values that are to be
summed
“criteria_range1, criteria_range2…” These are the ranges that contain values that are to be
checked against the criteria.
“criteria1, criteria2…” These are the actual criteria that would be checked
The methods for specifying the criteria_range and criteria are same as those for
COUNTIFS function
The rows 5 to 7 hold revenues and expenses for each month. Rows 10 to 13,
summarize the revenues by quarter. The top left cell of the table is A5.
Formula in cell B10 is nothing too brilliant; it sums an array of cells returned by
the Offset function. The array starts one row below and one column to the right
of cell A5. It is 1 row high and 3 columns wide.
Formulas in cells B11 to B13 show us the power of the Offset function when
combined with a little bit of arithmetic. These formulas calculate the address
(column offset, to be precise) of the target cell using the quarter number in
column A on the same row. Here is how it works
The first quarter starts at column B, at an offset of 1 column. For each quarter
after Q1, a minimum offset of 3 is required, plus 1 that is already needed.
Quarter Starts at Calculation
(column offset from for offset
A5)
1 1 0 + 1 (1-1)*3 + 1
2 4 3 + 1 (2-1)*3 + 1
3 7 6 + 1 (3-1)*3 + 1
3 10 9 + 1 (4-1)*3 + 1
7.10 Address
This function takes the representation of a cell location like Sheet Name, Row/
Column number, and returns a string containing the address of the concerned
cell.
Syntax for this function is as follows:
= ADDRESS(row_num, column_num, [abs_code], [a1],[sheet_text])
Arguments:
1. row_num: Row number to be used in cell reference.
2. column_num: Column number to be used in cell reference.
3. abs_code: This numeric value specifies the part of the address (row or
column) that should be made absolute. The values are as follows:
1 Row and column both absolute ($A$1)
(or omitted)
2 Row absolute, column relative (A$1)
3 Row relative, column absolute ($A1)
4 Row and column both relative (A1)
4. a1: This is a Boolean value that dictates if the address should be in A1 form
or R1C1 form. The default value is ‘True’ and means that the address should
be in A1 form. We will use the default value.
5. sheet_text: Name of sheet to be included in the address string. If the
worksheet is in some other file, the name of the file has to preceed the sheet
name and enclosed between square brackets ([ ]).
Let us consider some examples:
‘//** Returns $B$1 **//
= ADDRESS(1,2)
‘//** '[Test Book.xls]Sheet4'!$C$1 **//
=(ADDRESS(1,3,1,1,"[Test Book.xls]Sheet4"))
Note in the second example that the workbook name contains a space between
two words. The sheet name can also contain blank spaces, the manner of
entering the formula will remain same.
7.11 Getting values from tables (Index + Match)
Problem:
Often we need to fetch values from one or more tables of data by looking up a
combination of row and column values. The fetched value is then either
displayed or used in other formulas.
Solution:
We will use the Index function with the table as the reference range. The row
and column numbers will be fetched using the Match function on the header row
and the first column of the table.
Fig. 7.11b
In figure 7.11b, cell B2 is where the NPS is entered. Cells B5 to B7 are the
fetched parameters. These cells are on Sheet1 and the pipe table is on sheet3 in
the same workbook.
Cell B5 contains the formula with the INDEX and MATCH functions
=INDEX(Sheet3!$A$1:$E$11,
MATCH(Sheet1!$B$2,Sheet3!$A$1:$A$11,0),
MATCH(Sheet1!$A5,Sheet3!$A$1:$D$1,0))
See how the first MATCH function gets the row number and the second one gets
the column number for the INDEX function.
Notice also that in the second MATCH function the input cell is entered with
Column Absolute reference (missing $ sign in front of 5). This is done so that
the formula can easily be copied to the other two rows without need of changing
the row number.
7.12 The same thing done differently (VLookup +
Match)
In section 7.11 we saw how we can fetch value from a single cell from a table of
data.
We used 3 functions, two of them nested inside another one. Let’s try to reduce
the number of functions used.
We know a function that will take a lookup value, a table of data and a column
number and then will look up a value in the first column and return the value in
the specified column.
Yes, you are right. The function is VLOOKUP. We specify the lookup value and
the data range. The function finds the first row that matches the lookup value
and then travels right to the column that is specified to fetch the value in a cell.
For specifying the column, we will use the match function as done in section
7.11. Accordingly, the formula in cell B5 will now change to:
=VLOOKUP($B$2,Sheet3!$A$1:$E$11,
MATCH(Sheet1!$A5,Sheet3!$A$1:$D$1,0),0)
7.13 A better use for Index + Match
As noted in the section 7.11 when describing piping data, we saw that tabular
data is available for pipes of different schedules. So far we have fetched data
from a table for one schedule. What if we want to give the user a chance to select
the data for a different Schedule?
This is where we can fully utilize the capability of the Index function.
For this to work, we arrange the different tables of different schedules on the
same sheet. Preferably they should be arranged vertically one below the other.
The arrangement of tables for two schedules is shown in figure 7.13a. Notice
two things.
1. The rows and columns in each table are in the same order.
2. Pipe sizes across the tables are the same.
This might mean that in some tables we may need to have rows that have an
entry only in the first column, with blank cells or “na” in other columns. Two
such cases are shown in the bottom table in figure 7.13a where NPS 12 and NPS
20 are not supported for Schedule 80. (This is fictitious data just for the purpose
of illustration, in reality the sizes are supported. Data is also available for pipe
sizes smaller than NPS 8).
LBound:
This function returns a long integer value equal to the lower bound (lowest
allowable index) of an array for the specified dimension. Syntax is as follows:
lLb = LBound(arrInput, [lDimension])
Arguments:
1. arrInput: This is an array. It could be a multidimensional array.
2. lDimension: This is an optional argument. It’s a long integer value indicating
the dimension in the array whose lower bound is sought. We can omit this
argument for one-dimensional arrays.
UBound:
This function returns a long integer value equal to the upper bound (highest
allowable subscript) of an array for a specified dimension.
Syntax is as follows (arguments carry the same meaning as for LBound):
lUb = UBound(arrInput, [lDimension])
Example:
Consider the following three dimensional array:
Dim arrInput(1 To 100, 0 To 3, -3 To 4) As Integer
Length of an array:
Length of an array along any dimension is given by: Ubound – Lbound + 1
Hence considering the array arrInput described in the “UBOUND” function in
section 8.1, the following table shows the length along various dimensions
Dimension Length along this dimension
1 100 – 1 + 1 = 100
2 3 – 0 + 1 = 4
3 4 – (-3) + 1 = 8
Arguments:
1. start: This is a numeric value that indicates the position in source string at
which the search for target string will begin. It is an optional argument.
The default value of ‘1’ is used if we omit a value for this argument. We
cannot put a NULL value for this argument. In our examples we will always
use the value of 1.If the last argument compareType is specified, then it is
mandatory to put a value for start.
2. strSource: This is the string in which we want to find the target string. This
argument is required.
3. strTarget: The string that we are trying to find in the source string. This
argument is also required.
4. compareType: This argument specifies the method of comparison to be used
while searching. This is an optional argument.
If omitted, the value specified by the ‘Option Compare’ statement is used.
The ‘Option Compare’ statement is one of the first statements in a module,
written before any procedure or declaration statements.
We will not depend on the ‘Option Compare’ statement and will always use
the value of ‘1’ that is ‘Text Comparison’. Other comparison methods are
‘Binary Comparison’ (0) and ‘Database Comparison’ (2). We will not use
these setting either because better methods are available when dealing with
binary files and databases.
However, if we want the search to be case sensitive then we should use
‘Binary Comparison’. The examples will clarify this concept. Also, if an
Option compare statement is not present AND the argument is omitted, then
the Binary comparison is used.
The following table contains a list of the different values that the function would
return under different conditions:
Condition Return value
Source string is Null Null
Source string is of zero length 0
Target string is Null Null
Target string is of zero length Start
Target string is not found from the
0
starting position
Start > length(Target String) 0
Target string is found inside Source Position at which target string begins
string in source string
Let’s take some examples now. Consider the source string
strSource = “Hello, this is a HELLO string”
Following table shows the values returned for different values of the start and
’compareType’ parameters as well as different values of the Target string.
Function Return value Case Sensitive
InStr(1,strSource,”Hello”,1) 1 N
InStr(1,strSource,”HELLO”,1) 1 N
InStr(8,strSource,”hello”,1) 18 N
InStr(1,strSource,”what”,1) 0 N
InStr(1,strSource,”Hello”,0) 1 Y
InStr(8,strSource,”hello”,0) 0 Y
InStr(1,strSource,”what”,0) 0 Y
InStr(1,strSource,”HELLO”,0) 18 Y
Common use of this function:
InStr is most useful in finding out if a certain target string is present in a block of
text when we don’t know the exact position of the target string.
For example, for testing if the subject of an email has a certain word.In the
following code, strSubjectis a string containing subject of an email.
If InStr(1,strSubject,”Sales”,1) > 0 Then
‘//** Found the email we were looking for, process further **//
End If
Split:
This function is the opposite of Join function described in section 8.1. It takes as
argument an input string that has a specific character at certain locations. It then
breaks the string wherever the character is found, puts the pieces in a one-
dimensional array and returns the array. This array can be captured in a variable
of Variant data type.
The syntax for the function is as follows:
Split(strSource[, Delimiter[, Limit[,compareType]]])
Arguments:
1. strSource: String containing substrings and delimiters. If expression is a
zero-length string(""), Split returns an empty array, that is, an array with no
elements and no data. This is a required argument.
2. Delimiter: An optional argument. It is a string used to identify the positions
at which strSourcewill be broken. If omitted, the space character (" ") is
assumed to be the delimiter. If delimiter is a zero-length string (“”), a single-
element array containing the entirestrSourceis returned.
3. Limit:Specifies thenumber of substrings to be returned. This is an optional
argument. If omitted a default value of -1 is taken,meaning that all substrings
are returned. Value, if specified, must be a positive number.
If the value specified is more than the number of pieces that are possible, the
returned array will contain only the maximum possible number of strings.
If a value (say n) is specified, our macro will start breaking the string starting
at the leftmost (first) occurrence of Delimiter. This continues till (n-1) pieces
of strings are available. The remaining part of the original string is then
placed in the last element of the returned array. This concept will be clarified
by examples.
4. compareType:This is an optional argument. Its meaning is same as that
specified for the InStr function described earlier in this section.
Most of the times we will split strings using non-alphabetical characters.
Some examples are @ , / , ; , | , ,(comma) , $ , and so on. However,
sometimes we may want to use strings containing alphabets. In such cases we
may want to use Binary comparison (value 0). The examples will make this
point clear.
Special notes:
1. The lower-bound of the returned array is always 0.
2. The strings in the returned array will contain any non-alphabetical character
or white spaces that are present in the original string.
Let’s see some examples. In the examples, strSource is the string we will split.
strSource = "Hello Hi/ goodbye hi bye HI buddy"
Arguments:
1. str1, str2: These are the two strings to be compared. If any of these strings is
omitted, the function returns NULL.
2. compareType: This is an optional argument. Its meaning is same as that
specified for the InStr function described earlier in this section. We will most
often need only ‘Text Comparison’ (value 1). Sometimes we may wish to do
a case-sensitive comparison and in that case we should use ‘Binary
Comparison’ (value 0).
Return Condition
Value
0 str1 is exactly equal to str2
-1 str1 is less than str2
1 str1 is greater than str2
NULL Either str1 or str2 is NULL (omitted or zero length
string)
In case of Binary comparison, the return value is 0 only when both strings have
same characters at the same positions and the case(upper/lower) of the characters
match in each position.
How one string is greater than or less than the other depends on a combination of
things like the position of the characters and whether they are in upper or lower
case. A thorough discussion is out of the scope of this book as it requires
knowledge of how characters are represented in a computer.
Function Output
Format(dt, "ww") 24
Format(dt, "yyyy") 2016
Format(dt, "ddd, dd mmmm yyyy") Tue, 07 June 2016
Format(dt, "dddd, dd mmm yyyy") Tuesday, 07 Jun 2016
Format(dt, "dd,m,yy") 07,6,16
Format(dt, "dd.mm.yy") 07.06.16
Format(dt, "d!mm!yy") 7!06!16
Format(dblFraction, "0.00%") 25.48%
Format(dblVal, "0,000.00%") 1,254,783,523.46%
Format(dblVal, "0,000.00") 12,547,835.23
Format(dblVal, "0.000") 12547835.235
Debug.Print
This function is actually a method of a special type of object called the “Debug”
object. This object is available when we are writing our code in the Visual Basic
Editor.
The Print method takes a string argument and prints it in the “Immediate”
window thereby helping us to see the values of variables and output of functions
before we use further in the program.
This provides a kind of initial check so we can ensure that variables have the
correct values and that the correct processing is being done. This helps to avoid
errors.
For example:
Debug.Print "Week number " &_
Format(dt, "ww") & " of the year " & Format(dt, "yyyy")
Prints the following line in the Immediate window:
Week number 24 of the year 2016
MsgBox
Displays a message in a dialog box, waits for the user to click a button, and
returns an Integer indicating which button the user clicked. We don’t have to use
the return value we can just ignore it.
MsgBox(prompt[, buttons] [, title] [, helpfile, context])
Arguments:
1. prompt: this is a string argument and is the actual message displayed to the
user.
2. buttons: this is an integer specifying the combination of buttons to be
displayed. It is an optional argument. We can enter the integer value or the
related named constant that is predefined in Visual Basic.
Constant Value Display buttons
vbOKOnly 0 OK button only.
vbOKCancel 1 OK and Cancel buttons.
vbAbortRetryIgnore 2 Abort, Retry, and Ignore
buttons.
vbYesNoCancel 3 Yes, No, and Cancel buttons.
vbYesNo 4 Yes and No buttons.
vbRetryCancel 5 Retry and Cancel buttons.
3. title: this is a string containing the text to be displayed in the title bar of the
message box window. This is an optional argument.
The arguments ’helpfile’ and ’context’ are used in Visual Basic applications that
have help files associated with them. We will not be using these arguments.
The values returned by the function are integers. We can compare them with
integer values or with predefined Visual Basic constants listed in the table that
follows
Constant Value User pressed
vbOK 1 OK
vbCancel 2 Cancel
vbAbort 3 Abort
vbRetry 4 Retry
vbIgnore 5 Ignore
vbYes 6 Yes
vbNo 7 No
The code below shows how we can use the MsgBox function and then take
different actions based on the button pressed by the user. The Debug.Print
statements can be replaced by any valid lines of code or function calls.
Sub msgbxtest()
Dim retval As Variant
retval = MsgBox("press any one button", _
vbYesNoCancel, “Button Test”)
Select Case retval
Case vbCancel:
Debug.Print "You pressed Cancel"
Case vbYes:
Debug.Print "You pressed Yes"
Case vbNo:
Debug.Print "You pressed No"
End Select
End Sub
Figure 8.4a shows the output of the sub and the different parts of the message
box as they appear on the screen.
Most of the times in our macros we would just have to display a message with an
OK button to display a situation or warning to the user and then exit the
sub/function. In that case we can just use the prompt argument and ignore all
other arguments.
IsNumeric
Returns a Boolean value indicating whether an expression in the argument can
be evaluated as a number. The syntax is as follows:
IsNumeric(expression)
Notice how the variable ’str1’ has blank spaces but can still be evaluated as a
number
Chapter 9
Excel Tables
Tables are a great way to get data organized on a spreadsheet. Excel adds several
features to the range of data once we mark that range as a table. In this chapter
we will start with basics of creating and working with Tables manually on a
worksheet. Later on we will see how we can integrate the tables on worksheets
with our macros.
9.1 Creating a Table on the worksheet
For creating a table (refer to figure 9.1a),
1. We click anywhere inside the range containing our data.
2. On the “Insert” tab to reveal the Insert ribbon, click on “Table”.
3. Excel shows the range from which the table will be created. We can include
additional rows or columns.
4. Always have a header row and, in the “Create Table” dialog box, always
check the box that says “My Table has Headers”. This has many advantages
that we will highlight going forward.
Fig 9.1a (Left) Inserting a table, (Right) the ‘Create Table’ dialog box
Advantages of using tables:
1. The top row is marked as a header row (if the relevant option is selected).
This row is then frozen and does not scroll with datatherebygiving better
readability.
2. Filter drop downs are automatically added to all the columns.
3. The table expands automatically whenever an additional row or column is
added immediately adjoining the existing table.
4. If a formula is inserted in any cell in any column of the table, Excel will fill
that formula in the entire column automatically.
5. We can refer to the table and table columns by their names in Excel formulas
and, in VBA.
6. Most important, when applying Autofilter or when sorting a table, we don’t
need to remember or work out the column positions like we did in section
earlier sections. We can just use the column names.
Features 3 and 4 listed above may not be available by default. In case you notice
that your tables are not expanding automatically, you can enable the option in
Excel.
For doing this, go to “File” menu and click on “Options” on the left hand pane.
In the dialog box that comes up, proceed as shown in the Fig 9.1b.
Fig 9.1b Settings to make tables auto expand and auto calculate
9.2 Using Table and column names in Formulas
When a table is created, Excel automatically names the table and its columns
(based on the column headers). The default names are like Table1, Table2 and so
on.
We should always rename a table to a meaningful name representing the data
therein. Let us see how we can change the name of a table. Refer to Fig 9.2. The
number of steps correspond to the numbers marked in the figure.
1. Click on any cell inside the table.
2. The design tab will now appear in the main menu bar. Click on the Design
tab.
3. Notice on the far left, there is a field that accepts the table name. Enter a
suitable name and click enter.
Fig 9.2 Steps to change name of a table and for making a Pivot table.
When using the names of Table and columns in a formula the format would be
TableName[ColumnName]
For purpose of this section, consider a table holding sales data. The table name is
‘Sales’ having one column with header ‘Units Sold’ and another one with ‘Unit
Price’.
To create a chart based on this table, go to the sheet where the chart is required
(this can be different from the sheet that contains the table). Then proceed as
follows:
In the “Insert” ribbon, select the type of chart required. Excel will insert a chart.
This chart may not contain the series that we want. We will set that
Right click on the chart and select “Select Data”. This brings up the “Select Data
Source” dialog as shown in figure 9.4b.
On the right, there is a list named “Legend Entries”. Marked by number 1 in
figure 9.4b. If there is a series there that we don’t want, select it and click on
“Remove”.
Fig 9.4bSelect Data Source dialog box
To add a series, click on the “Add” button. To bring up the “Edit Series” dialog
box as shown in figure 9.4c. In the “Series name” box, enter a suitable name for
the series. Now comes the important part.
In the “Series values” box, we need to enter the name of the column from which
values will be taken to create the chart.
The code above shows that the DataBodyRange can be treated like any other
range object.
3. Index: This is an integer value representing the position of the column in the
table.
4. Name: This is a string value and is the name of a particular column. This
string when set will appear as the column header.
Methods
1. Delete: This method will remove a the table column represented by the
ListColumn object. Here is the syntax:
Set lo = Wks.ListObjects.Item(“Sales”)
lo.ListColumns.Item(“Units”).Delete
The effect is same as deleting the column from the worksheet. Columns from
the right are moved leftwards to occupy the deleted column.
The code below will add a new column at the end of the table and fill it with a
formula
Dim lo As ListObject
Dim lc As ListColumn
Set lo = Worksheets("Sheet3").ListObjects("Sales")
Set lc = lo.ListColumns.Add
lc.Name = "NewCol"
lc.DataBodyRange.Cells(1, 1).Formula = _
"=Left(Sales[[#This Row],[Part Number]],3)"
Notice that the formula is filled in the first cell of the column. The table will fill
the formula in the entire column.
The formula is to fetch the first 3 characters from the “Part Number” column
from the table in figure 9.4a.
Notice the use of ”#This Row” to refer to the row in which the formula is
entered.
Chapter 10
Dynamic Named Ranges
We know that we can very easily select a fixed range of cells on a worksheet and
give it a name. We can then use the name to refer to that group of cells in our
formulas.
In this chapter we will see how we can take the concept of Name a bit further
and make the names respond to changes made on a worksheet. Along the way
we will see some principles for developing spreadsheet based solutions.
This chapter is a rather long one. All efforts have been made to keep the writing
light so as not to bore the reader, but the main focus is on getting straight to
point.and avoiding lengthy narratives. Reader is requested to please bear with
the length of the chapter. Let’s get started.
Just to refresh our memory, let’s see how we can create a named range. Use Fig
10a as reference.
If we have a fixed reference cell and know the rows and column offset of the
start of our range, we have a starting point.
Now all we have to do is to make our height and width vary based on the data
that is put into our range and we can have a reference to the whole range.
What we need is a function that would calculate for us the total number of rows
and/or columns that have been filled. And we have already seen this function.
You’re are right, we can use the CountA function that we saw in section 7.5.
Let’s recall its syntax
= (CountA(rngReference) – 1)
rngReference would mean a row (called the reference row) or a column (called
the reference column) which will have data or, more appropriately, data-labels in
all of its cells. We will see the details soon.
But wait, some would say. Doesn’t that mean that the limitations of CountA
come into play? Because for CountA to work the following should hold:
1. There should not be any blank cell in between two non-empty cells, at least
in the reference row or in the reference column.
2. There should not be any content after the extreme right column or below the
last row in the reference column, otherwise that too would get counted.
Well, it’s true that layout of the data matters when we are creating dynamic
ranges. But it pales in comparison to the functionality we gain once the ranges
are created.
Another question. Why do we need a ‘reference’ row or column? Why not just
apply CountA formula to the actual range containing the data?
To get an answer, consider the layout of data shown in Fig 10.2a
Fig 10.2a Sample data for creating a dynamic range with one row
Every week we add a new column for another week. Suppose that we want to
create a dynamic range for ‘Revenues’. But, as we can see from the figure, we
don’t have data for some of the weeks. May be the value is 0 and we just didn’t
enter it.
Let’s say we used the following:
= CountA(6:6)-1
This would give us a value of ‘8’. So starting at column C and counting 8 cells
would give us a range only till column J, so Weeks 9 and10 get missed out.
Instead, let’s try using the following formula.
= CountA(5:5)-1
Basically this says:
‘Count all non-blank cells and remove one cell that holds the row label
Revenues’
This would give us a range exactly 10 columns wide which is what we want. The
width of our range is hence 10. The range in this case is 1 row, so the height of
the range is 1.
Now let’s create our range for ‘Revenues’ row. We call our trusted friend the
‘Offset’ function.
=OFFSET(ref.Cell, rows, cols, height, width)
ref.Cell : The reference cell would be the top left corner of our data. Cell B5.
Rows : Revenues range will lie one row below the reference cell. So the rows
offset is 1.
Cols : Revenues range will start one column to the left of the reference cell. So
the columns offset is 1.
Height : Our range consists of only one row, so it’s height is 1.
Width : This is the fun part. We want our width to be calculated. So, we enter the
CountA formula for width instead of a numeric value.
Here’s what our final formula would look like
=OFFSET(Sheet7!$B$5,1,1,1, CountA(5:5)-1)
But wait, we can’t just enter this formula into a cell. Remember that offset
returns a range, if we use this on a worksheet, we would have to enter it as an
array formula across a range of cells and that would serve only a limited
purpose. Instead, we will save this formula as a Name. That’s right. In the next
few sections we learn how we can use this technique to get references to ranges
having a single row/column as well as an entire table.
Fig 10.2.1a The Name manager dialog box
2. In the dialog Name Manager box, click the button marked “New”. This will
bring up the “New Name” dialog box
Very Important:
Ensure that “Workbook” is selected in the dropdown named “Scope”.
This will ensure that the range that the Name refers to can be used in formulas
inserted on some other sheet
3. In the field labelled as ‘Name’, we type in the name we want to use for the
range. In our case, it will be ‘Revenues’
4. By default, in the ‘Refers To’ field we see the address of the currently
selected cell.
5. We click in that box, and start typing the Offset formula.
6. Very important, when it comes to entering the reference cell and reference
row, we don’t type, we select the concerned cell and row. Why? The reason
will be clear in a minute.
7. Click on ‘Ok’. The new Name will appear in the Name manager box with list
of other Names.
As we can see, Excel inserts the sheet name and ‘$’ signs into the formula. Leave
them as they are, don’t delete them. Why? Let’s see.
The dialog box in figure 10.2.1b is the ‘Edit Name’ dialog box and is used to
make changes to an already existing name.
Open the Name Manager as described in step 1. Select any name from the
list that needs to be changed. Click “Edit”
Before we proceed to create the three new ranges, let’s take a minute and think.
Notice how we can align the design to our goals of maintainability and
flexibility
We can see that the formula for calculating the width of our range is used in 5
different names. Any change in the formula would require change at 5 places.
The same formula gets calculated and gives the exact same value in all 5 cases.
To avoid multiple copies of the same formula, we will save the result of
COUNTIF as another name. We could save it in one of the cells on our sheet but
there is a possibility that it might accidently get deleted or changed. Hence we
prefer a saved name.
We will do the same thing with our reference cell. That way, it becomes easy to
change it if required and makes the formulas more readable. We will also see a
more useful application of a separate name for the reference cell when we deal
with charts.
For the new table, the formulas and the modified Names are shown in figure
10.2.2b. Note that we have saved the result of the CountIf formula as a name
‘RangeWidth’.
Fig. 10.2.2a The modified layout with summary table and new rows
Fig. 10.2.2b Modified names in the Name Manager box.
Since this is a normal range of cells, we just pass it on to the sum function:
=Sum(OFFSET($F5, 4, -2, 1, RangeWidth))
Important Note: Notice that in the formula we have kept the Column absolute
and Row variable ($F5). Why? So that we can copy the formula to other rows
and the formula will vary accordingly.
If you have been doing the steps in a real worksheet, just click in cell H6 and
press (remember the shortcut from chapter 2?)
Ctrl + D
Go ahead and paste the formulas in cells H17 and H18. The numbers will get
updated.
Seems good. But wait! Entering the formula in cells H5 and H6? Wouldn’t that
make the dynamic named ranges we created earlier (‘Revenues’ and ‘Expenses’)
useless? Not quite. We will see another use for them a bit later.
Now notice this. Any time we want to add a new area, copy the rows 15 to 25,
scroll to the bottom, and paste. Update the Area name and the numbers for
Revenues, Expenses etc. That is it! We can even copy the bottom most table if
we like.
But why not copy the ‘Company’ table? We will get to that in a bit. For now,
let’s think about getting data into the Company table.
Remember the requirement for ‘Company’ table? It should show the combined
numbers for all the tables listed below it.
Consider the cell D9 in figure 10.2.3a. It should contain sum of values in Cells
D21, D33, D35 and so on. Also, when we enter the values for the coming week,
we should be able to copy that formula into the rightmost cell. What do we do?
Sure in D9 we can enter something like
= D21 + D33 + D35….
For Week2, we just click inside E9 and press Ctrl + R. But where’s the fun in that?
Not only is it boring, but for Expenses we sould have to click in cell D10 and
type
= D22 + D34 + D36….
What happens when we add another table, how many formulas do we update?
This is where conditional calculation formulas come to the rescue. Remember?
The ones we discussed in section 7.8 “Conditional Math”.
= SUMIF(CriteriaRange, CriteriaString, SumRange)
So, in cell D9 we sum all the cells in column D, below row number 15, where
the word Revenue appears in column C. In cell F12 we sum all the cells in
column F, below row number 15, where the word Production appears in column
C.
All we need to work out is the size of the ranges (the number of rows to be
included in criteria and sum ranges). Because remember, we should only include
values that are below the ‘Company’ table.
How do we do that? Here is the most straightforward approach.
Decide a large enough number of rows in advance. In our example, each area
requires a table with 10 rows. Even if we have 100 areas, we will need at most
1000 rows. We will be conservative and take 1500 rows. So our range starts at
row 14 and goes till row 1515.
The criteria range would be ’$C$15:$C$1515’. For column D the sum range
would be ’D$15:D$1515’.
The formula in cell D9 would be:
= SUMIF($C$15:$C$1515, “=”&$C9, D$15:D$1515)
Surely you must have figured out the reason for placing (and not placing) $ signs
at specific locations in the formula. Let’s just list it out to reinforce the principles
in memory. Please refer to figure 10.2.3a while reading.
1. We want to be able to copy the formula to the cells on the right. So the
column we are summing should be change (D to E to F and so on), but the
criteria column should remain fixed at C. Hence the $ sign for column C but
not for column D.
2. We want to be able to copy the formula to the cells in the rows 9 to 13, but the
rows to be summed (15 to 1515) should not change.
3. The criteria (Revenues, Expenses, Inventory, etc.) should be from the same
row where we are entering the formula. This number should change when we
copy the formula to the rows below row 9. Hence for cell D9, ‘9’ does not get
a $ sign.
The big question is: In how many cells do we need to enter this formula?
The big answer: Only one!
Here is what we do after entering the formula in cell D9.
1. Click in cell D8.
2. Press ’Ctrl + Right arrow’. This should take us to the right most column. In our
case its column K for Week8. But remember there could be many more
columns.
3. Press the down arrow key.
4. Press ’Ctrl + Shift + Left arrow’. The entire range till Cell D9 is now selected.
5. Release the ’Ctrl’ key.
6. Keeping the ’Shift’ key pressed, press the ’Down arrow’ key till all rows up to row
13 are selected. So now the entire range D9:K13 is selected.
7. Release the ’Shift’ key.
8. Hold down the ’Ctrl’ key and Press D and then R (or R and then D). And there
we have it, the formula is copied to all cells and each of them calculates the
correct values.
Now every time a new column gets added to the right, we simply copy the
formulas from the column on the left.
Now we address the question we raised a few pages back. When we want to add
a new area, why not copy the company table? The answer: Because it contains
all these formulas. Better to copy one of the Area tables since it only has static
data.
All that seems great. But what if we decide to add another row to all the tables?
Let’s say the count of Employees.
No worries. Insert a new row in each of the Area tables and fill in the numbers.
In the Company table, insert a new row. Just copy the formulas from the row
above. Done!
Fig 10.2.4 Sample data for creating a dynamic range with one column
This is like a transactional table that captures data for sales made in different
regions. As time passes, more rows are added at the bottom of the table. We
notice the following:
1. Blank cells are present in some columns.
2. The ProductID column has data in all rows below the header row.
Duplicate values are allowed. There is no data above the header row.
3. The header row has no blank cell
We will use the ProductID column as our reference column to count the total
number of cells to include in our range. Cell B4 will be the reference cell.
The total number of cells to be included in the range is given by the formula (In
column B, count all rows and remove the header row):
=(CountA(B:B)-1))
Here is the formula for the range that we can assign to a name (does not include
the column header):
=OFFSET($B$4, 1, 0, (CountA(B:B)-1), 1)
tblData is the name given to the dynamic tabular range. In cell D2 we will get
the result ‘Wiring Fittings’
With Array formulas:
Now let us see one interesting case where we can combine named ranges with
array formulas. Please refer to figures 10.2.2a and 10.2.2b.
Suppose we have to report the average Gross Profit (GP) percentage since week
one. Consider a simple case of five weeks.
For week ( i ), gp is given by :
(revi - expi)/revi = 1-(expi/revi)
2. In the ‘Legend Entries’ section, click on any series name to select it. Let’s say
we select ‘Revenues’.
3. Click ‘Edit’. This brings up the ‘Edit Series’ dialog box as shown in figure
10.4b
4. The first box ‘Series Name’ has the address of the cell that contains the name
label for the series. Usually we can leave it as it is. However, we can enter a
different string if we want. For example, we can enter =”Revenues”.
5. The second box, ‘Series Values’ is important for our work. Here we should
enter the name of our dynamic range in the following format:
=<workbook name>!<name>
‘//** Notice the ‘ = ‘ and ‘ ! ’ **//
6. Click on ‘OK’. This takes us back to the ‘Select Data Source’ dialog.
7. Perform steps 2 to 6 for all the series on the chart.
8. In the ‘Select Data Source’ dialog, click ‘Edit’ in the section marked
‘Horizontal(Category) Axis Labels’. This brings up the ‘Axis Labels’ dialog
shown in figure 10.4c
9. In the ‘Axis label range’, enter the name of the range created to hold the week
names.
10. Click Ok to return to ‘Select Data Source’ dialog. Click Ok again to exit.
That’s it!. Now each time we add (remove) a new week label and the
corresponding Revenues and Expences values, the chart will grow(shrink)
automatically.
Fig 10.5a Display different ranges on same chart based on value selected by the user
Solution:
Please refer back to figure 10.2.2b. We see that the named ranges Revenues,
Expenses and Rangewidth depend on one common parameter the reference cell
C8 on Sheet7. The address of this cell is currently saved as a name ‘RefCell’.
The reference cell is the top left cell of the first table shown in figure 10.2.3a.
This table shows the consolidated numbers for the entire company in the rows 8
to 13.
The figures for the individual areas are in other rows. For example, rows 20 to
25 show the revenues, expenses and other details for Area1. The top left cell of
the table for Area1 would be C20.
If we can apply a formula to the name RefCell to make it point to the correct
reference cell based on a value selected by the user, the dynamic ranges
‘Revenues’ and ‘Expenses’ will get updated accordingly and hence the charts
will also show tha appropriate data.
Note that we are using the features that we have already built. Refer back to
the discussion in section 1.4.
This is the initial plan. We will make a slight change when when we reach the
appropriate point. The change will allow us to avoid complicated nested
formulas and creation of Names that are not absolutely necessary.
Referring to figure 10.2.3a, we notice that the layout is such that:
1. For each table the reference cell will always be in column C. Only the row
differs.
2. The label marking each table (‘Company’, ‘Area1’, ‘Area2’) is in column B.
3. The number of rows between each label and the corresponding row of the
reference cell is fixed for each table. Four rows in our example.
With these observations in mind, we proceed as follows:
a) Selection of area
First, we allow the user to select the area for which the chart has to be displayed.
This selection can be made in cell C2 as shown in figure 10.5a
To create the dropdown, we will use cell validation with a hand typed list in cell
C2 as shown in figure 10.5b. Here are the steps:
1. Click in cell C2.
2. In the menubar click on ‘Data’ to reveal the ‘Data’ ribbon.
3. In the ‘Data’ ribbon, click on ‘Data Validation’. This brings up the ‘Data
Validation’ dialog box as shown in figure 10.5b.
4. Fill in the details as shown in the figure. Notice that the entries are exactly the
same as the labels for each table of data.
10.5b Hand typed validation list
The output of ‘Match’ function combined with the fixed number can be used in
the ‘Address’ function to calculate the exact address of the top left cell of each
table of each area. Since reference cell is in column C, one argument in Address
function is always fixed. Here is the formula:
=ADDRESS(MATCH(Graphs!$C$2,Sheet7!$B:$B,0)+4,3,,,"Sheet7")
Note:
We already have one formula with complicated arithmetic operations nested
within another function. Instead of nesting this formula into another function,
we will simplify the process by saving the result of this formula into a cell (say
G10) on the ‘Graph’ worksheet.
This approaches provides the following advantages:
· In the new named formulas, we can use the reference G10 instead of the
whole formula.
· It will avoid creation of an extra named formula-we have avoided using the
‘RefCell’
· It will allow us to check if the formula calculates the correct address when
different values are selected in cell C2.
This approach should always be used when the formula returns a single value.
Named formulas should be used only when the formula returns a range of
cells. This approach will consume less memory and also improve calculation
speeds.
We can also this approach for storing value of RangeWidth in a worksheet cell
instead of a Name.
Note that the cell G10 is safely hidden behind the chart. This prevents
accidental changes or deletion.
The output of the ‘Address’ function is a string that can be used in the ‘Indirect’
function to get the actual range of cells to be displayed on the chart. Let us
modify the named formula ‘Revenues’
=OFFSET(INDIRECT(Graphs!$G$10),1,1,1,RangeWidth)
Done! Now on the Graphs worksheet change the value in the dropdown in cell
C2 and see the changes reflected on the chart.
Now, suppose we have a new business area. We just copy one of the earlier
tables and paste at the appropriate location. Making sure we maintain the proper
number of rows between the bottom of an existing table to the label of the new
area and between this liable and the top of the new table
10.6 Charts with n-Period Rolling Ranges
Problem:
We want to create a calculation or chart that uses data only for a fixed number of
past periods.
Some applications would be:
Ø Calculation of rolling average of product demand.
Ø Showing trend of defective products for a rolling period of 12 weeks
In such cases, we often have to update the start and end of the concerned range
manually when we update the data.
Solution:
With our knowledge of named ranges, we can, at times, create a dynamic named
range that would update each time we add new data. Let us consider the example
we have been using so far.
We reproduce the company level table from previous example. This time, with
11 months of data.
For this illustration, assume that we have to show four weeks of data on a rolling
basis. So this week we show data from week 8 to week number 11. Once data for
week12 comes in we show data from week 9 till week 12.
Notice the relation between the three numbers that we can try to use in our
solution.
Length of rolling period = 4
Ending week Starting week would
number be
12 12-4+1 = 9
11 11-4+1 = 8
Now, the ending week number is equal to the total number of weeks present in
the table at any time. This value is already calculated with the RangeWidth
name.
Note also that the number we calculated (starting week number) is the offset
from the first column of the table. The reference cell is the one with
"Particulars".
So here is the principle, for the series that we want to display on a graph on a
rolling basis, we first find the total number of values we have. From that we
work out the first value that will be displayed on the chart. Then display values
from the first calculated value till the last value.
So our formulas for week labels, revenues and expenses will change as follows.
Compare these with the formulas we created in sections 10.2.
In the formulas, the cell ’Sheet7!$R$4’ holds the length of the rolling period (4
in our case). So yes, we can allow the user to vary the length of the rolling
period if we want. The charts will get updated to reflect the change.
Name: WeekLabelsRoll
=OFFSET(INDIRECT(Sheet7!$R$3),0, _
RangeWidth - Sheet7!$R$4 + 1,1,Sheet7!$R$4)
Name: RevenueRoll
=OFFSET(INDIRECT(Sheet7!$R$3),1,_
RangeWidth-Sheet7!$R$4+1,1,Sheet7!$R$4)
Name: ExpenseRoll
=OFFSET(INDIRECT(Sheet7!$R$3),2,_
RangeWidth-Sheet7!$R$4+1,1,Sheet7!$R$4)
The reference ’INDIRECT(Sheet7!$R$3)’ is from the work we did in section
10.5
Fig 10.6b Adding the rolling range as a chart series
Fig 10.7
Once a country is selected, we need to locate the table of that country listed in
columns C and beyond. We will do this by locating the row where the table for a
country begins.
We locate the row by using the match function on column C. We will use the
following formula that we will save with the name “Country_Row”. This is the
first purpose of having the country name in col C of the ’Location_List’
worksheet.
=MATCH(InputForm!$B$2,Location_List!$C:$C,0)
Notice that we are using the country name selected in cell B2 of “InputForm”
worksheet as input into the MATCH function. So the subsequent table selected
will depend on the country that the user selects.
Once the table is located, we need to find the number of rows and columns in the
table.
Although this can be accomplished by other methods that would require bit more
complicated formulas and few more names, we will adopt a simple approach.
We will use the COUNTIF formula to count the country name in column C. This
formula will be saved with the name “Country_Table_Height”. This is the
second use of listing of country names repeatedly in column C of
’Location_List’ worksheet.
=COUNTIF(Location_List!$C:$C,InputForm!$B$2) - 1
Remember, for this to work we need a country name in all the rows where a city
is listed. We reduce the number by 1 because we don't want the state name to be
listed in the city drop down.
Fig 10.7.3 Name manager box with names for ranges used for selection
The next number we need is the width of the country table. We do this by
applying the CountA formula to the country row. This formula will be saved
with the name “Country_Table_Width”.
=COUNTA(INDIRECT("Location_List!$" & _
Country_Row & ":$" & Country_Row)) - 1
If we dont have an entry in column ‘A’, the number would be one more than the
number of states listed for a given country. This would result in one blank entry
in the selection list. We can avoid this by using slightly more complicated
formulas or extra names, but for our purpose we will bear with one blank entry
at the end of the list.
The width of the table can now be used to create the list of states. Here is the
formula:
=OFFSET(INDIRECT("Location_List!C"&Country_Row), _
0,1,1,Country_Table_Width)
This formula is saved with the name “City_List”. Now use the named ranges
“Country_List”, “State_List” and “City_List” to apply data validation to the
cells B2 to B4 on “InputForm” sheet and the selection list is ready.
10.8 Using Names in VBA
Using Named ranges in VBA is quite simple. All we need to do is get a reference
to the range that the Name refers to.
Every name defined in a workbook can be associated with a “Name” object in
VBA. A particular name defined in the workbook, can be accessed using the
“Names” property of the workbook in which the Name is defined. This property
provides access to the collection of all names defined in a workbook.
Once a particular name is retrieved, we use the “RefersToRange” property of the
name object to get a reference to the concerned range. The code below shows
how this can be achieved. Once we obtain a range object, we can operate on it as
required.
Dim rng As Range
Dim nm As Name
Set nm = ThisWorkbook.Names("Country_List")
Set rng = nm.RefersToRange
Value property
This is one important property of a Name object. It returns a string containing
the formula in the refers to box (even if the name refers to a constant). Addresses
of ranges are in A1 style notation.
The string starts with an equal to sign in the beginning so if we intend to use the
value in any further processing, we should remove the “ = “ sign first. The code
below prints the value property of a name in immediate window. Please refer to
figure 10.7.3 to gain clarity.
Debug.Print nm.Value
The output is the formula that we would see in the Name Manager dialog box for
“Country_List”
Chapter 11
Working with charts in VBA
In this chapter we will see how we can access and do some basic operations on
our charts in VBA.
Although VBA provides capabilities to work with charts at a very detailed level
(like setting values, color, labels and so on.) it is always advisable to proceed as
follows:
1. Create the data range (can be static or dynamic) for the chart.
2. Create a chart with all required formatting.
3. Use VBA to transfer a copy of the chart outside of Excel. Either as a separate
image or to other programs.
11.1 Naming a chart
Yes, we can give names to charts too. What good will that do? Well, not so much
as in formulas. But names for charts come in handy when we want to work with
charts from inside a macro.
So how do we name a chart? Pretty much in the same way that we name a cell
range. Refer to figure 11.1.
Arguments:
a) filename: This is a string value and contains the name of the file to which the
chart will be saved. This argument cannot be omitted.
We need to provide the full path of the file along with extension.
b) filtername: This is an optional argument. It is of variant type and contains
what is called a Graphics Filter. For usual graphics formats like GIF and JPG
this can be omitted.
c) interactive: This is an optional argument. It is of Boolean type and dictates
whether Excel will display a dialog where the user can select settings for
Graphics filter. The default value is False, and we will go with that.
We want our macro to run in a way that minimum user intervention is
required. The code below will export our chart as a Gif file. The file is saved
in the same folder as the current workbook. Notice the use of ‘Path’ property
of Workbook object.
Dim co As ChartObject
Dim chrt as Chart
Dim wks As Worksheet
Set wks = Thisworkbook.Worksheets(“Sheet2”)
Set co = wks.ChartObjects(“RevExpChart”)
Set chrt = co.Chart
chrt.Export(ThisWorkbook.Path & "\chRevExp.gif")
Chapter 12
Other Cool Stuff
12.1 Trigger your macro when you are sleeping
Just trying to be playful with the title!
What it means is that we will look at ways to trigger our macros at a specific
time.
The technique depends on the scheduling capabilities of the operating system
and the code is written for Windows. Mac users would have to find a similar
option. Here is how we proceed.
Notes:
1. The technique described here requires that we switch off Excel’s macro
security. We should be very careful before running any macros that are
written by someone else.
If any of the concerned macros are written by other people, please run
those macros manually atleast once (on dummy data) before triggering
them automatically.
2. This technique requires that the launcher file is created in a new version of
Excel (2007 and above). If the launcher file has an extension of .xls, this
method can’t be used. The other files that contain the macro can be in
earlier versions, but the launcher file has to be in a newer version of Excel.
To enable all macros:
Click on “Developer” on the menu bar to activate the Developer ribbon. Then
click “Macro Security” in the “Code” group as shown in figure 12.1a
This would bring up the “Trust Centre” dialog box as shown in figure 12.1b
Select “Macro Settings” in the left hand pane.
In this dialog select the setting that says “Enable All Macros With
Notifications”.
Finally click on “OK”
In sub sections 12.1.1 amd 12.1.2 we will see how to write the code in “Open”
event depending on whether the macro(s) to be triggered is in the same or
different workbook(s).
Step 2: Schedule a Windows task
This is the step where we set the schedule for executing our launcher file. We
will be working with the Windows task scheduler.
a) Go to the Windows Control Panel and under “Administrtive Tools” look for
“Task Scheduler” or “Schedule Tasks”.
An easier way is to click on the “Start” button at the lower left of the screen,
and in the search box type “Schedule”.
This opens the Task Scheduler window as shown in figure 12.1d.
b) There are multiple ways to create tasks. We will go with the most basic steps
now. Other options can be explored once we are familiar with the basic steps.
Click on “Create Basic Task” option in the right hand pane marked “Actions”.
This is shown in figure 12.1d marked by number 1 in figure 12.1d. This step
opens the “Create Basic Task Wizard” dialog box as shown in figure 12.1e
Fig 12.1d Windows task scheduler
c) In the first screen of the wizard, provide a name and description for the task.
Click ‘Next’ to open the ‘Task Trigger’ screen of the Wizard shown in figure
12.1f
Fig 12.1e Create basic task dialog
d) Select one of the options in the “Task Trigger Screen”. Let’s say we want to
run our macro once a week, every week going forward. Select “Weekly” and
click “Next” to land on the next screen.
Task Trigger screen allows us to set when a particular task will be executed
e) The next screen of the Wizard allows us to set the exact schedule. A sample is
shown in figure 12.1g. Enter the details and click “Next to open the “Action”
dialog box shown in figure 12.1h
f) The Action denotes the “Type” of task we want to perform. Select “Start a
program” as shown in figure 12.1h and click “Next”. Clicking “Next” brings
up the “Start a program” dialog.
g) The “Start a program” dialog is where it gets really interesting. Click on the
“Browse” button and select the launcher file that we had created earlier. Click
“Next” to reach the finishing screen. Click on “Finish”
Fig 12.1i Start a program dialog for selecting the file to launch
We are ready to go. Now, as per the schedule, the task will get executed, the
launcher file will open and based on the code in the “Open” event procedure, the
appropriate macros will get executed.
End Sub
The illustration shown here is quite simple, but we can have code that calls
procedures with more complex arguments and also performs operations on
worksheet ranges.
However, this approach keeps the event procedure clean and easy to read. If any
procedure requires a change, we can do it in the related module without going
through a large individual block of code.
Notice that the name of the file is enclosed in single quotes ( ‘ ). This is
followed by an exclamation mark ( ! ). Then follows the name of Sub or
Function procedure. This entire combination is enclosed in double quotes.
We can have this value in a string variable, and then just use the variable in
calling the Run method.
2. arg1 to arg30: These are upto 30 arguments that are passed directly to the
macro that is being triggered. These should be specified in the exact same
order as they appear in the declaration of the Sub or Function being called.
It is important to note that these arguments can only be values, we cannot pass
objects to the macros we call with this method
Here is a sample code in the Open event of a launcher workbook. Notice how
two workbooks are opened one after the other and multiple macros are
executed
Workbooks.Open("C:\Reports\ReportBook.xlsx")
Application.Run "’ReportBook.xlsx’!Macro1"
Application.Run "’ReportBook.xlsx’!Sub2"
Workbooks(“ReportBook.xlsx”).Close(FALSE)
Workbooks.Open("C:\Sales\Sales AutoRpt.xlsx")
With Application
.Run "’Sales AutoRpt.xlsx’!GetData", Date
.Run "’Sales AutoRpt.xlsx’!CreateReport"
.Run "’Sales AutoRpt.xlsx’!SaveToPPT"
End With
Workbooks(“Sales AutoRpt.xlsx”).Close(FALSE)
The ’content’, in our case, is a specific sequence of bytes representing the EOCD
record.
Now, the EOCD record is a sequence of bytes. The problem is that majority of
these bytes are not printable characters, meaning we can’t simply type them from
our keyboards. So we will use the ’Chr’ and ’String’ Visual Basic functions.
The ’Chr’ function takes an integer that is the ASCII code for the character we
want to produce and returns the character. Its syntax is as follows
Chr(<ASCII code>)
We will use it to produce the first 4 characters of the EOCD record. First two are
the alphabets ‘P’ and ‘K’. The next two are non-printable characters.
The ’String’ function (note this is not the String datatype) takes the ASCII code
for a character and another integer. It then returns a string where the specified
character is repeated as many times as the second integer. The syntax is:
String(<Repetition> , <ASCII code>)
Now the last 18 characters of the EOCD record are all ‘NULL’ (used to represent
absence of a value in programming). It is different from Space (“ “) or Zero ( 0 ).
ASCII code of the NULL character is zero. Accordingly, for our purpose the
syntax required will be as follows:
String(18, 0)
The values returned by ’Chr’ and ’String ’ functions can be combined using the
usual string concatenation operator ( & )
Once the EOCD record is placed we use the ’Close’ function to close the file.
Now the file is marked as a compressed zip folder and the Windows operating
system will treat it like that.
Once we have an empty zip folder we need to add contents to it. This is where
we need to invoke the Windows Shell.
For our purpose, the Windows Shell can be thought of as a virtual representation
of thee hard disk in the windows operating system. Beware, this is not an
accurate picture, but we don't need the technical details.
To invoke the Shell & get a reference to it, we will use Visual Basic's
’CreateObject’ function. This function takes a string (the name of a certain
object that we want to create) and passes the request to the operating system
(OS). The OS will invoke the correct application, create the object and provide a
reference to it.
Each folder on the disk is called a ’Namespace’ in Shell. The files inside the
folders are called ’items’ and are held in the items collection.
A Namespace is obtained using the ’Namespace’ property of the ‘Shell’
application. The parameter to be passed is the path of the required folder
(existing or new one to be created). The catch is that ’Namespace’ property
requires the parameter of Variant datatype. If we simply type the path in the code
things will work. However, if we pass a string variable an error gets generated.
To overcome this problem we use the ’CVar’ function of Visual Basic to convert
a string variable to a Variant holding the same value. So the syntax to get a
reference to the required Namespace is
<Shell application>.Namespace(CVar(<strPath>))
Once we have a Namespace object we will use its method called ’CopyHere’
which takes an ’items’ collection as an argument and creates a copy of those
items inside itself.
We will use two Namespaces. The first, for the zip file we want to create. The
second for holding the files that we want to add to the zip file. The ’items’
collection of the second namespace will be passed as argument to the
’CopyHere’ method of the first.
Sub to create a zip file from files stored inside another folder
Sub CreateZipFile(ByVal strSourcePath As String,_
ByVal strZipPath As String)
Dim oApp , zipFolder, sourceFolder As Variant
Dim intFile As Integer
‘//** Get the next available new file number **//
intFile = FreeFile
‘//**Open the zip file for writing Binary data **//
‘//** since the file does not exist, it is created **//
Open strZipPath For Output As #intFile
‘//** Put in the EOCD record **//
Print #intFile, _
Chr(80) & Chr(75) & Chr(5) & Chr(6) & String(18, 0)
‘//** Close the file **//
Close #intFile
‘//** Invoke the Windows Shell **//
Set oApp = CreateObject("Shell.Application")
If oApp Is Nothing Then
MsgBox "Could not create zip application"
Exit Sub
End If
‘//** Mark the newly created Zip file as a Shell Folder **//
Set zipFolder = oApp.Namespace(CVar(strZipPath))
‘//** Mark the folder to be zipped as a Shell Folder **//
Set sourceFolder = oApp.Namespace(CVar(strSourcePath))
‘//** Copy the files inside Source Folder into the Zip Folder **//
zipFolder.CopyHere (sourceFolder.Items)
End Sub
Notice how the various Visual Basic functions (CreateObject, CVar, Chr, String,
Open, Print, Freefile) have been used in the code. Now we will use them to
unzip the files.
The following code shows a Sub-procedure to unzip a zip file and store contents
inside another folder
Sub UnZipFiles(ByVal strDestFolder As String, _
ByVal strZipPath as String)
Dim oApp As Object
Set oApp = CreateObject("Shell.Application")
‘//** Note that the following is a single line of code **//
‘//** Notice the ‘_’ at the end of the first line **//
oApp.Namespace(CVar(strDestFolder)).CopyHere _
oApp.Namespace(CVar(strZipPath)).Items
End Sub
12.3 Up(Down)loading files from web servers with ftp
Problem:
There are times when we have to put files on a server. The files may not
necessarily be in Excel format. They could be Comma Separated Values (csv) or
Images (for graphs).
Here are some reasons for doing so:
1. The server has a common repository from where our team members can
download the required files.
2. The server hosts a website. The server administrator could write a program to
read our files and then plug them into the website.
At times we may also have to fetch our input files from such servers.
Solution:
In such situations, if we have an FTP account on the server, we can build macros
that would automatically load the files in the correct location. We can even
download the required files from the servers using ftp.
‘FTP’ is short for File Transfer Protocol, which is a standard way to transfer files
between computers over the internet.
VBA does not have a built in function for FTP so we have to use some
workaround. Windows has a built in FTP utility but it does not employ the
secure FTP (SFTP) protocol that servers require nowadays. So, we must use a
third party software that can be automated with VBA.
We will use the WinSCP utility. WinSCP is a freeware that can be installed on
Windows and we prefer it because it well accepted in organizations as being
reliable and secure. WinSCP cannot be linked to VBA directly, but it can be
operated via the Windows command line. VBA has a way to send commands to
the Windows command prompt (without displaying it).
One more advantage of WinSCP is that it supports scritpting. Meaning that we
can store the various instructions for up/download in a text file and then just
command WinSCP to carry out those instructions in a sequence. We can create
text files from VBA. We will see the detailed procedure in chapter 14. Here we
will see the bare minimum functions required. Interested readers can digress for
a while and refer to section 14.3.
If we put all these facts together, we have a way to automatically upload and
download files. Let’s look at the technique in detail.
We will consider a basic setup where we have an account on an FTP server
accessible with a username and password. This is the setup we would normally
encounter more so if we are operating a personal website with some hosting
provider.
There are slightly different scenarios of connecting to the server that require the
use of things called “hostkeys” and SSL certificates (please don't worry about
the acronyms). In such cases, please consult the server tech support team and the
WinSCP documentation.
In any case, only the part for connecting to the server will change. Rest of the
procedure remains the same.
Note: This solution requires the use of FileSystemObject and its related
objects and procedures. This object is described in detail in chapter 14. The
reader may have to refer to that chapter at certain times. Appropriate cross
references are provided in this section whenever matter from chapter 14 is
used in the solution.
Get WinSCP
Do a Google search for "WinSCP" or, alternatively, go to visit -
https://winscp.net
Get the binary file for WinScp and install it. The website also provides extensive
documentation on scripting and alternative ways of using winscp.
Learn the basic WinSCP commands
Winscp has a long list of commands for various purposes. We will look at the
most basic ones required for up/downloading files.
Open
This command is used to establish a connection with the server. The syntax is
Open <protocol>://<username>:<password>@<domain.com>/
The parameters in the angle brackets have to be entered for a particular user.
“domain.com” is the site or server. Do not forget any of the “/”, “:”, or “@”
characters. Instead of domain name we can enter the IP addrest of the server.
Protocol is either, ftp, sftp of ftps and we are notified about it when the account
is first created. Here is one example with the IP address instead of the domain
name
Open sftp://myUserName:[email protected]/
Note: The IP address stated in the code above is fictitious. Any
resemblance with an actual IP address is purely co-incidental and not
intended by the author.
cd
For winscp, the folders where files are stored are called directories. It is the
common term used for folders on major operating systems like Unix and Linux.
All the up/down loading activities happen in a particular directory on the server.
That directory is called the “current working directory” or CWD in short.
When we log in, we may land up in the root directory of the server. However, we
may want to up/down load files in some other directory. In that case, the cd or
“change directory” command allows us to switch to the required directory. The
syntax for this command is as follows:
The folder path should start from the root level. For example, for a typical
website, the cd command can be....
cd /home/site1/public_html/wp-content/plugins/newPlugin/
Put
This is the actual command that uploads a file on to the server. The syntax is
Notice that the path should be enclosed in double quotes. This is the path on the
local computer (the one where our macro is being executed). The following set
of commands will load three different files from three different folders on the
local computer to the current directory on the server.
put "c:\New Reports\rpt_setup.pdf"
put "c:\Sales\Sales chart.JPG"
put "c:\Issues\Issue file.txt"
Get
This command downloads a file from the current working directory on the server
to a specified folder on the local computer. The syntax is
Get “<source file name>” "<destination path-local computer>”
Notice that the paths have to be enclosed in double quotes and sepatated by
spaces.
The commands below fetch files from the current working directory to two
different folders on the local machine.
get "service list.pdf" "c:\Input files\"
get “Dashboard.xls” "c:\New Reports\"
get “testfile.txt” "c:\Jan Sales Input\"
To fetch files from some other directory, use the cd command to first switch to
that directory.
Close
This command will terminate the connection from the server. There is no special
syntax. It is just a one-word command.
Exit
This commands stops the winscp program and returns control back to windows.
There is no special syntax. It is just a one-word command.
rm
This command deletes a file in the CWD. The syntax is:
rm “<file name>”
Notice the double quotes around the file name. Command below deletes an
image file
rm "revchart.PNG"
Command below will delete All text files in the current directory
rm *.text
rmdir
This command deletes an entire directory that is located in the CWD. The
directory that is being deleted should be empty. This command will cause an
error if there is even a single file or directory inside the directory being deleted.
Hence to delete a directory, first delete all the files inside it using rm then delete
the directory with this command. The syntax is:
rmdir “<directory name>”
Mkdir
This command creates a new directory in the CWD. We need to ensure that a
directory with the same name doesn’t already exist, else the command causes an
error. The syntax is:
mkdir “<directory name>”
Option
This command helps us to modify some of the behaviours of WinSCP when
operated via a script. There are a lot of options that can be turned on or off, but
we would work with only two and even these are not absolutely essential.
Following command stops the entire up/downloading operation if any single line
in the script causes an error.
option batch abort
Following command turns off the feature that asks for confirmation when trying
to upload files into a directory when files with same name already exist. The old
files are overwritten
option confirm off
#Comments.
In a winscp script file comments start with a #. A line containing a comment
should have # as the first character. This line is ignored and is not executed.
In case we are buiding a file that will be executed multiple times, it helps to add
comments so we can change it easily in future.
Create the script file from inside VBA
The next step is to create a text file with all the WinSCP commands in it. The
code shown here is for creating a script file for uploading files to the server. The
script first creates a new directory on the server and then uploads 3 files to that
directory. The script file created is shown in figure 12.3a
The script for downloading will be same except instead of “Put” command we
will have to write the “Get” commands. Comments have been added, but these
are not necessary.
Note: The code shown here requires use of the FileSystemObject. This object is
not present in VBA by default and has to be included as a reference. Please see
section 14.1 for details on how to do this.
Notice how the double quotes are added to the file paths using “”””
The variables ’strFile1’, ’strFile2’, ’strFile3’ are strings that hold paths of files to
be uploaded. In the example the paths are directly assigned to the variables, but
these can be taken from properties of File objects as described in Chapter 14.
Sub CreateScript()
Dim fs As New scripting.FileSystemObject
Dim ts As scripting.TextStream
Dim strFile1, strFile2, strFile3 As String
Set ts = fs.CreateTextFile(“C:\Scripts\winscpput.txt")
strFile1 = "c:\New Reports\rpt_setup.pdf"
strFile2 = "c:\Sales\Sales chart.JPG"
strFile3 = "c:\Issues\Issue file.txt"
ts.WriteLine "option batch abort" & vbCrLf & _
"# Disable overwrite confirmations" & vbCrLf & _
"option confirm off" & vbCrLf & _
"# Connect to SFTP server with password" & vbCrLf & _
"open sftp://user:[email protected]/"
ts.WriteLine "cd " & """" & "/home/site1/public_html/" & """"
ts.WriteLine "MkDir " & """" & "todays_files" & """"
ts.WriteLine "cd " & """" &_
"/home/site1/public_html/todays_files" & """"
ts.WriteLine "put " & """" & strFile1 & """"
ts.WriteLine "put " & """" &strFile2 & """"
ts.WriteLine "put " & """" &strFile3 & """"
ts.WriteLine "Close"
ts.WriteLine "Exit"
ts.Close
End Sub
The script file created is shown in figure 12.3a
Fig 12.3a Text file containing script for uploading files to servers using WinSCP
2. Full path of the folder where our script file is stored. For our example, assume
it is
"C:\script files\winscpput.txt"
We can execute our script manually from the windows command line. To do that
we need to bring up the windows command prompt either by typing “cmd” in
the app search box or, in older versions, by going to “Start button-> Run” and
typing “cmd”
At the command prompt we can type as follows (all in one line):
"C:\Program Files\WinSCP\winscp" /script="C:\scripts\winscpput.txt"
Notice the double quotes around the paths.
Now, we want to execute the same command from inside our macro. This is
where we use the Visual Basic ‘Shell’ function. Here is how this function works:
Shell function
This function runs an executable program and returns a positive if successful,
otherwise it returns zero. The syntax is as follows:
Shell(pathname[,windowstyle])
Arguments:
1. pathname: This is a string containing the full path of the file of the executable
(mostly with an .exe extension, and any ‘command line arguments’). The
documentation of the concerned application will tell us about the command
line arguments. In case of WinSCP, the path of the script file is the argument.
This is a required argument.
2. windowstyle: This is an optional argument. It is an integer and specifies the
size of the window that will open when the program is launched. The
allowable values are:
Value Description
0 Window is hidden
1 Window is restored to its original size and position.
2 Window is displayed as an icon (minimized). This is the
default value
3 Window is maximized.
In all these cases, the launched program receives “Focus” meaning any mouse
or keyboard input is directed to the program. If we want to work with Excel or
any other program, we need to click on the relevant window.
As an example, consider we want to start “Notepad” we can use the following
line of code
Shell("C:\WINDOWS\system32\notepad.exe")
Getting back to our use of WinSCP, the following Sub procedure first calls the
CreateScript sub and then runs the script
Sub CreateAndRunScript()
Dim strCmd As String
Dim retVal As Double
Call CreateScript
strCmd = """" & "C:\Program Files\WinSCP\winscp" & """"
strCmd = strCmd & " /script="
strCmd = strCmd & """" & "C:\scripts\winscpput.txt" & """"
retVal = Shell(strCmd)
End Sub
Notice how the command string has been compiled. The paths have been
surrounded with double quotes.
Why not execute a series of commands from VBA? because once WinSCP is
activated control passes to that application and subsequent windows DoS
commands may not be recognized. Our method is more reliable.
Chapter 13
Working with Email attachments
In this chapter we address the task of getting our input files from email
attachments and sending our finished work to others via email.
The email client software that we discuss in this chapter are by themselves
specialized tools and one chapter in one book will not be enough to discuss all
their capabilities. Instead, we focus our attention on solving certain specific
problems. Here is one of them:
Problem-1:
We receive periodic reports or data from a wide variety of team members. These
might be our sales team, members in other departments, customers and so on.
This data is sent to us over email.
We need to look for mails received on or after a certain date, from a certain
person, having a certain subject. Once we locate the email, we need to download
the attached data files. Then we can compile, analyze and refine the data to
extract information.
Along with the analysis that we want, the tasks of searching emails,
downloading files and compiling data can be automated in Excel.
Here is our solution scheme for searching the required emails and downloading
the attachments therein.
Solution scheme:
Log in to the email client (Outlook or Lotus Notes) with our credentials and go
to the Inbox
Get a smaller representation of the Inbox in which we can search quickly. The
actual Inboxes holding the emails are quite slow when we have to search for
emails one by one. Let us call the representation of emails as ‘entries’
If possible, filter the representation based on date or sender’s email address.
Now search through the smaller set of entries for the required subject or sender
name.
Once we get a required entry, get a reference to the underlying email.
Remember, we are working with a representation of the emails, not the emails
themselves.
Once we have the actual email, open it and download the attachments.
In this chapter we will look at ways to automate these steps in two of the popular
email clients MS Outlook and Lotus Notes.
Problem-2:
Another problem that we will deal with is to automatically send mails using an
Excel macro.
Solution:
This one will be easy because Outlook and Lotus Notes both have built in
functions that can help us to compile and send emails.
Note: Excel has a SendMail method that allows us to send the current
workbook (one containing our macro) via the default email system to a
number of receipients.
However, our focus is to find a general method not just to send the current
workbook but any other file(s) which may not be Excel workbooks. Also the
methods that we will explore, will allow us to send separate mails and
attachments to different people.
13.1 Working with Microsoft Outlook
Microsoft Outlook is an email client that comes bundled with Microsoft Office.
Like other Microsoft programs, this one too is extremely user-friendly, both for
onscreen users, and for people trying to automate it.
Note that Outlook is an email client. Meaning that just having Outlook does not
allow us to send/receive emails. We need to couple it with an email server or an
ISP’s email account to start using it.
For readers working in organizations where Outlook is the official email client,
your IT department will configure your account on Outlook with an Exchange
Server (a type of email server).
When we use Outlook on our own, we can configure it with our email accounts
with Gmail or Yahoo. We will see how to do this in the coming sections.
Outlook allows us to create multiple accounts with either a single consolidated
Inbox for all accounts or separate inboxes for each account.
We start by looking at the most important objects of Outlook’s object model.
Then we move on to using these objects for our basic tasks.
2. Quit: This method closes the Outlook session. The user is logged out of the
messaging system and any unsaved changes are discarded.
3. CreateItem: This method is used to create a new item. This will be useful
when we create a new email that we want to send. In that case the syntax
would be:
Dim olMail as Outlook.MailItem
Set olMail = olApp.CreateItem(olMailItem)
Each unique item in Outlook is called, well, an ‘Item’. The type of item is
based on its purpose and decides the location where it is stored.
For example:
· Emails are objects of type ‘MailItem’. These are stored in the Inbox
folder
· Contacts are objects of type ‘ContactItem’ and are stored in the
Contacts folder.
Most of these items would be created using the front end (GUI). We will
create only the MailItems from inside our macros.
NameSpace:
As noted already, this object is a gateway to the data of a particular profile.
If we have multiple profiles configured on a computer, we need to use the
‘Logon’ method to access a particular profile and start a session.
We will assume that we are working with a single profile on one computer.
Hence when Outlook is launched, we are directly logged in to the user’s profile.
Properties:
1. Accounts:
This object is a collection of all accounts set up for a particular profile. A
particular Account can be accessed using the ‘Item’ property of the collection,
and specifying the DisplayName of the Account. More on this when we
describe the Account object.
Note: if we have only one account setup on our computer, we can access the
data in that account directly without having to deal with the Accounts
collection.
Methods:
1. GetItemFromID:
This method is used to retrieve items stored in a particular folder under a
given namespace. Each item is given a unique ID called the ‘EntryID’. We
can use the entry ID to retrieve the object.
For now, we will just see the syntax of the method. We will see a concrete
example later in the chapter when we use this method to get an email from
our inbox.
Syntax for the method is as follows:
olNs.GetItemFromID(entryID, entryStoreID)
Arguments:
A) entryID: This is a long integer value holding the unique ID of an object
stored in Outlook.
We don’t have to be concerned with the exact value of the ID. We can
retrieve it in a variable using indirect methods.
However, this is a required argument and cannot be omitted. We will see
examples where we use this method and the EntryID to retrieve specific
emails.
B) entryStoreID: This is a string that identifies the delivery store (described
in a later section) from which we wish to retrieve an Outlook item.
This is an optional argument and we will omit this argument when we use
this method because we will access the default delivery store.
Account:
Account object represents one single email account configured in Outlook.
We can setup multiple accounts for any given profile. For example, one Gmail
account, one Yahoo and one account of our corporate mail server. (Assuming
off-course that our company allows us to use our personal mail)
More specifically, an account is a collection of folders for a particular profile.
Outlook organizes our data in folders. There are folders for received
emails(Inbox), a folder for contacts, a folder for Sent emails and so on.
Some of the folders are default, built into each account by Outlook when the
account is setup and some are created by the user.
A particular account can be accessed using the “DisplayName” property and
fetching it from the Accounts collection. The code for doing this would be as
follows:
Dim olAcc As Outlook.Account
Set olAcc= olNs.Accounts(strDisplayName)
Fig 13.1.2a Outlook home screen showing the accounts and the different folders
Another place to look for it is in the “Account Settings” dialog box as shown in
figure 13.1.2b. The DisplayNames of the two accounts are greyed out in both the
figures.
Fig 13.1.2b Account names listed in the “Account Settings” dialog box.
2. DeliveryStore:
This property returns a ‘Store’ object. It represents a file (.ost or .pst) on the
local computer or a network drive. It stores all the data items for the
concerned account. The folders in an account have to be stored in a physical
file. That file is represented by the DeliveryStore for the account.
The delivery store for a particular account can be obtained as follows:
Dim olStore as Outlook.Store
Set olStore = olAcc.DeliveryStore
We will not use the delivery store extensively so we don’t need to assign it to
any object. Our only purpose would be to get access to the folders contained
in the store. We will use the syntax shown in the description of
GetDefaultFolder method of the Store object.
Store:
This object represents a physical file created on a computer’s filesystem that
holds all the data of an account. It may be on the local computer’s hard drive or
on some network computer.
We will not be using the Store object extensively. But we will need one method
of that object to access our Inbox. Hence we provide a brief description of the
object here.
Methods
1. GetDefaultFolder:
This method returns a default outlook folder of a specified type.
By specified type we mean the type of data that is held in the folder. For
example, Inbox for emails and Calendar for invites, meetings, appointments
and so on.
By a default-folder, we mean the folders that outlook creates for a particular
account. The user can make other folders for his/her own purpose. The syntax
for the method is:
Dim olFld As Outlook.Folder
Set olFld = <DeliveryStore>.GetDefaultFolder(olFolderType)
Folder:
This object represents a set of items of a particular type. Inbox is a folder that
holds mail items (objects of type MailItem). Contacts is a folder that holds
contact items.
We obtain a folder object using the ‘GetDefaultFolder’ method of a Store object.
Properties
1. Items:
This is a collection of all the items held in the folder. To get the total number
of items in the folder, we can use:
lNumItems = olFld.Items.Count
Where olFld is the object that refers to the Inbox folder obtained using the
GetDefaultFolder method.
Methods
1. GetTable:
This method returns a Table object that represents (as a collection of rows) a
set of all the items contained inside a folder. The properties of each item in
the folder are represented by columns of the table.
The items contained in the Inbox are called MailItems. Each of these items
has some properties like the name of the sender, date of creation and a
subject.
When we call the GetTable method of the Inbox folder, we get a tabular
representation of all the mail items. Each email is a row in the table. One of
the columns will contain the time when the mail was created, and another one
will contain the Subject and so on.
Here is how we can use the method:
Dim olFld As Outlook.Folder
Dim olTblInbox as Outlook.Table
Set olFld = DeliveryStore.GetDefaultFolder(olFolderInbox)
Set olTblInbox = olFld.GetTable
It is not advisable to use this table right away as it will contain all the emails
contained in the folder. At the same time, it will not have all the columns that
we require.
A bit later, we will take a look at methods to restrict the rows contained in the
table to items that meet a specified criteria and also to add additional columns
to the table as per our requirements.
Table:
This object represents a set of items contained inside a folder in the form of rows
and columns. Each item becomes a row and each of its properties becomes a
column.
Once we have a table, we can visit each row and read data from it. We describe
this process in greater detail in the coming sections. However, there are two
special positions from where we cannot read data because they represent the
bounds of the table. The first point, is just before the first row. From this position
we can only move forward to the first row of the table. Another is the position
just after the last row. Also known as End of a table.
Properties
1. Columns:
This is a collection of all the columns held in a Table. As we mentioned, each
column in a table corresponds to one property of the items held by the Folder
underlying the concerned Table.
By default, Outlook provides only a small set of columns for every table. The
properties represented by those columns may not be enough to serve our
purpose.The following is a list of properties that Outlook provides, by default,
for the items in the Inbox folder:
a) EntryID: a unique ID for each Item in a delivery store. This property will
be useful to us in the future.
b) Subject: subject line of the email.
c) CreationTime: Not much useful as we are interested in the time when we
have received the email.
d) LastModificationTime
e) MessageClass
As we can see, not all the required properties are available. Usually we would
want to search emails using the sender’s name (or email) and the subject. If
we are creating periodic reports with inputs we receive from others, we would
want to restrict our search to items received on or after a particular date.
Fortunately, we can add certain non-default properties to the ‘Columns’
collection and then search items based on the values in those columns. The
regular ‘Add’ method of a collection comes in handy here. Here is the code
to add the columns for received time, sender’s email address and sender’s
name to the columns collection.
With olTblInbox.Columns
.Add ("SenderName")
.Add ("ReceivedTime")
.Add ("SenderEmailAddress")
End With
2. EndOfTable:
This is a Boolean value. A value of True indicates that the current reading
position is after the last row of the table. Any attempt to refer to a row beyond
this position will generate an error.
We will see an example where we use this property to loop through all the
rows of a table. We will keep looping till the value of this property changes to
True.
Methods
1. Restrict:
This method returns a Table object that represents a smaller set of the items
filtered out from an initial table based on certain criteria. The criteria are
specified with a Filter parameter using a string written in the Microsoft Jet
language.
Don’t worry, we won’t be getting into the nitty-gritty of the Jet language. We
will use only the most basic features.
We can avoid the use of filter altogether, only problem is that we would have
to check each and every row of the table, which is not very appealing. So let’s
take a look at methods for specifying filters for some basic properties.
We will use the following string variable for holding our filter criteria in all
our code examples:
Dim strFilter As String
a) Specifying a filter for ‘ReceivedTime’
We can specify a filter using ‘=’ operator for matching an exact date. The
syntax would be: (dates are specified im MM/DD/YYYY format
surrounded by single quotation marks)
strFilter = “[ReceivedTime]= ‘01/10/2008’”
We can filter emails received in a specific time period by using the ‘<=’
and ‘>=’ operators
strFilter = “[ReceivedTime] >= ‘01/10/2008’ AND ” & _
“[ReceivedTime] <= ‘02/28/2008’
b) Specifying a filter for ‘SenderName’
Please note that the sender name (John Doe in our example below) has to
exactly match the name as it appears in the Inbox in the ‘From’ column.
strFilter = “[SenderName] = ‘John Doe’”
We can search for more than one sender. The next example is for filtering
emails sent by John Doe or by Mary Jane.
strFilter = “[SenderName] = ‘John Doe’ OR ” & _
“[SenderName] = ‘Mary Jane’
c) Specifying a filter for SenderEmailAddress
Filtering by email address is a much better method because each email ID
is unique even if the persons have similar names.
Another advantage of using email address is that we don’t have to worry
about how the sender chooses to display his/her name in outlook. For
example, John Doe’s name might be displayed as ‘John D.’
strFilter = “[SenderEmailAddress] = ‘[email protected]’
Filtering based on multiple columns:
If we want to specify filter criteria for multiple columns, we can simply
combine the criteria using logical operators ‘AND’ and ‘OR’.
For example, see the following syntax for both, SenderName and
ReceivedTime.
strFilter = “([SenderName] =’John Doe' Or “ & _
“[SenderName] ='mary jane') And “ & _
“([ReceivedTime] >= '02/02/2007' And “ & _
“[ReceivedTime] <= '10/1/2007')”
The following piece of code checks if the subject of the email contains the word
“Sales Report” in it:
If(InStr(1, oRow(“Subject”), “Sales Report”)>0) Then
‘//** work with the email here. **//
End If
MailItem:
This object is the actual email we are looking for. Every object stored in Inbox
folder is of type ‘MailItem’.
The Row object described in the previous section gives us a reference to this
mail item. But to work further with the email, we need to extract this underlying
object.
How? Well this is where the EntryID property comes in. Recall from the
description of the ‘NameSpace’ object that we had a method which took the
EntryID and returned the specific Outlook Item.
Yes, you are right. We will use the ‘GetItemFromID’ method of the NameSpace
object.
Here is the syntax:
Dim olMail As Outlook.MailItem
Set olMail = olNS.GetItemFromID(oRow("EntryID"))
Here olNS is the NameSpace object which is instantiated at the beginning of our
macro.
Now we have the actual email with which we can work. Since we have already
obtained the subject and sender details. The next important task is to get the
attachments contained in the email.
But before that, let’s look at some of the important properties and methods of the
email item. Many of these are used when creating new emails.
Please recall that when we want to create a new email, we need to use the
CreateItem method of the Application object. Just to refresh our memories, here
is the syntax:
Dim olMail as Outlook.MailItem
Set olMail = olApp.CreateItem(olMailItem)
Properties
1. Body:
This is a string that contains the message text contained in the email. This
text does not contain any special formatting that may be applied.
This property is useful when we are creating emails that we want to send.
Here is an example of how we can create a body. The entire text of the body
is stored in a string variable and then added to the MailItem. Here is the code
Dim strBody As String
strBody = "Hi team," & vbCrLf & "Please find attached" & _
"the weekly status report" & vbCrLf & "regards"
olMail.Body=strBody
2. Attachments:
This property returns a collection object containing all attachments in an
email.
When the email is first created this collection is empty and we can add files
to We discuss this object in more detail in an upcoming section. this
collection.
3. Recipients:
This property is a collection of Recipient objects. As the name suggests,
these are people or teams who would receive our email. We discuss this
object in more detail in an upcoming section.
4. SendUsingAccount:
When creating a new email, the CreateItem method was called on the
Application object. But if our profile has multiple accounts configured, how
will Outlook know which account to use when sending the email or when
saving the mail as draft?
That is where this property comes in. For every email, we can explicitly set
the Account object. Here is the syntax:
Dim olAcc Outlook.Account
Set olNS = olApp.GetNamespace("MAPI")
Set olAcc = olNS.Accounts(<Account Display Name>)
olMail.SendUsingAccount = olAcc
Here ’olApp’ is an Application object that is already instantiated, and ’olMail’
is a new MailItem.
Important: This property has to be set before we call any method to send or
save the new email.
Methods
1. Send:
This method sends our email to the intended recipients that were added using
the Recipients collection.
The mail is sent from the account set for the SendUsingAccount property. The
syntax is pretty straightforward:
olMail.Send
2. Save:
This method saves our email in the Draft folder of the account that is set for
the SendUsingAccount property. Here is the syntax:
olMail.Save
Attachments collection & Attachment object:
‘Attachments’ collection is a collection of all attachments in an email. It is
obtained using the ‘Attachments’ property of a MailItem object.
Each member in the collection is an object of type ‘Attachment’ which is another
object in the Outlook object hierarchy.
We will not look at the entire list of properties and methods of Attachment(s).
Instead we will focus on two important tasks.
a) Add an attachment to a new email.
To add an attachment to an email, we call the Add method on the
Attachments collection of the email. The Add method has the following
syntax:
olMail.Attachments.Add(Source, Type, Position, DisplayName)
Arguments:
1. Source: This is a string containing the full path of the file that we want to
attach in the email. This argument cannot be omitted.
2. Type: This is a long integer value specifying the form in which the
attachment will be added in the email.
Each value is represented by a named constant. The valid values are shown
in the table below. We will always use olByValue. This is an optional
argument and can be safely ignored.
Named
Value Description
Constant
The attachment is a copy of the original file and can
olByValue 1 be accessed even if the original file is moved form
its location.
The attachment is an Outlook message format file
olEmbeddeditem 5
(.msg) and is a copy of some original message.
The attachment is an OLE document. An embedded
olOLE 6
chart or Excel sheet
3. Position: This is a long integer value and dictates the position of the
attachment relative to the text in the email body. It applies to messages that
use Rich Text Formatting for the body text. This is an optional argument,
and we will not use it. Our emails will have simple text bodies with default
attachment positions.
4. DisplayName: This is a string value that allows us to specify an alternate
name of our attachments in case we don’t want to show our original file
name. It is an optional argument and we will not use it. Here is the code for
adding an Excel file to a new email:
olMail.Attachments.Add(“C:\statusreport\Sales_Jun2015.xlsx”)
Recipients collection & Recipient object:
‘Receipients’ is a collection of all the people or teams who will receive our
email. It is obtained by using the ‘Recipients’ property of a MailItem object.
Each member of the collection is an object of type ‘Recipient’. We will not dive
into all individual methods and properties of this object. Instead, we will look at
two important tasks we do when creating a new email from within our macro.
For the code below, we assume that we already have a newly created MailItem
object in the variable ’olMail’.
a) Add recipients to our email.
To add a new recipient, we use the ‘Add’ method of the Recipients collection.
This method takes a string argument. The string can be either the
DisplayName or the email of the intended recipient.
We should use the email address which will always be unique and will free us
with details of specific display names.
The ‘Add’ method returns a new Recipient object which we can then modify
to set the To/CC/BCC. Here is the syntax:
Dim olRcp As Outlook.Recipient
Set olRcp = olMail.Recipients.Add(“[email protected]”)
The string argument for the Add method can be held in a string variable or it
can be read from cells in worksheet.
Suppose the email IDs are held in cells B2 to B10 on a worksheet named
‘MailingList’ in the current workbook (Cells A2 to A10 obviously hold the
name of the people) and, from row number 5 onward the recipients are to be
marked in CC. We can use the following code:
Set wks = ThisWorkbook.Worksheets(“MailingLIst”)
For i = 2 to 10 step 1
Set olRCP = olMail.Recipients.Add(wks.Cells(i,2).vlaue)
If (i>=5)then
olRCP.Type=olCC
End If
Next i
Following is a list of the essential objects and their important properties and
methods required for working with lotus notes email.
NotesSession:
This is the top-most object of the Object model. It represents the launching of
Lotus notes application from the ‘Start’ menu in windows.
We have to provide our password for starting the session.
Since we would need only one session for our entire code, we can instantiate the
notes session at the time of declaration.
Dim ns As New NotesSession
Methods:
1. Initialize:
This method represents beginning of notes. If Notes is already running, the
method returns the running session, else it launches Notes. Password of the
current user ID needs to be provided.
We assume that the user executing the code has Notes configured with his/her
userID
ns.Initialize ("userPassword")
2. GetDatabase:
This method is used to instantiate a NotesDatabase object with a particular
.nsf file. The method takes 3 arguments. The syntax for the method is as
follows
(You as a user should have valid access to the database for this to work)
Session.GetDatabase(Server,NsfPath,CreateIfFail)
Arguments:
A) Server: This is a string containing the name of the server on which the nsf
file is located. If we are working with a replica or archive on our local disk
(same where the macro is running), we can specify an empty string (“”) for
the server parameter. Sample code is provided at the end of this method’s
description.
B) NsfPath: This is a string containing the path of the nsf file as located on
the server.
C) CreateIfFail: This parameter should always be ‘False’ else Notes will try
to create the database if it is not found (which is more likely due to
mistakes in servername and path)
Here is a sample code when the database is accessed on a server:
Dim ndb As NotesDatabase
Set ndb = ns.GetDatabase _
("fx23mcq/M/ORG", "mail/usr12.nsf", False)
We can lookup the server and path parameters in our Notes workspace as follows
(Refer to figure 13.2.2a, bottom half of the dialog box has been cropped):
· Right-click on the icon of the database in the notes workspace and selecting
“Application” -> “Properties”.
· In the box that comes up, select “Database” near the top-left. Read the
parameters directly.
· Alternatively, in place of server name we could put the IP of the server in
double quotes. Check with your system administrator for obtaining the IP.
Here is a sample code when the database is an archive created on our harddisk:
Dim ndb As NotesDatabase
Set ndb = ns.GetDatabase("","archive/arch_2014.nsf", False)
Here is a sample code when the database is a replica created on our harddisk:
Dim ndb As NotesDatabase
Set ndb = ns.GetDatabase("", "mail/usr12.nsf ", False)
In case a valid database is not found, the NotesDatabase object (ndb) is set to
‘Nothing’. We should check for this before we proceed with our code.
If ndb is Nothing then
Msgbox (“No valid database found”)
Exit Sub
Else
‘//** code for working with the database goes here
**//
Endif
NotesDatabase:
This is the “.nsf” file where our emails reside. It is obtained using the
GetDatabase method described earlier.
Properties:
1. Views:
This property gives a collection of all the views for a database. The objects of
the collection are NotesView objects. This may be required when we want to
list out all the views of a database in case we don’t know the exact name of a
particular view which is required to work with documents. See the GetView
method below for an example.
The code shown here retrieves names (using the Name property of each view)
of all the views in a database and lists them in the intermediate window of the
code editor.
Dim vw As NotesView
For Each vw In ndb.Views
Set vw = ndb.GetView("$Inbox")
2. Replicate:
As the name suggests, the method replicates the database with its server copy.
The method returns True if replication process runs without error, else it
returns False. Syntax for the method is as follows
ndb.Replicate(ServerName)
Use this method immediately after the GetDatabase method to get the latest
server copy of the database. Check for successful replication – return value
should be ‘True’ . Exit the program if required.
If not(ndb.Replicate(“fx23mcq/M/ORG”)) then
Msgbox (“Replication failed”)
Exit sub
Else
‘//** code for working with the database goes here **//
Endif
3. CreateDocument:
As the name suggests, this method creates a document in a database. It returns
a NotesDocument object that represents the new document.
When working with emails, this produces the same effect as clicking “New
Memo” except that none of it is visible on the screen. This method takes no
argument.
Dim doc as NotesDocument
Set doc = ndb.CreateDocument
NotesView:
A view is a fundamental part of the database that we will be working with. A
view represents a collection of documents that users can see on their screens
when they open their Inbox or a Lotus Notes team room.
For emails, the Inbox is a fundamental view. Any folder created by a user is also
a View. A NotesView is obtained by using the GetView method of a given
database. Each document (in our case, an email message) is shown as a row in a
View. Each such row is called an Entry.
Collection of all rows is called an EntryCollection. It should be noted that these
rows by themselves are not the documents (or messages), but only visual
representation of some parts of the messages (Sender Name, Subject, Date etc.)
Properties
1. AllEntries:
This property gives a collection of all the entries in a given View. We can loop
through each entry one at a time. The property returns a
NotesViewEntryCollection object.
2. EntryCount:
This property gives the total number of entries in a view. For the Inbox, it is
the total number of messages (read and unread)
NotesViewEntryCollection:
This object is a collection of all the Entries present in a view. Although Lotus
Notes provides advanced methods for creating this collection, we will simply
focus on getting all the entries in a view and then looping through them.
Each member of the collection is a NotesViewEntey object.
Dim vw As NotesView
Dim vwEntColl As NotesViewEntryCollection
Dim vwEnt As NotesViewEntry
Set vw = ndb.GetView("$Inbox")
Set vwEntColl = vw.AllEntries
For Each vwEnt in vwEntColl
‘//** ------------------------------------------------------- **//
‘//** code for working with the database goes here **//
‘//** -------------------------------------------------------- **//
Next vwEnt
Properties
1. Count:
This property gives the total number of entries in a collection.
Methods
1. GetFirstEntry:
This method retrieves the very first entry in the view. The entry returned may
not be the topmost entry a user sees on the screen. This is because entries on
screen are sorted based on some column.
2. GetNextEntry:
This method takes one view entry as a parameter and returns the entry
immediately following it. If there are no more entries left in the view, it
returns ‘Nothing’.
The code below gives the main approach we will take when searching for
mails in an Inbox.
Dim vw As NotesView
Dim vwEntColl As NotesViewEntryCollection
Dim vwEnt As NotesViewEntry
Set vw = ndb.GetView("$Inbox")
Set vwEntColl = vw.AllEntries
Set vwEnt = vwEntColl.GetFirstEntry
While Not (vwEnt is Nothing)
‘//** -------------------------------------------------------- **//
‘//** code for working with the database goes here **//
‘//**-------------------------------------------------------- **//
‘//** Get the Entry immediately following the current entry held in
vwEnt **//
Set vwEnt = vwEntColl.GetNextEntry(vwEnt)
Wend
NotesViewEntry:
This object represents a row listed in a View. This object is obtained by using
one of the methods described earlier for NotesViewEntryCollection.
Properties:
1. ColumnValues:
This property gives a zero based array that contains the values for a particular
entry for each of the columns in the parent View.
For example, when the View is the Inbox, each entry is a row we see in the
inbox and the column values would be Sender’s Name, Subject, Date of
receipt, Size of the message and so on.
Not all the columns are visible in Lotus Notes. For example, there might be
columns between Sender’s Name and Subject columns that are not visible.
These may be related to things like Priority Flags, Availability on Sametime
messaging and so on.
Because of these hidden columns, it’s not possible to tell the exact position (or
array index) of a particular column that we are interested in (like Date, sender
or Subject) merely by looking at the Inbox screen.
A better way is to retrieve one entry from the view, and list out its column
values. The code below shows how to do this.
Dim vw As NotesView
Dim lColValUB As Long
Dim vwEntColl As NotesViewEntryCollection
Dim vwEnt As NotesViewEntry
Set vw = ndb.GetView("$Inbox")
Set vwEntColl = vw.AllEntries
Set vwEnt = vwEntColl.GetFirstEntry
lColValUB = vwEnt.ColumnValues
For i = 0 to lColValUB Step 1
Debug.Print vwEnt.ColumnValues(i)
Next i
Once the column locations are known, we can read the value at that location in
each row of the view and work with the rows that meet our requiremetns. The
upcoming code examples will make the clarify the technique.
2. Document:
Finally, we reach our email message. The ‘Document’ property of a
NotesEntry object gives us the actual document related to a given Entry.
Through this object we gain access to the body of the message (the text
written therein) and any attachments. We also get access to details about the
sender, subject etc, but as noted earlier we will take these details from the
Entry related to a document.
NotesDocument:
This object represents the actual content stored in the “.nsf” file. For our
purpose, this content is an email message.
Within the email message itself there are several parts. We will deal with the
main content of the message – what the sender has written and all the files that
are attached.
Properties
1. ColumnValues:
This property returns an array of values in the same order as they appear in the
document’sparent view.
The first value in the array is the value that appears in the view’s first column
for thedocument, the second value is the one that appears in the second
column, and so on.
Methods
1. Send:
This method sends a document by email. The syntax is as follows:
doc.Send(attachForm [, recipients ])
The first argument is a Boolean value that we set to false for emails. The
second argument is an array of strings that contains names of recipients of our
email. We ignore this argument and set the recipient email IDs with a different
method.
2. Save:
This method saves any changes you we have made to a document. The syntax
is as follows:
doc.Save(force , createResponse [, markRead ] )
All three arguments are of type Boolean and the third argument is optional.
These are not important when working with email. Hence we can specify
‘False’ for both the required arguments. So for a NotesDocument that is an
email that we have composed, we can write:
doc.Save False, False
3. ReplaceItemValue:
This method finds each item in the document that has a specified name and,
replaces each one new item, and then assigns the specified value to that new
item. If the document does not contain an item with the specified name, the
method creates a new item and adds it to the document.
This method comes in handy when composing emails. It allows us to add
values to fields like Subject, Emails of recipients etc.
The first line of code makes the document of type “Memo” or email (Lotus
notes can have many different ‘Types’ of documents). The next two lines add
the list of recipients and the mail subject.
doc.ReplaceItemValue "Form", "Memo"
doc.ReplaceItemValue "SendTo", "[email protected]"
doc.ReplaceItemValue "Subject", "test mail"
4. CreateRichTextItem:
Creates a new rich text item in a document, using a name we specify, and
returns the correspondingNotesRichTextItem object.
This is an indispensable method because the body of the email is an object of
type ‘NotesRichTextItem’ and contains any files attached in the email.
The code below creates a NotesRichTextItem named “Body” inside a
document. This code is useful at the time of composing a mail that we want to
send.
Dim rt As NotesRichTextItem
Set rt = doc.CreateRichTextItem("Body")
5. GetFirstItem:
This method takes a name and returns the first item in the document that
matches that name.
Beware that a mail document can have several items with the same name. This
method will return the first one. An example would be a mail that has been
forwarded multiple times or a mail having several sections. Such a mail can
have multiple items named “Body”.
When we describe the NotesItem object we will see a procedure that can be
used to work with multiple items with the same name. For now, since we are
concerned with getting input files attached in emails, we will assume that files
we are concerned with are in the first “Body” item.
The code below retrieves an item named “Body” from inside a document and
assigns it to a NotesRichTextItem object. This code is useful at the time of
reading a mail.
Dim rt As NotesRichTextItem
Set rt = doc.GetFirstItem("Body")
NotesItem:
A NotesItem object represents a specific piece of data contained within a
document.
Items are given names and our program can access individual items using their
names. However as mentioned earlier, a document can have multiple items with
the same name.
§ In case we want to process multiple items with the same name, we can get the
first item, assign it to a ’NotesItem’ object, process it, remove it using the
’Remove’ method of the ’NotesItem’ object, and then use the ’GetFirstItem’
method to get the next item of the same name.
Properties
1. Text:
This property returns a string with a plain text representation of a value held in
an item.
Methods
1. Remove:
This method deletes an item from a document. In order for the change to
become permanent, we have to call the ‘Save’ method of the document.
However, if we are only reading a mail document, we don’t need to save the
changes.
NotesRichTextItem:
This object represents the visual content we see in the email messages. This
object along with its child objects contains all the items like text, tables, tabs, file
attachments and collapsible sections that would exist within a document.
We will be mainly dealing with the email text and attached files.
Properties
1. EmbeddedObjects:
This property returns an array containing all the embedded objects, object
links, and file attachments contained in a rich text item. Members of the array
are objects of NotesEmbeddedObject class.
Methods
1. AppendText:
This method inserts text in a rich text item. We use this method at the time of
composing mails that we want to send out.
rt.AppendText(“Hello Team,”)
If we have the message stored in a string variable, we can use the method as
follows:
Dim strMsg as string
strMsg = “Please find attached compiled sales report”
rt.AppendText(strMsg)
2. AddNewLine:
Inserts a new line in the RichText item
rt.AddNewLine
NotesEmbeddedObject:
This object represents an individual embedded link or file attachment.
Properties
1. Type:
This property returns a long integer value representing the type of embedded
object.
Why is this important for us? A mail message can have several embedded
objects in the form of files as well as links. We are interested only in file
attachments, so before we go ahead with processing an embedded object we
need to ascertain its type.
For attached files, the value of Type property could be ‘1084’. However, we
will not rely too much on a numeric value. Instead we will rely on a defined
constant ‘EMBED_ATTACHMENT‘. So, to check if an object ’obj’ is a file
attachment, we use the code below:
If (obj.Type = EMBED_ATTACHMENT) then
‘//** Code to work with the attached file. **//
EndIf
2. Source:
This property returns a string value that represents:
§ Internal name that Notes uses to refer to the source document in case of
linked objects (we will not discuss these any further)
§ File name of the original file in case of File Attachments.
This property is most useful for checking the extension of multiple attached
files. In case our mails are expected to have multiple attached files like pdf,
text and Excel. We can check the extension of each file and download only the
relevant ones.
Methods
1. ExtractFile:
This method saves a copy of an attached file to the hard disk. Syntax for this
method is a follows:
obj.ExtractFile(strPath)
The argument strPath is a string containing the path where the extracted file
will be stored. It should include the name of the file. Since we are interested in
saving the files with the same names as in the email, we will compile the value
of the path. The following line of code shows how
strPath = “C:\Reports\” & obj.Source
Sub GetAttachedFiles()
Dim ns As New NotesSession
Dim ndb As NotesDatabase
Dim vw As NotesView
Dim vwEntColl As NotesViewEntryCollection
Dim vwEnt As NotesViewEntry
Dim doc As NotesDocument
Dim strSubject As String
Dim strAttachName As String
ns.Initialize (“password”)
Set ndb = ns.GetDatabase("server", "filepath", False)
Set vw = ndb.GetView("$Inbox")
If vw is Nothing then
Msgbox (“Inbox could not be retrieved”)
Exit Sub
Else
Set vwEntColl = vw.AllEntries
Set vwEnt = vwEntColl.GetFirstEntry
‘//** Loop over all entries **//
While not (vwEnt Is Nothing)
strSubject = vwEnt.ColumnValues(6)
If (vwEnt.ColumnValues(7)>= (Date-2)) And _
(InStr(1, “sales input”, strSubject)>0) Then
‘//** If the email was received in the past 2 days and **//
‘//** its subject contains ‘Sales input’, process it **//
Set doc = vwEnt.Document
Set rtitem = doc.GetFirstItem("Body")
If (rtitem.Type = RICHTEXT) Then
For Each embObj In rtitem.EmbeddedObjects
If (embObj.Type = EMBED_ATTACHMENT) Then
strAttachName = Trim(embObj.Source)
If InStr(1, “.xlsx”, strAttachName))>0 Then
‘//** Download only if the file has .xlsx extension **//
embObj.ExtractFile _
("c:\Reports\" & strAttachName)
End If
End If
Next embObj
End If
End If
‘//** Move to the next entry **//
Set vwEnt = vwEntColl.GetNextEntry
Wend
Endif
Set doc = Nothing
Set vwEntColl = Nothing
Set vw = Nothing
Set ns = Nothing
End Sub
In the Account Settings dialog box, click on the “New” button shown marked
with (2) in figure 13.3.2b. This will bring up the Add Account dialog box shown
marked with (3).
In the the Add Account dialog box select the radio button marked “Manual setup
or additional server types” marked with (4) and click on “Next”. This will take
us to the “Choose Service” screen of the same dialog. Part of this screen is
shown in figure 13.3.2c
On the “Choose Service” screen, select the “Pop or IMAP” option and click on
“Next”. This takes us to the “Pop or IMAP Account settings” screen shown in
figure 13.3.2d.
Fig 13.3.2c Choose Service screen
In the “Pop or IMAP Account settings” screen, please ensure the following:
· In the “Email Address” and “User Name” fields we need to enter the full
email ID including the “@gmail.com” part.
· Ensure that “IMAP” is selected in the “Account Type” dropdown.
· Incoming mail server should be: imap.gmail.com
· Outgoing mail server should be: smtp.gmail.com
Once the fields are filled up, click on “More Settings…” button marked with (1)
in figure 13.3.2d. This brings up the “Internet E-mail Settings” dialog box shown
in figure 13.3.2e.
In this dialog, go to the “Outgoing Server” tab and check the box labelled “My
outgoing server(SMTP) requires authentication”.
Select the “Log on using” option. In “User Name” box enter the full email ID.
Then, enter the gmail password.
Next, in the “Internet E-mail Settings” dialog box, go to the “Advanced” tab as
shown figure 13.3.2e. In this dialog, make the following entries:
In the dropdowns labelled “Use following type of encrypted connection:”, Select
“SSL” and enter the following
Fig 13.3.2f Internet Email Settings – Advanced Settings
Server Port Numbers:
· Incoming Server: 993
· Outgoing Server: 465
Once all values are entered, click on “OK”.
This should take us back to the “Pop or IMAP Account settings” screen of figure
13.3.2c. Now click on “Next”.
Outlook will test the connection by logging in to our Gmail account and then
sending a test mail.
And we are done !! We can use the techniques described in the earlier sections.
If the test is unsuccessful, ensure that all entries are correct and perform the steps
in second part of section 13.3.1.
Chapter 14
Managing files on disk
In this chapter, first we will discuss automation of tasks that deal with managing
(copying, moving, deleting and getting a list of) files residing on discs. By discs
we mean not just the computer’s harddisk but also any mapped drives and any
external storage media connected to our computer.
14.1 Manage files with VBA - FileSystemObject
One common task in automating Excel is to manage files stored on our harddisk.
Microsoft provides a very handy object for this purpose. It is called the
‘FileSystemObject’.
As far as Excel VBA is concerned, the most important task done using this
object is to get a list of all files and folders residing under a given folder path.
Once we get the full path of the file, we can use the methods of specific
applications (Word, PowerPoint, Lotus Notes and so on) to open, read and work
with the file.
Before we can use the FileSystemObject we need to add its library into our
project. Here’s how:
§ In the Visual Basic Editor go to the menus: Tools -> References
§ In the dialog box that comes up, scroll and select the ‘Microsoft Scripting
Runtime’ library and click OK.
14.1.1 FileSystemObject
This is the top level object representing the file system (collection of all drives
and the folders and files on a computer - the one on which the macro is running).
As we need only one object our macro, we can instantiate the object at the time
of its declaration.
Dim fso as New Scripting.FileSystemObject
For the code examples in this section, assume that we have:
§ A folder named “Reports” in our “C:” drive.
§ Subfolders named “Sales”, “Inventory”, inside Reports folder
§ Sales folder has 12 Excel files named “SalesReport_Jan2015.xlsx” to
“SalesReport_Dec2015.xlsx”.
§ Inventory folder 12 excel files named “InventoryReport_Jan2015.xlsx” to
“InventoryReport_Dec2015.xlsx”.
Properties
1. Drives: This is a collection of all the drives on the computer on which our
macro is running.
Methods
1. GetFolder: Takes a path to a folder as argument and returns a folder object.
The syntax for this method is:
Dim fld As Folder
To copy all the Excel files with names starting with ‘Sales_’ and having
any two characters followed by ‘2015’, to “Sales” folder, the syntax is
fso.CopyFile “C:\Sales_??2015.xlsx” ,“C:\Sales Reports\“
c) overwrite: This is a Boolean value. If set to True (the default value), any
existing files are overwritten. If set to False, files are not overwritten.
Notes:
1. If either ’source’ or ’destination’ does not exist, an error occurs. The macro
halts when the first error is encountered. No attempt is made to undo any
action that was taken
2. If there is a file that is marked as read only and, we are try to overwrite it,
an error occurs irrespective of the value of ’overwrite’.
3. Wildcards can occur only in the last portion of the ’source’ argument. For
example, the following value of source would cause an error:
“C:\*\Sales.xlsx“
6. CopyFolder: This method copies one or more folders from one location to
another. To move more than one folder, we have to use wildcard characters (
* and ?)
<filesystemobject>.CopyFoldersource, destination, overwrite
Arguments:
a) source: This is a string containing the path of one or more folders to be
copied. This string can contain wildcards but only in the last portion of the
path.
b) destination: This is a string value. Wildcards are not allowed in this string.
For example:
To copy all the folders with names ending with ‘Sales’, to the folder ‘Old
Reports’ the syntax would be
fso.CopyFolder “C:\Reports\*Sales”,“C:\Old Reports\”
To copy all the folders with names starting with ‘Sales_’ and having any
two characters followed by ‘2015’, syntax would be
fso.CopyFolder “C:\Reports\Sales_??2015”,“C:\Old Reports\”
c) overwrite: This is a Boolean value. If set to True (the default value), any
existing folders are overwritten. If set to False, folders are not overwritten.
Notes:
1. If either ’source’ or ’destination’ does not exist, an error occurs. The macro
halts when the first error is encountered. No attempt is made to undo any
action that was taken
2. The files and sub-folders within the source folder are also copied.
3. If ’destination’ is a read-only folder, or there is a read only file having the
same name as one of the copied files, an error occurs irrespective of the
value of ’overwrite’ argument.
4. Wildcards can occur only in the last portion of the ’source’ argument. For
example, the following value of ’source’ would cause an error:
“C:\*\Sales“
7. MoveFile & MoveFolder: These methods are similar to CopyFile and
CopyFolder methods except that the source files are deleted from the original
location after creating the copies. The syntax for these methods are:
<filesystemobject>.MoveFolder source, destination
<filesystemobject>.MoveFile source, destination
8. DeleteFile & DeleteFolder: These methods are used to delete specific file or
folder. The syntax for these methods are as follows:
<filesystemobject>.DeleteFolder strFolderPath, bForce
<filesystemobject>.DeleteFile strFilePath, bForce
The argument ’bForce’ is a Boolean argument with default value FALSE. If
set to TRUE, the methods will delete even those files or folders that are
marked as Read-only.
The ’strFolderPath’ and ’strFilePath’ are string arguments that hold the path
of the folder or file to be deleted.
An error occurs if the specified file or folder does not exist. Hence it is better
to check their existence by first using the ‘FolderExists’ or ‘FileExists’
methods.
9. CreateTextFile, OpenTextFile: These methods are used for creating new
text files and for working with existing text files. We will look into their
details in section 14.3 “Reading and Writing Text files”, where we will see
methods for working with text files.
14.1.2 Folder
Represents a particular folder on our computer. It is obtained by using the
‘GetFolder’ method of the FileSystemObject
Dim fld as Folder
Set fld = fso.GetFolder(“C:\Reports”)
Properties:
1. Name: This is a string value representing the name of a particular Folder. We
can read the name of a folder as well as change it.
‘//** Change name of Folder to “Report10” **//
fld.Name = “Report10”
14.1.3 Folders
This object is a collection of all folders inside a folder or drive. We obtain this
collection by using the SubFolders property of the Folder object.
We don’t need to set this to any object. We can use a For Each…Next loop to
work on each of the folders.
Dim fldColl As Folders
Set fldColl = fld.Subfolders
For Each f1 In fldColl
‘//** print the name of each folder **//
Debug.Print f.Name
Next
14.1.4 File
This object represents an individual file on the disk. Up to this point the type of
file does not matter. The file object just gives us a reference to the file on the
disk. In order to open the file, we need to launch the appropriate application. For
example, to open an Excel file, we need to launch Excel. A file object can be
instantiated in two ways:
1. Using the GetFile method of the FileSystemObject
Dim f as File
Set f = fso.GetFile(“C:\Reports\SalesReport_Jun2015.xlsx”)
2. Using the Files collection of a Folder Object
We can access the files one at a time using the code shown in the Section 14.1.2
(see the code given for ‘Files’ property)
Properties
1. Name: This is a string value representing the name of a particular File. We
can read the name of a file as well as change it.
The code below changes the name of the file we got in the code example
above.
f.Name = “SalesReport_June2015.xlsx”
Note that the file should not be open when we are trying to change its name.
2. Path: This is a string value representing the path of a particular File.
Methods
1. OpenAsTextStream: If the file object refers to a text file, then this method
can be used for opening and working that file. We will see more details in
section 14.3, “Reading and Writing Text files”, where we will see methods for
working with text files.
14.2 Let user select file(s) or folder(s) – FileDialog
Problem 1:
When processing files in bulk, our macro could not successfully process one or
more file. Now we need to execute the macro for only those files, so we need the
user to select the files and provide the location for our macro.
Problem 2:
Our input files are not consistently named, so we cannot program their names
into our macro (like SalesInput_<Region>.xlsx). We want the user to select the
files manually.
Problem 3:
We extracted our input files into a new folder. Now we need to select that folder
into our macro for processing.
Solution:
Use the FileDialog Object. In Excel VBA, this object is returned by FileDialog
property of Application object.
This object allows a user to select a single folder or, one or more files and
returns the paths of the selected objects in as collection.
We can use these paths with the FileSystemObject object described earlier, or if
the files selected are Excel files, we can use the paths as input to
Workbooks.Open method one by one.
The type of dialog, meaning whether it will pick files or folders, has to be
specified at the time of creating the object. The code below is used for creating
the dialog. Please note that creating a dialog does not display it on the screen. To
display the dialog, we have to use the ‘Show’ method described later.
Dim dlgFilePick As FileDialog
Set dlgFilePick = Application.FileDialog(dialogType)
The ’dialogType’ parameter takes one of four different values, out of which 2 are
important, these are:
· msoFileDialogFilePicker Allows user to select one or more
file(s).
· msoFileDialogFolderPicker Allows user to select a folder
We now list down the most important properties and methods of this object.
Properties (refer to figure 14.2 for details)
1. Title: This is a string value used to identify the dialog. It appears at the top of
the dialog’s window.
2. Filters: This is a collection representing the types of files that will be
available for selection. Filters are applicable only during File selection.
Each filter consists of two parts. First is a “Description”, a string that indicates
the type of files like, text, images, spreadsheet and so on. The second part
contains a string with the concerned file extensions separated by commas.
Each extension is prefixed with the wildcard character (*) to display all the
files of that particular type.
Filters appear as a dropdown at the bottom right of the dialog where the user
can select the type of files that will be displayed at a given time.
Filters are added to the collection using the Add method. The code below adds
two filters for the dialog. One for Text files and one for images.
dlgFilePick.Filters.Add "Text", "*.txt,*.rtf"
dlgFilePick.Filters.Add "Images", "*.jpg,*.png,*.gif"
In figure figure 14.2 we can see how the Title and Filters appear when the
dialog box is displayed.
Fig 14.2 A file dialog that prompts the user to select a file
Note that the “Type” property is set to msoFileDialogFilePicker but the
title has been set to “Select Input Folder”. This shows that the two
properties can be set independently of each other. At the time of writing
the macro we need to ensure the logical correctness.
If we want to use the same dialog box again, with a different set of filters,
we have to remove the existing filters first. This is done using the ’Clear’
method of the collection. Code below illustrates this:
dlgFilePick.Filters.Clear
3. AllowMultiSelect: This is a Boolean value and is used when the dialog is
used for selecting files.
Setting this property to True allows the user to select more than one file by
holding down the Shift or Ctrl key when clicking the file names.
4. SelectedItems: This is a collection having paths of all the selected Files (or
the single selected Folder). This collection should be accessed only after the
Show method has been executed.
The total number of paths is obtained by using the ’Count’ property of this
collection.
A particular path is obtained by using ’Items(intIndex)’ property. The code
below prints the path of each selected file into the Immediate window.
For i= 1 to dlgfilepick.SelectedItems.count step 1
Debug.print dlgfilepick.SelectedItems.Items(i)
Next i
If AllowMultiSelect is set to False, the path of one selected file is still returned
as a collection and it has only one item. In that case, the path can be obtained
by the code shown below:
dlgfilepick.SelectedItems.Items(1)
Once a path is obtained, we can use it as inputs for methods of
FileSystemObject and its child objects.
For example, if a folder is selected:
Set fld = fso.GetFolder _
(dlgFilePick.SelectedItems.Items(1))
If a file is selected:
Set f = fso.GetFile _
(dlgFilePick.SelectedItems.Items(i))
5. InitialFileName: This property can be used to set the initial location that will
be opened and shown to the user. It is provided as a string input.
If we specify only a folder, then the last character of the string should be “\”
We can specify a specific file name with its full path. For example,
dlgFilePick.InitialFileName = _
"C:\Reports\SalesInput_June.xls"
This will open the file open dialog and navigate to “C:\Reports”. The file
name ‘SalesInput_June.xls’ is visible in the “File Name” box. However, it is
always better to provide a folder path even if we are trying to select files.
Hence we would set the property value as:
dlgFilePick.InitialFileName = "C:\Reports\”
In case our input files or folder are located within the folder that has the
current workbook (the one containing our macro), we can set this property
very easily as follows:
dlgFilePick.InitialFileName = ThisWorkbook.Path & “\”
Notice that the ‘Path’ property of workbook object does not append the “\” so
we need to put that in.
Methods
1. Show: This method displays the FileDialog on the screen. Execution of our
macro pauses till the user clicks one of the buttons.
The method returns a long integer value indicating the action taken by the
user.
a) If, after selecting files/folder, the user clicks “Open” the value (-1) is
returned
b) If the user clicks “Cancel” the value (0) is returned.
We provide below code for two subroutines that use FileDialog object for
working with Files and Folder.
Code for using FileDialog to pick files:
Sub FileDialogTest()
Dim lResponse As Long
Dim fso as New FileSystemObject
Dim dlgFilePick As FileDialog
Set dlgFilePick = _
Application.FileDialog(msoFileDialogFilePicker)
With dlgFilePick
.Title = "Select Input File(s)"
.AllowMultiSelect = True
.InitialFileName = "C:\Reports"
.Filters.Clear
.Filters.Add "Text", "*.txt,*.rtf"
.Filters.Add "Excel", "*.xls,*.xlsm,*.xlsx"
End With
lResponse = dlgFilePick.Show
If lResponse <> -1 Then
MsgBox "Please select one or more files"
Exit Sub
Else
For i = 1 To dlgFilePick.SelectedItems.Count step 1
‘//** Work with the files here **//
Next i
End If
End Sub
Code for using FileDialog to pick a folder:
Sub FolderDialogTest()
Dim lResponse As Long
Dim fso as New FileSystemObject
Dim fld as Folder
Dim dlgFilePick As FileDialog
Set dlgFilePick = _
Application.FileDialog(msoFileDialogFolderPicker)
With dlgFilePick
.Title = "Select Input Folder
.InitialFileName = "C:\Reports"
End With
lResponse = dlgFilePick.Show
If lResponse <> -1Then
MsgBox "Please select a Folder"
Exit Sub
Else
Set Fld = _ fso.GetFolder(dlgFilePick.SelectedItems.Items(1)
‘//** Work folder and the files therein **//
End If
End Sub
14.3 Reading & Writing Text files – TextStream object
The Microsoft Scripting Runtime library provides us a very efficient way to read
from and write to text files.
Towards the end of this section, we will see two code listings. One for reading a
delimited file one line at a time and transferring the text to a worksheet. Another
for taking data from a worksheet, and writing it to a text file in a particular
format.
To work with a text file, first we need to have an object attached to it. This object
is called a TextStream and is part of the Microsoft Scripting Runtime library.
Let’s look at the details.
This object allows us to access a text file sequentially. Here is the code for
declaration of two objects that are always required when working with text files.
Dim fso as New Scripting.FileSystemObject
Dim ts as TextStream
Ø What does sequentially mean? Is there any other type of access?
Sequential file access means that to reach a particular point (character) in the
file we have to read each and every character up to that character.
If our file has several lines of data each of which represents a record, then to
retrieve one particular record we have to read each of the records preceding
that record. Even if skipping a line is allowed, we need to first read that line
and then discard its contents.
This differs considerably from random access where we can specifically
instruct the program to position itself in the file after a certain number of
bytes or characters.
Ø How does our program know about the position of the character or line?
Each TextStream object has a property called the file pointer. We can think of
this as a bookmark which marks the location inside the file from where the
TextStream will allow reading or writing.
Any attempt to read data will return data from the location of the file pointer.
Any data written to the file will be inserted after the location of the file
pointer.
Arguments:
1. FileName: This is a string containing the full path of the file that is to be
created. It a can be the path enclosed in quotes (“ ”) or a string variable.
2. OverWrite: This is argument takes a Boolean value. If set to true, any
existing file is overwritten. The default value is False.
If this argument is set to false, we should ensure that the file specified in
the FileName argument does not exist. Otherwise an error occurs when our
macro is executed.
3. Unicode: This Boolean argument that dictates the encoding of characters.
If set to True, Unicode encoding is used. The default value is False for
ASCII encoding.
ASCII is the most widely used format. Unicode encoding is used if text
can contain characters from languages other than English. We will ignore
this argument and use the default ASCII encoding since it is the encoding
style widely used for information transfer.
Example:
The code below creates a text file. We assume that the file does not exist.
But, we should ensure this by using the ‘FileExists’ method of
FileSystemObject and then deleting the existing file if required.
Both, ‘OverWrite’ and ‘Unicode’ arguments are ignored and hence their
default values ‘False’.
Notice that it is not necessary for the extension to be “.txt”.
Set ts = fso.CreateTextFile _
(“C:\DBUpload\InputData062015.dat”)
b) OpenTextFile method
This method opens an existing text file and then returns a TextStream object
referring to that file. We can use the new TextStream object to read from, write
to or append to the file. The syntax is
Set ts = fso.OpenTextFile _
(FileName [, iomode[, create[, fmt]]])
Arguments:
1. FileName: This is a string containing the full path of the file that is to be
opened. It can be the path enclosed in quotes (“ ”) or a string variable.
2. iomode: The Input-Output Mode. It is one of the predefined constants that
indicates the operation that the TextStream will perform on the file. It can
take one of the following three values:
Constant Stands For
ForReading Open the file only for reading. We cannot write to the
file.
ForAppending Open the file only for adding content to the end of the
file. Any content already existing in the file is
preserved.
ForWriting Open the file for writing. Any content that existed in the
file earlier is deleted before new content is added.
3. create: This is a Boolean value. If set to True, a new file is created in case
the file specified in ’FileName’ argument does not exist. The default value
is False.
It is better to use the default value and to check the existence of the file with
FileExists method of FileSystemObject object before we try to open the file.
4. fmt: It is one of the predefined constants that indicates the format of the file
to be opened. The default value is TriStateFalse which indicates that the file
is to be opened as an ASCII file.
We will always use the default value. This also means ensuring that we are
opening only ASCII encoded text files.
Here ’f’ is a File object as described in section 14.1.4. It is obtained by using
the GetFile method of the FileSystemObject object.
Arguments ’Iomode’ and ’fmt’ are the same as those described for method
(b).
Fig 15.1a Using Object Linking to bring Excel data into Word
Fig 15.1b Paste Special dialog box for pasting an Excel range.
A similar box appears when pasting charts.
1. For a data range.
If inserted as
A) “Formatted text” or “HTML Format” data is pasted as a table and items in
individual cells can be selected.
B) If “Worksheet object” or Bitmap option is selected, the data pasted as an
image. Text in cells cannot be selected.
It is better to paste it as text because, once we send out the Word report, some
team member may have to copy specific data points into another Excel or PPT
file. If we provide the data as an image, they would have to type the details by
hand.
2. For a chart
We can select either “Excel Chart object” or “Bitmap” option.
Advantage of Linking:
The good thing about linking is that when data in the chart or range changes, all
we have to do is right click on the pasted matter and select update link.
Disadvantages of Linking
a. If we send the Word document to someone else (or even move the Word file
to another computer), the linked workbook is not included. When the
recipient opens the word file a nasty error shows up shown in the following
figure.
Now if we click on the “Edit Links…” we would get the “Edit Links” dialog
box. Look at the “Status” column, it shows and error. This means that the
linked data cannot be updated.
The diagram here shows the most important objects arranged in a hierarchy.
Ovals represent collections of objects of a particular type, rectangles represent
individual objects.
Any object is accessed via the object above it. Connections between objects
don’t refer to the actual design of the objects, but to the path to be taken to
access a particular object.
Hence, to get a reference to a particular Range, we must first get a reference to a
Word Application object, then to the collection of all open Documents. From
there we get a reference to a particular Document that contains the range. We can
then access the range directly (difficult) or get a reference to a bookmark that
contains the range and then access the range (relatively easy if the bookmark is
exists).
15.4.1 Application
This is the topmost object in the Word object hierarchy. It gets created when a
user launches Word from the Start menu or another program launches Word.
Usage:
To get access to properties and methods that can be used with all open Word
Documents.
To start automating Word, we need to declare an application object in our macro.
Here is the syntax for declaration.
Dim wdApp As Word.Application
** Notice the six commas between the two arguments.
strFilePath: This is a string holding the path of the file that we want to open.
This is the only required argument.
bRevert: This is a Boolean value that indicates what to do if our user has
already opened the document. Here are the two values for this argument and
their effects
a) True: Unsaved changes will be discarded and document will be reopened
and reference will be provided to our macro.
b) False: Reference to the currently opened document will be provided to our
macro.
In our case it is possible that the user may be working on the document before
running our macro. Hence we should always set ‘Revert’ to False.
If we don’t want to use a lot off commas we can use names of the arguments
in the method call. Here is the alternate syntax
Set wdDoc = wdApp.Documents.Open _
FileName:="C:\Reports\Sales.doc", Revert:=False
15.4.5 Document
This object represents the word document that our macro will modify. Let’s look
at some important properties and methods. For the code in this section, consider
a Document object declared as wdDoc which has been initialized using the Open
method.
Properties
1. Bookmarks: Returns a collection of all bookmarks in the particular
Document. We will see the details of this object in the next section. The
syntax for accessing this object is:
wdDoc.Bookmarks
Methods
1. Close: This method closes the Word document. It takes three optional
arguments. The first is a Boolean value and deals with saving the changes.
The remaining two deal with format and routing. Here is the syntax that will
save the changes and closes the document
wdDoc.Close(True)
2. Range: This method returns a Range object that represents an area in the
document. The syntax for this method is:
Set wdRng = wdDoc.Range(start,end)
The arguments ’start’ and ’end’ are Variant values denoting the start and end
character positions of the Range. The first character in the document is at
position ‘0’.
In the code shown, wdRng is a Range object that we will see in detail in an
upcoming section. We will also ensure that we don’t have to know the exact
values of start and end arguments.
3. Save: This method saves ALL the open word documents in the Documents
collection. If a document has not been saved previously, a dialog box appears
asking the user to enter a name for the document.
wdDoc.Save
15.4.6 Range
A Range represents a specific area in a document. A range can be obtained by
specifying the starting and ending character positions in the document.
The advantage of using a range is that once we get a reference to a range in our
macro, we can place a variety of contents inside the range.
A range can contain text, images or tables. Let’s look at some important
properties and methods of this object.
For the code in this section consider a Range object declared as wdRng which
has been initialized using the Range method of the related Document.
Properties
1. Start: Holds a Long Integer value representing the starting character of the
range. Syntax for accessing/setting this property is:
wdRng.Start = 100
2. End: Holds a Long Integer value representing the last character of the range.
Syntax for accessing/setting this property is:
wdRng.End = 500
We will try to work without knowing the exact values of Start/End properties.
It is better to refer to the values of an existing range.
This concept will become clear when we write the code for data transfer.
3. Text: This property holds the text (without any formatting) that is contained
inside the range. Following line of code sets the text in a range.
wdRng.Text = “Hello world”
4. Tables: This property returns a collection of all the tables contained in a
range. A specific table can be accessed using its index number. The following
example shows how we can retrieve the first table in the range:
wdRng.Tables(1)
Although it is possible to have more than one table inside a range, it is better
to have only one table in each Range. This will avoid accidental deletion or
change in other tables when one table and its range are being modified.
Tables are themselves very complex objects with their own methods and
properties. Since our tables will be brought in from Excel we will not delve
into detail of the Table object.
However, there is one method of the Tables collection that we will require.
This method is used to delete a table contained inside a range. The following
line of code deletes the first table contained within a range:
wdRng.Tables(1).Delete
Methods
1. Delete: this method is used to delete the range. Although the method takes
arguments, all of them are optional and are not required when transferring data
from Excel to Word.
The syntax for this method is:
wdRng.Delete
2. PasteExcelTable: This method will paste a previously copied Excel Range.
The formatting applied will depend on the value of one of the arguments. The
syntax for this method is as follows:
wdRng.PasteExcelTable _
(bLinkToExcel, bWordFormatting, bRTF)
All three arguments are Boolean and are required to be specified. Here is a
description:
a. bLinkToExcel: A value of True will preserve a link to the Excel file.
Changes made in Excel will get reflected in the Word document when the
link is updated.
Setting this argument to False will paste a static table.
b. bWordFormatting: Setting this argument to True format the table based on
the formatting currently applied to Word document. Setting it to False will
preserve the original Excel formatting.
c. bRTF: Setting this argument to True will paste the table in RTF format.
Setting it to false will paste table in HTML format
We don’t want our tables to be linked to our Excel file, we want to preserve
the Excel color and font formats, so we will set the first two arguments to
False.
Pasting as RTF slightly distorts the text in the table, so we will paste the table
in HTML format. Hence we will set the third argument to False.
Hence in our example code we will use the following syntax:
wdRng.PasteExcelTable(False, False, False)
wdPasteBitmap 4 Bitmap.
Device-independent
wdPasteDeviceIndependentBitmap 5
bitmap.
wdPasteHTML 10 HTML.
wdPasteHyperlink 7 Hyperlink.
wdPasteShape 8 Shape.
Note that trying to access a bookmark that does not exist will cause an error and
our macro will stop executing. It is better to check if the bookmark exists before
trying to work with it. We can do the check using the Exists method listed here.
Let us see some important methods of this collection.
Methods
1. Add: This method adds a new bookmark in the document and then returns the
new bookmark. The syntax is as follows
Set wdBkMrk = _
wdDoc.Bookmarks.Add(strName, wdRng)
15.4.8 Bookmark
As noted earlier, a Bookmark represents a named area in a document. We can
think of it as a Range with a name.
The advantage is that we can access the bookmark and its corresponding Range
in our macro using the bookmark’s name. Once we get the Range, we can
modify the contents in the Range.
Properties
1. Range: This method returns the range that a particular bookmark refers to.
Methods
1. Delete: This method deletes a specific bookmark and its associated range.
15.5 Creating a Bookmark manually
To create Bookmarks for our content, we have to insert the content manually the
first time around. Once the content is placed, we proceed as follows:
Step1: Select the content
a. Selecting a chart or image
A chart, since it is pasted as an image, or an image itself, can be selected
simply by clicking on it. Small boxes appear around the image. Four at the
corners and another four each in the middle of each of the four sides.
b. Selecting a table
A table can be selected by first clicking anywhere inside the table so that a
crosshair appears at the top left corner of the table. Now we click on the cross
hair to select the full table.
c. Selecting text
Text can be selected in the same way as when we select for copying or cutting
the text. It is better to select one extra blank space at the beginning and the
end of the required piece of text.
Step 2:
In the “Insert” ribbon, click on “Bookmark” option in the “Links” group
This brings up the “Bookmark” dialog
1. The total volume of sales in the quarter was 50000 Fig. 15.7.1a Text that will
be updated by our macro
units
2. This shows a 10% increase Q-o-Q
3. The revenue for the quarter was $1.25 million
We need to create the following bookmarks (create one for each highlighted
piece of text):
Bookmark Holding value of Current value in figure
Name 15.7.1a
SalesVol Quarterly sales volume 50000
QoQIncrease Quarterly increment 10%
QtrRevenue Quarterly revenue $1.25 million
Let us look at the code for changing the text in these bookmarks. Note that in the
code we have set the text in 3 different ways. Directly typing the text in the code,
using a calculated value and using a value from a cell.
The variables used in the code are as follows:
nQoQ Variable (Integer) that is calculated in our macro.
dRev variable (Double) that takes its value from a cell on a
worksheet.
wdrng Word.Range object in the Word Document.
doc Word.Document set using the Open method
Here is the code:
Dim nQoQ as Integer
Dim dRev as Double
Dim wdrng as Word.Range
Set wdrng = doc.Bookmarks("SalesVol").Range
‘//** Putting text directly in the range **/
wdrng.Text = "50000"
‘//** Replacing the text of the range deletes the bookmark **//
‘//** so recreate the bookmark **//
‘//** To be cautious, just check for the bookmark, **//
‘//** if it doesn’t exist, recreate it **//
If Not doc.Bookmarks.Exists("SalesVol") Then
doc.Bookmarks.Add "SalesVol", wdrng
End If
‘//** Release the reference to the first range, **//
‘//** so that the variable can be used for another range **//
Set wdrng = Nothing
Set wdrng = doc.Bookmarks("QoQIncrease").Range
‘//** Setting the text using a calculated variable **//
wdrng.Text = nQoQ & “%”
‘//** Recreate the bookmark **//
If Not doc.Bookmarks.Exists("QoQIncrease") Then
doc.Bookmarks.Add "QoQIncrease", wdrng
End If
Set wdrng = Nothing
Set wdrng = doc.Bookmarks("QtrRevenue").Range
‘//** Setting the text from a value in a cell **//
fRev = ThisWorkbook.Worksheets(“Sales”).Cells(5,5).Value
wdrng.Text = “$” & fRev &" million"
If Not doc.Bookmarks.Exists("QtrRevenue") Then
doc.Bookmarks.Add "QtrRevenue", wdrng
End If
Notice that we have directly formed strings using numeric variables (’nQoQ’
and ’fRev’). If we require a special formatting (like specific decimal places,
thousand separators and so on), we whould use the ‘Format’ function described
in section 8.4.
Note: This method will transfer the entire data to Word. However, the
transferred table will look good only if the very first row of the table will start
at the very first line on a particular page.
This is one of those macros where reading the code alone will not help. The
reader is requested to actually create a long table in Excel and try transferring it
to Word
Here is the code for creating the table spread across multiple pages.
Sub CreateMultipageTable()
Dim nCut, nset As Long
Dim rwTableFirst, rwLast As Long
Dim colTableFirst, ncols As Integer
Dim wksTemp, wksMain As Worksheet
Dim bKeepLooping As Boolean
Set wksTemp = ThisWorkbook.Worksheets("Sheet38")
Set wksMain = ThisWorkbook.Worksheets("Sheet1")
rwLast = wksTemp.Cells(wksMain.Rows.Count,rwTableFirst). _
End(xlUp).Row
wksMain.Cells(rwTableFirst, colTableFirst). _
CurrentRegion.Copy(wksTemp.Cells(1, 1))
nset = 10
nCut = nset
bKeepLooping = True
While bKeepLooping
wksTemp.Rows(nCut + 1).Insert
wksMain.Cells(rwTableFirst, colTableFirst). _
Resize(1, nCols).Copy(wksTemp.Cells(nCut + 1, 1))
rwLast = rwLast + 1
nCut = nCut + nset
If nCut >= rwLast Then
bKeepLooping = False
End If
Wend
End Sub
Chapter 16
Working with MS PowerPoint
In this chapter we will discuss how we can transfer data automatically from
Excel to MS PowerPoint. We will look at the following types of data transfer
operations:
1) Transfer a piece of text to a specific slide
2) Transfer a fixed range (tabular data) from Excel to a particular silde
3) Transfer a chart from Excel and
4) Transfer a range (and hence the table of data) that would have variable
number of rows. This table may have to be spread across multiple slides.
Let’s get started righ away.
16.1 Connecting PowerPoint to Excel
In the Visual Basic Editor, go to “Tools” in the main menu and select
“References”. In the dialog box that comes up scroll and locate “Microsoft
PowerPoint X.X object Library”. The numbers “X” will depend on the version
available on our computer.
Select this option and click on “OK”
16.2 The MS PowerPoint Object Model
Let us look at the most important objects that are useful when transferring data
from Excel to PowerPoint.
The diagram here shows the most important objects arranged in a hierarchy.
Ovals represent collections of objects of a particular type, rectangles represent
individual objects.
Any object is contained in the object above it. Containment does not refer to the
actual design of the objects, but to the path to be taken to access a particular
object.
So, to get a reference to a particular Shape, we must first get a reference to a
Powerpoint Application object, then to the collection of all open Presentations.
From there we get a reference to a particular Presentation that contains the
Shape.
Within the Presentation, we first get a reference to the collection of all the Slides
in the file, then to the particular Slide that contains the Shape. From the Slide we
get a reference to the collection of all shapes on that particular slide. Then from
the collection we access the particular Shape we are looking for. Once we get the
shape, we can update its content (a table, chart or text)
16.2.1 Application
This is the topmost object in the PowerPoint object hierarchy. It gets created
when a user launches PowerPoint from the Start menu or another program
launches PowerPoint.
PowerPoint is a single instance application. So even if a user has already
launched the program, we can use the ‘New’ keyword in the declaration and get
a reference to the running application.
The syntax for obtaining a reference to the program is as follows:
Dim pptApp As New PowerPoint.Application
Properties
1. Presentations: This gives us a collection of all the Presentation (ppt, pptx
etc.) files that are currently open. The syntax for accessing this object is:
pptApp.Presentations
The ’FileName’ argument is a string holding the path of the presentation file
to be opened. It is the only required argument for this method, all others are
optional.
The ’WithWindow’ argument has the same meaning as defined for the Add
method. We will use the default value and not specify value for this argument.
The ’ReadOnly’ argument, if set to msoFalse, does not allow us to edit the
file. Since we are concerned with updating the data in the presentation, we
will leave this argument at the default value of msoTrue.
The ’Untitled’ argument, if set to msoTrue, will display the name of the file in
the title window. This is not much relevant at the time of editing the
presentation. We will leave this with its default value.
The syntax that we will most commonly use is:
Dim ppt As PowerPoint.Presentation
Set ppt = pptApp.Presentations.Open(strFilePath)
’strFilePath’ is a string variable that hold the path of the file to be opened.
16.2.3 Presentation
This object represents a specific open presentation file. We will now see a list of
important properties and methods of this object.
Properties
1. Slides: This property gives us a collection of all slides in the presentation.
We will see this collection in more detail in an upcoming section.
2. ReadOnly: This property returns a Boolean value. It returns True if the file is
opened as ReadOnly.
Methods
1. Save: This method saves the changes made to the presentation. The syntax is
ppt.Save
2. Close: This method closes the presentation. Note that this method will not
prompt if the user wants to save the changes. So before calling this method
we should always use the ‘Save’ method to save our changes.
ppt.Close
a. By its name:
Set sld = ppt.Slides(“SalesSlide”)
b. By its index:
Set sld = ppt.Slides(1)
Both, Name and Index are properties of the Slide object that we will see in an
upcoming section.
Properties
1. Count: This property gives a Long integer value showing the total number of
slides in the presentation file. The following line of code displays the total
number of slides
MsgBox ppt.Slides.Count
Methods
1. AddSlide: This method adds a new slide to the presentation file and returns a
slide object referring to the new slide. The syntax for this method is as
follows:
ppt.Slides.AddSlide(index, layout)
Or
Set srMySlides = ppt.Slides.Range(arrSldIndices)
arrSldNames: is an array of strings. The strings are names of the slides that
we want to group in the SlideRange
arrSldIndices: is an array of Long integers that are the indices of the slides
that we want to group in the SlideRange.
The advantage of this method is that we can include slides that are not in a
sequence, but which we want to treat as a group.
Note that there can be situations where we will have to produce a SlideRange
having just one slide.
3. Paste: If a slide, or group of slides, has been copied previously (see
section 16.2.5 on Slide object), this method will paste the slides in the
current presentation at the specified location.
The pasted slides are returned as a SlideRange object. Here is the syntax:
Set srMySlides = ppt.Slides.Paste(index)
index is a Long integer. It is the position of the slide before which the slides
will be pasted. For example, the following line of code will paste slides
before the slide that is currently at number 3 in the presentation.
ppt.Slides.Paste(3)
If we don’t specify a value for ‘index’, the slides are pasted after the last slide
in the presentation.
16.2.5 Slide
This object represents one specific slide in the presentation file. We have to
retrieve the slide from the Slides presentation. We repeat the syntax here:
Dim sld As PowerPoint.Slide
Both, Name and Index, are properties of the Slide object that we will see now.
Properties
1. Name: This property holds a string containing the name of a specific slide.
any slide that our macro updates in a presentation should have a unique and
meaningful name. This will allow us to refer to them as variables.
The following line of code sets the name of he fifth slide
ppt.Slides(5).Name ="sldSalesSummary"
2. SlideIndex: This property provides a long integer that marks the position of
the slide in the presentation file. The first slide has index 1.
Following line of code retrieves and stores the index of a slide object
Dim idx as Long
idx = SLD.SlideIndex
We cannot set the value of the index. If we paste another slide between any
two slides, the index values will change.
3. Shapes: this property gives us a collection of all shapes placed on a slide. A
shape is any item that is placed on a slide. It may be an image or a table or a
text box.
A specific shape can be accessed either using an index or the shape’s name.
The following line of code retrieves a shape from a slide
Dim shp as PowerPoint.Shape
set shp=sld.Shapes("chtSalesTrend")
Methods
1. Delete: This method deletes a specific slide. Syntax is:
sld.Delete
2. Copy: This method copies a particular slide into the clipboard. Then, this
slide can be pasted in the same or another presentation. The syntax is:
sld.Copy
3. MoveTo: This method moves a slide to a particular location within the same
presentation. The syntax is:
sld.MoveTo(pos)
pos is the new index position where we want the slide to be moved. Other
slides are automatically given new index values.
4. Duplicate: This method will create a copy of a particular slide and place it
immediately after the original slide. The new slide is returned as a Slide
object.
Dim sldDupe As PowerPoint.Slide
Set sldDupe = sld.Duplicate
16.2.6 SlideRange
This object represents a group of specific slides within a presentation. Unlike the
Slides collection it is not a collection of all the slides inside a presentation file.
We can choose which slides we want to add to the SlideRange.
Following lines of code create a SlideRange with three slides. First using slide
indices and then using slide names.
Dim srSlideGroup As SlideRange
Set srSlideGroup = ppt.Slides.Range(array(1,3,5))
Dim arrSldNames as Variant
arrSldNames = Array("sldSales", "sldRevenues", " sldInventory")
Set srSlideGroup = ppt.Slides.Range(arrSldNames)
All methods that are available for a single slide can be applied to the
SlideRange.
One particular use of this object is for deleting multiple slides that meet specific
criteria. If we loop through the slides and then check and delete specific ones, it
is not guaranteed to delete all slides. However, we can loop through the slides,
collect their indices/names and then generate a SlideRange. We can then delete
the entire SlideRange.
16.2.7 Shape
Any item that is placed on a slide becomes a Shape on that slide. The item could
be an image, a table or a text box that holds some text.
The syntax for declaring a shape object is as follows:
Dim shp As PowerPoint.Shape
Properties
1. Top: This property holds a numeric value. It represents the distance from the
top edge of the shape to the top edge of the slide.
2. Left: This property holds a numeric value. It represents the distance from the
top edge of the shape to the top edge of the slide.
3. Height: This property holds a numeric value. It represents the height of the
shape measured in Points.
4. Width: This property holds a numeric value. It represents the width of the
shape measured in Points.
Properties 1 to 4 all have the same Syntax. The following lines of code
illustrate the use of these properties
Dim shpTop As Double For pasting a copied Chart
shpTop = shp.Top Read the Top property of a Shape
shp.Left = 100 Set a numeric value for Left
property
shp.Width = shp2.Width Match width to that of another
Shape
shp2.Top = shpTop Match Top to a value stored in a
variable
We will never set the values of these properties as shown in ‘c’. When we
create the base file (discussed in section 16.4) we will let a macro place the
shapes at their default location. We will then manually reposition and resize
the shapes as required.
The macro that updates the contents will first use the syntax shown in ‘b’ to
read the value into a variable. The existing shape will then be deleted and
replaced with the updated content. Then we will use the syntax shown in ‘e’
to set the property values
5. Name: This property holds a string containing the name of the shape. We
should give a unique and meaningful name to any shape on our slides that we
want to update using our macros. The following line of code sets the name of
a shape
shp.Name = "tblSales"
The following line of code retrieves a particular shape from the Shapes
collection (described next) of a slide.
Dim shp As PowerPoint.Shape
Set shp = ppt.Slides("Sales").Shapes("tblSales")
6. TextFrame, TextRange & Text: These properties relate to a shape that can
contain text. For example, the area on the slide that holds the title of the slide
is such a shape. We can set the text in such area with the following line of
code
shp.TextFrame.TextRange.Text = "hello world"
Methods
1. Delete: This method deletes a specific shape from a slide. We will use it to
delete existing data before we add new data to a slide. The syntax is:
shp.Delete
c) Although multiple items can be copied and pasted, we should ensure that
we copy & paste only one item at a time. This ensures that the pasted item
is readily accessible at index position 1.
d) When a tabular Range is pasted using this method, the pasted data is
slightly out of alignment especially when a table is pasted across multiple
slides. In such cases, slight manual realignment may be required after the
macro is executed.
2. AddTextBox: This method adds a TextBox (an area that holds text) on a
slide. The textbox is added as a new Shape. The method returns a reference to
newly added shape. This shape has the TextFrame and TextRange properties
discussed in the section on Shape. The syntax for this method is as follows
(the variable shp is declared as a Shape object):
Set shp = sld.Shapes.AddTextbox _
(Orientation, Left, Top, Width, Height)
Bad news first. All the arguments are required. Also the ’Orientation’
argument is highly dependent on the language support installed on individual
computers.
The good news is, these arguments (in fact this method itself) are required
only for the first time when we are creating our base file (described in section
16.4).
For the ’Orientation ’ argument, we have one value that works for most
purposes. This value is ’msoTextOrientationHorizontal ’.
For the other arguments, we can initially assign arbitrary, but meaningful
numeric values, and then manually position and resize the textbox in our base
file. The macro that updates the elements doesn’t even need to know these
values. The following line of code adds a text box to a slide.
Set shp = sld.Shapes.AddTextbox _
(msoTextOrientationHorizontal, 100, 100, 100, 100)
Things to note when creating a TextBox:
a) Before we use PasteSpecial we should copy the required Excel chart or
Range using the ‘Copy’ method of the ChartObject or Range objects.
The method returns a ShapeRange object (which we will see in the upcoming
section 16.2.9) which is a collection of individual Shapes. To access a
particular piece of content we should use the ‘Item’ method of the collection
and indices of the shapes. The first object in the collection will have index 1.
Range: This method returns a ShapeRange object the details of which we
will see in the next section. For now, we will just see the syntax for creating
the Range.
Dim srMyShapes As PowerPoint.ShapeRange
fSet srMyShapes = sld.Shapes.Range(arrShpNames)
Or
Set srMyShapes = sld.Shapes.Range(arrShpIndices)
arrShpNames: is an array of strings. The strings are names of the shapes that
we want to group in the ShapeRange
arrShpIndices: is an array of Long integers that are the indices of the shapes
that we want to group in the ShapeRange
At times we may have to create ShapeRange having just a single Shape.
16.2.9 ShapeRange
This object represents a group of specific shapes present on a slide. Unlike the
Shapes collection it is not a collection of all shapes present on a slide. We can
choose which slides we want to add to the ShapeRange.
Following lines of code create a ShapeRange with three slides. First using shape
indices and then using slide names.
Dim srShapeGroup As ShapeRange
Set srShapeGroup = sld.Shapes.Range(array(1,3,5))
Dim arrShpNames as Variant
arrSnpNames = Array("shpSales", "shpRevenues", "shpInventory")
Set srShapeGroup = sld.Shapes.Range(arrSldNames)
We will not use this object extensively. However, we need to know it because the
PasteSpecial method that is used to transfer data from Excel to PowerPoint,
returns this object.
Methods
1. Item: This method returns a single shape from the group of shapes that is
held in a ShapeRange. This is the most important method of this object when
it comes to data transfer between Excel and PowerPoint. The syntax is:
Dim shp As Shape
Set shp = srShapeGroup.Item(idx)
The argument idx is either an index (integer) or name(string) of a particular
shape.
2. Delete: This method deletes all the shapes present in the ShapeRange. Syntax
is:
srShapeGroup.Delete
3. Copy: This method copies all the shapes in the ShapeRange into the
clipboard. Then these shapes can be pasted in the same or another
presentation or Slide. The syntax is
srShapeGroup.Copy
Once our macro has made the required changes, we will change the view back to
‘Normal’. The value for ViewType will be ’ppViewNormal’.
16.3 Using an already open presentation file
If we declare a PowerPoint application using the ‘New’ keyword, and if
PowerPoint is already running, we will receive a reference to the running
application.
However, if we try to open a file that is already open, we get a reference to a
read only copy of the file. Although our macro will execute without error and all
the updates will be made, when our macro tries to save the changes, macro
execution halts with a prompt to save the file with a different name.
To work around this problem, we will do the following:
1. Open the file any ways and obtain a reference to the file
2. Check the ‘ReadOnly’ property of the file.
3. If file is read only, meaning it is already open, close the newly opened version,
and then fetch the already opened version from the Presentations collection.
16.4 Creating the base file
In case of PowerPoint we will first have to write a macro that will aid us in
creating the base file. We will call this macro the “Base File Macro”. Here are
the steps:
1. Create the draft version of the presentation using organizational templates
and add any data that will be updated manually (without using a macro).
2. Note down the index numbers of the slides to be updated by our macro. Do
not add the dynamic content (the content that our macro will update).
3. Create a macro that will do the following:
a) Name the slides at the index numbers that we have noted.
b) Add the tables and charts using the paste special method on the relevant
slides.
c) Add relevant text boxes and add the required text.
When the data is added for the first time, the tables and images are placed on
the slides without proper alignment.
4. Open the presentation manually and, align and resize the tables, text boxes
and images. These positions and sizes will be used by the updating macro as
reference for positioning the updated charts and tables.
Following are the names we will use in our example:
Slide 2 will be named "sldSalesSummary". It will contain a chart which, in the
Excel workbook, is named "Top5Components". In the presentation the name of
this chart will be "chtSales". This slide will also hold a table named
"tblSalesSummary".
This slide’s contents and the names are shown in figure 16.4a.
Fig. 16.4a Contents and names of slide named sldSalesSummary
Slide 3 will be named "sldCurrIssues" and will hold a table. Initially the table
will fit on only one slide. But the number of rows may increase and the table
may have to be split across multiple slides. This table will be named "tblIssues".
These names can be anything based on the content of the slides. In the example
we assume that the slide shows the list of issues that the business is currently
facing. This slide’s contents and the names are shown in figure 16.4b.
The code that follows is for the Base File Macro that will insert contents from
Excel into a PowerPoint file. Wherever appropriate, comments have been
provided to explain the steps.
Sub CreatePPTTemplate()
Dim pptApp As PowerPoint.Application
Dim ppt As PowerPoint.Presentation
Dim shp As PowerPoint.Shape
Dim txtfrm As PowerPoint.TextFrame
Dim chobj As ChartObject
Dim strRevSummaryText As String
Set pptApp = New PowerPoint.Application
strRevSummaryText = _
"The total volume of sales in the quarter” & _
“ was 50000 units" & vbCrLf & _
"This was a 10%increase Q-o-Q" & vbCrLf & _
"The revenue for the quarter was $1.25 million"
'//** Open the presentation file **//
Set ppt = pptApp.Presentations.Open _
(ThisWorkbook.Path & "\" & _
"presentation.pptx", False)
‘//The file may already be open **//
‘//** in that case, the newly opened file will be a read-only copy **//
If ppt.ReadOnly Then
'//** Close the new file and get a reference to the already open file. **//
ppt.Close
Set ppt = pptApp.Presentations("presentation.pptx")
End If
'//** Setting names of slides, adding preliminary content **//
‘//** setting name of content **//
'//** Slide 2 - chart + table – sldSalesSummary **//
ppt.Slides(2).Name = "sldSalesSummary"
Set chobj = Worksheets("StageSheet"). _
ChartObjects("Top5Components")
'//** Copy the chart **//
chobj.Copy
'//** Paste the chart **//
Set shp = ppt.Slides(2).Shapes. _
PasteSpecial(ppPasteBitmap).Item(1)
Application.CutCopyMode = False
'//** Set name of the chart **//
shp.Name = "chtSales"
Set shp = Nothing
Set chobj = Nothing
Worksheets("StageSheet").Range("O1:Q6").Copy
Set shp = ppt.Slides(2).Shapes. _
PasteSpecial(ppPasteHTML).Item(1)
Application.CutCopyMode = False
shp.Name = "tblSalesSummary"
Set shp = Nothing
Set chobj = Nothing
'//** slide 3 - expanding table – sldCurrIssues **//
ppt.Slides(3).Name = "sldCurrIssues"
Worksheets("Sheet1").Range("A1:E10").Copy
Set shp = ppt.Slides(3).Shapes. _
PasteSpecial(ppPasteHTML).Item(1)
Application.CutCopyMode = False
shp.Name = "tblCurrIssues"
Set shp = Nothing
'//** Slide 4 - chart + textbox – sldRevSummary **//
ppt.Slides(4).Name = "sldRevSummary"
Set chobj = Worksheets("StageSheet")._
ChartObjects("Top5Components")
chobj.Copy
Set shp = ppt.Slides(4).Shapes.PasteSpecial _
(ppPasteBitmap).Item(1)
Application.CutCopyMode = False
shp.Name = "chtRev"
Set shp = Nothing
Set chobj = Nothing
Set shp = ppt.Slides(4).Shapes.AddTextbox _
(msoTextOrientationHorizontal, 100, 100, 100, 100)
shp.Name = "txtRevSummary"
shp.TextFrame.TextRange.Text = strRevSummaryText
Set shp = Nothing
ppt.Save
End Sub
Once the code is executed, we have to manually resize and align the various
charts and tables on the slides. We should also apply any required formatting to
the TextBoxes so that in future updates the formatting will be preserved. Once
done, future updates can be done with another macro.
16.5 Updating the base file with macros
In this section we will put together the code that we can use periodically to
update the presentation.
Note that the code only transfers updated content to a PowerPoint file. Updates
to the data should be done in Excel using methods described in other sections of
this book.
Once again, comments have been provided at appropriate places for explanation.
The complete code is around four pages long, so it has been split up into sections
based on functionality. All these lines can be part of the same sub-procedure or
function.
For the code listed in sections 16.5.1 to 16.5.4. the names (of slides, charts,
tables) and objects have the same meaning as in section 16.4.
The full solution is quite long so it has been split into parts and each part
combines explanation of general principles as well as specific examples.
For faster and better understanding, it is recommended that the reader should
copy the code lines into the Visual Basic Editor or just a plain text file and read
through the code.
Note down the values of the variables at different stages in the iterations. Do
this for atleast 3 iterations of the while loop.
For the purpose of example, we will continue with the presentation file (called
PPT file hereafter) that we described in section 16.4. Recall that we had
mentioned that Slide 3 will be named "sldCurrIssues" and will hold a table
where the number of rows may increase and the table may have to be split across
multiple slides also that this table will be named "tblIssues".
As per the scheme of the solution, when the table is split across multiple slides,
new slides will be created to hold the parts of the table. These new slides will be
copies of "sldCurrIssues". They will be named as "sldCurrIssues_n". Where "n"
is an integer that will take values like 1, 2, 3…and so on.
We should take note of the fact that we want to use the same macro in the future
as well. Which means the macro should be able to deal with existing slides
named as "sldCurrIssues_n".
Let’s look at the different parts of the macro one by one. Please note that this
whole code can be placed inside a single Sub procedure in the same order that
it appears here.
The parts marked with ( ) are explanatory notes and can be thought of as
comments.
Sub CreateMultiSlideTable(ByRef ppt As PowerPoint.Presentation)
Delete existing slides that hold parts of the table added earlier
'//** A string variable that will hold the list **//
‘//** of all slides to be deleted, separated by a comma. **//
Dim strSlideList As String
'//** Loop through all the slides of the PPT file **//
For Each sld In ppt.Slides
'//** Check if the slide name contains "sldCurrIssues_" **//
'//** Checking for _ is very important because **//
‘//* we want to retain the first slide-sldCurrIssues **//
If InStr(1, sld.Name, "sldCurrIssues_") > 0 Then
If Len(strSlideList) > 0 Then
'//** If the string already contains some text, append a comma **//
strSlideList = strSlideList & ","
End If
strSlideList = strSlideList & sld.Name
End If
Next sld
If Len(strSlideList) > 0 Then
'//** String length is greater than 0, **//
‘//** so there were slides having an underscore ( _ ) in their names **//
'//** Create a SlideRange out of those slides and delete all of them together. **//
ppt.Slides.Range(Split(strSlideList, ",")).Delete
End If
Get a reference of the existing slide (slide 3) and shapes on that slide
Dim shp As PowerPoint.Shape
Dim shpTop, shpLeft, shpHeight, shpWidth As Single
Dim sld As PowerPoint.Slide
Dim srTemp As Powerpoint.SlideRange
Set sld = ppt.Slides("sldCurrIssues")
Set shp = sld.Shapes("tblCurrIssues")
shpTop = shp.Top
shpLeft = shp.Left
shpHeight = shp.Height
shpWidth = shp.Width
shp.Delete
Set shp = Nothing
Loop to transfer table to slides
When we first create the basefile we will get an idea of the total number of rows,
including the header row that would fit on a slide. In the code this number is
denoted by numrows. Remember that his number also include the header row.
For the example assume that this numrows = 10
We also need to know the number of columns that we want to transfer on each
slide. In the code this number is denoted by numcols. Remember that his number
also include the header row. For the example assume that this numcols = 5
The full table that needs to be split up is present in the Excel workbook on
Sheet1. We will call it “Original Table”. We will loop over the original table and
in each run, or iteration, of the loop we will copy (numrows-1) of rows from the
original table to a temporary worksheet. This copied set of rows will be called
temporary table. On the temporary worksheet the data will be pasted in cell A1.
We will use the following variables to keep track of the rows being copied and
the slides being inserted in each iteration:
· rwStart: this is the first row of data (in the Originl Table) that will be copied
to the temporary table in each iteration. It will take a different value in each
iteration.
· rwTableFirst: is the first row in the original table. (For our example we
assume this to be row 1, meaning the data starts at row 2)
· rwTableLast: is the last row in the original table.
· colTableFirst: is the first column in the original table. (For our example we
assume this to be column 1)
· slidecount: is an integer variable that holds the number of the next slide that
will be added.
Figure 16.5.4 shows the Original Table and how the different variables will
vary with each iteration.
Ø Initialize the variables before the first iteration.
Set wksMain = ThisWorkbook.Worksheets("Sheet1")
Set wksTemp = ThisWorkbook.Worksheets.Add
rwTableFirst = 1
colTableFirst = 1
‘//** First row of data to be copied. **//
rwStart = rwTableFirst + 1
rwTableLast = wksMain.Cells(wksMain.Rows.Count, _
colTableFirst).End(xlUp).Row
ppt.Windows(1).Activate
ppt.Windows(1).ViewType = ppViewSlide
Ø This Boolean variable controls the loop. When we want to stop the loop, we
will set this to False.
bKeepLooping = True
Ø Assume initially that no new slides nave to be added
slidecount = 0
Ø Start the loop
While bKeepLooping
Ø Consider the first iteration of the loop. Starting at the first row of data in the
Original Table (this row is just below the header row, ’rwTableFirst’, in our
example it is row number 2), if we have to copy 9 rows ’(numrows – 1)’, we
can count and see that the last row of the copied data will be 10. A bit of
arithmetic shows that in each iteration, the last row of data to be copied is
given by (putting the values of ’rwStart = 2’ and ’numrows = 10’ should
give us 10 ) :
(rwStart + (numrows - 1) - 1)
At the end of the loop, this number will be either greater than or equal to the
last row of the Original Table. That is when we stop the loop.
If (rwStart + (numrows - 1) - 1) >= rwTableLast Then
numrows = (rwTableLast - rwStart + 1) + 1
bKeepLooping = False
End If
Fig 16.5.4 The ‘Original Table’ to be transferred to PowerPoint
Ø Clear the temporary table on the temporary sheet
wksTemp.Range("A1").CurrentRegion.ClearContents
Ø Copy the header row to the temporary sheet. Start at the first row & first
column of the Original Table and using the Resize method create a range
having 1 row and ’numcols’ columns.
wksMain.Cells(rwTableFirst, colTableFirst). _
Resize(1, _ numcols).Copy (wksTemp.Cells(1, 1))
Ø Next we start at the first column and row number ’rwStart’ of the Original
Table and using the Resize method create a range having ’ (numrows – 1)’
rows and ’numcols’ columns.
wksMain.Cells(rwStart, colTableFirst). _
Resize(numrows - 1, _ numcols).Copy (wksTemp.Cells(2, 1))
Ø If another slide has to be added, copy the last added slide, paste it just after the
last added slide, delete the table on this new slide, set the name of the new
slide to "sldCurrIssues_" followed by the slide number. Reference to the last
added slide is held in the variable sld.
Whenever a new slide is pasted, the variable is reset to refer to the new slide.
If no new slide is required, then the variable is set to the single reference slide
If slidecount > 0 Then
'//** additional slide required **//
'//** Switch to slide sorter view **//
ppt.Windows(1).ViewType = ppViewSlideSorter
'//** activate the slide to be copied **//
sld.Select
'//** make a copy of current slide object **//
sld.Copy
Set srTemp = ppt.Slides.Paste(sld.SlideIndex + 1)
Set sld = Nothing
Set sld = srTemp(1)
sld.Name = "sldCurrIssues_" & CStr(slidecount)
sld.Shapes("tblCurrIssues").Delete
Set srTemp = Nothing
'//** change the view back to SlideView **//
ppt.Windows(1).ViewType = ppViewSlide
Else
Set sld = ppt.Slides("sldCurrIssues")
End If
Ø Copy the newly pasted range from the temporary sheet and paste it in the new
slide. Set the name of the new table (on the slide).
Set rng = wksTemp.Range("a1").CurrentRegion
rng.Copy
Set shp = sld.Shapes.PasteSpecial(ppPasteHTML).Item(1)
Application.CutCopyMode = False
shp.Name = "tblCurrIssues"
Set rng = Nothing
Set shp = Nothing
Ø For the next iteration - Increase the slide count and calculate the start of the
new row.
slidecount = slidecount + 1
rwStart = rwStart + (numrows - 1)
Ø Notice that by this time the variable ’bKeepLooping’ may acquire a value of
False and the loop would end. The new values of ’rwStart’ and ’slidecount’
will not be used in that case.
In case ’bKeepLooping’ is still True move to the next iteration.
Wend
Set sld = Nothing
Ø Once the loop ends, we enter a second loop. Here we iterate through all the
slides that have "sldCurrIssues" in their name and set the size and position of
the table contained on them to the values we stored earlier.
For Each sld In ppt.Slides
If InStr(1, sld.Name, "sldCurrIssues") > 0 Then
Set shp = sld.Shapes("tblCurrIssues")
shp.Top = shpTop
shp.Left = shpLeft
shp.Height = shpHeight
shp.Width = shpWidth
Set shp = Nothing
End If
Next sld
Set sld = Nothing
ppt.Save
End Sub
Note: Depending on the complexity of the tables, some of the new slides may
not have perfect alignment. Some manual adjustment may be required which can
be done when checking the output.
Chapter 17
Microsoft Query & Databases
In this chapter we will look at a technique that Excel provides us to retrieve data
from a variety of databases into our Excel file. The concept we are going to
discuss is called ‘Microsoft Query’ (MS Query).
The first section of this chapter describes some very elementary database
concepts. It is written for readers who have absolutely no familiarity with
databases. Readers who are aware of concepts related to databases can safely
skip that section. However, reading it might provide some pointers to effectively
pulling data into Excel.
We will then move on to working with MS Query. The steps involved are as
follows:
1. Launch MS Query
2. Connect to the required database.
3. Specify the data to be retrieved (forms the bulk of this chapter)
4. Refresh the data at a later date (we will cover both manual & automatic
modes).
5. Launch our macros automatically when data is refreshed in order to process
the data further.
We will cover steps 1, 3, 4 and 5 in order first. This is becase these steps are not
dependent on the database we are connecting to. For illustrations we will use MS
Access however the steps are the same for any other database.
Step 2 depends on the specific database we are connecting to and will be covered
in the last few sections. In those sections we will see how to connect to some
widely used databases. Of special interest is the section on MySQL (pronounced
as ‘My Sequel’) database because Microsoft Office does not ship with a default
bridge (technically called a ‘Driver’) for MySQL. However, considering that a
large number of websites today run on WordPress, which uses MySQL, it should
provide something of value to readers who own WordPress websites.
There will be very few lines of code in this chapter. Why? For the following
reasons:
a) We cannot write code for interacting with most of the databases unless we
know a little bit of Structured Query Language, aka. SQL (pronounced as
'sequel').
SQL is a special language used to extract, as well as change the data residing
in a database. It has its own syntax and its own set of keywords. Furthermore,
SQL syntax varies slightly between different databases. Learning SQL is
hence out of the scope of this book.
b) If we absolutely have to write code, there are much better options available.
However, these require use of some specialized programming concepts like
ADO (ActiveX Data Objects) or DAO (Data Access Objects) which allow us
to not only extract data but also to change data in a database. However,
programming with these objects again falls outside the scope of this book.
c) Even if we use ADO or DAO (without using the features to change the data),
it would allow us only to pull certain filtered dataset from the database.
Majority of calculations, analysis and charting would still have to be done in
Excel.
17.1 Short visit to the Database world.
Most of the databases used for commercial applications belong to a family called
Relational Database Management Systems or RDBMS. Some of the famous
ones are Oracle, MySQL, Microsoft Access, Sybase, SQLLite, PostgressSQL,
DB2 and so on.
There are some special databases that employ non-relational designs but these
are used for specialized purposes like analytics or even email (Lotus Notes
“.NSF” file is an example).
We will limit our discussion to RDBMS since that is what we will most likely
encounter. Furthermore, if an appropriate connector (Driver) is provided for non-
relational databases, either by Microsoft or by an independent party, we can use
the methods in this chapter to interact with those databases. For example,
NotesSQL is a driver for Lotus Notes databases. We will discuss a bit more
about drivers in a short while.
Coming back to our topic, in an RDBMS data is stored in the form of tables, a
set of rows and columns. The columns are also called ‘Fields’.
To give a crude analogy with Excel, each database is similar to one Excel
Workbook and the tables in the database are similar to the Worksheets in a
Workbook.
Each table holds data for a particular class of items or entities.
For example, employees of a company represent one class of entities about
which the company may want to store data. Each row will have data for one
employee. The fields would contain details like first name, last names, date of
joining, department of the employee, a unique employee number for each
employee and so on.
Another set of entities would be all the orders received by the company. Each
row would contain information about one order. The columns would contain
information like the unique ID or number of the order, the date when the order
was received, name of the sales representative who took the order, an identifier
for the customer and so on.
Querying a database
The act of interacting with a database is called ‘Querying’. Queries are
commands that a user sends to a database. As you must have rightly guessed,
queries are written in SQL (Structured Query Language).
Queries can be used for a variety of purposes. For example, updating existing
data, inserting new data, deleting data, creating new tables, deleting entire tables
and (the subject of our interest) retrieving data from a database.
Database can also have Views. Views are virtual tables or technically speaking
Stored Queries. They are instructions written in SQL that pull data from different
but related tables based on specific criteria. These data are then combined and
presented in the form of a table. Whenever a user tries to open a View, the data is
pulled on the fly and presented as a table.
Relations between tables in a database
When we pull data into Excel we may want to combine data in two or more
tables for our analysis. For this purpose, there is one important concept from
databases that we need to understand.
Consider the two tables shown in figure 17.1a Only the first few rows are shown
here. Suppose we have imported data from the Employees table into a worksheet
named “Employees” and data from Orders table into a worksheet named
“Orders”.
Fig 17.1a Employees tables
Notice that in the ‘Orders’ table the ‘ID’ of the sales representative is the
‘Employee ID’ of that person from the ‘Employees’ table. If we want to create a
sheet that shows the name of the sale representative against each order, we can
use a VLOOKUP function to bring in the names from the ‘Employee’ sheet. This
function would be inserted in the ‘Orders’ sheet and the first argument (lookup
value) would be the ‘Employee ID’ of the sales rep.
Notice that in this case, the employee ID is the key column or field that relates
the two tables. However, in the ‘Employees’ table this column has a unique
value in each row and uniquely identifies each row or record. Such a column is
called a Primary key column for that table. So ‘Employee ID’ column is the
primary key for the ‘Employees’ table.
In the ‘Orders’ table the column containing the Employee IDs is allowed to have
repetition. In this table the Employee ID is called a foreign key. For the ‘Orders’
table the ‘Order ID’ column is the primary key.
Here are some important points to remember:
1. A primary key column does not permit duplicate entries or blank entries
(called NULL values in database parlance).
2. If the database is well designed, even the foreign key columns will not permit
NULL values. Even orders that are entered through a website by the
customers should have an identifier to separate them from orders taken by
sales representatives.
3. A table can have more than one primary key columns. However, the primary
key is unique. In such cases, the primary key is formed by combining the
values in the multiple primary key columns. For each row, the combination of
values in each of the key columns will be the unique identifier for that row. In
such cases, the primary key for each record is called the ‘Composite Key’.
It is helpful to know what those columns are so that we don’t forget to extract
them into our worksheet. These can then be combined (may be by
concatenation) used in our lookup functions.
4. Since the primary and foreign key columns won't have blank cells they are
very useful to find the last row of the data in our worksheets.
17.2 Working with MS Query
17.2.1 Launch MS query
On the menu bar, click on ‘Data’ to show the ‘Data’ ribbon. On the ‘Data’
ribbon, click on 'From Other Sources'. We can see that several options are
already listed for specific databases like MS Access, SQL server and so on.
These work only on specific databases and sometimes have a limitation that we
would have to retrieve an entire table. We cannot select specific columns or
combine tables.
We will discuss MS Query which is a much more flexible method. Select “From
Microsoft Query” option in this menu.
A dialog box appears where we have to select the data source (the database) that
we are interested in. For purpose of illustrations we will select “MS Access
Database”. We will deal with this step in more detail later on from section 17.5
onwards. For now, just ensure that the little check box that says "Use the query
wizard to create/edit queries" is checked.
Fig 17.2.1 Dialog box to select a data source
Once we select a data source, we are presented with the first dialog box of the
Query Wizard. This interface allows us to quickly select the required table and
columns therein and also to specify filters and sorting orders on one or more
columns without going into complicated operations of MS Query.
We will first see how to include data from a single table. In a later section we
will deal wih multiple tables.
Just like in Excel, the column selected first gets the priority when sorting. So if
we provide our selection as shown in figure 17.2.2d then we get the following
result.
Fig. 17.2.2e
1. Open the main MS Query window, where we can add more tables, add
relations between tables and specify additional criteria. We will see this in an
upcoming section on multiple tables.
2. Send the data into Excel. Since we have selected only a single table we can
select this option and click ‘Finish’.
Step 5 - Present the extracted data in Excel
MS Query provides several options for presenting the extracted data. We can
save it as an Excel Table, a PivotTable or a PivotChart with PivotTable.
Fig. 17.2.2f Sending selected data to Excel
2. Buttons or Icons
Several buttons are available that are mainly shortcuts to the menu items. We
will describe some buttons here others will be described as related topics are
discussed
Ø Auto-query button ( )
If this button is pressed, MS Query retrieves a new set of rows whenever we
make any change in any of the areas. Meaning everytime we add/remove a
column to the fetched rows area, or add/remove some criteria, MS Query will
run a new query on the database and pull a new set of rows automatically.
This is OK if we are working with a database on a local machine or we are
fetching a small set of rows over a high speed corporate network. However, for
a large number of rows fetched over normal networks it would take a long time
and MS Query may stay hung up for sometime.
We will avoid using the Auto-query mode. If Auto Query mode is on this button
appears depressed. We can click it again to turn off Auto Query mode.
Ø Query now button( )
This button is used to retrieve a new set of rows when Auto Query mode is
switched off. The query will not run automatically. We will need to click on this
button to retrieve a new set of rows after changing some criteria or
adding/removing columns.
This functionality is good because we can add all required columns and criteria
first and MS Query will then have to retrieve a relatively smaller set of rows at a
time instead of repeatedly refreshing the data.
3. Tables area
It shows all the tables that are available for inclusion in the dataset. Each table is
shown as a box with a list of all its columns.
If this area is not visible go to 'View' menu and select 'Tables'or click on the ( )
icon.
4. Criteria area
This area is used to specify the filtering criteria based on which rows will be
fetched from the database.
If this area is not visible go to 'View' menu and select 'Criteria' or click on the (
) icon
5. Fetched rows.
This area shows all the rows that are currently extracted. This area can get pretty
large based on the number of rows retrieved.
The ‘And’ and ‘Or’ radio buttons are used to specify if the criteria have to be
satisfied simultaneously or independently.
The ‘Total’ field has a drop down where we can select the summary function that
is either already applied or will be applied to the column. This should be done
before we run the query with the new criteria.
The next drop down called ‘Field’ allows us to select the column on which we
will apply the criteria.
The next three fields are same as those described in ‘Step 2 –Filetering the Data’
of section 17.2.2 “Single table – The Query Wizard”.
The “Values” button presents a list of values available in the selected column.
We can select one or more values from the list (by holding down control key and
clicking on individual values). This is useful when we use the “equals” operator
or the “is one of' operator”.
Click on “Add” button. MS Query will create a new column in the criteria area
along with the criterion.
We can add more criteria if required. We just need to ensure that one of the
options “And” or “Or” is appropriately selected.
Once the required criteria are added, we can close the dialog box.
We need to check if the criteria added by MS Query are in the appropriate rows.
If not, select the criteria string, cut it and paste it in the correct cell.
v How the criteria are evaluated when fetching rows
1. A set of rows from all the tables that satisfy the join conditions we extracted.
Let’s call this ‘recordset’.
2. MS Query takes the first row in the criteria area. Let’s call this ‘criteria row’.
From the recordset, the rows that simultaneously satisfy the criteria in each
cell of the criteria row are selected. Let’s call this set the ‘result set’.
3. Then the second row in criteria area is taken and becomes the new criteria
row. All the rows (in recordset) that simultaneously satisfy the criteria in each
cell of the criteria row are selected. These are then added to the ‘result set’
along with the rows present earlier. If any of the rows were already added,
they are not added again.
3. Step 2 is repeated for each of the remaining rows of the criteria area. till a
final result set is formed.
4. From this result set, the columns to be displayed are fetched and all the rows
are added to the fetched rows area.
Let us see few examples, figure 17.2.7b can be used for reference
We had already specified the criteria for order date column to be between 1
March and 31 March 2006.
Along with this, suppose we want to select only those orders that were shipped
to Chicago, Memphis or New York. This can be added using the 'is one of'
operator. Since this criterion should be satisfied along with the criteria on the
‘Order Date’ column, we should select the “and” option in the “Add Criteria”
dialog.
After closing the “Add Criteria” dialog, we should check if the new criterion is
in the same row as the criteria for date filters. The criterion is marked with
number (1) in figure17.2.7b
Now, suppose we want to include the rows where ‘Ship City’ has the characters
“LA” in its name. These rows should be in addition to those selected by the
previous criteria so we need to add this criterion with the “Or” option.
We will notice that MS Query will add all rows where ship city has “LA” in their
names irrespective of whether the criteria of order dates are satisfied.
So we need to add the order date criteria in the second criteria row as well. To do
this we simply select the “Order Date” criteria in the the first row, copy it and
paste in the second row in the same column. If we how hit the ‘Query Now’
button the total number of rows reduces.
The criterion is marked with number (2) in figure 17.2.7b.
Let’s add another criterion using a different method. This will be easier now that
we know how the And/Or combinations work.
Suppose we want to include rows where order date is earlier than 1st Mar 06, but
only those where the total order value is above 1000 USD.
We need a new field in the criteria area, the ‘Price Total’ column from “Order
Price Totals” table. To get the new field, go to the rightmost blank column in the
criteria area and click in the top most cell in the row labeled “Criteria Field”. A
dropdown arrow appears. Click on this the dropdown arrow and we will see a
list of all fields in all tables. The format used is [table name].[field name]
If the full name is not visible, we can widen the column by dragging the column
boundaries just like resizing a column on an Excel worksheet.
Warning: It is possible that columns in different tables would use the same name
(for example there is an ID column in the Employees table, the Orders table as
well as the Customer table), so be very careful when selecting the field. Be sure
to select the field in the correct table.
Now, we are adding a new independent criterion (one that has to be satisfied
even if the other criteria in other rows are not satisfied), so we will be doing all
the entries in a new row in the criteria area.
For entering the criteria for “Order Date”, (in the criteria area) double click in
the cell in the column of “Order Date”. The “Add Criteria” dialog box comes up.
The only difference is that the “Select column” drop down is missing since we
have already pointed to the column having the required table column. Add the
criteria as done before.
Now, in the same row, double click in the column where we selected the ‘Price
Total’ column. Enter the required criteria.
Click on ”Query Now” and we get the new set of rows that include the rows as
per the newly added criteria.
The new criterion is marked with number (3) in figure17.2.7b
v Removing criteria
To remove a criterion, just click in the cell of the criteria area, and delete the
criteria string.
v Required output
We need to see the total demand for products in each region. Usually we would
produce a pivot table like shown in figure 17.3.1b
However, MS Query cannot produce such a summary. The fields can only be
arranged along rows.
This type of summary is shown in figure 17.3.1d and the arrangement of fields
for the pivot table is shown in figure 17.3.1e
v Some terminology
We will call the ‘Product’ and ‘Region’ fields as the Objective fields because we
are summarizing for each value of region and product. The ‘Quantity’ column in
the original data which is summed up will be called the Value field. All other
fields like ‘Order Type’ and ‘SubRegion’ will be called Non-Objective fields.
Fig 17.3.1d
Fig 17.3.1e
When such a summary is produced in MS Query, the objective columns will not
have any blank cells. The final product is shown in figure 17.3.1f. Notice that the
sub and grand totals are not present because MS Query will not show such totals.
Fig 17.3.1f Final summary that will be created in MS Query
Fig 17.3.1g (left) New pivot table and (right) arrangement of fields
Fig 17.3.1h (left) New pivot table with Quantity summarized and (right) the arrangement of fields
We find that the figures don't change. This is because for each of the objective
fields say ‘Pipe’ and ‘North’, the non objective fields ‘SubRegion’ has distinct
values that can't be grouped.
To refine our summary, we should either remove ‘SubRegion’ or apply a
summary on that column. Being a text field we cannot Sum or Average it. Let us
summarize it with the Count function. The new PivotTable and its layout are
shown in fig 17.3.1i.
Fig 17.3.1i New pivot table with Sub-region summarized and the arrangement of fields
We still see that the number of rows is more than required and the non-objective
field ‘Order Type’ is still present. It again has two distinct values
(internal/external) for each group of Product and Region. Since we can't Sum or
Average it, let's summarize it with he Count function. The resulting table is
shown in figure 17.3.1j. This is much closer to the required table shown in figure
17.3.1f.
Fig 17.3.1j New pivot table with Order Type summarized and the arrangement of fields
Note that we don't necessarily have to summarize the non objective fields. We
can remove them all together. Figure 17.3.1k shows the table with the non
objective fields removed. This matches the table in fig 17.3.1f
Fig 17.3.1k New pivot table with non-objective field removed and the arrangement of fields
v Key Takeaways
1. All the non objective fields should be either removed from data extracted or
some summary function should be applied on them.
2. The objective fields should have as few distinct values as possible.
In the ‘Add Criteria’ dialog we had a field called ‘Total’. We can select the
summary function we want to apply and then specify a criterion for that
summarized value
For example, suppose we want to find sales reps who have gathered less than or
greater a certain number of orders.the figure 17.3.3 shows how the criterion is
set
It should be noted that for applying such criteria the fetched rows should already
be summarized. Again, the field on which we set the criteria need not be in the
display. For example, in our case, we can remove the ‘Count of Company’ field
from the Fetched Rows area but still apply a filter criterion on ‘Count of
Company’.
17.4 Working with imported data
The data that is extracted from databases and placed on our worksheets is
referred to as imported data. In this section we will see some basic operations
that we can perform with the imported data.
Fig 17.4.2a (Left) Connections group on the Data Menu. (Right) Data Properties dialog.
3. Near the connection name (disabled) there is a small icon (shown by number
1 in figure 17.4.2a right) this brings up the ‘Connections properties’ dialog
box shown in figure 17.4.2b. In this dialog, click on the tab labelled ‘Usage’.
4. There is a check box called 'Refresh Every' marked by number (1). Activate it
by placing a check mark in it. Then enter a positive whole number in the small
box next to it. This number is the interval in minutes between two refresh
cycles. Meaning if we enter '5' the data will get refreshed every 5 minutes as
long as the connection to the database is maintained.
There is another check box named 'Enable Background Refresh’. Background
refresh means that we can keep working with Excel while the data is being
pulled from the database.
Remember that if our query is extracting a large number of rows and columns or,
if it is a complex query having many criteria and summary functions, then it can
take considerable amount of time for the data to get refreshed. The time taken is
even more if we are accessing the database over a network. If background
refresh is not enabled, then during the refreshing process Excel will hang up and
we won't be able to do any work.
To have Excel available during the data refresh cycle place, a check mark in the
'Enable Background Refesh' check box. However, we should be careful that
while data is being refreshed, we do not carry out any processing that depends
on the updated data or else, our results may be incorrect.
Fig 17.4.2b Connection Properties dialog box. ‘Usage’ tab.
To be certain that the formula column is actually associated with the table, we
can do a check as follows
· Click in any of the cells of the table, this should show the Design tab in the
main menu.
· In the Design ribbon, look at the “Properties” group and click on “Resize
Table” (shown in the adjacent figure)
This does two things (both are shown in figure 17.4.4a)
1. The Resize Table dialog box is shown. We should ensure that the data range
shown in that box includes all our formula columns.
2. The boundary of the table gets highlighted with “Walking Ants” as shown in
the figure. Our formula columns should be within this boundary.
If our formula columns are not part of the table, we can just type in the address
of the new range (that include our formula columns) in the Resize Table dialog
box. This new range would now include the columns with formulas.
v When the imported data is displayed as a pivot table
it is not advisable to have formulas immediately next to a pivot table. Why?
Because, while a table.can expand and contract along its rows when new data is
added, a pivot table can expand and contract along its columns as well. So if the
number of columns increases after refresh, it would wipe out the existing
data/formula columns, if it contracts, blank columns would appear.
It is better to keep the pivot table as a standalone range of data and then use
methods described in chapter 5 to fetch data from the PivotTable and do
calculations with the returned data.
17.5 Query data and VBA
In this section we will look at ways to interact with the imported data using
VBA. Note that macros will not be used to fetch the data. Only to operate on
data that is already imported.
In the left drop down select ‘Worksheet’. In the right side drop down select
'Change'. Excel will automatically create an empty sub procedure for the the
change event.
We can see that the sub procedure has one argument called ‘Target’. This
argument is passed to the sub procedure and is available for use to the code
inside the sub procedure. It represents the range that has undergone a change,
either manually by the user or through some macro.
The problem is that the ‘Change’ event would get triggered if any cell on the
worksheet undergoes a change. So we need to confirm that the ‘Target’ argument
actually represents the range of the queried data. Let’s see how we can do this
v When queried data is displayed as a pivot table
Every PivotTable has a property called “TableRange2”. It represents the entire
area occupied by the PivotTable inclusive of the page filter fields.
For the PivotTable shown in figure17.5.3, TableRange2 will be the range
$A$1:$C$8. The ‘Page Filter’ fields are the cells A1:B1.
Notice that this range doesnt include any formulas that may have been written in
adjcent columns.
This entire range gets refreshed when we refresh the data in the PivotTable.
So in our Change event if the address of the ‘Target’ argument is same as the
address of ‘TableRange2’ of our pivot table, it is confirmed that the table has
been updated. Thecode in the Change event will be as follows:
Private Sub Worksheet_Change(ByVal Target As Range)
If StrComp(Target.Address, PivotTables("pvt"). _
TableRange2.Address) = 0 Then
‘//** Code here…… **//
End If
End Sub
v When data is displayed as a table
The range that changes can be obtained using the ‘ResultRange’ property of the
‘QueryTable’ object.
Notice that the ‘ResultRange’ includes the extra columns that we may have
added. Hence in the ‘Change’ event of our worksheet we should compare the
addresses of ‘Target’ and the ‘ResultRange’.
Here is how the code will look like.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim lo as ListObject
Dim strChkAddress as String
Set lo = ListObjects("QueryFromAccess")
strChkADdress = lo.QueryTable.ResultRange.Address
If StrComp(Target.Address, strChkAddress) = 0 Then
‘//** Code here…. **//
End If
End Sub
17.6 Connecting to MS Access
Connecting with MS Access is the easiest thing to do when using MS Query.
First launch MS Query as described in section 17.2.1. The “Choose Data
Source” dialog comes up
In the “Databases” tab, select “MS Access Database” in the list and click on the
“Browse” button.
Just browse and select the concerned MS Access file.
Important Note:
If our Access file is located on a server and has to be accessed over a network,
we would have to first map that network drive on our compouter. Then when
browsing for the Access file, select that mapped drive and look for the file.
17.7 Connecting to MySQL (ODBC +
WooCommerce)
Connecting to MySQL database is a bit tricky because at the time of writing of
this book, Microsoft does not provide a driver for MySQL.
Fortunately for us, MS Query can accept what is technically called an ‘ODBC
driver’. ODBC stands for Open Database Connectivity. It is a software bridge
that database makers provide to allow different programs to connect to their
database without knowing a lot of technical details. The catch is that after
installing the driver we need to take one more step - that of setting up an ODBC
Data source on the computer through which we want to access the database.
There are ways of access a Data source created on another computer, through a
different type of data source (a File Data source described in ‘Types of Data
sources’), but we will limit ourselves to a single computer for now.
As an example we will connect to a MySQL database on a webserver.
4. In the ‘Create New Data Source’ dialog scroll and select the ODBC driver for
MySQL, select the driver and click on ‘Finish’. This will bring up the
‘MySQL connector/ODBC configuration’ dialog box shown in figure 17.7.2c.
The difference between the two, in terms of the components placed on the form,
is mainly the place where user is allowed to type text. On a spreadsheet we have
to use single cells or merged cells. On a userform we need to use the Textbox
control.
18.2 Scenario for data capture
We want to build an application that will allow an operator to quickly capture
details of sales transactions. User will be able to add one item at a time or
multiple items in one go (conditions apply of course).
Let us look at how the form will be used. In real world scenarios we would have
to get this information from users through discussions. We will ensure that the
user does not have to memorize a large amount of information.
The form will be used as follows (numbers in figure 18.2a correspond to the step
number that we are discussing. Example – step 2 deals with current date and
order ID, both are marked by number 2 in figure 18.2a):
1. A blank order form is presented to the user. The controls contain only their
default values.
2. The current date and latest order ID are automatically filled in. Details of how
the order ID is created is provided in the next section.
3. User now selects, from the dropdown (ComboBox) labelled “Customer”, the
customer for whom the order is being prepared. This list is created from a
range of data present in the workbook.
4. Now the user can add items to the order. There are two options:
a) Adding single item
All available items are listed in a dropdown list (ComboBox) labelled
“Select Item”. User selects one item and enters the quantity in the cell
labelled “Quantity”.
b) Adding multiple items
· User clicks in the checkbox labelled “Add multiple items with standard
quantities”. This enables two ListBoxes. The ListBox on the left contains a
list of all available products. The one on the right is empty.
· User selects one or more items in the ListBox on the left, clicks on “Add”
button. The selected items are added to the ListBox on the right.
· If required, user can remove items from the right side ListBox by selecting
those items and clicking on “Remove”
5. Applying Special discount, if any
· The user clicks on the related checkbox (inside the rectangle labelled
‘Special Discount’). If the box is not checked, any value entered is ignored.
· User selects the value of special discount (%) using the SpinButton.
Default value is ‘0’, Maximum value is 2%. The value is incremented in
steps of ‘0.2%’.
· When the invoice for any is generated, the unit price of each item will be
reduced by the Special Discount applied to that item in that particular order.
· If multiple items are selected, the same discount value is applied to each
item.
6. User clicks on the button labelled “Add Item to List” to save the selected item
and the values and Special Discount to a temporary location along with any
items previously added for the same order.
7. User selects the region in which the sale will be accounted using the
OptionButtons.
8. Applying Cash discount, if any
· The user clicks on the related checkbox (inside the rectangle labelled
‘Order Details’). The CheckBox is labelled ‘Cash Discount’. If this box is
not checked, any value entered is ignored.
· User selects the value of cash discount (%) using a ScrollBar. Default
value is ‘0’, Maximum value is 8.5%.
· When the invoice for any order is generated, the value of the entire invoice
will be reduced by the value of Cash discount applied.
· This discount is in addition to the Special Discount which is applied to an
individual item.
9. Any comments related to the order can be added in the ‘Comments’ section.
10. At any time, the user can cancel the entire order by clicking on the button
labelled ‘Cancel’. All values entered till that point are discarded.
11. When all values have been entered, user can save the entire order by clicking
on the button labelled ‘Save Complete Order’. Two things happen,
A macro will save the details of the entered items and the order (Date, ID,
Region, Cash discount) to a permanent location in the workbook.
The values of controls are restored to their default values. Checkboxes are
unchecked, Discount values are set to ‘0’, ListBoxes are disabled and the
‘Comments’ section is cleared. Order ID is updated for the next order.
18.3 Setting up the database.
Our database will contain two sheets. One which, we will name ‘MasterData’,
will hold all the data that will be referenced either on input forms or by formulas
and macros. The second that we will name SalesDB will hold all the transaction
related data
The MasterData sheet will have two tables as shown in the figures18.3a and
18.3b. One (called ProdDB) holds a list of all the products that we offer. The
other (called RegionList) holds a list of all our sales regions. These tables can be
created on the same worksheet or separate worksheets. For convenience we will
hold them on the same sheet.
We will ensure that we don’t add any spurious data in the rows below these
tables. This is done so that we can easily expand the list when needed and so that
we can use dynamic ranges.
The MasterData sheet will also have one cell that holds the order ID of the last
sale that was made. This is simply the year and month numbers concatenated
with a 5-digit transaction number. We don’t expect over 100000 transanctions in
any given month. For a new order, the last order number is taken and the month,
year and order numbers are increased as per the situation. For each new month,
the first order is given a number ‘000001’.
The SalesDb sheet will contain records of all our sales transactions. The
arrangement of data on this sheet is shown in figure 18.3c. This information is
stored in two separate tables. The table in columns A to D holds data on each
product (denoted by the ProductID) that is included in an order. The table in
columns H to L holds data that is specific to each order (Irrespective of the
number of items included)
Fig 18.3.2a
Values in Columns AY and BA will change each time the user selects a new item
in the form.
When ‘Add Item’ button is clicked, these values will be transferred to a list of
items in the order.
That list is shown in Fig 18.3.2c
b) Order specific details (applicable to entire order)
Fig 18.3.2b
Values in Columns BC and BE will change only when a new order is created.
They will remain same for all items added in the order. When ‘Save Complete
Order’ button is clicked, these values will be transferred to the tables on the
SalesDB worksheet
c) Compiled order having all the items
Values in these cells hold a list of all the items that have been added in the order.
Here is how this last range (shown in figure 18.3.2c) is used under different
actions taken by the user:
1. When the ‘Add Item’ button is clicked and a single item is to be added, a
macro will copy the values (not the entire contents with the formulas) from the
range “BA2:BA4” (figure 18.3.2a) and pastes them at the bottom of this range
(columns BG to BI)
When ‘Add Item’ button is clicked and the user has opted for multi item
addition, a macro will read the selected items, and their IDs and will add as
many rows as the number of selected items at the bottom of this range.
2. When ‘Save Complete Order’ button is clicked, a macro will copy all non-
empty the rows in columns BG to BI starting at row 2, and paste them in
columns A to D on SalesDB page (figure 18.3c) in the last non-empty row.
Then the macro will clear all the cells in this range so that a new order can be
created.
3. When ‘Cancel Order’ button is clicked, a macro will clear all the rows in this
range.
Chapter 19
Creating forms on a worksheet
19.1 Setting up the Worksheet
Before we can create a form, we need to develop a canvas on which we can
place the controls. Here is one approach to do this:
1. Create a grid of worksheet cells by giving the certain fixed height and width
to the cells. For our form (shown in figure 18.1a) we have set the width of
columns A to AJ to a value of 3 and, rows 1 to 30 have been given height of
14.
Select the columns. Right click. Select ‘Column Width’. Set the width.
Select the rows. Right click. Select ‘Row Height’. Set the height.
2. Provide a suitable color to the entire sheet or to the portion where form will
be constructed.
3. Put a suitable border around the cells if required
4. Switch off the gridlines.
5. To allow user to type-in text we can put another border around a larger
rectangular group of Cells. We can merge these cells so that the entire group
can be referred to with a single cell address.
19.2 Adding controls
When adding controls to spreadsheet we have two options.
1. Use the Microsoft Excel forms controls
2. Use Microsoft ActiveX controls.
We will use the Excel forms controls. What is the difference and why don’t we
use the ActiveX?
Excel forms controls exist as if they are built into Excel. They are not dependent
on anything outside Excel. This means these controls are always available and
are compatible across versions of Excel.
The ActiveX controls, on the other hand, are built into separate libraries as if
they live outside of Excel. In many organizations, use of these controls may not
be allowed. Also if the libraries holding these controls are not installed on a
user’s computer, the controls may not function.
controls
In this section, we will see how we can set the name and properties of controls
that we add on a Worksheet. Naming a control will allow us to refer to the
control in our macros as if it were a variable.
We will discuss the common properties briefly. For illustration purpose we will
look at the ListBox control. In later chapters when we see individual controls we
will discuss specific properties in detail.
Setting properties:
Select the control.
· Left-click on the control while holding down the ‘Ctrl’ key. OR
· Right click on the control.
Once the control is selected, right click. (not required if control was selected by
right clicking)
In the context menu that comes up, select ‘Format control’
This line of text is a part of the control and is called ‘Caption’ of the control as
shown in figure 19.3c.
For the caption, we can write text on the control to guide the user. Here’s how:
· Select the control or Right click on it.
· In the pop-up menu select ‘Edit Text’. Type the text to be displayed.
Caption is not available for controls like ListBox, ComboBox, ScrollBar and
SpinButton.
Setting Name:
When working with controls from our macro, we have to refer to each control by
its name. Names allow us to address the exact control we want without having to
loop through all of them.
Figure19.3d shows how we can set the Name of a ComboBox. The method
remains same for all controls.
· Select the control.
· Click in the ‘Name’ box available beside the ‘Formula’ bar. We can see that
Excel by default provides a name. We can use this name in our code, but we
should put in another name that depicts the purpose of the control
· Type in the desired name
Press ‘Enter’. - Extremely important.
Fig 19.3d Setting name of a control
For naming controls in our application, we will use the initial 2-4 letters of the
name to indicate the type of control (‘cb’ stands for combobox). This will be
followed by a description of what the control offers or does. (‘ProdList’ means a
list of products to choose from). The individual words in the name are separated
by capitalizing the first letter.
These are just guidelines. We can form our own convention for names. We just
need to ensure that the length of the name does not exceed 20-25 characters.
19.4 Link to cells, formulas & macros
Form controls have many aspects that are common for all controls. We will see
specific techniques for each control when we discuss individual controls in
upcoming sections. In this section, we will review the common aspects that are
applicable to all the controls so that we can avoid repetition in upcoming
sections.
Every control placed on a worksheet can be linked to a cell that will hold a value
that is supplied to the control. The value depends on the type of control, but it
can be one of the following types:
1) Numeric integer value:
Linked Cell of Holds
Listbox or a Combobox The position of the selected item as it appears in the
list of values.
A group of option An integer value representing the selected button.
buttons Example, out of four buttons if the third one is
selected, the Linked cell will hold ‘3’.
A SpinButton or An integer value lying in the range specified for the
ScrollBar control.
2) Boolean value
Linked cell of a Checkbox holds a value TRUE if the box is checked, FALSE
otherwise.
The value in the linked cell acts just like a regular cell value. It can be used in
formulas that accept the value of that particular type (integer, true/false).
19.5 Controls & Macros (general facts)
19.5.1 Linking controls to macros
For working with controls that are placed on a worksheet from within our macro,
there are two steps that are initially necessary. These steps are required for any
control we use, only the name of the objects will change. So we will view the
steps here to avoid repetition in later sections.
In later sections, when we see details of a specific control we will skip the
explanation and only look at the syntax for that particular control.
The steps are as follows:
1. Declare an object in Visual Basic for the specific type of control. The
following line of code declares an object that will later on refer to a ListBox
we created on the ‘OrderForm’ worksheet.
Dim lbProdList As ListBox
2. Tie the Visual Basic object to a real control on a Worksheet. For this, we
should note that all the controls placed on a Worksheet are available in
collections of their respective types. All the ListBoxes are held in a collection
called, well, ‘ListBoxes’. The collection itself is a property of the worksheet
and can be accessed with the following syntax:
<Worksheet object>.ListBoxes
A particular ListBox can be accessed as follows:
<Worksheet object>.ListBoxes(strControlName)
Where ’strControlName’ is a string containing the name of the concerned
ListBox.
For example, the code that follows will set the Visual Basic ListBox object to
the ‘lbProdList’ ListBox control we placed on ‘OrderForm’ worksheet.
Dim wks As Worksheet
Set wks = Thisworkbook.Worksheets(“OrderForm”)
Set lbProdList = wks.ListBoxes(“lbProdList”)
And here is a similar code for a ScrollBar control on the same worksheet.
Dim scrlCashDiscount As ScrollBar
SetscrlCashDiscount= wks.ScrollBars(“scrlCashDiscount”)
Warning: The collections like ‘ListBoxes’, ‘DropDowns’and so on are often
not listed by ‘intellisense’ as a property of a worksheet. But these are always
available.
One observation:
Names of most collections (of controls) is the plural form of the control’s name.
All ListBox controls are in ‘ListBoxes’, all ScrollBar controls are in ‘ScrollBars’
collection. Exceptions are the ComboBox and the SpinButton controls. All
ComboBoxes placed on a worksheet are held in a collection called
‘DropDowns’. All SpinButtons placed on a worksheet are held in a collection
called ‘Spinners’. We will revisit this fact when we see ComboBox and
SpinButton controls in detail.
That’s it. Now we can start working with the Visual Basic object in our macro,
and the effects will be reflected on the control placed on our worksheet.
19.5.2 Events
We will see the specifics of controls and macros when we discuss details of each
control. In this section however, we will see the general method of associating a
macro to a control’s event.
An ‘Event’, when talking about controls, can be thought of as a specific macro
that runs when the user does something to the control. Each of the Forms
controls placed on a worksheet, allow us to create one event macro. The
following table lists when the macro associated with each event would get
triggered:
Control(s) Event Name Macro is executed when
ListBox, Change User changes the value of the control either by
Combobox, selecting (ListBox/Combobox) or by changing
ScrollBar, the value (SpinButton/ScrollBar).
SpinButton
CheckBox, Click User clicks on the control. The value
Optionbutton, automatically changes from ‘True’ to ‘False’
or the other way depending on the value it was
holding when user clicked.
Button Click User clicks on the control.
To respond to an event, we have two options (refer to figures 19.5.2a and
19.5.2b):
1. Assign a blank default macro and write our code in it.
· Right click on the control and select ‘Assign Macro’ in the pop-up menu.
· In the ‘Assign Macro’ dialog box that is shown, click the “New” button.
· Excel will generate an empty Sub procedure. We can type in our code
into this sub-procedure
2. Create our own sub procedure and assign it to the control
· First create a sub-procedure (the name need not contain ‘Click’ or
‘Change’)
· Right click on the control and select ‘Assign Macro’ in the pop-up menu.
· In the Assign Macro dialog, select the sub-procedure just created and
click ’OK’
Important note: The new value of the control is available for use inside the
event’s macro. It’s as if the value is set and then the event macro’s code is
executed.
For example, when the user clicks on a previously unchecked checkbox, the
value is set to TRUE and then the macro executes. Within the macro if we check
the value of the control, we get a TRUE value.
Fig 19.5.2a Invoking the Assign Macro dialog
The following code snippets show the usage of the LinkedCell property for a
ScrollBar control.
a) Printing the current value
Debug.Print scrlCashDiscount.LinkedCell
b) Setting to a cell on the same or different worksheet
scrlCashDiscount.LinkedCell = “MasterData!$AK$1”
c) Setting to a named range (name can refer to a cell or a formula that returns
a single cell)
scrlCashDiscount.LinkedCell = “DummyLinkCell”
Important note:
A disabled control is a bit confusing because it appears on the worksheet as a
normal control. But when the user clicks on it nothing happens. This might
cause a confusion for the user. If we want to prevent input in a control, it is
better to set the visible property to ‘False’.
4. Caption: This property has a string value and allows us to read and set the
caption of the control. (Note: This property is available only for Button,
OptionButton and CheckBox)
For our OptionButton control, the syntax to print the caption is:
Debug.Print opBtnNorth.Caption
The syntax to set a new value for the caption is:opBtnNorth.Caption = “Please select a
value”
Fig 19.6a
Listbox control can be placed on a worksheet by clicking on the icon marked in
the figure 19.6a
When placing the control, it’s better to draw a rectangle high enough so that at
least 8-10 lines of items are visible.
In section 19.3, we saw how we can specify the name of a control. Now let’s see
how we can set the essential properties of a ListBox control.
· Select the control.
· Right click and select ‘Format Control’ from the popup menu.
· ‘Format Control’ dialog box is displayed as shown in the figure 19.6b.
Referring to figure 19.6c, cell B4 on Sheet4 contains the formula with ‘Index’
function. The ‘RowNum’ argument of ‘Index’ function is equal to the value
returned by the list box. The Index function fetches the ‘ProductID’ of the
selected product.
Now let’s see the important properties and methods of this control.
Properties
The properties listed in the table below are common to all the controls and have
been described in detail in section 19.5.3. We will just see specific syntax for a
ListBox here:
LinkedCell lbProdList.LinkedCell = “MasterData!$AK$1”
Visible lbProdList.Visible = False
Enabled lbProdList.Enabled = True
Now we will see some other important properties specific to a ListBox.
1. List: This property holds an array of all the items in the ListBox. The
datatype of the returned array is Variant.
Each item in the list has an index position. The first item has the index
position of ‘1’. The item at the position ‘i’ is given by:
lbProdList.List(i)
If a multicolumn range was specified in the ‘Input range’ property, then only
the first column is returned as an array in the ‘List’ property.
The code below loops through and prints all the items in a ListBox. Notice
how we have avoided the problem of not knowing lower bound of the returned
array.
Dim startPos, endPos as Long
startPos = LBound(lbProdList.List)
endPos = LBound(lbProdList.List)
For i = startPos to endPos step 1
Debug.Print lbProdList.List(i)
Next i
The values held in the array are actually Strings (if the entries are
alphanumeric) or Double or Integers based on the values held in the range that
we provide in the ‘Input range’ property.
For example, if we specify the range shown the adjoining figure 19.6e, we will
not be able to perform mathematical operations on all the values because the 3rd
and 7th values are strings.
Fig 19.6e Sample values in a range
Hence we need to be careful that the range we specify has values of the
same datatype in all the cells. These should match the operations we want
to perform on the values in our macro.
2. ListCount: This property returns a Long integer value showing the total
number of items available in the ListBox. We can only read this property.
Syntax is as follows:
lbProdList.ListCount
3. ListFillRange: This property holds a string value containing the address or
Name of the cell range specified in the ‘Input range’ field in the ‘Fromat
Control’ dialog box. The following code snippets show the usage of this
property.
a) Printing the current value
Debug.Print lbProdList.ListFillRange
b) Setting to a range on the same or different worksheet
lbSelList.ListFillRange = "MasterData!$B$2:$B$9"
c) Setting to a named range (name should refer to a cell or a formula that
returns a single cell)
lbProdList.ListFillRange = “ProdList”
The Name ’ProdList’ refers to a formula
“=INDEX(ProdDB,,2)”
The Name ’ProdDB’ and ’ProdList’ are named ranges we created in
section 18.3.1.
Although we can set this property in macros, it is advisable to set the
property in the ‘Format Control’ dialog box when we are designing the
form and then only read this property when working in a macro.
4. Selected: This property holds an array of Boolean values. The length of the
array is the same as the array returned by the ‘List’ property.
The ‘i’th item in the array is given by
lbProdList.Selected(i)
The value of the item is TRUE if the item has been selected by the user, else it
is false.
In a single selection ListBox only one item will be TRUE, while in a multi
selection ListBox, multiple items will have a TRUE value.
The following code loops through all the values in the ListBox and prints only
the selected values.
Dim startPos, endPos as Long
startPos = LBound(lbProdList.List)
endPos = LBound(lbProdList.List)
For i = startPos to endPos step 1
If (lbProdList.Selected(i)) Then
Debug.Print lbProdList.List(i)
End If
Next i
5. Value: This property returns a Variant datatype that holds the value of a
single selection ListBox. This property cannot be used when the selection
type of the ListBox is set to multi/extended types.
‘Value’ in this case does not refer to what is displayed in the ListBox but the
position of the item that the user selected in the ListBox. The code below
prints out the current value of the ListBox
Debug.Print lbProdList.Value
But what if we want to get the value displayed in the list and not just the
position?
We combine the ‘List’ and ‘Value’ properties. Here is the code to print the
selected text that appears in ListBox.
Debug.Print lbProdList.List(lbProdList.Value)
Methods
1. AddItem: This method is used to add items in a ListBox whose ‘Input range’
property has not been set. The syntax is as follows:
lbProdList.AddItem(item, position)
Arguments:
a) Item: this should be a string or number. It specifies the value to be added
in the ListBox.
b) Position: this is an integer specifying the position where the new item has
to be added. This is an optional argument, omitting it would add the item
in the bottommost position in the list.
We should ensure that the input range is not specified for the ListBox for
which this method is being called. Otherwise, the original list is first cleared
and then the new item is added to the list.
2. RemoveAllItems: This method deletes all the items from a ListBox. If an
Input range has been specified, that value is cleared without reporting an
error. Hence we should make sure we don’t invoke this method on a ListBox
for which an Input range has been specified using the ‘Format Control’ dialog
box.
lbProdList.RemoveAllItems
3. RemoveItem: This method allows us to remove an item from a particular
location in a list box for which the Input range is not specified. The syntax is
lbProdList.RemoveItem(index)
We should ensure that the input range is not specified for the ListBox for
which this method is being called or else the macro displays an error and
hangs.
Events
1. Change: This event gets triggered as follows:
a) Selection Type is set to ‘Single’: In this case the event occurs whenever the
user selects an item hence causing the ‘Value’ property to change.
b) Selection Type is set to ‘Multi’ or ‘Extend’: Event occurs whenever the user
clicks in the ListBox to either select or deselect an item.
19.7 ComboBox
ComboBox is a control that presents a list of items to the user in a dropdown list.
User can scroll through the list and can select only one item. ComboBox control
can be placed on a worksheet by clicking on the icon marked in the following
figure.
Placing a combobox.
Next we will set the essential properties and name of a combobox. Refer figure
19.7a.
A combobox returns a value that is equal to the position of the selected item in
the list. The first item has a value of ‘1’. This value is held in the cell specified in
the ‘Cell link’ property.
Heads up:
The term ComboBox is actually a misnomer. Real ComboBox is one where a
user can select from a dropdown list but if required, the user can type in the
desired text. The Excel form control we are discussing just allows the user to
select a value from a dropdown list.
The actual term should be ‘DropDown’. However, ComboBox is the term that
Excel came up with, so we will use that. However, things will change when we
move on to working with the control in macros.
Linking to cells/formulas on a worksheet
We can use the position of the selected item returned by a combobox in Excel
formulas to retrieve other information about the selected item from a range using
Offset or Index functions.
For an example from our application, see fig 19.7b. Here the value returned by
the “Customer” ComboBox is used as the ‘Row number’ argument of the ‘Index’
function.
Fig19.7b
We have used the position of the selected customer to retrieve the customer’s ID
from the ‘CustomerDB’.
We will store the ID in the sales tables with the other order details and not the
customer name. The formula for retrieving the customer ID is in cell BE5.
Now let’s see the important properties and methods of this control.
Properties
The properties listed in the table below are common to all the controls and have
been described in detail in section 19.5.3. We will just see specific syntax for a
ComboBox here:
LinkedCell cbCustList.LinkedCell = “MasterData!$AK$1”
Visible cbCustList.Visible = False
Enabled cbCustList.Enabled = True
Now we will see some other important properties specific to a ComboBox.
1. List: This property holds an array of all the items in the ComboBox. The
datatype of the returned array is Variant. Each item in the array has an index
position. The first item has the index position of ‘1’.
The item at the position ‘i’ is given by:
cbCustList.List(i)
The code below loops through and prints all the items in a ComboBox. Notice
how we have avoided the problem of not knowing lower bound of the
returned array.
Dim startPos, endPos as Long
startPos = LBound(cbCustList.List)
endPos = LBound(cbCustList.List)
For i = startPos to endPos step 1
Debug.Print cbCustList.List(i)
Next i
The values held in the array are actually Strings (if the entries are alphanumeric)
or Double or Integers based on the values held in the range that we provide in
the ‘Input range’ property.
For example, if we specify the range shown in figure 19.7c. Then we will be able
to perform mathematical oparations on all the values except for the 3rd and 7th
values.
Fig19.7c
Hence, we need to be careful that the range we specify has only values of the
same datatype. These should match the operations we want to perform on the
values in our macro.
2. ListCount: This property returns a Long integer value showing the total
number items available in the ComboBox. We can only read this property.
Syntax is as follows:
cbCustList.ListCount
3. ListFillRange: This property holds a string value containing the address or
Name of the cell range specified in the ‘Input range’ field in the ‘Fromat
Control’ dialog box. The following code snippets show the usage of this
property.
a) Printing the current value
Debug.Print cbCustList.ListFillRange
b) Setting to a range on the same or different worksheet
cbCustList.ListFillRange = "MasterData!$N$2:$N$9"
c) Setting to a named range (name can refer to a cell or a formula that returns
a single cell)
cbCustList.ListFillRange = “CustList”
The Name ’CustList’ refers to a formula
“=INDEX(CustomerDB,,2)”
The Name ’CustomerDB’ and ’CustList’ are named ranges we created in
section 18.3.1.
Although we can set this property in macros, it is advisable to set the
property in the ‘Format Control’ dialog box when we are designing the
form and then only read this property when working in a macro.
4. Value: This property returns a Variant datatype that holds the value of the
ComboBox.
‘Value’ in this case does not refer to what is displayed in the ComboBox but
the position of the item that the user selected in the ComboBox.The code
below prints out the current value of the ComboBox
Debug.Print cbCustList.Value
Methods
1. AddItem: This method is used to add items in a ComboBox whose Input
range property has not been set from the ‘Format Control’ dialog box. The
syntax is as follows:
cbCustList.AddItem(item, position)
Arguments:
a) Item: this should be a string or number. It specifies the value to be added
in the ComboBox.
b) Position: this is an integer specifying the position where the new item has
to be added. This is an optional argument, omitting it would add the item
in the bottommost position in the list.
We should ensure that the input range is not specified for the ComboBox
for which this method is being called. Otherwise, the original list is first
cleared and then the new item is added to the list.
2. RemoveAllItems: This method deletes all the items from a ComboBox. If an
Input range has been specified, it is cleared without reporting an error. Hence
we should make sure we don’t invoke this method on a ComboBox for which
an Input range has been specified.
cbCustList.RemoveAllItems
3. RemoveItem: This method allows us to remove an item from a particular
location in a ComboBox for which the Input range is not specified. The
syntax is
cbCustList.RemoveItem(index)
We should ensure that the input range is not specified for the ComboBox for
which this method is being called or else the macro displays an error and
hangs up.
Events
1. Change: This is the only event available for this control. It gets triggered as
when the user selects a new value in the ComboBox hence updating the Value
of the control.
19.8 CheckBox
Checkbox is a control that provides a choice for one specific item. The user can
put a check mark in a checkbox if the item is to be selected. A Checkbox control
can be placed on a worksheet by clicking on the icon marked in the following
figure.
If the user puts a check mark in the box by clicking on it, the checkbox control
holds a value of ‘True’ in the linked cell. If the box is unchecked, the control’s
value in the linked cell is ‘False’
Next we will set the essential properties and name of a checkbox. Refer figure
19.8a.
Name of the control can be set using method shown in section 19.3
As per figure 19.8a, value of the checkbox will be held in cell A7. After setting
the properties, the box will be initially unchecked.
The ‘Value’ property can be set to one of the following:
Checked A ‘tick’ mark appears in the CheckBox control. Value of the
control is set to ‘True’ that appears in the linked cell
UnChecked A ‘tick’ mark does not appear in the CheckBox control. Value of
the control is set to ‘False’ that appears in the linked cell
Mixed Checkbox is greyed and no tick mark appears. The value in the
linked cell is ‘#N/A’.
This value represents a state between checked and unchecked or,
a NULL state. It appears only initially after setting the property.
Once the checkbox is in use, the value will be either ‘checked’
or ‘unchecked’. It can be set again only from a macro.
If the value of the checkbox held in the linked cell is used in
formulas, then we should avoid this state.
Linking to cells/formulas on a worksheet
We can use the value returned by a CheckBox in Excel formulas and functions
that accept a Boolean value. For an example from our application, see fig 19.8b.
Value returned by the CheckBox labelled ‘Cash Discount’ is stored in cell BC2.
If user checks the box, we will apply the cash discount whose value is in cell
AC14. If the user has not checked the box, we will ignore that value.
Properties and Methods in Macros
In section 19.5.1 we have seen how we can link a control placed on a worksheet
with an object in our macro’s code. Here is the code for a ComboBox.
Dim chkCashDiscount As CheckBox
Set chkCashDiscount = wks.CheckBoxes(“chkCashDiscount”)
Now let’s see the important properties and methods of this control.
Properties
The properties listed in the table below are common to all the controls and have
been described in detail in section 19.3. We will just see specific syntax for a
CheckBox here:
LinkedCell chkCashDiscount.LinkedCell = “MasterData!$AK$1”
Visible chkCashDiscount.Visible = False
Enabled chkCashDiscount.Enabled = True
Now we will see some other important properties specific to a CheckBox.
1. Value: This property returns a Variant datatype that holds the value of the
CheckBox.
Important note: this value is not the same as that held in the linked cell. The
linked cell will hold a Boolean value, while in macro the value is of Integer
datatype. Following table shows the values and their effects:
Value Linked cell Represents
contains
Below 0 FALSE ‘UnChecked’ state. No tick mark appears in the
(-4146) checkbox. Box is empty.
1 TRUE ‘Checked’ state. A tick mark appears in the
checkbox.
2 #N/A ‘Mixed’ State. Checkbox is greyed out.
The code below prints out the current value of the CheckBox
Debug.Print chkCashDiscount.Value
In macros, the most common task is to execute some code based on value of the
checkbox. We can either check the value directly from the control or check it
from the linked cell on the worksheet. The table below shows the code for both
these techniques:
Checking Value property Checking Value in the linked cell on a
of the checkbox directly worksheet
Dim chVal as Variant Dim chVal as Variant
Dim wks, sAdr As String
Chkval = _ wks = _
Split(chkCashDiscount.LinkedCell,”!”)(0)
chkCashDiscount.Value
sAdr = _ Split(chkCashDiscount.LinkedCell,”!”)(1)
chval = _
Worksheets(wks).Range(sAdr).Value
‘//** Checkbox is checked **// ‘//** Checkbox is checked **//
If (chkval=1) Then If (chkval) Then
End If End If
‘//** Checkbox is unchecked **// ‘//** Checkbox is unchecked **//
If (chkval<0) Then If Not(chkval) Then
End If End If
‘//** Checkbox is greyed **// ‘//** Checkbox is greyed **//
If (chkval = 2) Then If IsError(chkval) Then
End If End If
Methods
This control does not have any method of much practical use. Available methods
deal with positioning of the control on the screen. We will not explore them as it
is better not to move the controls with a macro once a form is designed.
Events
1. Click: This is the only event available for this control. This event gets
triggered whenever the user clicks on the checkbox. A click on the checkbox
causes the ‘Value’ property to toggle.
The macro to run in response to this event can be created as discussed in
section 19.5
19.9 OptionButton
OptionButton is a control that allows the user to select one item out of a fixed
list of items. If user selects any one item, the others are automatically deselected.
An OptionButton control can be placed on a worksheet by clicking on the icon
marked (on the ‘Forms Controls’ pallet) in the following.
Since more than one items have to be presented, OptionButtons are always
placed in groups of two or more. A user can select an option by clicking on the
corresponding OptionButton.
Before we proceed we need to get familiar with one term that we will need going
forward.
Option Group:
An option group is a list of items, presented to the user as a group of
OptionButtons, from which the user can select one item.
Other items in the group get automatically deselected.
Name of the control can be set using method shown in section 19.3. However,
we should set a different name for each individual OptionButton. We cannot set
one name for all the buttons in the entire group.
Fig 19.9a
For example, on our form we have an option group for the Sales Region shown
in figure 19.9a. Each of the buttons is given a unique name like ‘opbtnNorth’,
‘opbtnSouth’ and so on representing one sales region.
Next we will set the essential properties of a checkbox. Refer figure 19.9b. The
only property to be set is the ‘Cell Link’. However, when we set this property for
one OptionButton in an option group, the other OptionButtons in the same group
automatically gain this value.
Important:
Value stored in cell referred to by ‘Cell Link’ is the value for the entire option
group. This value is an integer representing the option button that is currently
selected.
For example, when collecting data on a group of students we might have two
option groups as shown in Fig 19.9.1a
1. For the level of education
(cell link set to B28)
2. Gender (cell link set to C28)
Trying to forcefully change the ‘Cell link’ property produces erroneous results.
This is where the ‘GroupBox’ control comes in.
Apart from making our form visually appealing, the GroupBox control performs
an important function of separating the OptionGroups. All OptionButtons placed
inside one GroupBox become part of the same OptionGroup
Fig 19.9.1b
A GroupBox can be placed on the form by clicking on the icon shown in figure
19.9.1b.
This control doesn’t have any properties. We can only set its caption. For
example, ‘Group’ or ‘Gender’ in figure19.9.1a. Setting its name is not of much
use
Here are the general principles to be followed when creating option groups on
forms:
1. As far as possible, decide the option groups and all the options within the
group before placing the controls.
Remember, option groups are effective if we want to present upto 5 items to
the user, an option group is not recommended for a very long list.
Above 5 items, an option group takes up lot of space and ruins the visual
appeal of the form. In case of longer lists, it is better to use a ComboBox.
2. For every option group, first place the GroupBox and then place all the
option buttons within that group one at a time. Do not copy the previously
placed buttons. This may seem like lot of work, but it will avoid problems
later on.
3. Set the ‘Cell link’ for any one OptionButton in a particular option group and
check that all the buttons in the same group have the same value for this
property.
4. Set the name and caption for each individual OptionButton.
5. Move on to creating the next option group.
Linking to cells on a worksheet
Since the value of a group of OptionButtons is an integer, we can use it in
formulas that require an integer.
Let’s see how we can couple an option group with the ‘VLookup’ function. The
principle can be extended to other functions.
Now let’s see the important properties and methods of this control.
The properties listed in the table below are common to all the controls and have
been described in detail in section 19.5.3. We will just see specific syntax for an
OptionButton here:
LinkedCell optbtnDesc.LinkedCell = “MasterData!$AD$2”
Visible optbtnDesc.Visible = False
Enabled optbtnDesc.Enabled = True
Now we will see some other important properties specific to an OptionButton
1. Value: This property returns a Variant datatype that holds the value of the
OptionBtton.
Important note: this value is not the same as that held in the linked cell. The
linked cell will hold an Integer value for the entire option group, while in macro
the value is of Integer datatype for an individual button. Following table shows
the values and their effects:
Value Linked cell Represents
contains
Below 0 Integer Button is not selected. Some other option in the
(-4146) group is selected.
1 Integer Button is selected. None of the other options in the
group are selected.
The code below prints out the current value of the CheckBox
Debug.Print optbtnNorth.Value
In macros, the most common task is to execute some code based on the options
that is selected. We can check the value either directly from the control (integer)
or from the linked cell on the worksheet. In either case our macro has to check
the entire option group and decide what to do.
The best way to achieve this is to use our trusted ‘Select-Case’ block. Here is the
code. We will use the option group shown in figure 19.9c
We are just displaying the option selected, but of-course we can take more
complex actions.
‘wks’ is the Worksheet object set to the Sheet holding the controls in figure19.9c
Checking value of Linked Cell:
Select Case wks.Range("AD2").Value
Case 1:
MsgBox "So you want to show Description, eh?"
Case 2:
MsgBox "So you want to show Spacing, eh?"
Case 3:
MsgBox "So you want to show Nut/Bolt size, eh?"
Case 4:
MsgBox "So you want to show Max. Load, eh?"
End Select
Checking value of the control:
The OptionButtons on the worksheet have been given names. They are self-
explanatory from the code.
Notice the little trickery used in the Select-Case block.
Usually we take a variable whose value can change and compare it with a
constant in each ‘Case’ statement. Like in the previous piece of code we
compared cell values with constants 1,2,3 or 4.
Here we have taken a constant (1) and in the ‘Case’ statements, we are holding
the variables (Values of OptionButtons. These keep changing)
Select Case 1
Case wks.OptionButtons("opbtnDesc").Value:
MsgBox "So you want to show Description?"
Case wks.OptionButtons("opbtnSpacing").Value:
MsgBox "So you want to show Spacing?"
Case wks.OptionButtons("opbtnNutBolt").Value:
MsgBox "So you want to show Nut/Bolt size?"
Case wks.OptionButtons("opbtnMaxLoad").Value:
MsgBox "So you want to show Max. Load?"
End Select
19.10Button
Button is a control that provides a way to start an action (via a macro). User can
click on a button and this action can trigger a macro. Button control can be
placed on a worksheet by clicking on the icon marked on the Forms Control
palette shown in the following figure.
Name of the control can be set using method shown in section 19.3.
The ‘Format control’ dialog box allows us to set to the font of the text that
appears on the button. We can use this feature to make the button more visually
appealing.
The basic function of a Button is to activate a macro. For this we have to
associate a Sub procedure (functions cannot be associated) with a button. The
procedure to associate a macro to a button or any control is discussed in detail in
section 19.5.2 “Events”.
19.11 ScrollBar
Scroll is a control that provides a way to select one numeric value from a range
of values. ScrollBar control can be placed on a worksheet by clicking on the icon
marked in the figure shown on the left.
Name of the control can be set using method shown in section 19.3
Fig 19.11a shows the terms associated with different parts of a scrollbar control.
We need these terms for setting the properties of a scrollbar.
ScrollArrows: These are arrows at each end of the scrollbar. Clicking on any of
the arrows once will cause the value property to change by a fixed amount.
ScrollBox: The part that the user can drag towards one of the ends of the
scrollbar to change the scrollbar’s value.
Value can also be changed by:
· Clicking on one of the Scroll arrows.
· Clicking between the Scroll box and one of the scroll arrows
Important Note:
Referring to figure 19.11b. All these values are positive integers. We cannot
have fractional values or negative values. We will not be setting the ‘Current
Value’ property. We can leave it at default and let it vary as the control is used.
a) Maximum Value: This is the value the control will have when the scroll box
is at the ‘Point of Max value’ as marked in figure 19.11a.
This value has to be greater than the Minimum value. Largest value allowed
is 30000.
b) Minimum Value: This is the value the control will have when the scroll box
is at the ‘Point of Min value’ as marked in figure 19.11a.
This value has to be less than the maximum value. Least value allowed is 0.
c) Incremental change: The amount by which value of the control will change
when the user clicks once on one of the scroll arrows.
It should be noted that the increment does not dictate the value of the control.
For example, for the settings shown in figure 19.11b, it is possible for the
control to take a value of ‘190’ based on position of the scroll box.
d) Page change: The amount by which value of the control will change when
the user clicks once between one of the scroll arrows and the scroll box.
e) Cell link: Address of the worksheet cell that will hold the control’s value.
19.11.1 Fractional/Negative values & reversing the
direction
In case of a scrollbar there is only one cell that we can link to the control in a
straightforward manner.
In this section we will discuss how to tackle two important issues we face when
using a scrollbar. And for understanding the solution, we need a new concept
that we will call a ‘Target Cell’
A Target cell is a cell different from a linked cell and will hold a value derived
from the linked cell. Our formulas and macros will use the value in the target
cell instead of that in the linked cell.
1. Getting fractional values from a ScrollBar
Suppose in our application, we decide to give a cash discount on an order and
we want it to be at least 0.5% but not exceed of 8.5%. Also, user should be
able to increase it by 0.25% by clicking on the scroll arrows.
How do we achieve that with a scrollbar control that provides only integer
values?
For obtaining decimal values, we need to convert each of the decimal values
to fractions of the form (a/b) ‘a over b’. Where ‘a’ and ‘b’ are positive
integers.
The catch is that value of ‘b’ should be same for Minimum, Maximum and
Increment properties and such that all these three properties are positive
intergers.
For our example, let us take different values of b and work out the fractions
for Minimum, Maximum and Increment properties.
Value of Minimum Maximum Incremental
‘b’
(should be (should be change
equal to 0.5) equal to 8.5) (should be
equal to 0.25)
10 5/10 85/10 2.5/10
100 50/100 850/100 25/100
We notice that taking b = 10, the value of ‘a’ or numerator for the ‘Incremental
Change’ is still a decimal number. To convert that to integer we had to take
b=100.
Now that the value of b is finalized, here is how we proceed.
1. Set the values of Minimum, Maximum and ‘Incremental Change’ properties
to their respective integer values.
2. In the target cell we enter the formula “= <linked cell>/ b”
For our example, the values in the ‘Format Object’ dialog box are shown in
figure 19.11.1a
The Target Cell will have the formula
= $AV$2/100
Now when we move the ScrollBox, the ‘Target cell' will hold the desired
decimal value, that lies between 0.5 and 8.5
2. Inverting the max and min positions of a ScrollBar
Suppose we want.to show a vertical scroll bar that acts a means to vary
temperature or water level.
Fig 19.11.1b Settings for inverting the Max and Min values
The values in the ‘Format Control’ dialog box shown in figure 19.11.1b are for
obtaining a value of 10000 at the top of the scrollbar and value of 100 at the
bottom
The Target Cell will have the formula
= 10000 - $I$41
2. Negative values
Dealing with negative values when both, the Maximum and Minimum
property values are negative is straightforward.
We set the Maximum and Minimum property values to the respective positive
numbers and then in the target cell we enter the formula
“=-*<linked cell value>”.
Here we will discuss a more general situation where the value of Minimum
property has to be negative and that for Maximum property has to be positive.
We proceed as follows.
1. Let us denote the desired maximum and minimum values be maxval and
minval so that we don’t confuse them with the Maximum and Minimum
properties.
Let us take a specific example where maxval is 100 and minval is (-30)
2. Work out the difference between maxval and minval. Let us call this value
‘diff' for difference. Note that to subtract a negative value from a positive
one we have to add the two positive values.
Diff = maxval – minval = 100 – (-30) = 130
3. Set the Minimum property of the scrollbar to 0 and the Maximum property
to ‘diff’.
4. In the target cell, enter the formula
“=maxval-<linked cell value>”
Now when we move the scroll bar towards the top arrow, the value in the
target cell will move towards the maxval, 100 in our example.
Name of the control can be set using method shown in section 19.3. User can
change the value of the control by clicking on up or down arrows.
Next we will set the essential properties of a SpinButton. Refer figure 19.12a.
Important Note:
Referring to Fig 19.12a
All these values are positive integers. We cannot have fractional values or
negative values. We will not set the ‘Current Value’ property. We can leave it at
default and let it vary as the control is used.
a) Maximum Value: This is the largest value the control is allowed to have.
This value must be greater than the Minimum value. Largest value allowed is
30000.
b) Minimum Value: This is the smallest value the control is allowed to have.
This value has to be less than the Maximum value. Least value allowed is 0.
c) Incremental change: The amount by which value of the control will change
when the user clicks once on one of the arrows.
d) Cell link: Address of the worksheet cell that will hold the control’s value.
Important Note:
On a worksheet a SpinButton can be placed only with a vertical orientation as
shown in the adjoining figure on the right.
Clicking the UpArrow moves the value towards ‘Maximum Value’.
Clicking the Down Arrow moves the control’s value towards ‘Minimum Value’
Whenever a UserForm is opened in the Visual Basic Editor such that we can add
controls and make changes to the form and its controls, the form is said to be
open in the Design Mode
If at any time we close the ToolBox palette, we can view it again by selecting the
‘Toolbox’ option in the ‘View’ menu.
Changing the size of a UserForm:
We can select the whole form by clicking on any area that does not contain a
control. Best way is to click on the ‘Title Bar’, the area having the name of the
form (“UserForm2” in figure 20.1b).
Once selected, eight small boxes (aka. ‘Handles’) appear. Four at the corners and
four on each side. Dragging these with our mouse, we can increase or decrease
the area of the form.
Important note:
A disabled control is a bit confusing because it appears on a form as a normal
control. But when the user clicks on it nothing happens. This might cause a
confusion for the user.
We have two options if we want to prevent input to a control,
1. Set the visible property to ‘False’ or
2. Disable the control and also sent its background color to a different one.
The code below shows how we can do this.
Here we have set the background color to a predefined VB constant. This is
the only time we will be using the ‘BackColor’ property from within a macro.
With ufOrderForm.lbProdList
.Enabled = False
.BackColor = vbActiveBorder
End With
Now if we want to enable the control again, we can use the following code
Fig 20.6 shows the effect of this code on two list boxes.
With ufOrderForm.lbProdList
.Enabled = True
.BackColor = vbWindowBackground
End With
In the code above we have used two System color constants
1. vbActiveBorder: Color of border of an active form.
2. vbWindowBackground: Default window background color
These two constants can be used anywhere in our code where we have to
specify colors. However, their effect is system dependent. So it is better to test
the effect by executing the code once.
Some other Visual Basic color constants that we can use are:
· vbRed
· vbBlack
· vbGreen
· vbYellow
· vbBlue
· vbCyan
· vbWhite
But none of these provide the dimmed visual effect required by a disabled
control. Hence we had to use the System Color constants.
4. Caption: This property has a string value and allows us to read and set the
caption of the control. (Note: This property is available only for Button,
OptionButton and CheckBox)
For our OptionButton control, the syntax to print the caption is:
Debug.Print ufOrderForm.rBtnNorth.Caption
In the UserForm that we are developing, there is a TextBox for entering the
quantity of items ordered. In the ‘Enter’ event for this control, we will set its
‘BackColor’ property to Yellow.
Figure 20.7.2a shows the TextBox when it has received focus.
Let’s see the code for this event. The name of the textbox is set to ‘txtQuantity’.
Before we change the background color, we will store the current value of the
color in a variable (module level variable declared outside the sub). This will
help when we have to reset the color back to the original value.
Dim prevColor as Long
Private Sub txtQuantity_Enter()
prevColor = txtQuantity.BackColor
txtQuantity.BackColor = vbYellow
End Sub
2. Change: Triggered when the value of a control changes. Like when the user
selects a new item in a ComboBox or ListBox. Or when user checks or
unchecks a check box or selects another OptionButton in an option group.
Examples and sample code for this event are given a bit later in the portion
titled “What we can achieve with Events”
Important Notes:
It should be noted that the new value is available within the event macro.
Hence we can first check the new value and then take appropriate action.
For a multi-selection ListBox, this event gets triggered whenever an
additional item is selected.
For a TextBox control, the ‘Change’ event is triggered every time the user
types in a new character, so it is not advisable to write a code for this event
for a TextBox.
3. Exit: triggered when a control loses focus. Best way to use this event is as
follows.
a) Check the value entered by the user. If the value is not acceptable, display
a message and bring the focus back to the control so that user can enter the
correct value.
b) Reverse the change made in the Enter event if any.
Recall the TextBox control we saw when discussing the ‘Enter’ event (Fig.
20.7.2a). Suppose the user has entered a value and is now moving on to
another control. The TextBox will lose focus.
We want to ensure that if the user has entered a non-numeric value, we should
display a message and the focus should remain in the TextBox. The code
below shows how we can handle this in the TextBox’s Exit event.
The Exit event has the following signature
Private Sub <Control Name>_Exit(
ByVal Cancel As MSForms.ReturnBoolean)
The argument ’Cancel’ takes a Boolean value and dictates what happens after
the Event macro is executed.
If we set ’Cancel’ to True, focus stays within the current control.
If we set ’Cancel’ to False, focus control loses focus.
If user has entered a numeric value, we will make the control lose focus and
reset the background color to the value we saved earlier in the ‘Enter’ event.
Private Sub txtQuantity_Exit( _
ByVal Cancel As MSForms.ReturnBoolean)
If Not (IsNumeric(txtQuantity.Value)) Then
MsgBox "Please enter a numeric value for Quantity"
Cancel = True
Else
‘//** User has entered a valid value, **//
‘//** reset the background color to the saved value. **//
‘//* No need to retain focus. **//
Cancel = False
txtQuantity.BackColor = prevColor
End If
End Sub
4. Initialize: This event is available only for a UserForm. It is triggered just
before the form is shown on the screen. At this stage all the controls are
available as properties and we can operate upon them. The best use of this
form is to
a. Hide the controls that will be displayed later based on some action of the
user
b. Set default values for controls.
c. Set the source for listboxes and comboboxes
The code below shows how the Initialize event can be used. The various
controls and their properties will be described in upcoming sections.
Private Sub UserForm_Initialize()
‘//** Set the source for List and Combobox **//
lbProdList.RowSource = _
"[Book1Notes.xls]Sheet2!$B$1:$B$9"
cbProdList.RowSource = "Sheet4!AG2:AG5"
‘//** Set one of the checkboxes to NULL value **//
chkSpecDiscount.Value = Null
‘//** Set the value for one of the OptionButtons. **//
opbtnNorth.Value = True
End Sub
What can we achieve with Events:
1 Set the state (Visible/Enabled) of one control based on value of another
control.
2 Display a message to the user about the consequences of entering certain type
of data and also about what data will be captured.
Fig 20.7.2 Disabled Listboxes & changed background colors
The code that follows is for the ‘Change’ event of a CheckBox we placed on our
order form.
That checkbox is pointed out in figure 20.7.2. Name of the CheckBox is
‘chkAddMultipleItems’.
When the form is displayed initially:
1. The two ListBoxes on our UserForm are disabled.
2. The CheckBox does not have a checkmark.
If the user puts a checkmark in the CheckBox, a message is displayed to the user
in a message box.
When the user clicks on the ‘OK’ button of the message box, the two ListBoxes
are enabled. When the user clears the checkmark, the ListBoxes are again
disabled.
The following code illustrates points 1 and 2 discussed so far.
Private Sub chkAddMultipleItems_Change()
If chkAddMultipleItems Then
MsgBox "You are about to enter multiple items" & _
vbCrLf & _
"Items will be added with standard quantities" & _
vbCrLf & _
"Special Discount will be applied to all items"
lbProdList.Enabled = True
lbProdList.BackColor = vbWindowBackground
lbSelItemList.BackColor = vbWindowBackground
Else
lbProdList.Enabled = False
lbProdList.BackColor = vbActiveBorder
lbSelItemList.BackColor = vbActiveBorder
End If
End Sub
3 Set the value of cells on a worksheet using value of a control (say a ScrollBar
named scrlCashDiscount). Here is the code.
Wks.Cells(3,25).Value = scrlCashDiscount.Value
4 Set the value of one control based on value in another.
5 Check the value of the current control (one for which the event is triggered)
and limit the value if required
Suppose on our form we have
Control Name Features
SpinButton spnValue Maximum value = 100, Minimum value= 10
TextBox txtValue Its value is set to the value of the SpinButton
CheckBox chkCeiling If the box is checked, value of SpinButton is not
allowed to exceed 75.
Following code illustrates points 4 and 5 discussed above.
Private Sub spnValue_Change()
If chkCeiling.Value And (spnValue.Value > 75) Then
MsgBox ("Value can’t exceed75")
spnValue.Value = 75
End If
txtValue.Value = spnValue.Value
End Sub
20.8 ListBox
Listbox is a control that presents a list of items to the user. User can scroll
through the list and can select one or more items (depending on the settings of
the control). Listbox control can be placed on a UserForm by clicking on the
icon shown in the following figure.
When placing the control, it’s better to draw a rectangle high enough so that at
least 8-10 lines of items are visible.
We have already seen how to set the name for a control in section 20.2
Features related to different values of ‘MultiSelect’ Property
MultiSelect Features
fmMultiSelectSingle User can select only one value from the list.
fmMultiSelectExtend User can select the first item by clicking on it.
After that, if user clicks on any other item, that item
becomes the selected item, the first item is discarded.
To select additional items, the user has to hold down
the ‘Ctrl’ key and make subsequent selections.
To deselect an item, the user has to hold down the
‘Ctrl’ key click on that item again.
fmMultiSelectMulti User can the first item by clicking on it.
After that, if user clicks on any other item, that item
also gets selected
To deselect an item, the user has to click on that item
again.
Properties & Methods in Macros
Properties
Now we will see some other important properties specific to a ListBox.
1. RowSource: This property holds a string value containing the address or
Name of the cell range that has the data which will be listed in our ListBox.
The following code snippets show the usage of this property.
a) Printing the current value
Debug.Print lbProdList.RowSource
b) Setting to a range on the same or different worksheet
lbSelList.RowSource = "MasterData!$B$2:$B$9"
c) Setting to a named range(name can refer to a cell or a formula that returns a
single column)
lbProdList.RowSource = “ProdList”
The Name ’ProdList’ refers to a formula “=INDEX(ProdDB,,2)”
The Name ’ProdDB’ and ProdList’ are named ranges we created in section
18.3.1.
2. List: This property holds an array of all the items in the ListBox. The returned
array is of Variant datatype. Each item in the list has an index position. The
first item has the index position of ‘0’. The item at the position ‘i’ is given
by:
lbProdList.List(i)
If a multicolumn range was specified in the ‘Input range’ property, then only
the first column is returned as an array in the ‘List’ property.
The code below loops through and prints all the items in a ListBox. Notice
how we have avoided the problem of not knowing lower bound of the returned
array.
Dim startPos, endPos As Long
startPos = LBound(lbProdList.List)
endPos = LBound(lbProdList.List)
For i = startPos To endPos Step 1
Debug.Print lbProdList.List(i)
Next i
The values held in the array are actually Strings (if the entries are
alphanumeric) or Double or Integers based on the values held in the range that
we provide in the ‘RowSource property.
Fig 20.8b
For example, if we specify the range shown in figure 20.8b, we will be able to
perform mathematical oparations on all the values except for the 3rd and 7th
values.
Hence we need to be careful that the range we specify has only values of the
same datatype. These should match the operations we want to perform on the
values in our macro.
3. ListCount: This property returns a Long integer value showing the total
number items available in the ListBox. We can only read this property. Syntax
is as follows:
lbProdList.ListCount
4. Selected: This property holds an array of Boolean values. The length of the
array is the same as the array returned by the ‘List’ property.
The item at row ‘i’ in the array is given by lbProdList.Selected(i). The value of
the item is TRUE if the item has been selected by the user, else it is false.
In a single selection ListBox only one item will be TRUE, while in a multi
selection ListBox, multiple items will have a TRUE value.
The following code loops through all the values in the ListBox and prints only
the selected values.
Dim startPos, endPos as Long
startPos = LBound(lbProdList.List)
endPos = LBound(lbProdList.List)
For i = startPos to endPos step 1
If (lbProdList.Selected(i)) Then
Debug.Print lbProdList.List(i)
End If
Next i
5. Value: This property returns a Variant datatype that holds the value of a single
selection ListBox. This property cannot be used when the selection type of the
ListBox is set to multi/extended types.
‘Value’ in this case refers to the text is displayed in the ListBox. The code
below prints out the current value of the ListBox
Debug.Print lbProdList.Value
Methods
1. AddItem: This method is used to add items in a ListBox whose Input range
property has not been set. The syntax is as follows:
lbProdList.AddItem(item, position)
Arguments
a) Item: this should be a string or number. It specifies the value to be added in
the ListBox.
b) Position: this is an integer specifying the position where the new item has
to be added. This is an optional argument, omitting it would add the item in
the bottommost position in the list.
We should ensure that the ‘RowSrouce’ property is not set for the ListBox
for which this method is being called. Otherwise, the macro reports an error
and execution stops.
2. Clear: This method deletes all the items from a List box.
If the ‘RowSource’ has been specified, our macro stops after reporting an
error. So we should make sure we don’t invoke this method on a ListBox for
which the ‘RowSource’ has been specified.
lbProdList.Clear
3. RemoveItem: This method allows us to remove an item from a particular
location in a list box for which the ‘RowSource’ property is not specified.
The syntax is:
lbProdList.RemoveItem(index)
The argument index specifies the row that we want to delete from the ListBox.
We should ensure that the ‘RowSource’ is not specified for the ListBox for
which this method is being called or else the macro displays an error and
hangs.
Events
· Change: This event gets triggered as follows:
a) Selection Type is set to ‘Single’: In this case the event occurs whenever the
user selects an item hence causing the ‘Value’ property to change.
b) Selection type is set to ‘Multi’ or ‘Extend’: Event occurs whenever the user
clicks in the ListBox to either select or deselect an item.
For this control, the code for ‘Change’ event along with ‘Enter’ and ‘Exit’ events
can be created as detailed in section 20.7.
20.9 ComboBox
ComboBox is a control that presents a list of items to the user in a dropdown list.
User can scroll through the list and can select only one item. A Combobox
control can be placed on a UserForm by clicking on the icon marked in the
following figure.
We have already seen how to set the name and other essential properties for a
control in section 20.2.
Now we will see some other important properties specific to a ComboBox. Note
that we will be accessing these properties through macros and not in the
Properties window.
1. RowSource: This property holds a string value containing the address or
Name of the cell range from which data is displayed in the ComboBox. The
following code snippets show the usage of this property.
a) Printing the current value
Debug.Print cbCustList.RowSource
b) Setting to a range on the same or different worksheet
cbCustList.RowSource = "MasterData!$N$2:$N$9"
c) Setting to a named range (name can refer to a cell or a formula that returns
a single column)
cbCustList.RowSource = “CustList”
The Name ’CustList’ refers to a formula “=INDEX(CustomerDB,,2)”
The Name ’CustomerDB’ and ’CustList’ are named ranges we created in
section 18.3.1.
2. List: This property holds an array of all the items in the ComboBox. The
datatype of the returned array is Variant.
Each item in the list has an index position. The first item has the index
position of ‘1’. The item at the position ‘i’ is given by:
cbCustList.List(i)
The code below loops through and prints all the items in a ComboBox. Notice
how we have avoided the problem of not knowing lower bound of the returned
array.
Dim startPos, endPos as Long
startPos = LBound(cbCustList.List)
endPos = UBound(cbCustList.List)
For i = startPos To endPos Step 1
Debug.Print cbCustList.List(i)
Next i
The values held in the array are actually Strings (if the entries are
alphanumeric) or Double or Integers based on the values present in the range
that we provide in the ‘RowSource’ property.
Fig 20.9b
For example, if we specify the range shown in figure 20.9b, then we will be able
to perform mathematical oparations on all the values except for the 3rd and 7th
values.
So we need to be careful that the range we specify has only values of the same
datatype. These should match the operations we want to perform on the values in
our macro.
3. ListCount: This property returns a Long integer value showing the total
number items available in the ComboBox. We can only read this property.
Syntax is as follows:
cbCustList.ListCount
4. ListIndex: This property returns a Variant datatype that holds the position of
the item currently selected in the ComboBox.
The first item in the list has an index of ‘0’. The largest value that this
property can take is ‘(ListCount - 1)’. The code below prints out the current
value of the ComboBox.
Debug.Print cbCustList.ListIndex
5. Value: This property returns a Variant datatype that holds the value of the
ComboBox.
‘Value’ in this case refers to what is displayed in the ComboBox. The code
below prints out the current value of the ComboBox
Debug.Print cbCustList.Value
Methods
1. AddItem: This method is used to add items in a ComboBox whose Input
range property has not been set. The syntax is as follows:
cbCustList.AddItem(item, position)
Arguments
a) Item: this should be a string or number. It specifies the value to be added in
the ComboBox.
b) Position: this is an integer specifying the position where the new item has
to be added. This is an optional argument, omitting it would add the item in
the bottommost position in the list.
We should ensure that the ‘RowSource’ property is not specified for the
ComboBox for which this method is being called. Otherwise, the macro stops
executing and reports an error.
2. Clear: This method deletes all the items from a ComboBox.
If the ‘RowSource’ property has been set, the macro stops executing and
reports an error. The syntax is
cbCustList.Clear
3. RemoveItem: This method allows us to remove an item from a particular
location in a ComboBox. The syntax is
cbCustList.RemoveItem(index)
We should ensure that the RowSource property is not specified for the
ComboBox for which this method is being called or else the macro displays
an error and hangs.
Events
All the Events, described in section 20.7 (except for Initialize), can be created
for a ComboBox.
20.10 Caveat about setting RowSource
In the previous sections we saw how we can set the RowSource property for a
ListBox and ComboBox. In those sections we had assumed that the workbook
that contains the form is the one that is currently displayed on the screen (the
ActiveWorkbook). However, if our macro works on multiple workbooks, it is
possible that a different workbook may be active on the screen and the one
containing the form or range for RowSource may be minimized.
In such cases the string specifying the RowSource should contain the name of
the workbook containing the range that we are using for the RowSource. The
following lines of code show how we can do this
UserForm1.lbProdList.RowSource = _
"'[" & ThisWorkbook.Name & "]MasterData'!B2:B9"
UserForm1.cbCustList.RowSource = _
"'[" & ThisWorkbook.Name & "]MasterData'!$N$2:$N$9"
Here the code that sets the range for RowSource is in the same workbook the
range itself. A detailed explanation on the format in which the string on the right
has to be formed is given in section 3.6.1 “Formulas referring to another
workbook or worksheet”
20.11 CheckBox
Checkbox is a control that provides a choice for one specific item. The user can
put a check mark in a checkbox if the item is desired. Checkbox control can be
placed on a worksheet by clicking on the icon marked in the following figure.
If the user puts a check mark in the box by clicking on it, the checkbox control’s
value is ‘True’. If the box is unchecked, the control’s value is ‘False’.
Common properties discussed in section 20.6 are all applicable to this control.
We will discuss here one property that is specific to a CheckBox.
TripleState:
This property holds a Boolean value and if set to ‘TRUE’ allows the use of
‘NULL’ as a value for the CheckBox. It is set in the Properties window and is
marked in figure 20.11a
If set to ‘FALSE’ a CheckBox can have only two values ‘TRUE’ and ‘FALSE’.
Most common applications have TripleState set to False.
Fig 20.11a Properties for a ListBox
The table below shows a checkbox with its TripleState property set to TRUE and
the different appearances of a check box and the value it can hold depending on
how the user interacts with the checkbox.
Appearance/State Value
Unchecked False
Null
(Checked & NULL
Dimmed)
Checked True
Note that we are setting this property in the Properties window, not from a
macro.
Properties & Methods in Macros
The TripleState property can be checked and changed from inside a macro.
If ufOrderForm.chkCashDiscount.TripleState Then
‘//** Triple state is true, take required action **//
End If
The TripleState property can be checked and changed from inside a macro.
Methods:
This control does not have any method of much practical use.
Events:
All the Events described in section 20.7 can be created for a CheckBox.
The code that follows is for the ‘Change’ event of a checkbox named
chkCashDiscount. It uses a ‘Select Case’ block to respond to different states of
the checkbox
Private Sub chkCashDiscount_Change()
Select CasechkCashDiscount Value
Case TRUE:
MsgBox "Box is checked”
Case FALSE:
MsgBox "Box is unchecked”
End Select
End Sub
Important notes when TripleState is set to ‘True’:
· If the CheckBox is currently in NULL state, then it cannot trigger events.
User has to click at least once to bring the value to either TRUE or FALSE.
· When the UserForm is first displayed, the CheckBox is unchecked. Clicking
on it sends it to NULL state. Clicking again takes the CheckBox to checked
state. Clicking again takes it to Unchecked state. Then the cycle repeats.
· If we want to set the CheckBox to NULL state when the form is first
displayed, we should set the value to NULL before we use the ‘Show’ method
on the UserForm. Here is the code for doing that:
If chkCashDiscount.TripleState Then
chkCashDiscount.Value = NULL
End If
20.12 OptionButton
OptionButton is a control that allows the user to select one item out of a fixed
list of items. If user selects any one item, the others are automatically deselected.
OptionButton control can be placed on a UserForm by clicking on the icon
marked in the following figure.
The following code checks the value of each optionbutton in a group and then
displays the choice. The code is in the ‘Click’ event of a CommandButton
(described next) placed on the same form as the OptionButtons.
Private Sub CommandButton1_Click()
Select Case True
Case opbtnNorth.Value
MsgBox "You selected North"
Case opbtnEast.Value
MsgBox "You selected East"
Case opbtnWest.Value
MsgBox "You selected West"
Case opbtnSouth.Value
MsgBox "You selected South"
Case Else
MsgBox "Please select a region first"
End Select
End Sub
20.13 CommandButton
CommandButton control can be placed on a worksheet by clicking on the icon
on the ToolBox marked in the following figure.
Name and Common properties of the control can be set using methods described
in section 20.2.
The most important properties of this control that we need to set in the properties
window are Name, the text appearing on the button- called the ‘Caption’ and the
‘Font’ of the caption.
The basic function of a CommandButton is to allow the user to activate or run a
piece of code when required. This is done by using the ‘Click’ event of this
control.
To use a CommandButton’s Click event, we open our form in the design view
and double click on the button. This opens up the code mudule of the button.
Since Click event is the default event for a button, Excel will automatically add a
blank sub for this event. If this is not visible select ‘CommandButton’ from the
left (object) drop down and select ‘Click’ from the right (procedure) drop down.
Now we can write the required code in this procedure. It is recommended that
we should call some other sub or function procedure from within this sub, this
sub should contain only the minimum required code.
Usage in Macros
Apart from the Enabled and Visible properties described in section 20.6, we
might want to control the Caption of a CommandButton from our macros. The
code below is just for illustration. It will toggle the caption between 2 values
when the button is clicked.
Private Sub CommandButton1_Click()
If (InStr(1, CommandButton1.Caption, "unclick", _
vbTextCompare) > 0) Then
CommandButton1.Caption = "Click to Click"
Else
CommandButton1.Caption = "Click to UnClick"
End If
End Sub
20.14 SpinButton
Spinbutton is a control that provides a way to select one numeric value from a
range of values but, only in fixed steps. This is unlike the ScrollBar where the
user could select a proportionately from a range of values. SpinButton control
can be placed on a worksheet by clicking on the icon marked in the following
figure.
Name and common properties of the control can be set using methods described
in section 20.2. User can change the value of the control by clicking on the
arrows of the SpinButton.
We will see how we can set some other important properties specific to a
SpinButton control. Before that, let’s clear one important concept about how the
control works.
Name and Common properties of the control can be set using methods described
in section 20.2. The important properties that can be set in the properties window
are shown the figure 20.16a.
We can set the Font of the text in the box and also if the text box allows multi
line text entry.
If “MultiLine” property is set to TRUE, user is allowed to enter paragraphs of
text instead of a single line or word.
The value entered by the user is always available as a string. If we are expecting
numeric values (like the TextBox for ‘Cash discount’ and ‘Quantity’), it is
possible that the user can enter text characters. In that case our macro has to first
check that the user has entered appropriate values before processing it further.
The following line of code will print the text entered by the user in the
immediate window:
Dim strTxt As String
strTxt = TextBox1.Text
Debug.Print strTxt
20.17 Label
Label is one control that does not capture data from the user. Instead, it is used to
display data. Label control can be placed on a worksheet by clicking on the icon
marked in the following figure.
Name and Common properties of the control can be set using methods described
in section 20.2.
An important property is “Caption” which is the text that is displayed on the
label. Usually labels are kept next to controls to display static text. For example,
figure 20.18 shows several labrels used to display static text.
However, the real advantage of a label is in displaying dynamic values that are
either calculated in the macro code or taken from value of some other control.
The code below is written inside the change event of a scrollbar and it sets the
caption to the latest value of the scrollbar whenever user changes the value of the
scrollbar.
Private Sub ScrollBar1_Change()
Label5.Caption = ScrollBar1.Value
End Sub
Chapter 21
Middleware – The Bridge
In this section we will see how to tie our forms and database together mainly
using code.
This code will be independent of how the form is created (on a worksheet or on a
UserForm). We just have to take care of the connecting points. Where a
distinction has to be made, it will be pointed out explicitly.
21.1 Converting Form data to database values
On a form, we provide data that can be understood by the user, like the name of
the customer or item to be entered. However, the control provides us a different
value, for example the position of the value in the list of values. Or an integer
value or a Boolean value showing whether an option was selected by the user.
Now the data we want to store in the database is different from these two. For
example, we will be storing the IDs of the customer and the items in our
database. Why? Well because when we generate reports we can fetch the name
and other details of items and customers from the MasterData reference.
But then why not store the customer details directly? Because at a later date we
may want to change some thing like the spelling of the customer’s organization.
If we have taken several different orders, at how many places do we make a
change?
An ID is something that is not visible to the human users, so for the same ID, we
can keep changing the text and numeric details. That is why, when recording
transactions, it is better to store the customer ID. However, we have no use for
the position of the item as presented on the form.
For these reasons we have to convert the value provided by the form to the value
fit to be stored. In this section we will see some techniques for achieving this
conversion. First let’s look at forms created on worksheets.
Figure 21.1.1b shows the values that are stored in cells when user adds the items
one at a time. The formulas/functions used are also shown.
When the user clicks “Add Item to List” button, a macro will copy the values in
cells BA2 to BA4 to the row below the last row in the columns BG to BI shown
in figure 21.1.1c. This is the range where the order will be compiled.
Let us look at the code that compiles the order. The code is written in the ‘Click’
event of the button labelled “Add Item to List”.
However, do remember that the same button has to allow addition of single as
well as multiple items. We will build the code for both these cases.
First let us make some arrangements for allowing addition of multiple items.
First we need a way to transfer items between the ListBoxes.
We should also keep in mind that the user might want to Add and Remove items
several times before clicking the “Add Items to List” button. We need a way to
store the selected items such that addition and deletion can be done easily.
Fig 21.1.1d Section of the form that allows adding multiple items
In figure 21.1.1d, the ListBox on the left is a list of all items that are available
for sale.
When user selects one or more items in the ListBox on the left hand side and
clicks on the “Add” button, the selected items get added to the ListBox on the
left.
The two ListBoxes and the buttons between them are initially hidden. The user
can add only one item at a time. To add multiple items, the user has to put a
checkmark in the checkbox shown in figure 21.1.1e.
When this box is checked, the ListBoxes and the related buttons are made
visible.
Name of the checkbox is ’chkAddMultiItems’. Here is the code for the click
event of the CheckBox.
Sub chkAddMultiItems_click()
With ThisWorkbook.Worksheets("OrderForm")
If .CheckBoxes("chkAddMultiItems").Value < 0 Then
.DropDowns("cbProdList").Visible = True
.ListBoxes("lbProdList").Visible = False
.ListBoxes("lbSelList").Visible = False
.Buttons("btnAddSelected").Visible = False
.Buttons("btnAddAll").Visible = False
.Buttons("btnRemoveSel").Visible = False
.Buttons("btnRemoveAll").Visible = False
End If
If .CheckBoxes("chkAddMultiItems").Value = 1 Then
.Range("R6").Value = 0
.DropDowns("cbProdList").Visible = False
.ListBoxes("lbProdList").Visible = True
.ListBoxes("lbSelList").Visible = True
.Buttons("btnAddSelected").Visible = True
.Buttons("btnAddAll").Visible = True
.Buttons("btnRemoveSel").Visible = True
.Buttons("btnRemoveAll").Visible = True
End If
End With
End Sub
Let us write the code for the Add and Remove buttons:
1) Adding items from the available products ListBox
When user clicks on the Add button, our macro checks the ‘Selected’ array for
the ListBox on the left side.
Fig 21.1.1f
The for each selected item, the value of the product ID is fetched and stored in
column BM on the same sheet in the last blank row. As shown in figure
21.1.1f. Text of the Selected item is added to the ListBox on the right hand
side.
At this time, the position of the ID in column BM will match the position of the
item in the right side ListBox
This is an important fact that we will use when writing code for the Remove
button.
Here is the sub routine that will be added to the Click event of the ‘Add’
button.
Sub AddMultiItemToSelList()
Dim wks As Worksheet
Dim lbProdList, lbSelList As ListBox
Dim startPos, endPos, rwlastMultiItemID As Long
Dim strProdID As String
Dim rngFound As Range
Set wks = ThisWorkbook.Worksheets("OrderForm")
Set lbProdList = wks.ListBoxes("lbProdList")
Set lbSelList = wks.ListBoxes("lbSelList")
'//** First row contains column headings. **//
‘//** If no items have been added yet, start at the second row **//
rwlastMultiItemID = Application.WorksheetFunction.CountA( _
wks.Columns(colMultiItemID)) + 1
startPos = LBound(lbProdList.Selected)
endPos = UBound(lbProdList.Selected)
For i = startPos To endPos Step 1
If (lbProdList.Selected(i)) Then
'//** Item is selected, get its product ID **//
strProdID = ThisWorkbook.Names("ProdDB").RefersToRange. _
Cells(i, 1).Value
'//** Check if the ID is already added to the column BM **//
Set rngFound = wks.Columns(colMultiItemID).Find(strProdID)
If (rngFound Is Nothing) Then
'//** ID is not added, add it to the column BM **//
wks.Cells(rwlastMultiItemID, colMultiItemID).Value _
=strProdID
'//** Increase the row number as there might be more items **//
rwlastMultiItemID = rwlastMultiItemID + 1
'//** Add the text of the item into the selected items ListBox **//
lbSelList.AddItem (lbProdList.List(i))
'//** Deselect the item since it is now added **//
lbProdList.Selected(i) = False
Else
'//** ID of the selected product is already added, no action required **//
Set rngFound = Nothing
End If
End If
Next i
'//** Cleanup code **//
Set rngFound = Nothing
Set lbProdList = Nothing
Set lbSelList = Nothing
Set wks = Nothing
End Sub
2) Removing items from the Selected Items ListBox
When user clicks on the Remove button, our macro takes all the IDs from
column BM into a Visual Basic array (except for the first row) and clears all
the rows.
Then the macro checks the ‘Selected’ array for the ListBox on the right side.
For each selected item, the value in the Visual Basic array is set to null. We
can do this because the positions in the ‘Selected’ array of the ListBox and the
Visual Basic array holding product IDs matches.
Each item that is not selected is copied to column BM in the bottommost
blank row.
We then loop over the ‘Selected’ array once again and this time remove the
items that are selected.
Here is the sub routine for the Click event of the ‘Remove’ button.
Sub RemoveMultiItemFromSelList()
Dim wks As Worksheet
Dim lbSelList As ListBox
Dim startPos, endPos, rwlastMultiItemID, lngSelIndex As Long
Dim strProdID As String
Dim rngAdded As Range
Dim arrAddedId As Variant
Set wks = ThisWorkbook.Worksheets("OrderForm")
Set lbSelList = wks.ListBoxes("lbSelList")
rwlastMultiItemID = Application.WorksheetFunction._
CountA(wks.Columns(colMultiItemID))
If rwlastMultiItemID < 2 Then
rwlastMultiItemID = 2
End If
Set rngAdded = wks.Range(wks.Cells(2, colMultiItemID), _
wks.Cells(rwlastMultiItemID, colMultiItemID))
arrAddedId = rngAdded.Value
rngAdded.ClearContents
rwlastMultiItemID = 2
startPos = LBound(arrAddedId, 1)
endPos = UBound(arrAddedId, 1)
lngSelIndex = LBound(lbSelList.Selected)
For r = startPos To endPos Step 1
If Not (lbSelList.Selected(lngSelIndex)) Then
'//** Item is not selected for removal, **//
‘//** add it back to the column **//
wks.Cells(rwlastMultiItemID, colMultiItemID).Value = _
arrAddedId(r, 1)
'//** Increment the row counter so that **//
‘//** the next addition happens in the next row **//
rwlastMultiItemID = rwlastMultiItemID + 1
End If
lngSelIndex = lngSelIndex + 1
Next r
'//** Actual deletion from the listbox **//
lngSelIndex = LBound(lbSelList.Selected)
Dim bKeepLooping As Boolean
bKeepLooping = True
While (bKeepLooping)
If lngSelIndex > UBound(lbSelList.Selected) Then
bKeepLooping = False
Else
If (lbSelList.Selected(lngSelIndex)) Then
'//** If the current item is selected, remove it from the ListBox **//
'//** Do not increment the selected index as the next item may be selected.
**//
'//** That item will become the current item in the next iteration **//
lbSelList.RemoveItem (lngSelIndex)
If lbSelList.ListCount < 1 Then
'//** If all items have been removed, stop looping **//
bKeepLooping = False
End If
Else
'//** If the current item is not selected, increment the selected index **//
lngSelIndex = lngSelIndex + 1
End If
End If
Wend
Set lbSelList = Nothing
Set wks = Nothing
End Sub
For the reader: Please write the code for ‘Add All’ and ‘Remove All’
buttons.
3) Adding items to the compiled order
Once the single or multiple item(s) to be included in the order have been
selected, the user will set the value of Special discount using the SpinButton.
Then the user will click on the “Add Item to List” button. Here is the sub
routine that will be assigned to the ‘Click’ event of that button.
Sub AddItemsToList()
Dim wks As Worksheet
Dim chkAddMultiItems As CheckBox
Dim rwlastCompiledOrder As Integer
Dim rwlastItemDetail As Integer
Set wks = ThisWorkbook.Worksheets("OrderForm")
rwlastCompiledOrder = Application.WorksheetFunction.CountA _
(wks.Columns(colCompiledOrderStart)) + 1
Set chkAddMultiItems = wks.CheckBoxes("chkAddMultiItems")
If chkAddMultiItems.Value < 0 Then
'//Single item has to be added
Dim colSingleItemDetail As Integer
colSingleItemDetail = 53
rwlastItemDetail = Application.WorksheetFunction.CountA _
(wks.Columns(colSingleItemDetail))
For Count = 0 To (rwlastItemDetail - 1) Step 1
'//** Following is one single line of code **//
wks.Cells(rwlastCompiledOrder, _
colCompiledOrderStart + Count).Value = _
wks.Cells(2 + Count, colSingleItemDetail).Value
Next Count
Else
'//** Multiple items have to be added **//
Dim rwlastMultiItemID As Integer
Dim rngFind As Range
Dim strProdID As String
Dim dblStdQty, dblSpecDiscount As Double
'//** Note down the value of Special Discount **//
dblSpecDiscount = wks.Range("X6").Value
'//** Get the last row in the column that stores product IDs **//
rwlastMultiItemID = Application.WorksheetFunction._
CountA(wks.Columns(colMultiItemID))
While (rwlastMultiItemID > 1)
'//** For each item listed in the column **//
strProdID = _
wks.Cells(rwlastMultiItemID, colMultiItemID).Value
'//** Find this ID in the first column of the products table. **//
Set rngFind = ThisWorkbook.Names("ProdDB"). _
RefersToRange.Columns(1).Find(strProdID)
dblStdQty = rngFind.Offset(0, 4).Value
With wks.Cells(rwlastCompiledOrder, colCompiledOrderStart)
.Value = strProdID
.Offset(0,1).Value = dblStdQty
.Offset(0,2).Value = dblSpecDiscount
End With
wks.Cells(rwlastMultiItemID,colMultiItemID).ClearContents
rwlastCompiledOrder = rwlastCompiledOrder + 1
rwlastMultiItemID = rwlastMultiItemID - 1
Wend
wks.ListBoxes("lbSelList").RemoveAllItems
End If
Set chkAddMultiItems = Nothing
End Sub
For the reader:
Can you add the code to check that if the user has opted to add multiple items,
then atleast one item is present in the right hand side ListBox?
If no item is selected, then display a message to the user and exit from the
procedure.
4) Saving the compiled order
Once the order is compiled, the user will set the value of ‘Cash Discount’
using a ScrollBar and select the ‘Sales Region’ using one of the
OptionButtons.
Then the user will click on the ‘Save Complete Order’ button which will do
the following:
A) Append item details in columns A to D of worksheet named ‘SalesDB’
B) Append order details in column H to L of worksheet named ‘SalesDB’
This mainly involves copying the items from one sheet to another and finally
preparing the form for entering the next order.
Sub SaveCompiledOrder()
Dim wksDB, wksOrder As Worksheet
Dim rwlastSavedItem, rwlastCompiledOrde As Long
Dim rngTemp As Range
Dim colSalesDBStart, colOrderDBStart As Integer
Dim strOID, strCID As String
colSalesDBStart = 1
colOrderDBStart = 8
Set wksOrder = ThisWorkbook.Worksheets("OrderForm")
Set wksDB = ThisWorkbook.Worksheets("SalesDB")
rwlastSavedItem = Application.WorksheetFunction. _
CountA(wksDB.Columns(colSalesDBStart)) + 1
'//** Last row of the compiled order **//
rwlastCompiledOrder = Application.WorksheetFunction. –
CountA(wksOrder.Columns(colCompiledOrderStart))
Set rngTemp = wksOrder.Cells(2, colCompiledOrderStart). _
Resize(rwlastCompiledOrder - 1, 3)
rngTemp.Copy (wksDB.Cells(rwlastSavedItem, 2))
rngTemp.ClearContents
Set rngTemp = Nothing
Set rngTemp = wksDB.Cells(rwlastSavedItem, 1).Resize _
(rwlastCompiledOrder - 1, 1)
wksOrder.Range("BE6").Copy
rngTemp.PasteSpecial (xlPasteValues)
Application.CutCopyMode = False
'//** Storing order details **//
rwlastSavedItem = Application.WorksheetFunction.CountA _
(wksDB.Columns(colOrderDBStart)) + 1
With wksDB.Cells(rwlastSavedItem, colOrderDBStart)
wksOrder.Range("BE6").Copy
.PasteSpecial (xlPasteValues)
Application.CutCopyMode = False
.Offset(0, 1).Value = wksOrder.Range("BE3").Value
.Offset(0, 2).Value = wksOrder.Range("BE2").Value
.Offset(0, 3).Value = wksOrder.Range("BE5").Value
.Offset(0, 4).Value = wksOrder.Range("BE4").Value
.Offset(0, 5).Value = _
Trim(Left(wksOrder.Range("C21").Value, 300))
End With
'//** Store latest order ID for reference in next order **//
ThisWorkbook.Worksheets("MasterData").Range("J2").Value = _
wksOrder.Range("BE6").Value
'//** Restore controls to default value **//
Call ResetControls
End Sub
Here is the code to restore the controls to their default values. We have added
this is as another sub-procedure. The reason being we would need this when
writing the code for the ‘Cancel’ button.
Sub ResetControls()
Dim wksOrder As Worksheet
Set wksOrder = ThisWorkbook.Worksheets("OrderForm")
With ThisWorkbook.Worksheets("OrderForm")
.CheckBoxes("chkAddMultiItems").Value = -4146
.CheckBoxes("chkCashDiscount").Value = -4146
.CheckBoxes("chkSpecDiscount").Value = -4146
.Spinners("spbtnSpecDiscount").Value = _
.Spinners("spbtnSpecDiscount").Min
.ScrollBars("scrlCashDiscount").Value = _
.ScrollBars("scrlCashDiscount").Min
.DropDowns("cbProdList").Visible = True
.ListBoxes("lbProdList").Visible = False
.ListBoxes("lbSelList").Visible = False
.Buttons("btnAddSelected").Visible = False
.Buttons("btnAddAll").Visible = False
.Buttons("btnRemoveSel").Visible = False
.Buttons("btnRemoveAll").Visible = False
End With
End Sub
We can write a function that can check the rule and then return a Boolean (True)
if the values are valid and False if the rule is violated. Here is the code for the
function
FunctionIsFormValid() As Boolean
Dim wks As Worksheet
Dim chkAddMultiItems As CheckBox
Dim rwlastCompiledOrder As Integer
Dim rwlastItemDetail As Integer
Set wks = ThisWorkbook.Worksheets("OrderForm")
Set chkAddMultiItems = wks.CheckBoxes("chkAddMultiItems")
If chkAddMultiItems.Value < 0 Then
'//** Single item has to be added **//
Dim colSingleItemDetail As Integer
colSingleItemDetail = 53
'//** If block to check the values **//
If _
wks.Cells(2,colSingleItemDetail).Value = “PP001AFD” And _
wks.Cells(3,colSingleItemDetail).Value <100 And _
wks.Cells(4,colSingleItemDetail).Value <> 0 Then
'//** The rules are violated, return False and exit function **//
IsFormValid = False
Exit Function
End If
Else
'//** Multiple items have to be added **//
Dim rwlastMultiItemID As Integer
Dim rngFind As Range
Dim strProdID As String
Dim dblStdQty, dblSpecDiscount As Double
Dim colMultiItemID As Integer
colMultiItemID = 65
'//Note down the value of Special Discount
dblSpecDiscount = wks.Range("X6").Value
'//Get the last row in the column that stores product IDs
rwlastMultiItemID = Application.WorksheetFunction._
CountA(wks.Columns(colMultiItemID))
While (rwlastMultiItemID > 1)
'//For each item listed in the column
strProdID = wks.Cells(rwlastMultiItemID, _
colMultiItemID).Value
'//Find this ID in the first column of the products table.
Set rngFind = ThisWorkbook.Names("ProdDB"). _
RefersToRange.Columns(1).Find(strProdID)
dblStdQty = rngFind.Offset(0, 4).Value
If (strProdID = “PP001AFD” And dblStdQty<100 And _
dblSpecDiscount<> 0) Then
IsFormValid = False
Exit Function
End If
rwlastMultiItemID = rwlastMultiItemID - 1
Wend
End If
Set chkAddMultiItems = Nothing
'//If the function is not exited so far, it means the values are valid. Return True.
IsFormValid = True
End Function
Then in the click event of “Add Item to List” button (similar to the
AddItemsToList sub that we created in section 21.1.1) we should call this
function and exit the sub if the returned value is False.
Sub AddItemsToList()
Dim strValidationMsg as String
strValidationMsg = _
“Special Discount can’t be applied to Steel” & _
“for Quantity below 100” & VbCrLf & _
“Please cancel and re-enter the order”
If Not(IsFormValid) Then
MsgBox strValidationMsg
End Sub
End If
'//Remaining code of the sub
If chkAddMultiItems.Value < 0 Then…..
'//Remaining code of the sub
End Sub
21.3 The SwitchBoard Form
Switchboard form is sort of a menu from where the user can launch different
functions of the application. This is provided so that the users can perform
different tasks as per their convenience and not necessarily in a sequence. For
example, the user might want to enter two orders one after the other and then
generate the invoices for the orders at a later date.
Switchboard is usually created on a worksheet and usually it is the first form
displayed to the user when the application is launched.
As for controls, a switchboard mainly contains only buttons and checkboxes
since too much data entry is not expected. The switchboard for our application is
shown in the figure 21.3. There is a new term on the form ‘Picklist’. It is actually
an output that we can generate from data stored in our database.
A picklist is a list of items and related quantities to be delivered for a particular
order. It is provided to the employees in the warehouse so that they can pick out
the items and pack them for delivery.
The Invoice and the Picklist are generated for a specific order that has already
been entered. Hence a user would have to specify the related order ID.
21.4.1 Invoice
Once we have entered an order, we may want to generate an invoice that will be
sent to the customer. We will write a macro that will take the OrderID entered by
the user and creates the invoice for that order.
The figure 21.4.1 shows the invoice that will be created. The report is formatted
and sized so as to fit on one standard A4 sized sheet of paper. This is a blank
template and actual invoice will be created in a copy of this worksheet.
Note: In the figure, some of the rows between 18 to 38 and between 44 to 49
have been hidden in order to save space.
Fig. 21.4.1 Template for invoice
One sheet (of pater) can have a list of 20 items. If the number of items exceeds
20, we will create another copy of the template sheet. The multiple worksheets
of the invoice will be stored in a separate file.
Here is how the macro will work.
First details of customer and order will be filled on the template sheet this is
done so that if more than one copy of the template is required we won't have to
fill in the details repeatedly.
A new workbook will be created. A copy of the invoice template will be placed
in the new workbook. Let us call this the ‘Invoice sheet’.
Based on the order ID entered by the user, rows will be filtered from ‘SalesDB’.
these will be copied in the switchboard form in an area that is out of view. We
can use any other worksheet for this purpose because this is only a temporay
holding place.
Starting at the first row of copied data, items are entered in the ‘Invoice sheet’.
The price (after special discount) is calculated and the value is added to the
running sum of the total invoice value.
After the twentieth item is added, and if there are more items remaining, a new
copy of the invoice template is added to the new workbook.
Once all items are added, the new workbook is saved with the name
<ordered>_invoice.xlsx
The customer and order details are removed from the original invoice template
sheet so that it can be used for some other order.
Let's see the code. Comments have been provided at appropriate places.
Sub makeInvoice()
Dim strOrderID, CustID As String
Dim rng As Range
Dim rwcount, invrw, itmcount As Long
Dim wks, wksInv, wksSwitch, wksSales As Worksheet
Dim pagecount As Integer
Dim wbkInv As Workbook
Dim strprodID, strprodDesc, strMeasure As String
Dim dblQty, dblPrice, dblSpecDisc, dblInvValue As Double
Set wksSwitch = ThisWorkbook.Worksheets("SwitchBoard")
Set wksSales = ThisWorkbook.Worksheets("SalesDB")
‘//** Get the OrderID entered by the user on SwitchBoard sheet **//
strOrderID = Trim(wksSwitch.Range("G7").Value)
rwcount = Application.WorksheetFunction.CountA _
(wksSales.Columns(1))
Set rng = wksSales.Range("A1").Resize(rwcount, 4)
‘//** Filter the range based on OrderID **//
rng.AutoFilter Field:=1, Criteria1:=strOrderID
rng.SpecialCells(xlCellTypeVisible).Copy
‘//** Copy filtered rows to temporary area on the SwitchBoard **//
wksSwitch.Range("$AA$1").PasteSpecial (xlPasteValues)
rng.AutoFilter
Set rng = Nothing
rwcount = Application.WorksheetFunction.CountA _
(wksSwitch.Columns(28))
invrw = 17
itmcount = 1
dblInvValue = 0
pagecount = 1
'//** Find the customer Id using the order ID **//
Set rng = wksSales.Range("H:H").Find(strOrderID)
CustID = rng.Offset(0, 3).Value
'//** Locate the row of that customer in the CustomerDB **//
Set rng = ThisWorkbook.Names("CustomerDB"). _
RefersToRange.Columns(1).Find(CustID)
'//** Fill up the invoice template sheet with customer and order details. **//
With ThisWorkbook.Worksheets("Invtemp")
.Range("D5").Value = strOrderID
.Range("D5").Value = rng.Offset(0, 1).Value
.Range("B7").Value = rng.Offset(0, 2).Value
.Range("G7").Value = rng.Offset(0, 3).Value
End With
'//** Create a new workbook (Invoice workbook) and **//
‘//** Paste first invoice sheet into it just before the first sheet **//
Set wbkInv = Workbooks.Add
ThisWorkbook.Worksheets("Invtemp").Copy _
Before:=wbkInv.Worksheets(1)
Set wksInv = ActiveSheet
wksInv.Name = "Page_" & Format(pagecount, "00")
For r = 2 To rwcount Step 1
strprodID = ""
dblSpecDisc = 0
dblQty = 0
dblPrice = 0
With wksInv
strprodID = wksSwitch.Cells(r, 28).Value
dblSpecDisc = wksSwitch.Cells(r, 30).Value
dblQty = wksSwitch.Cells(r, 29).Value
Set rng = ThisWorkbook.Names("ProdDB"). _ RefersToRange.Columns(1).Find(strprodID)
dblPrice = rng.Offset(0, 2).Value
strprodDesc = rng.Offset(0, 1).Value
strMeasure = rng.Offset(0, 3).Value
'//** Set the values in invoice **//
.Cells(invrw, 2).Value = itmcount
.Cells(invrw, 3).Value = strprodDesc
.Cells(invrw, 7).Value = dblPrice
.Cells(invrw, 8).Value = dblSpecDisc
dblPrice = dblPrice * (100 - dblSpecDisc) / 100
.Cells(invrw, 9).Value = dblPrice
.Cells(invrw, 10).Value = dblQty & " " & strMeasure
.Cells(invrw, 11).Value = dblPrice * dblQty
dblInvValue = dblInvValue + dblPrice * dblQty
If (itmcount Mod 20) = 0 Then
'//** For every 20 items, paste a new copy of the Invoice template **//
'//** Make the new sheet the active sheet that will receive the input **//
'//** Name the new sheet as per the next page number **//
ThisWorkbook.Worksheets("Invtemp").Copy After:=wksInv
Set wksInv = ActiveSheet
pagecount = pagecount + 1
wksInv.Name = "Page_" & Format(pagecount, "00")
invrw = 17
Else
invrw = invrw + 1
End If
itmcount = itmcount + 1
Set rng = Nothing
End With
Next r
'//** Insert the total value of the invoice in the last sheet **//
wksInv.Range("J39").Value = dblInvValue
'//** Display the first sheet in the Invoice workbook, then save and close the workbook **//
wbkInv.Worksheets("Page_01").Activate
wbkInv.SaveAs ("C:\reports\" & strOrderID & "_Invoice.xlsx")
wbkInv.Close
'//** Clear all the cells on the Switchboard and Invoice template worksheets **//
wksSwitch.Range("AA:AD").ClearContents
With ThisWorkbook.Worksheets("Invtemp")
.Range("D3").ClearContents
.Range("D5").ClearContents
.Range("B7").ClearContents
.Range("G7").ClearContents
End With
End Sub
Fig 21.4.2a
Figure shows the table on SalesDB worksheet with the formula applied in
column E
C) Apply a filter and take out the rows that fall between the start and end dates.
D) Transfer the filtered rows to Column A of a worksheet named “StageSheet”.
Notice that we are not going to transfer the dates that we looked up.
E) Now, only for this small set of rows, look up the sales region from the
orderdb again using OrderId as the key. Convert the formulas to values.
Fig 21.4.2b
Figure shows the table on StageSheet worksheet with the formula applied in
column E
F) Next, create a pivot table with components in rows and region in columns.
Values will be the sum of sales quantities. This pivot table will be on the
staging sheet.
Notice that we are not displaying the totals. In step H we will copy the cells from
H2:L10.
G) Copy the names of components (first col. Of pivot table) to col O on the
staging sheet.
H) Copy the total units sold FOR ONLY ONE REGION to column Q.
I) Sort the columns O to Q in descending order of unitsnsold.
J) Remove any rows where the units sold are 0 or blank.
K) Remove any non blank rows beyond five components.
L) In col. P lookup the names of the components from the ProdDb table (Notice
again how two tables are linked to provide only the required information).
M) For the remaining rows, create a pie chart showing the values in percentage.
Only columns P and Q should be included.
Fig 21.4.2d The data and pie chart for one region
N) This chart is for just one region. Save it as an image so that later it can be
transferred to a PowerPoint presentation file.
O) Repeat steps H to L for other regions. The.pie chart will get updated each
time.
Notice the following: In some regions we may be selling only 2 or 3 products. In
that case the chart will be based on only those rows. In case of five or more
components we need to use only the first five rows.
The best way to deal with this changing data, is to create an expanding named
range. We will create two ranges. One for the component names and other for
the number of units. With reference to figure 21.4.2d, the formulas for the ranges
are as follows:
Top5CompName
=OFFSET(StageSheet!$O$1,1,1,COUNTA(StageSheet!$Q:$Q)-1,1)
Top5CompVal
=OFFSET(StageSheet!$O$1,1,2,COUNTA(StageSheet!$Q:$Q)-1,1)
Now we set our chart to refer to that range. See section 10.4 “Using dynamic
ranges in chart series” for the steps to set a chart series with a named range.
Next time when the report has to be produced, our macro will perform the
following steps:
1) Refresh the data in columns A to E on the staging sheet by following steps A
to E described earlier.
2) Set the range of the pivot table to the new data and refresh the pivot table.
3) Repeat steps G through O.
Now we can start coding our macro. Read through the code and see how the
cells on the worksheets would change. Comments are provided at appropriate
places.
Sub Top5ComponentsReport()
‘//** Declaring the variables **//
Dim endDate, startDate As Date
Dim colOrderDate, colOrderRegion, c As Integer
Dim strOrderDateFormula, strOrderRegionFormula As String
Dim strCompNameFormula, strcrit1, strcrit2, strAddr As String
Dim rcount As Long
Dim rngtemp As Range
Dim pvt As PivotTable
Dim wksStage As Worksheet
colOrderDate = 5
colOrderRegion = 5
'//** Compile all the formula strings **//
strOrderDateFormula = "=vlookup(a2,H:M,5,0)"
strOrderRegionFormula = "=vlookup(a2,SalesDB!H:M,2,0)"
strCompNameFormula = "=vlookup(O2,ProdDB,2,0)"
Set wksStage = ThisWorkbook.Worksheets("StageSheet")
wksStage.Range("A:E").ClearContents
'//** Get the start and end dates **//
If Day(Date) < 25 Then
endDate = DateSerial(Year(Date), Month(Date), 1)
endDate = DateAdd("d", -1, endDate)
startDate = DateAdd("m", -3, endDate)
Else
endDate = Date
startDate = DateAdd("m", -2, endDate)
startDate = DateSerial(Year(startDate), Month(startDate), 1)
End If
strcrit1 = ">" & Format(startDate, "mm/dd/yyyy")
strcrit2 = "<=" & Format(endDate, "mm/dd/yyyy")
With ThisWorkbook.Worksheets("SalesDB")
.Cells(1, colOrderDate).Value = "OrderDate"
'//** Lookup the orderdates in the transactions table on SalesDB worksheet **//
.Cells(2, colOrderDate).Formula = strOrderDateFormula
rcount = Application.WorksheetFunction.CountA(.Columns(1))
.Cells(2, colOrderDate).Resize(rcount - 1, 1).FillDown
'//** Filter based on the order dates **//
Set rngtemp = .Cells(1, 1).Resize(rcount, colOrderDate)
rngtemp.AutoFilter Field:=colOrderDate, _
Criteria1:=strcrit1, Operator:=xlAnd, _
Criteria2:=strcrit2
Set rngtemp = Nothing
'//** Copy filtered cells to Staging sheet **//
Set rngtemp = .Cells(1, 1).Resize(rcount, colOrderDate - 1)
rngtemp.SpecialCells(xlCellTypeVisible).Copy _
(wksStage.Cells(1, 1))
'//** Remove filter **//
.Cells(1, 1).Resize(rcount, colOrderDate).AutoFilter
Set rngtemp = Nothing
End With
With wksStage
.Cells(1, colOrderRegion).Value = "Region"
'//** Lookup order region **//
.Cells(2, colOrderRegion).Formula = strOrderRegionFormula
rcount = Application.WorksheetFunction.CountA(.Columns(1))
.Cells(2, colOrderRegion).Resize(rcount - 1, 1).FillDown
.Cells(2, colOrderRegion).Resize(rcount - 1, 1).Copy
.Cells(2, colOrderRegion).PasteSpecial (xlPasteValues)
Set rngtemp = .Cells(1, 1).Resize(rcount, colOrderRegion)
End With
'//** Set the data range for pivot table and refresh **//
Set pvt = wksStage.PivotTables("ProdRegionsalesSummary")
pvt.SourceData = rngtemp.Worksheet.Name & "!" & rngtemp.Address
pvt.RefreshTable
Set rngtemp = Nothing
'//**Get a reference to range of pivot table having values and row and column headings **//
'//** Refer to figure 21.4.2c **//
Set rngtemp = pvt.TableRange2.Cells(2, 1).Resize_
(pvt.TableRange2.Rows.Count - 1,pvt.TableRange2.Columns.Count)
'//** Start looping throught the columns for each of the regions **//
For c = 2 To rngtemp.Columns.Count Step 1
wksStage.Columns("O").ClearContents
wksStage.Columns("Q").ClearContents
rngtemp.Columns(1).Copy(wksStage.Range("O1"))
rngtemp.Columns(c).Copy(wksStage.Range("Q1"))
rcount = rngtemp.Rows.Count – 1
'//** Sort the rows based on the values of units sold in the region **//
With wksStage.Sort
.SortFields.Clear
.SetRange (wksStage.Range("O1:Q" & (rcount + 1)))
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortFields.Add Key:=wksStage.Range("Q1").Offset(1,0)._
Resize(rcount, 1), SortOn:=SortOnValues, _
Order:=xlAscending
.Apply
End With
'//** Delete all contents beyond the top 5 rows **//
strAddr = "O7:Q" & (rcount + 1)
wksStageRange(strAddr).ClearContents
'//** take the top 5 rows, if any of these has 0 sales values, remove those rows. **//
For r = 6 To 2 Step -1
If wksStage.Cells(r, 17).Value <= 0 Then
wksStage.Range("O" & r & ":Q" & r).ClearContents
End If
Next r
'//** Bring in component names in place of **//
wksStage.Range("P2").Formula = strCompNameFormula
wksStage.Range("P2:P6").FillDown
'//** save the chart picture to a file. Refer to methods described in Chapter 11 **//
Next c
End Sub
Appendix A
Error Handling
In this chapter we will focus on the basic methods available to handle errors that
might occur in our macros.
Please note that our prime focus should be to avoid errors instead of building
ways to handle them. However, error handling is an important part of
programming and in this chapter we will see some general techniques of error
handling. Let’s gets started.
A.1 Some Terminology
Error: An error is any unplanned or unforeseen event that would prevent our
macro from carrying out its intended purpose.
Compile time errors: These are made by the programmer at the time of writing
the macro. These are immediately detected and reported by the Visual Basic
Editor so we can make corrections in the code.
Runtime errors: These errors occur only when our macro is executed. These are
fatal because the macro execution stops abruptly, any objects and variables
ceated and active till that point, are held in memory unless the user accepts to
forcefully stop the macro.
We need to anticipate them and write code that would tell visual basic what
action to take if the error occurs.
Error handler: Block(s) of code that gets executed as a response to a runtime
error. These are written in the same function or sub procedure in which we
expect the error to occur. We will clarify this with examples. However, here are
few points to remember when we use this method:
1. Each block of error handling code should be placed after the last line of our
main code. Even if we have multiple error handlers, they should be placed one
after the other but after our main code.
2. Each error handler should end with ‘Exit Sub’, ‘Resume’ or ‘Resume Next’
statements. This allows control to move back to the main code. Otherwise macro
execution will continue to the immediately next line of code which might belong
to another error handler.
3. Each error handler should be marked with a unique and meaningful label. This
label and the related error handler should be part of the same function or sub
procedure where we expect the error to occur.
4. We should place an ‘Exit Sub’ or ‘Exit Function’ statement immediately
before the label of the first error handler. If not, after executing the main code,
Excel tries to execute the lines in the error handlers even if no error has occured.
Err object: When the execution of our macro begins, Visual Basic automatically
creates an object that would help our code respond to errors. This object is the
‘Err’ object. Every error that occurs at run time has a unique code and this code
is captured in the ‘Number’ property of the Err object.
We can have a ‘Select Case’ or ‘If-Else’ block that can check the ‘Number’
property to determine the type of error that has occurred and then take the
necessary action. If no error occurs the number property has a value zero. We
will not try to learn about all possible error numbers. Instead we will just check
if the number property is non-zero and then check the variables and objects of
our code directly for any errorneous conditions.
In the sections that follow, we will use the phrase “Main Code” to denote the
code that our macro will execute if no error occurs. This is to differentiate it
from code of error handlers. If an error occurs, execution of the main code will
either resume once the error is handled, or the macro execution will stop.
A.2 On Error.... please do something
VBA provides three statements that help us to activate specific error handling
code.
These statements begin with "On Error.." and should be placed before the code
that we expect to cause an error.
The "On Error" statements can be used to transfer control of our macro to a
different section of the procedure or function that contains a different set of lines
of code. This section is called the “Error Handler” for that particular sub or
function procedure. In this section we can either rectify the effects the error or
exit from the procedure or stop the macro execution.
A) On Error Go To 0
This statement disables any error handling in the currently executing
procedure.
Unless we have advanced programming knowledge there is no use in turning
error handling on and off. We will never use this statement.
B) On Error Resume Next
With this statement, execution proceeds to the statement immediately after the
line of code that caused the error. Few things to remember when using this
statement
1. The immediately next line of code should have a block that checks for each
expected error condition.
2. The statement that caused the error is not executed, and it is not possible to
return back to it. So we have to rewrite that code after the error-handling
code.
3. At the end of the error-handling code we must clear out the Err object to
reset its Number property to Zero. Or else some other subsequent code that
checks the Number property may get executed.
4. We should keep the entire error handling code inside an ‘If-Else’ block or
else much of that code will get executed even if an error does not occur.
For example, consider the following line of code:
wbk.Worksheets("Sales").Range("A1"). Value =100/dblDivisor
Notice two things in the code just shown:
· The order of checking the objects/variables is important. The wbk object has
to be checked before we can check for existence of the worksheet. Otherwise,
we will not know which object caused an error in the line
Set wks = wbk.Worksheets("Sales")
· If the entire code was not placed inside the "If Err.Number <> 0 Then" block, the
following lines would get executed even if no error occurred:
Dim wks As Worksheet
Set wks = wbk.Worksheets("Sales")
wbk.Worksheets("Sales").Range("A1").Value = 100 / dblDivisor
Err.Clear
B.2.3 Workbook Object (get the path, save, get all the sheets&
more)
Usage: To work on one individual open workbook.
For the code examples below, assume that we have an object ’wbk’ that
represents “C:\MyReports\Input.xlsx”. Our macro resides in the workbook
‘Report.xlsx’
Properties
1. Worksheets: Returns collection of all the worksheets inside a given
workbook.
2. Path: Returns a String containing the path to the folder where current
workbook is stored.
‘//** Declare a string variable **//
Dim strFilePath as String
‘//** strFilePath now holds “C:\MyReports\”. **//
strFilePath = wbk.Path
3. Name: Returns a String containing the name of the workbook.
‘//** Declare a string variable **//
Dim strFileName as String
‘//** strFileName now holds “Input.xlsx”. **//
strFileName = wbk.Name
4. FullName:Returns a String containing the path and name of the workbook.
‘//** Declare a string variable **//
Dim strFullName as String
‘//** strFullName now holds “C:\MyReports\Input.xlsx” **//
strFullName = wbk.FullName
5. Thisworkbook: Returns a reference to the workbook in which the macro is
running.
‘//** Returns the string “Report.xlsx”. **//
Thisworkbook.Name
Methods
1. Save: Used to save a particular workbook. The method does not take any
arguments
‘//Save the workbook referred to by the wbk object
wbk.Save
‘//** Save the current workbook that holds the macro **//
Thisworkbook.Save
2. Close: To close a particular workbook.
This method usually takes 3 arguments, normally only 1 of them is important
Arguments:
SaveChanges: This argument takes a Boolean value. Set this to ‘True’ if the
changes made in the workbook have to be saved. If the workbook is an input
file from which data is only read, this argument can be set to ‘False’.
wbk.Close SaveChanges:=False
To Save changes made to the current workbook (in which the macro is
written) the code would be.
Thisworkbook.Close SaveChanges:=True
Methods
1. Calculate: This method calculates all the formulas on a worksheet when the
calculation mode is set to ‘Manual’
wks.Calculate
2. PivotTables: This method returns a collection of all the pivot tables that are
present on a given worksheet.
A particular PivotTable can be obtained using the number or its name. We
cover PivotTables in detail in Chapter 5.
3. Copy: This method allows us to copy a worksheet within the workbook. The
main use is that it also allows us to save a worksheet to a new workbook.
The code below makes a copy of Sheet3 and places it immediately after
Sheet4 in the same workbook. Note that instead of ‘After’ we could use the
argument ‘Before’ to position the new copy before (to the left of) Sheet4
Thisworkbook.Worksheets(“Sheet3”).Copy _
After:= Thisworkbook.Worksheets(“Sheet4”)
Properties
1. Address: This is a string value representing the address of a particular range
on a worksheet. The default syntax for this property is as follows:
‘//** Capture the address of a range into a string variable **//
strRangeAddr = rng.Address
However, this property takes 5 parameters (all optional) and these can be used
to return addresses in different formats. The complete code for this property
written along with parameters is as follows:
rng.Address(RowAbsolute, ColumnAbsolute, _
ReferenceStyle , External, RelativeTo)
The 4 most important ones are described below with code examples. If we
choose to use only 1 or 2 parameters, we need to leave commas for parameters
to the left, the ones to the right can be ignored. Code examples will make this
clear.
For the code examples listed below assume that our macro is in workbook
‘Report.xlsx’ and that there is another open workbook ‘ReferenceBook.xlsx’.
This latter book has a sheet ‘Lookup Sheet’ (notice the space in the name)
which has data in the range A5:B505.
Parameters:
RowAbsolute: This parameter takes a Boolean value. If the value is ‘True’, the
address string returned has its rows fixed. The default value is True.
‘//** Returns $A5:$B505 **//
rng.Address(False)
ReferenceStyle: If this value is ‘True’, the address string returned has its rows
fixed. The default value is True.
‘//** Returns R5C:R505C2 **//
rng.Address(, , xlR1C1)
External: This parameter takes Boolean values. If the value is ‘True’, the
address string returned has the name of the worksheet and workbook on which
the range is actually present. The default value is False. Set the value to ‘True’
when creating a formula that refers to a sheet in another workbook.
‘//** Returns '[ReferenceBook.xlsx’]Lookup Sheet’!$A$5:$B$505 **//
rng.Address(,,,True)
2. Cells: This property returns individual cells in a range. Based on the number
of rows/columns the array can be 1 or 2 dimensional. Each individual cell is a
Range object
Each cell can be accessed individually by specifying its row and column with
the first row being row 1 and left most column being column 1. So in the
example above, the cell C5 is same as:
'//** Column C of spreadsheet is 2nd column of range, **//
‘//** row 5 of spreadsheet, is row 5 of range. **//
rng.Cells(5,2)
‘//** Cell C5, fifth row and third column **//
Wks.Cells(5,3)
Note: for the properties 3 to 9, refer to figure B.2.6. In the code examples,
the object ’rng’ refers to the range “A1:E9” shown in the figure. The object
’wks’ Is the worksheet containing this range. Most of the properties return a
reference to another Range. We can assign this to another Range object
’rngNew’
3. Rows: This is a collection of all the cells in a range. Each row can be uniquely
identified using its row number. The property returns a Range object that can
be set to another Range object
‘//** Reference to A6:E6 **//
Set rngNew = rng.Rows(6)
4. Columns: This is a collection of all the columns in a range. Each column can
be uniquely identified using its column number.
‘//** Reference to C1:C9 **//
Set rngNew = rng.Columns(3)
Parameters:
1. rowOffset: This is an optional argument. It is of Long Integer datatype and
represents the number of rows (positive, negative, or 0 (zero)) by which the
range is to be offset. Positive values are offset downward, and negative values
are offset upward. The default value is 0.
2. columnOffset: This an optional argument. It is of Long Integer datatype
and represents the number of columns (positive, negative, or 0 (zero)) by
which the range is to be offset. Positive values are offset towards right, and
negative values are offset towards. The default value is 0.
‘//** Reference to A6:B7 **//
Set rngNew = Wks.Range(“B3:C4”).Offset(3,-1)
9. Resize: This property resizes the specified range and returns a new object that
represents the new resized range.
rng.Resize(rowSize, columnSize)
Parameters:
1. rowSize: This is an optional argument. It is of Variant datatype and
represents the number of rows in the resized range. If omitted, the number
of rows remains same as the original range
2. columnSize: This is an optional argument. It is of Variant datatype and
represents the number of columns in the resized range. If omitted, the
number of colums remains same as the original range
The following code shows how the Offset and Resize properties can be
combined. Refer to figure B.2.6. It returns a reference to the range B2:C4
Set rngNew = Wks.Range(“B1”).Offset(1,0).Resize(3,2)
Methods
1. SpecialCells: Returns a Range object that represents all the cells that match
the specified type and value.
rng.SpecialCells(Type, Value)
Valid values for ‘Value’ argument
XlSpecialCellsValue
Value
constants
2. Copy: Copies a given range to another range (in case the ‘Destination’
argument is specified) or to the Clipboard.
If copied to clipboard, the ‘CutCopyMode’ has to be turned off after the range
is pasted to another location.
rng.Copy(DestinationRange)
The destination range should be the top left corner cell of the range where we
want to paste the copied range.
The example below copies a range A1:D7 from Sheet2 to Range E5:H11 of
Sheet1.Notice that for the destination argument we have specified only the
top-left corner of the destination range.
Set rng = ThisWorkbook.Worksheets(“Sheet2”).Range(“A1:D7))
rng.Copy(ThisWorkbook.Worksheets(“Sheet1”).Cells(5,5))
If the link argument is set to ‘True’ Cell E5 on sheet1 will have a formula
“=Sheet2!A1”. Similarly, cell G10 on Sheet1 will have the formula
“=Sheet2!C6”.
5. PasteSpecial: This method represents the PasteSpecial option available on the
spreadsheet. Its most important use is to convert formulas to values in a range.
Syntax for the method is as follows
rDst.PasteSpecial(PasteType, Operation, SkipBlanks, Transpose)
In the above line of code, ’rDst’ is the destination range (the one where we
want to paste copied data)
In case the copied data contains more than one cell, ’rDst’ will be the top left
cell of the destination range.
Arguments:
1. PasteType: This argument dictates what part of the copied data is pasted.
Value of this argument is one of the xlPasteType named constants. There
are in all 12 constants.
A cell on a spread sheet can have Formula, a Value (calculated by the formula
and displayed in the cell), some formatting in terms of cell color, some border,
Comments, some cell validation and so on.
When using PasteSpecial method, we can control what gets copied to the
destination range. The most useful values for this argument are listed in the
table below. The default value is ’xlPasteAll ’
Name Description
xlPasteAll Everything will be pasted.
Format from the source range is
xlPasteFormats
pasted.
xlPasteFormulas Formulas are pasted.
Formulas and Number formats are
xlPasteFormulasAndNumberFormats
pasted.
xlPasteValues Values are pasted.
Values and Number formats are
xlPasteValuesAndNumberFormats
pasted.
2. Operation: When a numeric value is copied, we can use it to perform
mathematical operations by pasting it on another range having numeric values.
The Operation parameter takes its value from one of the predefined constants
and specified the type of operation to be done.
Name Description
Copied data will be added to the values in the
xlPasteSpecialOperationAdd
destination range.
Copied data will divide the values in the destination
xlPasteSpecialOperationDivide
range.
Copied data will be multiplied with the values in the
xlPasteSpecialOperationMultiply
destination range.
xlPasteSpecialOperationNone No calculation will be done in the paste operation.
Copied data will be subtracted from the values in
xlPasteSpecialOperationSubtract
the destination range.
We consider 2 examples, one for division and the other for multiplication, but
the concepts are applicable to all the operations allowable in PasteSpecial
method.
Example 1
Suppose we have closing stock prices in cells C2 to C501 of the worksheet
“StockPrice”. The stock has undergone a 2:1 split so all the prices have to be
divided by two. We proceed as follows (we should ensure that the cell AA2 is
blank)
With Worksheets(“StockPrice”)
.Range(“AA2”).Value = 2
.Range(“AA2”).Copy
.Range(“C2:C501”).PasteSpecial _
xlPasteValues, xlPasteSpecialOperationDivide
.Range(“AA2”).ClearContents
End With
Application.CutCopyMode = False
Example 2
Suppose for a group of 50 workers we maintain data in a spreadsheet in rows 2
to 51.
Column A contains Names of workers, Column B has the number of hours
worked in a week and Column C has the hourly pay.
We assume that the current sheet named “TempSheet” is a temporary sheet so we
don’t mind changing the values in some of the cells permanently.
We want to calculate the total amount we will be paying this week. We do this
by multiplying the values in column B with values in Column C.
We proceed as follows.
Dim dblAmt as Double
With worksheets(“TempSheet”)
.Range(“B2:B51”).Copy
.Range(“C2:C51”).PasteSpecial _
xlPasteValues, xlPasteSpecialOperationDivide
Application.CutCopyMode = False
dblAmt = _
Application.worksheetFunction.Sum(.Range(“C2:C51”))
End With
We now continue with the description of the remaining two arguments
3. SkipBlanks: This is a Boolean argument and controls if blank cells that
have been copied should be pasted in the destination. The default value is
‘False’ (don’t ignore the blanks). Setting this property to ‘True’ will ignore
blank copied cells. We will always use the default value of ‘False’.
4. Transpose: This Boolean argument specifies if the rows of the pasted
range should be turned to columns and vice-versa. Most of the time we will
use the default value of ‘False’ meaning - don’t transpose at the time of
pasting.
6. Find: This method finds specific information in a range, and returns another
range object (a single cell) that represents the first cell where that information
is found. The syntax is as follows:
rngSource.Find(What, After, LookIn, LookAt, SearchOrder, _
SearchDirection, MatchCase, MatchByte, SearchFormat)
The range in which the search will be conducted is denoted by ’rngSource’ we
will call it the “Source Range”.
Arguments:
1. What: This argument is the value that we want to find, it can be of any
primitive datatype.
2. LookIn: This argument specifies the part of contents of the source range
where the search is conducted. It can take one of the following values
a)xlValues: The values in the cells are checked even if the values
are results of formulas in the cells.
b)xlFormulas: The formulas in the cells are checked. For example, we
can search for the word “look” to see if any cells in the
range have a “VLOOKUP” formula.
c)xlComments:The comments in the cells are checked to see if they
contain the value that is being searched.
3. LookAt: This argument is used to specify if a partial match is acceptable or
we want an exact match. We will explain this with examples along with the
values that this argument can take. The acceptable values are as follows:
a) xlPart: If the argument ’What’ has the value “King” then even cells
containing the words “Kingdom” or “Shrinking” are returned
as search results.
b) xlWhole:Only the cells that contain the work “King” are returned.
Note that the word “King” may be in upper or lower case.
4. MatchCase: This is a Boolean argument. If set to TRUE, the search
becomes case sensitive. Meaning if ’What’ has the value “King”, then the
cells with value “KING” or even “kINg” are not included in the results. If
omitted, the default FALSE is used.
The following arguments are applicable only in special cases and are not
useful in routine work. These are optional arguments and can be safely
omitted. We will describe them briefly but not use them.
Arguments:
rngAfter: This argument is a range (a single cell) beyond which we want to
find the next value. For example, rngSource is A1:A500 and rngAfter is A5,
then the new search will be in the range A6:A500.
Usually ’rngAfter’ is the range returned by a previous execution of the Find
method. To continuously find one value after another we can use a code like
so:
Set rngFound = rngSource.FindNext(rngFound)
We should use such code with care because it can result either in an error
(when nothing is found in the previous search) or in an infinite loop because
when the search reaches the end of the specified source range, it wraps around
to the beginning of the range.
In sections 3.2 we will see detailed examples of proper use of the Find and
FindNext methods.
8. Replace: This method first finds one set of specified characters in a range and
then replaces them with with another set of specified characters. The syntax is:
rngSource.Replace(What, Replacement, LookAt, SearchOrder, _
MatchCase, MatchByte, SearchFormat, ReplaceFormat)
The objects and arguments ’rngSource’, ’What’, ’LookAt’, ’SearchOrder’,
’MatchCase’, ’MatchByte’ and ’SearchFormat’ have the same meaning as
described for the Find method a bit earlier. The other arguments are as
follows:
1. Replacement: This argument holds the value that will be replaced in place
of What. It should be of the same datatype as What.
2. ReplaceFormat: Applicable only when we use SearchFormat. It specifies
the changed format to be applied to the format that was earlier searched for.
In sections 3.3 we will see detailed example of Replace method.
Fig B.3.1a The code module for a worksheet. Events listed in the Procedure Dropodown
The names of the events tell us when the events will be triggered. When we
select any one event, Excel will insert an empty sub procedure in which we can
put code that will get executed when the event is triggered.
The entire list of events is not of much use. We will list down some of the events
that we can use frequently along with a brief explanation of their arguments.
Reader is requested to explore the other events.
Specific code examples won’t be provided here. Examples can be found in
sections 17.5, and section 12.1.
· Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Occurs before the workbook is saved either by a user or by a macro.
Inside the code for this sub, we should set the argument ’SaveAsUI’ to True if
we want to show “Save As” dialog to the user.
The argument ’Cancel’ has the value False when the event is triggered. If inside
the sub we set this argument to True, the workbook will not get saved.
· Workbook_Open()
Occurs when the workbook (holding the code for the event) is opened either
manually by the user or by a macro.
· Worksheet_SelectionChange(ByVal Target As Range)
Occurs when on a worksheet the user clicks on (selects) any one cell or uses
arrow keys to move to another cell.
The argument ’Target’ is the newly selected range.
This event is a bit annoying because it gets triggered even when user moves to
another cell without any specific purpose. If we want our code to run only when
a specific cell is selected, we should compare the address of ’Target’ range with
our desired cell.
· Worksheet_Change(ByVal Target As Range)
Occurs when contents of the worksheet are changed, either manually by the user
or through a macro.
This event is not triggered if only the formatting of the content is change. This
event is better than ’Worksheet_SelectionChange’ because it is not triggered
every time the user moves to a new cell.
The argument ’Target’ is the range that has been changed.
The event is not triggered when cells change due to a calculation. There is a
separate event called ’Worksheet_Calculate’ that we can use to respond to
calculations.
Appendix C
Visual Basic Fundamentals
Before learning VBA (Visual Basic for Applications), we need to understand
some fundamentals of VB (Visual Basic) which is the base for VBA. This
appendix will help the reader learn the most basic concepts required for
programming with Excel VBA.
The emphasis here is not to delve deep into programming techniques. The aim is
to learn a good variety of general concepts that can be safely combined and
applied to create a wide array of solutions. Let’s get started.
C.1 Writing code - The Visual Basic Editor
Code for macros is written in an interface called the Visual Basic Editor (VBE).
This is Microsoft’s IDE (Integrated Development Environment) for writing
macros.
You can access the VBE in two ways
1) One option is to press ‘Alt + F11’
2) Another option is as follows.
In the main ribbon, ensure the ‘Developer’ tab is visible.
If the developer tab is not visible, go to: File menu -> Click options in the left
hand pane. In the ‘Excel Options’ dialog box that comes up (see figure C.1a),
click on ‘Customize Ribbon’ on the left pane (marked by number 1) and check
the ‘Developer’ Option in the list on the right (marked by number 2). Finally,
click on OK.
For a standard Module, this dropdown lists the name of all the sub and
function procedures created in that module.
Selecting any event or procedure name, positions the cursor in the area where
that procedure is declared
If the entry “(General)” in the Object dropdown, we get an entry
“(Declaration)”. Selecting this entry will position the cursor at the beginning
of the module where module level variables are declared.
4. The Immediate window
This window is used to see program output using the ‘Debug.Print’ command.
This window is very useful when we are in the process of developing our code
and we want to see if the code written upto a certain point is working properly
before we execute the code on files or workbooks.
For example, while we are still developing our macro, if we are compiling a
string holding the path of a file that we will later open, it is better to print the
path in the Immediate window to check it, instead of executing it directly and
getting an error in case the path is incorrect.
Syntax:
At several places in this book we will come across the term ‘Syntax’.
Syntax is the specific ordered combination of words that forms one single
program instruction.
In any line of code if we change the position of a single character from the
prescribed syntax the line is treated as invalid and results in an error.
Following is the correct syntax to get a reference to the second worksheet in a
workbook (we will see the details in appendix B)
Set wks = wbk.Worksheets(2)
The following is the incorrect version of the same line of code. Notice that one
missing word causes an error.
Wks = wbk.Worksheets(2)
One important thing we need to ensure when writing code in Visual Basic Editor
(VBE) is that the ‘Intellisense’ feature is turned on.
Intellisense helps us to use the correct syntax when writing our code. It provides
the complete list of all properties and methods for any object along with the
correct spelling for the property and method names. Further it gives us the
correct order and name for arguments of methods, sub procedures and functions
(we will discuss all of these concepts shortly).
The following figure shows how Intellisense gives us the correct syntax (in this
case the entire list of arguments) for the Open method of a Workbooks object.
This helps avoid coding errors and also saves us the effort of memorizing the
exact names and positions of arguments.
By default, the Intellisense feature is turned on when we open the VBE.
However, it is better to do a quick check before writing any code. In VBE, go to
the ‘Tools’ menu and click on ‘Options…’. The ‘Options’ dialog box comes up
(refer to figure C.1d).
Fig C.1d Visual Basic Editor options
If the Comment or Uncomment Block icons are not visible, do the following:
1. In the Visual Basic Editor, click on the ‘View’ menu.
2. Hover the mouse pointer over the “Toolbars” option to open a small Sub-
menu
3. Click on the “Edit” option to show the editing toolbar. This toolbar can be
dragged and positioned just like a normal windows tool bar. It should contain
the two required icons.
Comments can be used to deactivate certain lines of code. For example, lines
that we need only during testing purpose or lines that we have kept as options to
the primary code. For example, the second line of code shown below will not be
executed.
rng.Copy(ThisWorkbook.Worksheets("InvTemp").Range("$R$1"))
‘rng.Copy(ThisWorkbook.Worksheets("Invoice").Range("$A$1"))
C.3 Primitive Datatypes, Variables, Expressions and
Declaration.
In a computer program, we need to tell the computer about the real world items
that the program would be dealing with. As an example, suppose we have a
program that calculates Sales Revenue, we have the formula
Revenue = Units Sold x Price per Unit
The computer does not understand things like ‘Revenue’ or ‘Price’. However, it
does understand things like whole numbers, decimal numbers, single letters,
string of letters and so on.
In our example, assume for now, that Units sold would be a whole number or
‘Integer’ like 5000 books or 1000 blenders.
Revenue and Price would be dollar figures, numbers with fractions. These are
called floating point numbers.
If each product sold has a product identification number, it will most likely be
some form of alpha-numeric identification. For example, “CPU00010A590”.
This is a ‘String’.
Integer, Float and String are called primitive or native datatypes, things that the
computer understands.
Following are some points to bear in mind when writing macros:
Ø Every representation of a real world item that is processed by our
program/macro is called a ‘variable’.
Ø Variables are given names so that the program and the programmer can
identify them.
Ø There are certain words called ‘reserved words’ or ‘keywords’ that are used
exclusively for writing program instructions. These should not be used as
variable names.
Ø In reality, a variable is a name given to some space reserved in the computer’s
memory. Our macro can then store values in that space or change values that
are already stored.
The act of telling the program which item of the real world corresponds to which
datatype is called ‘Declaring’ the variable. This is done using the ‘Dim’
keyword.
Declaring the variable tells the program to reserve some space in the memory
(RAM) where different values for that variable can be stored.
Continuing with our example, the declaration would go like this
Dim unitsSold As Integer
Here we are saying,
“Hey macro, whenever I say unitSold, you should think of it as an integer.
In the computer’s memory, please create space for one more integer. I will tell
you what value to put there at different times in the program.”
Important points about primitive datatypes:
Ø Conceptually, primitive datatypes cannot have any subcomponents. They are
the basic building blocks.
Ø Each datatype can hold only certain range of values. Like maximum value for
a number or maximum number of characters in text. Exceeding this limit
would cause errors. Fortunately, datatypes have a large enough limit to cover
all practical purposes.
Expressions:
In the discussion that follows, we will use the term ‘expression’ at many places.
An expression is a line of code that consists of variables and operations on
variables. For example, at the beginning of this section we saw the following
expression
Units_Sold x Price_per_Unit
Here ’Units_Sold’ and ’Price_per_Unit’ are variables and the operation is that of
multiplying the two variables.
Operations are performed using ‘Operators’. For example, multiplication is
performed using ( * ) and not (x) as shown here. We will discuss operators in
detail in an upcoming section.
Expressions produce a result or value. The result is of a particular datatype. This
result can be used in the following ways
a. Place the result in another variable. For example, when we write
Revenue = Units_Sold x Price_per_Unit
We are placing the result of the expression (right hand side) in the variable
‘Revenue’ on the left hand side.
The act of putting a value in a variable is called ‘Assignment’. It is an
operation and is performed using the assignment operator ( = ).
The variable on the left hand side should be of the appropriate datatype. For
example, in our case, ’Units_Sold’ would most likely be an integer and ’Price_
per_Unit’ would be a decimal number (Double datatype). The result of
multiplication will be a Double and so the variable ‘Revenue’ should be
declared as a Double.
An error occurs if the datatype is not compatible or if the value produced by
the expression is outside the range of the variable on the left hand side.
b. Place the result in a cell of a worksheet.
Worksheets(“Sheet3”).Range(“B2”).Value = _
Units_Sold * Price_per_Unit
An expression need not be limited to only two variables. We can have several
variables and operators as well as constants. The following line of code
calculates a hypothetical adjusted amount
adj_amount = (((Units_Sold * Price_per_Unit) - 13)/100) + 50
Note that whenever we have a long expression with several variables and
operators we must use parentheses or brackets to make the results clear. For
instance, in the expression above it is clear that
1. ’Units_Sold’ should be multiplied with ’Price_per_Unit’
2. From result of step 1 a fixed value of 13 should be deducted using the
substraction operator ( - )
3. Result of step 2 should be divided by 100 using the division operator ( / )
4. Result of step 3 should be added to 50 using the addition operator ( + )
We could have written something like:
adj_amount = Units_Sold * Price_per_Unit - 13/100 + 50
A Boolean variable can also take its value from an expression that results in a
True or False value. In the following code ’bCheck’ will get a value TRUE if
’number1’ is greater than ’number2’ else it will get a value of FALSE. The
variables ’number1’ and ’number2’ will be numeric datatypes like integer or
double
bCheck = number1 > number2
2. Date: Used to hold date & time values. Range of values for dates is 1 Jan 100
to 31 Dec 9999 and range for time is 00:00:00 to 23:59:59. The syntax for
declaration of a date variable is as follows:
Dim dtStart As Date
C.6.1 Modules
Modules are like the topmost containers of code. They contain some variable
declaration and functions and sub procedures. A module is basically a collection
of functions and sub procedures. Module can contain declararion for variables
that are shared between functions and sub procedures in that module.
We cannot write code directly inside a module. We should first create a function
or sub procedure that will contain the code.
Creating and naming a module:
Right click in the Project Explorer window and select ‘Insert’ in the pop-up
menu and then select ‘Module’.
If no other module exists in the project, then first a separate folder named
‘Modules’ is created in the Project Explorer. The new module is added in the
Modules folder.
To set the name of the module, click and select the module. In the Properties
Window, type the new name in the space labelled “Name”.
C.6.2 Sub-procedures
A sub procedure is a set of code or program instructions that together achieve a
single purpose. The group of code is given a name which can be used to exeute
the code written therein. Going forward we will use the term ‘Sub’ instead of
‘Sub-procedure’.
A Sub is created using the ‘Sub – End Sub’ code block as follows:
Sub <proc name>(arglist)
‘//** code statements **//
Exit Sub
‘//** code statements **//
End Sub
Click on the “Run” menu and click on “Run Sub”. Alternatively, press F5
v Arguments of a Sub
A sub procedure can take input variables which can be used to control the
actions of the sub procedure. For example, if the sub reads data from a text file
into Excel, then the input can be a string having the path of the file. This way we
can use the same procedure to read different files having the same format. Hence
the same sub can produce different results based on the parameters it receives.
The parameters themselves are called “Arguments” and the act of specifying
parameters for a sub is called “Passing the Arguments”. Arguments act as named
variables inside the code of the sub.
Ø By Value
In the sub ’put_in_sheet’ that we created in the beginning of this section, the first
argument in the definition is ’strwksname’. The name of the argument is
preceeded by the word “ByVal”. This term indicates that the argument is passed
by value.
In the calling sub ’stub_proc’ the name of a worksheet is held in a string variable.
This variable is passed to ’put_in_sheet’. Since the argument is passed by value,
a new location is created in the computer’s memory for a string variable. It is
labelled as ’strwksname’ and the value held in ’strSheetName’ is now copied
there. So now there are two locations in memory that hold the same value.
If we execute the ’stub_proc’, we will see that the string "Sheet1" is printed twice.
This is because in sub ’put_in_sheet’ the value of the copy is updated and the
original string stays unaffected.
Ø By Reference
Another way to pass an argument to a sub is by reference. In this case, the name
of the argument is preceeded by the word “ByRef”. The definition of the sub
with the first argument passed by reference will look as follows:
Sub put_in_sheet(ByRef strwksname As String, _
ByVal nrow As Long, _
ByVal ncol As Integer, _
ByVal varValue As Variant)
‘// ** code for the sub **//
End Sub
Notice that in the same sub we can pass some arguments by value and others by
reference.
Passing by reference means we create a new reference variable that points to the
same memory location. No new string variable is created. So if a variable is
passed by reference to any sub, that sub is able to modify the passed variable.
Let us consider one example.
If we execute our ’stub_proc’ now we will see that “Sheet1” is printed once and
"Changed sheet name" is printed next after the procedure is called. This is
because ’put_in_sheet’ is able to change the variable ’strwksname’ even if it is
not exclusively declared in that sub.
It is important to note that object variables and array variables should always be
passed by reference. So whenever we create a Sub that takes workbooks or
worksheets or presentation files and so on as arguments, they have to be ByRef.
Ø Optional arguments and default values
Many of the subs and functions that are built into Visual Basic and Excel can
have arguments that are optional. We will see examples in almost all chapters.
When calling these procedures, we may choose not to specify any value for such
arguments.
Improper use of optional arguments can lead to unpredictable effects so we will
not delve into creating our own subs/functions with optional arguments.
However, we should be aware that whenever a procedure uses optional
argument, there is a default value assigned to that argument. In case we don’t
specify a value for that argument when calling the procedure, the default value is
used.
v Exiting a Sub before completion
A) From inside the macro based on conditions
The statements written inside a sub are executed in the order in which they are
written. However, there is a way to stop execution if some unwanted condition
develops. this is done with the “Exit Sub” statement. Any number of Exit Sub
statements can occur in a Sub, but these should all be based on some conditional
check.
Once an “Exit Sub” is encountered, no subsequent statement is executed. Also,
we should undo as many changes as possible before we decide to exit the sub
before completion. In our Sub we can have statements as follows
If (nrow<1) Or (ncol<1) Then
MsgBox(“Row or Column numbers should be greater than 0”)
Exit Sub
End If
The function ’MsgBox’ is a built in Visual Basic function and is described in
detail in Chapter 8.
B) Manually using mouse or keyboard
Sometimes we may want to manually exit a macro in the middle of
execution. Examples of such conditions are:
· The macro stops responding and simply hangs up either because a file to
be processed is too large and can't be opened or it is waiting to connect to a
server over a slow connection.
· An error in the programming logic has caused an infinite loop (see section
C.9.2).
1. To break the running VBA program, do one of the following:
· On the “Run” menu, click “Break”.
Fig C.6.4b Module level variables retain their values between function calls
The useful feature about Module level variables is that they get created when
any Macro execution begins (remember that a macro is a collection of Sub and
Function procedures) and they don’t lose their values even if program control
passes out of the module in which they are declared. Their values are lost only
when macro execution stops and program control passes to Excel and the user.
Suppose we have another module named “Module2”, which contains a sub that
calls the subs in Module1. The module, its code and its output are shown in
figure C.6.4b.
’MainSub’ assigns the values, ’TestFunction’ adds them, the output is printed by
sub ’test2’.
C.7 Many items of the same type – Arrays &
Collections
Many times in a program, we require a large number of variables (or objects) of
the same type in order to perform the same type of operations on each them.
For example, suppose we are creating a list of 50 students in a class. We need 50
string variables but, should we create 50 strings? We will have to use 50
different variable names and call some function 50 times. What if tomorrow one
more student gets added? At how many places in our code should we make
changes?
What if we could have another sort of variable that could hold the 50 strings and
from which we can extract any one string as per our requirement? We can have
only one name. As for calling functions or subs, we could pass this single
variable as argument, and the function could pull out the names one by one and
work on them.
Visual Basic provides two types of variables that allow us to work with a bigger
set of variables. Let’s look at them one by one.
v Arrays
An array is a set of sequentially ordered elements having the same primitive data
type. Each element of an array has a unique identifying index number. Changes
made to one element of an array don't affect the other elements.
There are various ways of declaring and initialiazing arrays. We will focus on
two major approaches that relieve us of keeping track of the first index. This is
helpful if we are augmenting code written by someone else.
We will cover the basic case here. The other approaches have been covered in
the main sections of the book. A reference is provided to the concerned section.
First case:
This is the case when we know the total number of elements we want in our
array.
‘//** Array of 11 long integers, lower index 0 **//
Dim arrLong_0(0 To 10) As Long
‘//** Array of 10 long integers, lower index 1 **//
Dim arrLong_1(1 To 10) As Long
Values have to be assigned individually to each element as follows
arrLong_1(1) = 15
arrLong_1(2) = 200
‘//** We would require 10 such lines. **//
In the Dim statements, numbers in the brackets are the range of the arrays
“index”. Index is the relative position of an element in the array. so in the first
array, the first element is arrLong_0(0), the third one is at arrLong_0(3).
The smaller value of the Index is called the “Lower Bound” of the array. The
larger value is called “Upper Bound” of the array.
If the number of elements is known along with the individual values of the
elements we can use the “Array()” function. This function is described in detail
in section 8.1 “Functions for Arrays.
Second Case:
The second case is more important for us when writing macros in Excel. In this
case we don’t know the length or the contents of the array. The array is read
from an Excel worksheet. This method is described in detail in section 3.6
“Using VB Arrays with worksheet Range”
Ø Dimensions of an array:
One thing to note about the arrays we have created so far is that they are one
dimensional. They can be thought of as a variables of similar data type arranged
in a row. This is shown in figure C.7a
A single Array: An array of arrays
(58, 6, 76, 45, 89) ( (45,2),
(12,64),
(78,63))
Fig C.7b Two-Dimensional array of 6 integers
Fig C.7a One-Dimensional array of 5 integers
However, arrays can be two dimensional. in that case, we can think of them as
variables arranged in a table as shown in figure C.7b. Although this concept is
not frequently required when writing Excel macros, it is useful to know it
especially when we plan to create arrays by reading values from cells on a
worksheet.
In such a case, to access a single element we require indices of both the
dimensions. Each element is like a cell in a table. Such arrays are called two
dimensional arrays and these can be declared as follows
Dim arr2DLng(1 To 10, 1 To 20) as long
The first set of indixes is for the first dimension or the rows of the table. The
second set (1 to 20) is for the second timension or the columns of the table. we
can assign value to a particular element as follows
arr2DLng(2,5)= 200
Although it helps to visualize a 2-Dimensional array as a table, in reality it is a
1-Dimensional array where each element is another 1-Dimensional array. Hence,
if the array shown in figure C.7b is named ’arr2DTest’ then, assuming the first
index is 1,
‘//** will be the array (45,2) **//
arr1D = arr2DTest(1)
‘//** will be the integer (45) **//
arr1D(1)
v Collections
A collection is a special type of object that can hold a large number of other
objects all of which are of the same type. Some examples are
1. The collection of all worksheets in an Excel workbook – aptly named the
‘Worksheets’ collection.
2. The collection of all open Excel workbooks – called the ‘Workbooks’
collection.
Since collections are special type of objects we will not create any collection of
our own. However, we will be working with collections that are already provided
by the applications we are trying to automate. The Worksheets and Workbooks
collections mentioned earlier are available in Excel. Every PowerPoint file has a
Slides collection which we will use when sending data automatically to
PowerPoint.
Every collection, regardless of the objects it contains, always has one property
and one method readily available. Let’s see what those are
Ø Count Property: This property returns a long integer value that represents the
total number of items held in the collection. The syntax for retrieving this
property is
<collection variable name>.Count
Ø Add Method: This method allows us to add a new object to the collection. Its
behavior differs slightly based on how the collection has been created. We will
deal with this method when we look at the individual collections.
Salient points about collections:
a. Collections, themselves being objects, can become properties of other objects.
The Worksheets collection is a property of a Workbook object.
b. Within a collection, objects are stored in an ordered manner and are
numbered. The first object is at position 1. For example, the code below shows
how to access the first and fifth worksheets in a workbook set to the wbk
variable.
wbk.Worksheets(1) and wbk.Worksheets(5)
c. Collections in MS Office allow us to set names for many of the objects and
these can be retrieved from their respective collections using their names. This
is good news because we don’t have to remember the specific location of the
objects that we are interested in. For example, a worksheet named
“DataSheet” can be retrieved with the following line of code.
Wbk.Worksheets(“DataSheet”)
v Passing Arrays to Subs and Functions
Let us look at code examples for passing array variables to Subs and functions as
arguments. First, consider the function ’arrFunTest1’ as shown.
Function arrFunTest1(ByVal strPrefix As String, _
ByRef arrNums() As Integer) As String
Dim dblSum As Double
dblSum = Application.WorksheetFunction.Sum(arrNums)
arrFunTest2 = strPrefix & dblSum
End Function
Notice the second argument. The empty parentheses after the argument name
specify that the arugment passed will be an array. We do not need to know the
length or dimensions of the array in case it is a one dimensional array.
This code also illustrates how functions available on the worksheet can be used
inside macros. The WorksheetFunction property is discussed in a bit more detail
in section B.2.1
The following sub calls the function ’arrFunTest1’ and passes an integer array as
one of the arguments.
Sub arrtest()
Dim arrvar(1 To 4) As Integer
For i = 1 To 4 Step 1
arrvar(i) = 2 * i
Next i
Debug.Print arrFunTest1("Testing Arr passing: ", arrInt)
End Sub
When we create our arrays by reading values from a range on Excel worksheet,
the array created is always 2-dimensional and is of Variant datatype. In such
cases, the function arrFunTest2 will be more useful to us. This type of function is
also required when the argument passed is an array of multiple dimension even if
the datatype is not variant.
Function arrFunTest2(ByVal strPrefix As String, _
ByRef arrNums As Variant) As String
Dim dblSum As Double
dblSum = Application.WorksheetFunction.Sum(arrNums)
arrFunTest = strPrefix & dblSum
End Function
The following sub calls the function ’arrFunTest2’ and passes a variant array as
one of the arguments.
Sub arrtest()
Dim arrtest As Variant
arrtest = Array(1, 2, 3, 4, 5)
Debug.Print arrFunTest2("Testing Arr passing: ", arrvar)
End Sub
v Passing Collections to Subs and Functions
Passing collections to procedures is pretty straight forward expecially when
writing macros because the type of objects in the collection is always explicitly
mentioned. Let’s look at an example. The following sub takes a collection of
Worksheets and prints out their names
Sub PrintSheetNames(ByRef wksColl As Worksheets)
Dim wks As Worksheet
For Each wks in wksColl
Debug.Print wks.Name
Next wks
End Sub
C.8 Choosing a path to follow – Decision structures
At several points in our macro, a decision is required about what to do next
based on certain conditions that develop when macro is executed. Most of the
times the conditions are determined by value of one or more variables. For
example:
1. Macro prompts the user to select a file. If user chooses an Excel file, we
should open it using Excel. But, if a text file is chosen, we should use another
method to work on the file. If it is neither of these, the macro should display a
message and just stop.
In this case, the variable would be a string holding the file extension that is
retrieved from the path of the selected file.
2. While checking payments from customers one at a time, if payment from any
customer has been pending beyond a due date, we should mark the row having
the customer details in red and copy it to another sheet.
In this case the variable would be an Integer, holding value of difference
between the due date and current date for the concerned customer.
The features of programming languages that allow us to build decision making
into our code are collectively called Decision Structures.
In this section we examine the two most important Decision Structures available
in any programming language.
C.8.1 If-Then-Else
The syntax of this decision structure is as follows:
If condition-1 Then
[statements] – Group 1
End If
It is also normal to have just the “If-End If” part if the situation so demands
If condition Then
‘//Code to execute if the condition is true
End If
Now, recall the example of file types mentioned at the beginning of this section.
The code for working with the appropriate files is shown below. Don’t worry
about the term ‘TextStream’ it is covered in detail in section 14.3 “Reading &
writing text files”.
Dim strFileExt As String
If ((strFileExt = “.xlsx”) Or (strFileExt = “.xlsx”)) Then
‘//** Use Excel to open this file **//
ElseIf (strFileExt = “.txt”) Then
‘//** Open this file as a TextStream **//
Else
‘//** Display a message to the user to select the proper file and then stop. **//
MsgBox “Please select either a Text or an Excel file”
Exit Sub
End If
If we are working with an object datatype, we have to ensure that the object is
instantiated before we can work with it. In that case, the following construct of
IF comes in handy (notice the Is operator and the Nothing keyword)
If obj Is Nothing Then
‘//** Code to execute if the condition is true **//
End If
There is nothing like ‘Exit For’
We must note that unlike “Exit For” in “For Loop”, there is nothing available for
a While loop to stop its execution based on some test condition. However, we
can work around this limitation by declaring a Boolean variable whose value
depends on a test condition. Then we just use this Boolean variable in place of
the test condition.
Consider example 4 from the section on ‘For Loop’. We want to exit the loop
when we get the word “Total” in column B for a row. But at the same time, we
just want to look at rows 2 to 100, and place some value in every row that is a
multiple of 5.
Dim bKeepLooping As Boolean
Dim r As Integer
r = 2
‘//** Initially set the Boolean variable to TRUE **//
bKeepLooping = True
While (bKeepLooping)
If (r Mod 5)=0 Then
wks.Cells(r,1).Value = r
End If
r =r + 1
If (r >100 Or wks.Cells(r,2).Value=”Total”) Then
‘//** No more iterations will happen **//
bKeepLooping = False
End If
Wend
Notice the blank space between first two words due to Tab character