ExcelTips The Macros, Seventh Edition
ExcelTips The Macros, Seventh Edition
The Macros
Seventh Edition
Published by:
Copyright © 2013 by Sharon Parq Associates, Inc. All rights reserved. No part of this document or the related files may be
reproduced or transmitted in any form, by any means (electronic, photocopying, recording, or otherwise) without the prior written
permission of the publisher.
For information on purchasing, distributing, or reselling books published by Sharon Parq Associates, Inc., please visit our website
(www.SharonParq.com) or call 801-607-2035. Our books are also available through select online resellers such as Amazon.
Revision history:
Limit of Liability and Disclaimer of Warranty: The publisher has used its best efforts in preparing this book, and the information
provided herein is provided “as is.” Sharon Parq Associates, Inc., makes no representation or warranties with respect to the
accuracy or completeness of the contents of this book and specifically disclaims any implied warranties of merchantability or
fitness for any particular purpose and shall in no event be liable for any loss of profit or any other commercial damage, including
but not limited to special, incidental, consequential, or other damages.
Trademarks: This book identifies product names and services known to be trademarks, registered trademarks, or service marks
of their respective holders. They are used throughout this book in an editorial fashion only. In addition, terms suspected of being
trademarks, registered trademarks, or service marks have been appropriately capitalized, although Sharon Parq Associates, Inc.,
cannot attest to the accuracy of this information. Use of a term in this book should not be regarded as affecting the validity of any
trademark, registered trademark, or service mark. Sharon Parq Associates, Inc., is not associated with any product or vendor
mentioned in this book.
Internet Addresses. This book includes various Internet addresses, including URLs and e-mail addresses. These addresses are
believed to be valid addresses at the time of writing. Due to the fluid nature of the Internet, it is possible that some addresses may
become invalid at any time. All addresses are provided for the convenience of the reader, but no address is guaranteed to be valid
or still useful to the reader at the time of reading.
Table of Contents
Introduction 1
A FEW WORDS ABOUT VERSIONS 1
HOW TO USE THE MACROS IN THIS BOOK 2
NEED A GOOD MACRO TUTORIAL? 3
NEED MORE TIPS? 3
WANT TO LEARN ABOUT VBA PROGRAMMING IN WORD? 4
SHARING THIS DOCUMENT 4
I t's hard for me to believe that ExcelTips: The Macros has reached this milestone; very few
books make it past the second edition. Yet, here we are, with the seventh edition in your
hands. This edition is revised and expanded from previous editions—as you should expect—
providing the most up-to-date information possible about programming Excel macros. In fact,
this edition includes almost 900 pages of information about how to put macros to work on your
own system.
ExcelTips: The Macros is designed for those wanting to learn how to create, use, and expand
their understanding of Excel macros. The tips in this book are pulled from a wide range of
ExcelTips issues. In these pages you will find how to make your use of Excel even more
productive. Macros are a great way to expand the utility of Excel and the ways you can use the
program. I believe it speaks to the usefulness of macros that they are such a large part of the
solutions provided weekly in ExcelTips.
This book makes some assumptions about you, the reader. This is not unusual; any book makes
assumptions about the background and interests of its readers. The first (and most obvious)
assumption is that you are interested in creating macros in Excel. Another assumption is that you
are the curious type—you don’t mind poking and prodding “under the hood” to get what you
want. If this describes you, then you will definitely feel right at home in these pages.
There is one assumption I do not make, however: I make no assumption about your experience
level with macros. Some of the tips included here are for beginners, while others are definitely
for more advanced users. This means there is plenty of information here for a broad range of
readers. It also means you can progress from tip to tip at your own speed, as you become
comfortable with the content of each tip.
Most of the macros in this book will work with most of the versions of Excel presently in use,
provided those versions use VBA. There will, unavoidably, be some instances where a macro
won’t work with a particular version of Excel. For instance, if a macro manipulates a toolbar or
menu, it should work in versions of Excel from 2003 on back, but probably won’t work with
Excel 2007 or later. Why? Because the latest versions of Excel don’t use menus and toolbars;
they rely upon a completely different user interface.
There is one big thing to remember when it comes to VBA and versions of Excel—Microsoft
changed filename extensions starting with Excel 2007. Before that time, workbooks were always
stored in XLS files. Starting with Excel 2007, this filename extension starting varying depending
on whether the workbook contained macros or not: A workbook without macros uses the
extension XLSX and those containing macros use XLSM.
What does this have to do with the macros in this book? Some of the code may take the different
filenames into account and other code may not. This means that you should check any code that
references filenames to make sure that it reflects the workbook files you expect the code to work
with. For instance, if the code references "MyWorkbook.xlsx" and you are using, say, Excel
2003, then you would not use the XLSX filename extension. Instead, you would use
"MyWorkbook.xls".
In general I've tried, throughout this book, to indicate if a particular tip (or portion of a tip) will
or won't work with some versions of Excel. If there is no indication, then in most instances it will
work with all VBA-based versions of the software. This may not be universally true, however—I
may have missed a few places where I could have noted version information.
If you are in doubt as to whether a particular macro will work with your version of Excel, the
best way to find out is to simply give it a try. Testing—the age-old friend of any programmer—is
a valuable way to discover what can and can’t be done with macros in Excel.
Finally, you'll find screen shots throughout this book, especially images of dialog boxes that you
may see while following the steps presented in the tip. Those screen shots may be captured from
almost any version of Excel. They are an attempt to be helpful on what you may see, but it
should be obvious that if you are using a version of Excel different from the one used to capture
the screen shot, what you see may be different. (Microsoft has a bad habit of changing dialog
boxes from one version to the next.) Don't worry; variations in the screen shots should be
expected. Even so, the information in the tip should be helpful and instructive.
ExcelTips: The Macros is saved in PDF format, the defacto standard for electronic books. This
type of file is opened using Adobe’s Acrobat or Acrobat Reader programs. In this format you can
easily read, search, and print information. If you want to copy macro code lines from the PDF
file and place it in the VBA Editor, follow these steps:
1. Display the page containing the code lines that you want to copy.
2. On the Acrobat toolbar, click the Text Select Tool.
3. Position the mouse pointer (which should now look like an I-beam) at the beginning of
the first line of code you want to copy.
4. Click and hold down the mouse button.
5. Drag the mouse pointer to the end of the last line you want to copy. (The text behind the
mouse pointer, back to the starting point, is selected.
6. Release the mouse button.
7. Press CTRL+C to copy the text to the Clipboard.
8. In the VBA Editor, position the insertion point at the location where you want to paste
the code.
9. Press CTRL+V. The code is pasted at the location you specified.
The only thing lost when pasting information that originated in a PDF file is the indentation of
each line. Other than that, you should be able to paste exactly what you copied.
If you purchased ExcelTips: The Macros on a CD-ROM, then the book is provided in Microsoft
Word format, as well. If you want to copy a macro from the book with spacing intact, then you
may want to open up the Word file and copy it from there instead of from the PDF.
Other people, however, see better success if they use a tutorial-based approach to a topic.
ExcelTips: The Macros is not a tutorial; it won't lead you step-by-step from the absolute
beginning. If you feel that you would do better with such a tutorial approach, then I would
humbly suggest that you consider Microsoft Excel VBA Guidebook. It provides foundational
information about writing macros that you might find helpful in your quest to conquer the
program. To learn more about it, visit this page at the Tips.Net store:
http://store.tips.net/T011084
http://excel.tips.net
The ExcelTips site focuses on older versions of Excel, particularly Excel 97 through 2003. If you
are using one of the latest versions of Excel (2007 through 2013), then you’ll want to visit this
site, instead:
http://excelribbon.tips.net
http://www.tips.net
If you want to learn more about programming macros in Word, you will definitely be interested
in WordTips: The Macros. This book uses the same great approach to macros that makes
ExcelTips: The Macros so useful. You can find more information about WordTips: The Macros
here:
http://store.tips.net/T010057
If, instead, you need a tutorial introduction to working with macros in Word, then you'll find
Microsoft Word VBA Guidebook, now in its second edition, to be most helpful:
http://store.tips.net/T010352
The information in this document is copyrighted. I would ask that you do not share this
information with others—you purchased this book, and you have a right to use it on your system.
Another person who has not purchased this book does not have that right. It is the sales of this
valuable information that makes the continued publishing of ExcelTips possible. If enough
people disregard that simple economic fact, the newsletter will no longer be viable or available.
If your friends think this information is valuable enough to ask you for it, they should think it is
valuable enough to purchase on their own. After all, the price is low enough that just about
anyone should be able to afford it.
It should go without saying that you cannot post this document or the information it contains on
any electronic bulletin board, Web site, FTP site, newsgroup, or … well, you get the idea. The
only place from which this document should be available is the ExcelTips Web site. If you want
an original copy, visit the Tips.Net store at the following address:
http://store.tips.net/
Understanding Macros
A macro is similar to a computer program. It consists of a series of instructions that the computer
follows in a sequence you specify. The macro is given a name that is used to run the instructions
it contains. Excel provides two general ways to create a macro. The first (and easiest) method is
to record a macro using the macro recorder. The other method is to write a macro from scratch
using the VBA Editor. While writing from scratch is perfectly acceptable, it is often a good idea,
especially for smaller macros, to record the basic steps you want performed and then edit the
recorded macro to create the final instructions.
Anything you do in Excel that is of a repetitive nature is a good candidate for a macro. For
instance, you might have the job of creating financial analysis reports for your company and you
want to create a macro that will enter the company name in the current cell and format it using
the proper font. Such a task is easily done with a macro.
When you create a macro, you have the opportunity to store it in any of three places. Where you
store a macro determines when it is available and how it can be later used. The following are the
storage options available in Excel:
• Personal Macro Workbook. The macro is stored in a special workbook that contains
only macros. This workbook is open all the time, but is hidden. The filename for this
workbook is Personal.xls (versions of Excel before Excel 2007) or Personal.xlsb (Excel
2007 and later versions).
• This Workbook. The macro is stored as a part of the current workbook. (This is the
default storage location used by Excel.)
• New Workbook. A new workbook is created and the macro is stored within it.
Remember that macros are only available if the workbook in which they are stored is open. Thus,
only those stored in your Personal Macro Workbook will be available at all times. This works
because the Personal Macro Workbook is always open (even if it is not visible). Macros you
store in other workbooks are only available if that workbook is open.
Recording a Macro
If you have a repetitive task that is a good candidate for a macro, you can use the macro
recording capabilities of Excel to turn your actions into a macro. To record a macro, follow these
steps if you are using Excel 2007 or later:
3. In the Macro Name field, provide a name you want used for your macro. You can
accept the default name, if you desire, but if you plan on using the macro more than
once or twice, you will want to use a more descriptive name. The name you provide
must not include any spaces.
4. In the Description box you can provide an optional comment about your macro.
5. Use the Store Macro In drop-down list to specify where you want the macro stored.
6. Click OK.
1. Choose the Macro option from the Tools menu. Excel displays a submenu.
2. Choose the Record New Macro option from the submenu. Excel displays the Record
Macro dialog box.
3. In the Macro Name field, provide a name you want used for your macro. You can
accept the default name, if you desire, but if you plan on using the macro more than
once or twice, you will want to use a more descriptive name. The name you provide
must not include any spaces.
4. In the Description box you can provide an optional comment about your macro.
5. Use the Store Macro In drop-down list to specify where you want the macro stored.
6. Click OK.
Excel displays the Stop Recording toolbar (in versions of Excel before 2007) and starts recording
everything you do. The actions you take become steps in the macro, and will be repeated when
you later execute the macro. The Stop Recording toolbar is very small and consists of only two
tools.
When you have finished the steps you want recorded in your macro, click on the stop button on
the Stop Recording toolbar (Excel 2003 and earlier) or again display the Developer tab of the
ribbon and click the Stop Recording tool (Excel 2007 and later). The macro is then saved and
available for use at any time.
When you later select cell E12 and play back this macro, you might expect that the macro would
move down one cell, to E13, as if you had pressed the DOWN ARROW key. Instead, when that line
of the macro is executed, cell B8 is selected.
The reason this happens is that Excel memorized your absolute steps. It didn't record the press of
the DOWN ARROW key, but instead recorded the movement to cell B8. This exemplifies the default
condition of the macro recorder—to record all movements and cell references absolutely.
If you instead want your macros to be recorded relatively (so that the macro moves down one
cell instead of moving to cell B8), then you need to instruct Excel to do so. You do this by
clicking Relative References on the Developer tab of the ribbon. After clicking, all your
subsequent actions are interpreted relative to the currently selected cell. Click the tool a second
time, and you are back to subsequent actions being interpreted absolutely.
It is important that you remember to click the appropriate tool before you take an action that is
recorded. The tool's state (on or off) affects only the recording of future actions, not what has
been already recorded.
2. Using the Macros In drop-down list (at the bottom of the dialog box), select where you
want your new macro stored.
3. In the Macro Name box, type a descriptive name you want assigned to the macro you
are writing. (Make sure there are no spaces in the name you use.)
4. Click on Create. Visual Basic for Applications will start up and you can write your
macro.
5. When you are through, close the macro window by selecting the Close and Return to
Microsoft Excel option from the File menu, or press ALT+Q.
Editing Macros
Taking a look at the programming code used in a macro (either ones you have recorded or
macros created by others) is a great way to help you understand how the macro is put together
and how it works. You can do this examination, and make changes to your macros, by editing
them. To edit a macro you first need to display the Macro dialog box. The easiest way to do this
is to simply press ALT+F8.
In the list of available macros you should choose the macro you want to edit. When you have
selected one, you can click on the Edit button and the VBA Editor is displayed with the selected
macro loaded and ready to edit.
Once your macro is displayed, you can make changes to it as desired. You use many of the same
editing functions you use when making changes to a regular worksheet. Unless you fully
understand the consequences of your changes, it is typically best to stick to recording simple
macros. If you feel adventurous, however, there is nothing wrong with making changes to your
macros to see what they will do. If you do so, it is a good idea to keep these tips in mind:
• As you are developing and testing macros, always use copies of your data. Keep the
original data stored in a safe place.
• Keep a good macro language function reference or command reference close by.
• Test often as you make changes. Don’t make all your changes at once and then expect
to perform one test and have everything work. Unfortunately, life is not that simple.
If you keep these tips in mind, there is very little chance that you will hurt anything. In fact, your
chances of learning more about Excel and macro programming are much greater than the risk of
damaging any data. Give it a try!
When you have finished making changes to your macro, you should close the VBA Editor.
Closing the VBA Editor is done in the same manner as when you close any other Windows
program—simply double-click on the Close icon in the upper-right corner of the window.
VBA, however, requires a special character sequence to signify that you want to continue the
current program line on the next. This sequence consists of a space and an underscore. Consider
the following example code:
This code continues a program line over three physical lines by using the space and underscore at
the end of each line being continued. You can use the continuation characters to continue any
programming lines you desire. The only thing you need to remember is that you can only use the
characters for continuation purposes if you place them between regular tokens or keywords used
in the program line. If you place them in the middle of a keyword or in a string (between quote
marks), VBA won't know what you intended, and may generate an error.
The easiest way to find this information is to follow these steps in Excel 2007:
At this point you can browse through the available options to see what has changed. If you prefer
to access the same information in a browser window instead of in the Help system, simply visit
this page:
http://msdn.microsoft.com/en-us/library/bb242669.aspx
The number of changes from VBA in Excel 2003 to VBA in Excel 2007 is not great. But it is
important to remember that the limits of the program itself are greatly expanded. For instance, if
your old macros only checked for data in 65,536 rows, the number of rows in Excel 2007 has
greatly increased. If your macro was written to old limits, you’ll want to check to make sure it
adjusts properly to the expanded limits.
The first task is to set up your InputBox so it displays the information to the user. For example,
let’s say you have five options and you want the user to select one option from those five. You
can use the following code to put together five options, each on their own line:
You can now use the Prompt string when you invoke the InputBox function in your macro. You
then translate what the user responds with into a number that represents their choice from your
five options. The code to do this is as follows:
In this example, the response from the InputBox function is assigned to the UserResp variable,
which should be a string. The UR variable, which is a numeric, is then set based on the value of
the string. (The Val function returns the value in a string.)
The only thing left to do is to take an action based on which number was chosen, 1 through 5.
You can use the Select Case structure to do this. The full subroutine could appear as follows:
Sub Macro1()
Dim Prompt As String
Dim UserResp As String
Dim UR As Single
Notice that this example uses a While … Wend loop around the InputBox function. This is done
to make sure that the user enters a number between 1 and 5. If the value entered is outside that
range, then the user is simply asked again.
Renaming a Macro
A macro is nothing more than a series of instructions you want the computer to execute. It is a
program which is run in the context of the application you are using. As you create macros, you
will probably come across a need to rename a few of the existing macros. To do this, follow
these steps:
Remember that if you rename a macro, you may need to make other changes, as well. For
instance, if you have the macro referenced (called) from a different macro, you'll need to change
that other macro to reflect the name as you just changed it. If the macro is also referenced in
toolbar buttons or in menus, you'll need to make changes in those to reflect the new name, as
well.
Perhaps the easiest way to do this is to create a new Excel template that contains only the macros
you want to distribute. Then, you can use that template as a basis for your distribution workbook.
Simply copy your PivotTable to the workbook, and it will be ready to distribute, as needed.
If you would rather not use a template, then you can create a macro that will copy macro
procedures from one workbook to another. Such a macro can get rather involved, and would take
some testing. A good place to start in developing such a macro is a great online resource located
at this Web page:
http://www.cpearson.com/excel/vbe.aspx
Debugging a Macro
In Excel, macros are written in a language called Visual Basic for Applications, or VBA. When
you write a macro, you need to test it and correct any errors in the macro. This process is called
debugging. The process of debugging a macro in VBA is the same as debugging in any other
programming language. All you need to do is step through the macro, one command at a time,
and make sure it works as you think it should. You do this by viewing both the windows for your
macro and a test worksheet. As you step through the macro (using the commands available in the
Debug menu of the VBA Editor), you can correct any errors you locate. (I particularly like to use
the F8 key to step through the macro one line at a time.)
As you are debugging macros, you need to make sure you think through every possible way the
macro could be used and all the possible conditions that could exist at the time the macro is
invoked. Try the macro out in all these ways and under all these conditions. In this way, you will
make your macro much more useful.
Don't be surprised, however, if you give your workbook to some friends and they discover bugs
you never thought of. In those cases, the debugging process is the exact same as mentioned
above—except you use their data as your test worksheet. Try to go through the macro using their
data, one line at a time, until you discover where your code went wrong and then fix it.
<code>
...
sOp = "counting lines already filled"
...
<code>
...
sOp = "copying source data table"
...
<code>
...
sOp = "saving and closing"
...
<code>
These statements are nothing more than one might put in as remarks, but they have the advantage
that when an error occurs, the user can be informed what was going on at the time. At its
simplest the error handler just needs to contain a single statement like this:
The value of sOp can also be used to determine the next action (resume, exit, etc). Using this
technique in your own macros can make them easier to debug and more friendly for users.
This is actually quite easy to do—all that needs to be done is to arrange the Excel window and
the VB Editor window so that both of them are visible at the same time. In other words, neither
one of them should be “full screen.” You can arrange the window sizes so that you maximize
what you can see in your worksheet, and minimize what you see in the VB Editor—perhaps
showing only a few lines of code in the window.
Another closely related approach is to make the Excel workbook full-screen, and then make the
VB Editor window as small as possible, overlaying the Excel screen. With the VB Editor
window active, you can step through the macro using F8 and view the results in the background,
on the Excel workbook.
runs the macro outright, it seems to take forever to run, often taking 20 minutes or more to
execute. Even though Fredric’s workbook is large (46 MB), the time differential between the two
methods of running is bothersome.
Problems like this can be baffling, and they often take some heavy-duty analysis in order to
figure out. A good place to start is to add some “timer code” in your macro. Add a small routine
that saves a time value and another routine that compares that saved value to the current time and
displays the difference. At the beginning of a section of code you want to analyze, you call the
first routine (which saves the start time) and then at the end of the section of code you call the
second routine. In that way, you can determine which portions of your code are taking the
longest time to execute. These are the code sections you then focus on, so you can figure out
what they are doing that is taking so long.
Another thing to make sure is that you add these two lines at the beginning of your macro:
Application.ScreenUpdating = False
Application.EnableEvents = False
These turn off screen updating, which can slow down a running macro, and disable events. This
last line is included so that changes done by the macro in your worksheet won’t trigger Excel’s
recalculation routines. If your macro is making a lot of changes in the data in the worksheet, and
a full recalculation is triggered after each change, then with such a large workbook, lots and lots
of time can be spent just doing the recalc. At the end of your macro, you reverse the effect of the
two lines you added:
Application.EnableEvents = True
Application.ScreenUpdating = True
Many other subscribers weighed in on this problem, and most felt that the questioned solution
wouldn’t necessarily solve the problem. While some resources might be made available, the
system would still eventually run out of memory? Why? Because out-of-memory problems are
typically due to coding problems in the original macro. “Memory leaks” (which lead to the out-
of-memory condition) can be caused by any number of problems in macro code.
The best solution is a round of late-night debugging, stepping through macro code and analyzing
where the problem is creeping in. Look for the most obvious (but easily overlooked) problems
first, such as infinite loops. If the macro is doing a lot of repetitive processing (looping through
worksheets), then make sure you’re releasing all the memory you declare for your macro. For
instance, for every SET statement you use, you should have a corresponding statement setting
the object to NOTHING, and those statements should be within the loop.
If you can step through the macros without them failing, then there is a good chance the problem
lies in some sort of timing issue in the threads—a timing issue that only shows up, of course,
when the macro is running full-tilt on its own. If you suspect this is the problem, then perhaps a
re-sequencing of the events in the macro can work around it. If the macro uses DDE, you should
be aware that Microsoft is recommending the use of OLE automation instead of DDE. Timing
problems are fairly common with DDE, and Microsoft now considers it obsolete and too flaky to
fix (meaning they won’t support it). In VBA, multiple calls to a subroutine can also cause
memory leaks, and such subroutines have to be rewritten as user-defined functions.
Next, if the macro is opening other workbooks, then try using an Application.Events = False
statement before the Open command. This stops autoexec macros in the workbook running.
You should also make sure that all your variable references are fully declared. Some readers
reported having a couple of macros that get confused between worksheets and even if you use
ALT+TAB to remove focus from Excel.
Finally, one subscriber reported that he found Excel’s VBA to be a little bit “sensitive” (for lack
of a better word). Macros would work for a while, get edited and then continually fail. The only
solution this subscriber found was to export all the code to a text file, delete all code from the
model, and then bring it back in again. Strange? Yes, but it is hard to argue with success.
The best way to do additional work is to open another instance of Excel. As you are working on
one workbook in the foreground, the other instance of Excel continues to work away at the
macro in the background. This approach works because Windows allows multiple instances of a
program, each in its own workspace. The only thing you cannot do is work in the foreground on
the same workbook which the macro is using.
In order to open a second instance of Excel, simply follow the steps you followed to open the
first instance. For example, if you started Excel by calling up the Start menu and then the
Programs submenu, you could do the same thing to open the second instance.
You should realize that the macro running in the background instance of Excel will be affected
by you working on a different instance of Excel in the foreground. This, again, is related to how
Windows treats different programs. On most systems, the background programs are given a
smaller percentage of the CPU’s attention than the foreground program.
2. From the list of available macros, select the macro whose shortcut key you want to
change.
3. Click on Options. Excel displays the Macro Options dialog box.
4. In the Shortcut Key area, indicate the key you want used with the CTRL key as your
shortcut. For instance, if you want CTRL+Y to execute your macro, then enter a Y in the
Shortcut Key area.
5. Click on OK to close the Macro Options dialog box.
6. Click on Cancel to close the Macro dialog box.
The short answer is that there is no way to make this happen without making some changes to
the macros themselves. Shortcut keys are “global” to the instance of the application that is
running (in this case, Excel). As workbooks are opened, their shortcuts are added to an internal
table that functions as an index of all the shortcuts and the macro they are designated to run.
This index seems to be sorted alphabetically, by workbook name. When you use a shortcut key,
Excel looks at the index and picks the first matching shortcut in the index. Also if you have a
shortcut that uses one of the built-in shortcuts, the created macro will always run before the built-
in one. If the macros have the same name, the first one opened is run.
Since the index table maintained by Excel is created by application instance, you could get
around the conflict by making sure that you open each workbook in its own instance of Excel.
Don’t use the Open dialog box to load the second workbook; instead double-click the
workbook’s icon in Windows.
If you tire of remembering to open the workbooks in this manner, the only other option is to start
making changes to macros. The easy change would be to modify the shortcut keys so they are
not the same. You could maintain the same shortcut keys by adding some code to the beginning
of each macro. Have each macro check the name of the active workbook. If the name matches
the expected name for that macro, then the code can continue to execute. If it does not match,
then the code can activate the other workbook and directly run the macro in that one.
At some point you may want to remove the association between a shortcut key and a macro. In
order to do this, follow these steps:
ActiveCell.Offset(2, 1).Select
If you want to select a larger range than just a single cell, you can combine the Offset method
with the Address Method to find actual cell addresses, and then use your findings to actually
select the range itself. For instance, you might want to select the range that begins two rows
down and one column to the right, but then extends for four rows and three columns. You can
accomplish this in the following manner:
An alternative method of accomplishing the same task is to use the Resize method. In this
technique, you would first select the upper-right cell of the desired range (as was done in the first
use of Offset, above), and then use Resize to change the size of the selection. This is how it is
done:
ActiveCell.Offset(2, 1).Select
Selection.Resize(4, 3).Select
Rest assured that the macro processing is only affecting Excel, however. You can open a
different application and work within it while the macro chunks away in Excel in the
background. Of course, the attention being paid to the macro by your system will probably slow
down the response of the other program, but this depends on the version of Windows you are
using on your system. The reason? Sharing of resources requires a process known as
multitasking. Different versions of Windows handle multitasking in different ways.
You may wonder how you can do other work in Excel while the program is busy running a
macro. Easy—just open another instance of Excel (run it again from your Start menu) and do
some other work. All you need to do is make sure that you don’t try to work on the same
workbook (or workbooks) being utilized by the macros in your first instance of Excel.
There are several ways you can approach this problem. The first is to build a “do you want to
exit” prompt into your macro, and then have the macro display the prompt periodically. For
instance, consider the following code:
Do ...
Counter = Counter + 1
If Counter Mod 25 = 0 Then
If MsgBox("Stop Macro?", vbYesNo) = vbYes Then End
End If
Loop
The macro construction is based on the premise that you have a series of steps you want to repeat
over and over again, through the use of a Do … Loop structure. Every time through the loop, the
value of Counter is incremented. Every 25 times through the loop, the “stop macro?” prompt is
displayed, and the user has a chance to exit.
This approach is easy to implement and may work quite well for some purposes. The biggest
drawback to this approach, however, is that it doesn’t allow immediacy—the user must wait to
exit the macro until at least 25 iterations have occurred.
Another approach is to “hide” the VBA code and apply a password to it. You do this by
following these steps from within the VBA Editor:
1. Choose the VBAProject Properties option from the Tools menu. The editor displays the
Project Properties dialog.
2. Make sure the Protection tab is displayed.
Close the VBA Editor, then save the workbook. With the VBA project protected, the user can
still click CTRL+BREAK to stop the macro, but they won’t be able to get to the actual program
code. They will only be able to choose from the Continue or End buttons, both of which protect
your code. As an added benefit, this approach also restricts the user from viewing your code by
using menu, toolbar, or ribbon choices.
Perhaps the best approach, however, is to create an error handler that will essentially take charge
whenever the user presses ESC or CTRL+BREAK. The handler that is run can then ask the user if
they really want to quit, and then shut down gracefully if they do. Here’s some example code
that shows how this is done:
Sub Looptest()
Application.EnableCancelKey = xlErrorHandler
On Error GoTo ErrHandler
Dim x As Long
Dim y As Long
Dim lContinue As Long
y = 100000000
For x = 1 To y Step 1
Next
Application.EnableCancelKey = xlInterrupt
Exit Sub
ErrHandler:
If Err.Number = 18 Then
lContinue = MsgBox(prompt:=Format(x / y, "0.0%") & _
" complete" & vbCrLf & _
"Do you want to Continue (YES)?" & vbCrLf & _
"Do you want to QUIT? [Click NO]", _
Buttons:=vbYesNo)
If lContinue = vbYes Then
Resume
Else
Application.EnableCancelKey = xlInterrupt
MsgBox ("Program ended at your request")
Exit Sub
End If
End If
Application.EnableCancelKey = xlInterrupt
End Sub
Notice that this example uses the EnableCancelKey method, assigning it the name of the label
that should be jumped to if the cancel key (ESC or CTRL+BREAK) is pressed. In this case,
ErrHandler is jumped to, and the user is asked what to do. If the user chooses to exit, then the
macro is shut down gracefully.
Notice that the first thing done after the ErrHandler label is to check if the Number property of
the Err object is equal to 18. If it is, you know that a cancel key was pressed. If not, then some
other type of error occurred, and it should be handled in whatever way is appropriate for your
macro.
As you make changes to macros, adding and removing code, the actual file used to store the
macros (the workbook) can get quite fragmented. It seems that internally the macros are stored in
blocks and, much like a disk drive, the blocks can become “non-contiguous” over time. (This
happens only through editing, not through use of the macros themselves.) Some readers have
reported that there are times the fragmentation can get so bad that the macros may fail or the
workbook become unusable.
The solution to this potential problem is to do your macro development in a different workbook
than the one that will eventually hold the macros. Thus, when the macro is transferred to its final
home, it will be transferred as a contiguous block, rather than being fragmented.
If you want to make sure that the macro fragmentation is completely removed from a current
workbook, all you need to do is export your VBA modules to text files, create a brand new
workbook, and import the modules into it.
When you create a macro, you have the opportunity to specify exactly where it should be stored.
If you store it in a workbook, and then later save the workbook as a template, the macro is still
there because the workbook is converted to a template that contains all the original macros stored
with the workbook.
The problem is with the toolbar button. When you create a toolbar button and assign a macro to
it, Excel remembers where the macro is stored. When the workbook was originally created, the
macro was stored in the workbook. This means that the toolbar button "points" to the macro in
the workbook. Even after the workbook is saved as a template, the toolbar button still points to
the macro in the workbook, not in the template.
To correct this situation, all you need to do is—after you save the workbook as a template—
make sure you open the template and reassign macros to the toolbar buttons. These macros
should be ones that reside in the template itself, not in any other workbook you have open at the
time. You can then save the template and everything should work fine. You can reassign the
macros by following these steps if you are using a version of Excel prior to Excel 2007:
1. Open the new template file. (Make sure you open the actual XLT file, and that you don't
create a new XLS file based on the template.)
2. Right-click the toolbar button that runs the macro.
3. Choose Customize from the resulting Context menu. Excel displays the Customize
dialog box.
4. Again right-click on the toolbar button that runs the macro.
5. Choose Assign Macro from the resulting Context menu. Excel displays the Assign
Macro dialog box.
6. In the Macro Name box you will see the name of the macro assigned to the button. It
should consist of a worksheet name (XLS) and the macro name, separated by an
exclamation point. Change the worksheet name to the template name. (This may be as
simple as changing the letters XLS to XLT.)
7. Click on OK.
8. Click on Close
9. Resave your template.
This is where the Volatile method comes in handy. All you need to do is include the following
statement within your macro:
Application.Volatile
This informs Excel that the results of the macro are dependent on the values in the worksheet,
and that it should be executed whenever the worksheet is recalculated. For instance, consider the
following user-defined function:
This function, if used in a cell, counts the number of cells that contain formulas within a
specified range. However, the function will only run the first time it is entered into a cell, or
whenever the cell containing the formula is edited. If you want the function to recalculate every
time the worksheet is recalculated, you would add the Volatile method near the beginning of the
function:
End If
Next cell
CountCells = iCount
End Function
The inclusion of the Application.Volatile method means that every time the worksheet is
recalculated, this function (macro) is again run.
Deleting a Macro
Many macros that you record or create are used for a specific purpose; they are not intended to
be used over and over again for long periods of time. This means that as your needs change, you
will have occasion to delete macros. To delete a macro, follow these steps:
2. From the list of macros, select the macro you want to delete. The Delete button
becomes available.
3. Click on Delete.
4. Repeat steps 2 and 3 for each macro you want to delete.
5. Click on Close when finished.
As an example, consider the following macro, which uses the sendkeys function to garner all the
macro names and place them in a worksheet:
Sub ListMacros()
Dim VBComp As VBComponent
Dim VBCodeMod As CodeModule
Dim oListsheet As Object
Dim StartLine As Long
Dim ProcName As String
Dim iCount As Integer
Application.ScreenUpdating = False
On Error Resume Next
Set oListsheet = ActiveWorkbook.Worksheets.Add
iCount = 1
oListsheet.[a1] = "Macro"
oListsheet.[a1].Offset(iCount, 0).Value = _
.ProcOfLine(StartLine, vbext_pk_Proc)
iCount = iCount + 1
StartLine = StartLine + _
.ProcCountLines(.ProcOfLine(StartLine, _
vbext_pk_Proc), vbext_pk_Proc)
Loop
End With
Set VBCodeMod = Nothing
Next VBComp
Application.ScreenUpdating = True
End Sub
In order to use this macro, you must make sure you have the Microsoft VBA extensibility
reference set. To do this, follow these steps:
1. In the VBA Editor, choose References from the Tools menu. The References dialog box
is displayed.
2. Scroll through the list of Available References and make sure the Microsoft Visual
Basic for Applications Extensibility check box is selected.
3. Close the dialog box.
When you run the macro, it adds a new worksheet to your workbook, and then lists the names of
all the macros in all the modules in the workbook.
Because of this naming practice, it is real easy to “muck up” your workbooks with macros you
no longer need. Heck, you probably can’t even remember what they do! The solution to this
situation is to periodically clean out your macro list. I make it a habit to always delete anything
that is in this default naming sequence. Doing this periodically means that your files take less
space and your Excel workbooks take less time to load.
To delete a macro, just display the Macro dialog box (press ALT+F8), select the macro you want
to delete, and then click the Delete button.
This sounds odd—after all, you know there are no macros in the workbook. Are there phantom
macros at work here? No, not really. The reason Excel behaves this way is that when you create
your first macro in a workbook, Excel creates a new module in which to retain the macro. When
you later delete the macro, the module remains behind, ready to hold any other macros you may
create. It is modules that Excel checks for when you open a workbook, not individual macros. If
there is a module, you get the warning.
1. Make sure the offending workbook (the one with the phantom macros) is open.
2. Press ALT+F11 to display the Visual Basic Editor.
3. Near the upper-left side of the editor is the Project Explorer. This contains a hierarchical
tree that shows the different modules in your workbook. If the Project Explorer is not
visible on your screen, press CTRL+R to display it.
4. Within the Project Explorer should be a folder called Modules. If it is not already open,
double-click on the Modules folder to display its contents.
5. Right-click on a module in the folder. A Context menu is displayed.
6. Choose the Remove option from the Context menu. You are asked if you want to export
the module before removing it.
7. Click on the No button. The module is removed.
8. Repeat steps 5 through 7 for each module in the Modules folder.
9. Close the Visual Basic Editor.
10. Resave your workbook.
At this point your workbook contains no modules, and you will not get any notification when
you subsequently open it.
You may notice that every time you open a workbook that contains macros, Excel asks you if
you want to enable the macros. This is part of the security system built into Excel. (This system
has saved my bacon on more than one occasion.) You may also have noticed that if you delete all
the macros in your workbook, Excel still asks you if you want to enable macros when you later
open the workbook.
Why would Excel do this? After all, you deleted all the macros in the workbook, right? The
reason is that the module automatically created by Excel to hold your macros is not
automatically deleted when you get rid of the last macro—it’s still there. As long as the module
is there, Excel will dutifully ask you if you want to enable your macros whenever you load the
workbook.
To overcome this problem (and get rid of the macro prompt for this particular workbook), follow
these steps:
At this point your workbook contains no modules, and you will not get any notification when
you subsequently open it.
Your worksheets have now been moved to a new workbook—one that does not have any macros
attached to it.
The second approach is to simply work with the existing workbook, and is a viable choice if you
feel comfortable with macros in the first place. Follow these steps:
only right-click on a module that is associated with the workbook that you want to
cleanse.) Excel displays a Context menu.
3. Choose the Remove option from the Context menu. The actual wording of the option
will include the name of the module you want to remove, such as Remove Module1.
4. When asked if you want to export the module before removing it, click on No.
5. Repeat steps 2 through 4 for any other modules you want to remove.
6. Close the VBA Editor.
In Excel, your custom macros are stored either in regular workbooks or in the Personal
workbook, and changes to toolbars and menus are stored in a file with the .XLB extension. (In
Excel, there should only be one .XLB file accessed at a time, and it is for this very purpose—
managing toolbar and menu customizations.) The location of these files can vary from system to
system, but you can use the Windows Search feature to locate them.
If you want, you can copy both the workbook with the macros and the .XLB file from your
system to someone else’s system. You just need to make sure that the other system is using the
same version of Excel that you are, and you need to make sure that you place the .XLB file in the
same location as the existing .XLB file on the other system. The only problem with this, of
course, is that when you replace the files on their system, you also get rid of any macros and/or
customizations they may have previously made on their system.
To get around this problem, the best way to share macros is to add them into a file and save it as
an add-in file (*.xla). The add-in should contain additional code to create the toolbar
customizations and any menu items when the add-in is installed (workbook_AddInInstall event)
and then remove them when the add-in is removed (workbook_AddInUnInstall event). In other
words,. you are not saving the exact toolbar and menu customizations on your system, but you
are using macros to recreate the customizations on the other person’s system.
Creating the customizations is not too difficult, but it is still not a trivial task—and definitely
beyond the scope of this tip. Menu customizations, explained properly, normally occupy an
entire chapter in a good reference book.
So where should you look to find additional information? Chip Pearson has some good info on
creating menus with VBA at this page:
http://www.cpearson.com/excel/menus.htm
John Walkenbach's site has a file with some example code, at this page:
http://j-walk.com/ss/excel/tips/tip90.htm
A good reference is John's Excel Power Programming with VBA series. He has editions for many
versions of Excel, available here:
http://spreadsheetpage.com/index.php/books/
For instance, you may see a message that says “object library not registered,” and be completely
lost as to what it means. In this case, it is helpful to understand how Excel works with external
programs.
During part of the startup process, Excel may load any number of add-ins that provide additional
functionality to your copy of Excel. Basically, these add-ins are collections of macros that
perform certain tasks. Macros, in turn, can rely upon other files that contain information that
helps them perform their duties. These external files are called libraries.
Excel comes with an amazing number of libraries, but not all of them are accessible at the same
time. A library is only available after it has been “registered” with Excel. If the library is not
registered, then Excel cannot use it and the macros in the add-in cannot use it. The result: an
error message.
The best way to troubleshoot this problem is for you to determine what add-ins are being loaded
when you start Excel. Examine your Excel Startup folder, and make sure you know what they are
all doing. (You don’t need to know what they are doing, step by step, but you should be familiar
with what the add-in does, in general.)
Next, find another system that loads the same add-ins. (This should be easy to do if you work in
an office, but more difficult if you are a home user.) Once you find the similar system, make sure
it starts up without problem. If it does, then on the problem-free system, do the following:
3. Make note (on a piece of paper) of the names of the libraries that have check marks next
to them. Write the exact names, as there could be many libraries with similar names.
Also, all the selected libraries—those with check marks—should be listed at the top of
the reference list.
4. Close the References dialog box.
5. Close the VBA Editor.
Now, on the system that has problems, do these same steps, except in Step 3 you need to make
sure that the same libraries are selected as those you wrote down. When you close the VBA
Editor, restart Excel to see if the problem is still there. If it is, or if you couldn’t find one of the
noted libraries on the problem system, then you may need to re-register Excel completely. If so,
follow these steps:
4. In the Open box, enter the full path name to your Excel program, followed by the
/regserver switch. If the full path name includes spaces, surround the full path name by
quote marks. The following is an example of what you can enter in the Open box (your
path may be different):
5. Click OK.
When you restart Excel, the problem should have disappeared. If it didn’t, then you need to
figure out exactly which add-in is causing the problem. You do this by locating the add-in files in
the Startup folder, and renaming them or moving them to a temporary folder. Do this one file at a
time, restarting Excel after each renaming or move. When the problem disappears, you know you
found the problem add-in, and you can contact the vendor to find out how to resolve the
problem.
Hiding Macros
Most readers already know that you can create functions and subroutines using VBA. This is no
different than it is under VBA’s namesake, Visual Basic. Normally, a macro shows up in the
macro list when you display the Macros dialog box (press ALT+F8), unless one of three
conditions is met:
• The macro is a function. Functions typically return information, and they require
information to be passed to them. Since running a macro from the macro list doesn’t
allow either of these things to happen, Excel figures there is no need to list it. User-
defined functions, which are quite useful in Excel, are not displayed in the Macros
dialog box because they are, after all, functions.
• The macro is a subroutine with parameters. Excel assumes that since parameters are
necessary, and you cannot provide parameters by choosing the subroutine from the
macro list, there is no need to list it.
• The subroutine has been declared Private. This means that the subroutine is only useful
to code within the module in which it is declared.
The only type of macro listed in the Macros dialog box is a non-private subroutine with no
parameters. In certain situations, however, you may not want those listed either. For instance,
you may have created some universal subroutines that don’t do anything useful if called on their
own; they are designed to be called from other code. For instance, consider the following macro:
Sub MySub()
MsgBox "We are running the macro"
End Sub
This macro will appear in the Macros dialog box. If you don’t want it to appear, there are several
solutions you can pursue, all of which become obvious from examining the three ways in which
macros are excluded from the macro list. The first potential solution is to examine your code and
find out if it is really “universal.” Do you need the code from more than a single module? If you
don’t, then declare the subroutine Private; it will not appear in the Macros dialog box. Thus, the
previous problem macro becomes the following:
The second way to hide the macro is to simply convert it to a function. This may sound odd,
particularly if you don’t want to return any values, but it is perfectly permissible. In VBA a
function does not have to return a value. In the absence of explicitly declaring a return value, the
function will return a default result (for example, Boolean returns False, String returns "", etc.)
Thus, the problem procedure could be changed to a function and declared as shown here:
This procedure doesn’t show in the Macros dialog box and does not require arguments. It returns
False by default, but this result can be ignored. Depending on the nature of the subroutine you
are changing, it may be to your benefit to really allow the converted function to return True or
False depending on the success of what is being done in the code. In this case, the converted
function is a real function, and not really a dummy subroutine, since it is returning something of
value.
The third potential solution is to use some dummy parameters with the subroutine. You don’t
need to do anything with them within the subroutine itself, but by including them the procedure
is not listed in the macro list. In this scenario, the problem subroutine is changed to something
like the following:
Now the procedure is not listed in the macro list, but you need to change the way in which the
subroutine is called. You must modify every instance so that a parameter is passed, even though
it is never used.
Disabled Macros
If you recently upgraded to new version of Excel, you may have run into a situation where the
macros you created in the earlier version no longer run because they are disabled. This can be
disturbing, particularly if you absolutely need the macros to get your work done.
The reason this happens is that the more recent versions of Excel (beginning with Excel 2000)
include a macro security feature not present in earlier versions. The default setting is to
automatically disable any macros in any workbook that are not digitally signed by a “trusted
source” (for more info, search for Macro Security in Excel’s online help).
This automatically presents a couple of possible solutions. The first possible solution is to get
your macros “digitally signed.” Such a process is beyond the scope of this tip, but you can find
help on the process in the online help files or at the Microsoft Web site.
Finally, you can lower the default setting for the macro security used by Excel. For instance, you
can set it so that Excel displays only a warning message about the macros rather than outright
disabling them. To change the security setting, follow these steps if you are using Excel 2007 or
later:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
2. At the left side of the dialog box click Trust Center.
3. Click Trust Center Settings. Excel displays the Trust Center dialog box.
4. At the left side of the dialog box click Macro Settings.
If you are using an older version of Excel, follow these steps instead:
1. Choose Macro from the Tools menu, and then choose Security from the submenu. Excel
displays the Security dialog box.
2. Make sure the Security Level tab is displayed.
Excel apparently has a limit on VBA code such that you cannot have more than 64K of compiled
code in a single procedure. The solution to this problem is to chop up your long macro into
shorter procedures. For instance, you might divide your monster macro into, say, a dozen smaller
macros. You can make the smaller macros Private instead of Public (so they don’t show up in the
Macros list in Excel), and then call them sequentially from a “controller” macro.
When you separate your code into individual procedures, make sure that each separate procedure
has all loops and logic self-contained. Also make sure that any variables used in more than one
procedure are declared as global variables so they are accessible by all the procedures.
Unfortunately, there is little that can be done to disable add-ins at start-up because no particular
workbook is already open. (The add-ins are loaded before any workbooks.) There are a couple of
things you could try, however.
The first thing is that you could create your own add-in that does nothing more than ask if the
large add-in should be loaded or not. Depending on the user’s response, the add-in could then be
loaded by using the following line of code:
Of course, you’ll need to replace “Big Add-in” with the name of the actual add-in to be loaded. If
the user doesn’t want the add-in loaded, just skip the line of code. In the Close event for your
little add-in you could then add a line like the following that unloads the big add-in:
In this way, the add-in is added only if the user says it is OK to add, and then always unloaded at
the end of your Excel session.
Another approach is to never load the large add-in, but put a routine in your Personal.xls file that
gives the user a chance to load the add-in. The following could be added to the Workbook_Open
event in Personal.xls:
The purpose of this macro is to give the user a period of time—in this case five seconds—to
press the Tab key so that the large add-in is loaded. The .OnKey method runs the installation
routine, if Tab is pressed, and the .OnTime routine starts a timer that runs the disable routine
once the five seconds is elapsed. Notice that this macro calls two routines; these can go in a
regular module for Personal.xls.
Sub InstallMyAddIn()
AddIns("Big Add-in").Installed = True
DisableTABProc
End Sub
Sub DisableTABProc()
Application.OnKey "{TAB}", ""
End Sub
Of course, you’ll need to add some code for the Workbook_Close event of Personal.xls, in this
case to unload the add-in:
If you prefer to not use macros, then you can always just move the big add-in from it’s directory
location or rename the add-in prior to starting Excel. If Excel cannot locate the add-in, it
continues to load without loading it.
Lori’s exact problem is a little hard to reproduce, as testing shows that macros are still available
in both protected worksheets and protected workbooks. You can still display the Macros dialog
box and see the list of available macros. You can still choose one of the macros and run it.
Of course, seeing and running the macros may not be Lori’s problem; it could be that the macro
fails to run correctly when used on a protected worksheet. If that is the case, the problem
typically only crops up if the macro is attempting to perform some action that violates the
protection applied to the worksheet. For instance, if the protection doesn’t allow for rows or
columns to be deleted and the macro tries to do such, then it won’t work.
The solution in this case is to modify your macro so that it unprotects the worksheet before
making its changes. The following shows the basics of how this is done:
Sub ModifyProtectedSheet()
ActiveSheet.Unprotect password:="yourpassword"
ActiveSheet.Protect password:="yourpassword", _
DrawingObjects:=True, Contents:=True, Scenarios:=True
End Sub
The first line of this example unprotects the worksheet, you can then perform your processing,
and then the last line again protects it. If your workbook uses protection, then the same technique
can be used with the workbook—unprotect it, then make changes, then reprotect it.
Lori’s problem could also be related to the word “sharing,” which she used in her problem
statement. If, by sharing, Lori means using Share Workbook to make the workbook “sharable”
by others, then you will see a warning when sharing is activated. The warning indicates that
macros cannot be “viewed or edited” in shared workbooks. This does not, however, mean that
the macros are disabled, since you can still display the Macros dialog box to see a list of macros
and choose one to run. You cannot, however, display the VBA Editor and look at the actual
macro code.
Finally, there are some features of Excel that are simply disabled in shared workbooks. If your
macro tries to perform any of these disabled actions, it won’t work properly. This is a limitation
of Excel, and there is nothing that can be done about it. (For more information on what cannot be
done in a shared workbook, use the online help system and search for “shared workbooks,
limitations.”) The only way around these limitations is to not share the workbook.
This doesn't seem to be a common problem, as far as we can tell. It is possible that there is
something errant going on in this particular system. For instance, it is possible that the
EnableCancelKey property has been set to disabled, which would stop the normal functioning of
CTRL+BREAK. This property can be affected by the following macro line:
Application.EnableCancelKey = xlDisabled
This command could have been run in a macro which then did not enable the property. (Perhaps
the macro coding either didn't include the enabling or the macro ended abnormally and never got
to the command line to enable the properlty.) It is also possible that the command could have
been entered in the immediate window of the VB Editor.
The setting of the property is persistent, and stays with a workbook if the workbook is saved
after the setting is changed. You can check the setting by opening the VB Editor and entering the
following in the immediate window:
? Application.EnableCancelKey
If you see a 0 displayed, this means that the property has been disabled. You should then enter
the following in the immediate window:
Application.EnableCancelKey = xlInterrupt
After doing so, save the workbook. You should also try to track down where the property was
initially disabled and make sure that the coding is corrected so you won't have the problem again.
The answer depends, in large part, on how corrupted the workbook really is and where the
corruption is located within the workbook. Much has been written about how to recover
corrupted workbooks; the following resources will be of interest in this regard:
Most of these pages refer specifically to recovering data, not to recovering the macros in a
module associated with a workbook. (It is interesting that the Microsoft Knowledge Base doesn't
have any articles on recovering data from a corrupted Excel 2007, 2010, or 2013 workbook.
Perhaps one will come, with time.) One thing that you might try in order to get your macros is
the following:
Another way to attempt recovery is to use OpenOffice, a free alternative to Microsoft Office.
The spreadsheet program in OpenOffice will open Excel files, and it isn’t as sensitive to some
corruption issues.
If this still doesn’t work, try using a low-level file manipulation tool that allow you to read files
sector by sector from a disk, and then allow you to see the information in each sector. With most
types of files this won’t be very helpful. In fact, it wouldn’t help you recover any data from an
Excel workbook. Recovering macros is a different story, however. They are stored in the
workbook in plain ASCII text, so you should be able to recognize the macro code and then copy
it from the disk tool.
It’s unclear why Excel would be generating an error message when trying to record such a
simple macro. We were able to record the steps with no problem. It could be that the error is
related, somehow, to the conditions existing when trying to do the recording. For instance, the
Clipboard may not actually contain a formula that could be pasted, or you could be trying to
paste in a protected worksheet.
Be that as it may, it is just as easy to create a Paste Special—Formula macro from scratch. The
following is the same as what would have been recorded by the Macro recorder, and it can be
entered directly into a macros module in the VBA Editor:
Sub PasteFormulas()
Selection.PasteSpecial Paste:=xlPasteFormulas, _
Operation:=xlNone, SkipBlanks:=False, _
Transpose:=False
End Sub
Note that the macro has only a single line to do the actual pasting. In order to use it, simply copy
some cells to the Clipboard, select where you want the formulas pasted, and then run the macro.
You can assign it to a shortcut key to make using it even easier.
One way to help users utilize your macros is to digitally sign them. This capability was
introduced by Microsoft in Excel 2002. A digital signature allows a user to know that a macro
comes from a trusted source and that it hasn't been modified since it was originally saved by that
trusted source. In other words, it is a way for users to be sure that a macro hasn't been tampered
with. (Sort of like the product safety seals on some consumer foods and pharmaceuticals.)
In order to digitally sign a macro, you need to first obtain a digital certificate. A certificate is a
"seal of approval" from a trusted third party that you are who you say you are. You can get
digital certificates from a variety of commercial certificate authorities, each of which has
different requirements of how you go about certifying your identity.
You can also create your own digital certificate for testing purposes using the program
SelfCert.exe, which is provided with Microsoft Office 2002 and 2003. This route is great for
testing, but it won't help you when you distribute your macros to others; you'll still need the
certificate from the third-party authority. You can find more information about the SelfCert.exe
program by using Excel's online help and searching for "selfcert."
Once you have a digital certificate, you can digitally sign your macro project in this manner:
1. In the Visual Basic Editor, use the Project Explorer to select the project you want to
sign.
2. Choose the Digital Signature option from the Tools menu. Excel displays the Digital
Signature dialog box.
3. If there is no digital certificate associated with the workbook, or if you want to use a
different digital certificate to sign the macro project than what you used for the
workbook, click Choose. You can then select which available certificate you want to
use.
4. Click OK to dismiss the Digital Signature dialog box. The certificate you selected (or
the certificate used for the workbook) is then used to sign the macro project.
You can find more information about digital signatures in Excel's Help system. You can also
find some great information about both certificates and signatures at this page in the Knowledge
Base if you are using Excel 2002:
http://support.microsoft.com/?kbid=288985
If you are using Excel 2003 or later, see this page instead:
http://support.microsoft.com/?kbid=820738
To move macros from the Personal workbook to a different workbook, follow these general
steps:
1. Make sure the workbook that is the target of your macro transfer operation is loaded.
2. If you are using a version of Excel before Excel 2007, unhide the Personal.xls file by
choosing Unhide from the Window menu.
3. If you are using Excel 2007 or a later version, unhide the Personal.xlsb file by
displaying the View tab of the ribbon and clicking Unhide within the Window group.
4. Press ALT+F11 to display the VBA editor.
5. Using the Project window, display the macros that you want to move.
6. Select (highlight) and cut (CTRL+X) the macros from their original location in
Personal.xls.
7. Using the Project window, display the module in the workbook where you want the
macros to be. (If there is not an existing module in the workbook, you may need to
create one.)
8. Paste (CTRL+V) the macros in the module.
9. Close the VBA editor.
10. If you are using a version of Excel before Excel 2007, hide the Personal.xls file by
choosing Hide from the Window menu.
11. If you are using Excel 2007 or a later version, hide the Personal.xlsb file by displaying
the View tab of the ribbon and clicking Hide within the Window group.
12. Close and save the workbooks.
It should be noted that when you move the location of the macros, the address by which they are
called and invoked is also changed. Thus, if you have any menu items or toolbar buttons that
were used to run the macros, these will need to be changed to point to the new location.
There are a couple of things you need to keep in mind. First, if you are only making changes to
macros in the Personal workbook, you don’t need to unhide the workbook to work on those
macros. Instead, display the VBA Editor and use the object browser to make sure you are
working on the macros in the Personal workbook. When you are done editing the macros, you
can save them without ever needing to make the workbook visible.
If this still doesn’t work for you—perhaps you have some other reason to make the Personal
workbook visible—then you could make some sort of editing change to the first worksheet in the
workbook. For instance, place the text “THIS IS PERSONAL” into cell A1 of the workbook. Do
something to make it stand out (bold, colors, flashing, etc.), and you will never again miss that
you are working in the Personal workbook when you first start Excel.
If you want a macro approach to make sure that the workbook is hidden, then you could add the
following code to the ThisWorkbook object for the Personal workbook:
The macro is executed just before the workbook is closed (when happens when Excel is exited).
It hides the workbook and then saves it. That way, the next time you start Excel, the Personal
workbook will be automatically hidden.
Honestly, you can’t make that choice in Excel 2007. If you are using the program, all of your
macros that previously were stored in Personal.xls should be transferred to your new
Personal.xlsb file. Why? Because the Personal.xlsb file is for use on your machine, so there is no
issue of backward compatibility for your clients. Workbooks that you save in the older Excel
2003 format will continue to save just fine and be readable by your clients using the older
version of Excel.
If, however, you have clients with whom you need to share the macros in your Personal.xlsb file,
and they aren’t using Excel 2007, then you will need to unhide the workbook and save it in the
older format explicitly. It is this older format that you will save with them, and such saving will
still not affect the Personal.xlsb file on your system.
The Personal.xlsb worksheet (and its predecessor Personal.xls worksheets in earlier versions of
Excel) is used, most often, to contain macros that you want available whenever you are using
Excel. Normally the worksheet is hidden, unless it has been specifically unhidden and Excel
saved.
To solve the problem, just start Excel and when the Personal.xlsb file is visible, display the View
tab of the ribbon and click Hide in the Window group. The worksheet goes away, and you should
immediately exit Excel. (If you are asked if you want to save your changes, respond in the
affirmative.)
The next time you start Excel, you should no longer see Personal.xlsb because it is hidden.
Instead you should see a regular blank worksheet, exactly as you expect.
The easiest way to do this would be to periodically export the macro code to a text file, and then
archive the text files. This could be done every day, week, month, etc., or it could be done
anytime there is a change in the code. Simply give each text file a different descriptive name so
you can tell which version the file contains.
Once in text-file format, the files can be easily compared against one another to highlight
differences; there are any number of commercial products that could be used for comparing the
text files. (You could even use Microsoft Word to compare different versions of files.)
It could, realistically, be any number of conditions causing the problem. Because of this, it can
be hard to track down the cause. There are a couple of clues that suggest that the problem may be
due to either an add-in or to a problem with your macro modules.
You can figure out if it is an add-in by simply starting Excel with all the add-ins disabled. Add
them back in, one at a time, until you notice the error again cropping up. You will then have a
pretty good idea that the problem is caused by the last add-in you enabled.
If the problem is not due to your add-ins, then you should suspect your macro modules. If you
spend a lot of time editing your modules, they can become corrupted over time. (This has been a
known problem for some time in VBA.) You can usually get around this problem by recreating
the workbook in which the problem occurs. Copy the worksheets from the old to the new
workbook, and then use the VBA export and import capabilities to move the macro modules
from the old workbook to the new one.
A good discussion on memory problems with Excel can be found at these pages:
http://www.decisionmodels.com/memlimitsc.htm
Mark knows he can manually correct this by going to the VB editor and clearing the reference to
the missing VBA library, but he wonders if there is any way to have the workbook check the
Excel version automatically and update the VBA library reference accordingly.
One way to deal with this is to create and save the workbook using the earlier version of Excel.
When it is opened in the later version of Excel, the reference to the VBA library will be
automatically updated. Excel does the updates going to later versions, but it doesn’t do them
going to earlier versions.
This means, however, that even if the workbook is created in the earlier version of Excel, once it
is opened and subsequently saved in the later version of Excel, users of the earlier version will
have problems opening it.
The solution, according to some sources, is to resort to what is known as “late binding.” This
simply means that the macro is written so that it looks up specific functions only during run-
time, not when the macro is saved. This is referenced a bit in the following Microsoft Knowledge
Base article:
http://support.microsoft.com/?kbid=244167
You can try late binding techniques by opening the VBA editor and removing any references
previously established. Then add code similar to the following near the start of your macro:
At the end of the macro make sure that you set any defined objects (such as oExcel) to Nothing.
A good example of code that is more robust than what is presented here can be found in these
articles:
http://www.vbaexpress.com/kb/getarticle.php?kb_id=267
http://www.vbaexpress.com/kb/getarticle.php?kb_id=272
The first thing to check is whether your macros are actually in the workbook you are sharing
with others. Open it on your system, go to the VBA editor, and make sure that the macros are in
the project associated with the workbook being shared. If not, you will want to move the macros
to the workbook.
A more likely cause of this problem, however, is that your macros are referencing a function or
feature that is in a module that you have access to but that the other people do not. An easy way
to check this out is to go to their system (if possible) and open the workbook. Then go to the
VBA editor and choose Tools | References. Go through the list of available modules and see if
there are any that are prefaced with the word “missing.” These are modules that are required for
your macros, but are missing on the current system.
If you find missing modules, or perhaps modules that the user needs to reference in VBA in
order to use your macros, then it might be best to rewrite your macros so that they don’t use
those modules. This may be easier said than done, but it may (again) be the easiest, cleanest way
to let others use your macros.
This problem is caused by the fact that Excel stores a fully qualified path to a macro as part of its
toolbar info (that means it includes the name of the workbook in which the macro is stored), but
it doesn't with the shortcut key info—that only has the macro name itself. This means that a
shortcut doesn't know how to find a macro unless it is in a workbook that is open.
The easiest way around the problem would be to move the macros to the Personal.xlsm (or, in
older versions of Excel, Personal.xls) workbook. This workbook is loaded automatically loaded
when Excel is started, so the macros would always be available and the shortcut keys always
work. Detailed information on the workbook can be found here:
http://office.microsoft.com/en-us/excel-help/deploy-your-excel-macros-from-a-
central-file-HA001087296.aspx
Of course, you can bypass the Personal.xlsm approach by simply moving the workbook
containing the macros to the Startup folder used by Excel. Anything in the folder is automatically
opened when you first start Excel, which means that the macros in those workbooks would also
be accessible.
The workbook containing your macros could also be compiled into an Excel add-in, which
would be available at all times. (How you create and use an add-in has been covered in other
ExcelTips.)
Understanding Subroutines
When you write macros in Excel, you use a programming language called Visual Basic for
Applications (VBA). This is based on the BASIC programming language, with extensions
specific to Excel. One of the features of the language is the capability to use subroutines in your
programs. For instance, consider the following VBA macro:
Sub Macro1()
TestSub
End Sub
Sub TestSub()
MsgBox "In the subroutine"
End Sub
This simple macro (Macro1) does nothing but call a subroutine (TestSub), which in turn displays
a message box to inform you that it is in the subroutine. When you click on OK to dismiss the
message box, the subroutine ends and returns control to the main program. You can have as
many subroutines in a VBA program as you desire. The purpose of each should be to perform
common tasks so you don’t have to rewrite the same code all the time.
You can also pass parameters to your subroutines. These parameters can then be acted upon by
your subroutine. For instance, consider the following macro:
Sub Macro1()
A = 1
PrintIt A
End Sub
Sub PrintIt(x)
MsgBox "Value: " & x
End Sub
This is a simple macro that sets a variable, and then passes it in a subroutine call to PrintIt. This
subroutine displays the value of the variable in a message box, and then (after you press OK)
returns to the calling program.
Notice that the subroutine does not use the same variable name as it was passed. This is because
VBA reassigns the value of x (what the subroutine expects to receive) so that it matches the
value of A (what the program is passing to the subroutine). The important thing to remember in
passing parameters to subroutines is that your program must pass the same number of parameters
as the subroutine expects, and that the parameters must be of matching types and in the proper
order.
Understanding Functions
You already know that you can use subroutines in your macros. VBA also allows you to define
functions that can be used in your macros. The difference between functions and subroutines is
that functions can return values, whereas subroutines cannot. Consider the following macro:
Sub Macro1()
TooMany = TestFunc
If TooMany Then MsgBox "Too many columns selected"
End Sub
Function TestFunc()
TestFunc = False
If Selection.Columns.Count > 10 Then
TestFunc = True
End If
End Function
The macro (Macro1) calls the TestFunc function. This function returns either the value False or
True, depending on a test it performs. Macro1 then acts upon the value returned. Notice that the
function name can appear on the right side of an equal sign. This makes functions very powerful
and an important part of any program. Within the function the result is assigned to TestFunc,
which is the name of the function itself; this is the value returned by the function.
As with subroutines, you can also pass parameters to your functions. This is illustrated in the
following macro:
Sub Macro1()
A = 12.3456
MsgBox A & vbCrLf & RoundIt(A)
End Sub
This simple macro (Macro1) defines a number, and then uses a message box to display it and the
result of passing the number to the RoundIt function. The output is 12.3456 and 12. Notice that
the parameter should be passed to the function within parentheses. Also notice that the function
does not use the same variable name as it was passed. This is because VBA reassigns the value
of X (what the function needs) so it matches the value of A (what the program is passing to the
function). The important thing to remember in passing parameters to functions is that your
program must pass the same number of parameters as the function expects, and the parameters
must be of matching types and in the proper order.
Many ExcelTips readers report that the best way to handle this situation is to put all your
functions into a single worksheet, and then compile the worksheet into an Excel add-in. You can
then place the add-in on a shared network directory, from which everyone can access the add-in.
If you need to change the functions in the future, simply update the add-in and copy it to the
shared directory. The next time a user starts Excel, the newly updated add-in is loaded, and the
updated functions are automatically available.
Later in this book you will find tips on how to create and use add-ins.
Worksheet Events
One of the beauties of creating macros for Excel is that they can be event-driven. This means that
you can create macros that will run automatically when specific, well-defined events happen
within Excel. These events can happen either on a worksheet or a workbook level.
The easiest way to see what worksheet events are available is to follow these steps:
At this point, the right-hand drop-down list contains all the events that you can “trap” for this
worksheet. The available events may vary, according to your version of Excel. In Excel 2003 and
later versions, the following events are available:
• Activate
• BeforeDoubleClick
• BeforeRightClick
• Calculate
• Change
• Deactivate
• FollowHyperlink
• PivotTableUpdate
• SelectionChange
The names of the events should be descriptive enough that you can tell what triggers each of
them. If you choose one of the events, you can create the macro you want run when the event
actually occurs.
Workbook Events
In the previous tip you learned how you can discover the various events that you can trap and
program for in your macros. Excel also allows you to trap different events on a workbook level.
You can discover a list of those events in much the same manner as you do for worksheets:
At this point, the right-hand drop-down list contains all the events that you can “trap” for the
workbook. The available events may vary, according to your version of Excel. In Excel 2003,
there are 28 different events (29 in Excel 2007 and Excel 2010), too many to list here.
The names of the events should be descriptive enough that you can tell what triggers each of
them. Notice that some of the events start with the word “Sheet” and duplicate the names of the
worksheet events detailed in the previous tip. These events, because they are at a workbook
level, apply to the workbook as a whole, even though they are triggered by events on a
worksheet.
For example, if you choose to trap the SheetActivate event, then the macro will be run when any
worksheet in the workbook is activated. Contrast this to the Activate event on the worksheet
level, which is activated only when that particular worksheet is activated.
If you choose one of the events in the right-hand drop-down list, you can create the macro you
want run when the event actually occurs.
For X = 1 To 99
program statements
Next X
You are not limited to using the X variable; you can use any numeric variable you desire. You
are also not limited to the numbers 1 and 99 in the first line; you can use any numbers you desire,
or you can use numeric variables. When a macro is executing, and this structure is encountered,
Excel repeats every program statement between the For and Next keywords a certain number of
times. In the syntax example, the statements would be executed 99 times (1 through 99). The
first time through the structure, X would be equal to 1, the second time through it would be equal
to 2, then 3, 4, 5, and so on, until it equaled 99 on the last iteration.
Normally, as Excel is working through the For … Next structure, it increments the counter by
one on each iteration. You can also add a Step modifier to the For … Next structure, thereby
changing the value by which the counter is incremented. For instance, consider the following
example:
For X = 1 To 99 Step 5
program statements
Next X
The first time through this example, X will be equal to 1, and the second time through, X is equal
to 6 because it has been incremented by 5. Similarly, the third time through X is equal to 11. You
can also use negative numbers for Step values, which allows you to count downwards. For
instance, take a look at the following:
For X = 24 To 0 Step -3
program statements
Next X
In this example, the first time through the structure X is equal to 24, the second time it is equal to
21, and the third time it is equal to 18.
AddIt = False
For J = 1 to NumEntries
If NumValues(J) = ToAdd Then AddIt = True
Next J
If AddIt Then
NumEntries = NumEntries + 1
NumValues(NumEntries) = ToAdd
End If
This works great, but if the array gets large, you can end up going through the For ... Next loop
quite a few times. Now consider the following code, which accomplishes the same task, but
dumps out of the For ... Next loop early if a match is detected.
AddIt = False
For J = 1 to NumEntries
If NumValues(J) = ToAdd Then
AddIt = True
Exit For
End If
Next J
If AddIt Then
NumEntries = NumEntries + 1
NumValues(NumEntries) = ToAdd
End If
Now if a match is found early on in the loop, all the rest of the iterations are skipped because the
Exit For statement is encountered and the loop is basically exited right away. The result is a
faster running macro.
If condition Then
program statements
Else
program statements
End If
When a macro is executing, and this structure is encountered, Excel tests whatever condition you
have defined. If the condition is true, then the program statements right after the Then keyword
are executed. If they are not true, then the statements after the Else keyword are executed. The
Else keyword and any following program statements (which together make up an Else clause)
are optional; you do not need to include them in your macro.
Regardless of whether the program statements in the If ... End If structure are executed, when
Excel is done with the structure, the macro continues running with the statement following the
End If keyword.
While condition
program statements
Wend
When a macro is executing and this structure is encountered, the language tests whatever
condition you have defined. You can see examples of conditions in many of the macros used in
ExcelTips. If the condition is true, then the program statements between the While and Wend
keywords are executed. If the condition is not true, execution of the macro continues with the
statement following the Wend keyword. If the conditions are true when Wend is encountered, the
macro will loop back up to the While statement and keep executing the loop until the condition
becomes false.
program statements
Case Else
program statements
End Select
When a macro is executing, and this structure is encountered, Excel uses the expression to test
each subsequent Case statement to see if the code under the Case statement should be executed.
For instance, consider the following code:
This code assumes you enter it with DayOfWeek already set to a numeric value. Let's say (for
example’s sake) the value is 4. In this structure, the only code that would be executed is the code
under the Case 4 statement—in other words, the macro would set DayName to “Thursday.” If
DayOfWeek were set to some other value not accounted for by the Case statements (outside of
the 1 to 7 range), then the code under Case Else would execute, and the macro would set
DayName to "Unknown day."
VBA allows you to use quite a few different types of variables. There are eleven types of
variables you can use in your macros. These are known as data types, and you should use the
data type that most closely matches the characteristics of the information you are storing in the
variable. VBA supports the following data types:
An additional data type (Decimal) is also specified in the VBA documentation, but is not
currently supported by the language. As in other versions of BASIC, VBA also allows you to
define variable arrays and you can also create user-defined data types. The full range of variable
specifications is much too complex for a simple ExcelTip, however. If you need specific
information about how to work with variables, refer to a good Visual Basic or VBA
programming book. You can also look in the VBA on-line help under the Dim statement.
Declaring Variables
If you have ever programmed any macros, you are probably familiar with how you define
variables using the Dim keyword. For instance, you can define an integer variable with the name
MyVar as follows:
This is very straightforward, and will work fine in your code. To save a few lines in your code
you may be tempted to define multiple variables per line:
Dim x, y, z As Integer
In some versions of the BASIC language, this will define and initialize three variables, each as
an integer. In VBA it also appears to run properly, and no error is generated. However, there is a
small problem—only the last variable (z) is actually defined as an integer. You can see how this
works by using the following code:
Sub DimTest()
Dim x, y, z As Integer
Dim sTemp As String
MsgBox sTemp
End Sub
When you run the macro, the message box shows that the variable type for x and y are 0, which
means that the variable is a variant (the default data type for undeclared variables). Only the last
message box (for z) shows a variable type of 2, meaning an integer.
The solution is to make sure that you declare your variables one per line, or using the full syntax
for each variable, as in the following:
TempNum = MyNum1
MyNum1 = MyNum2
MyNum2 = TempNum
As you can probably tell by the example variable names, this code works with numeric variables.
It will also work just as well with string variables, if you use the same technique. When
completed, the values in MyNum1 and MyNum2 have been swapped, and TempNum doesn’t
matter since it was intended (by this technique) as a temporary variable anyway.
Comparing Strings
It is not uncommon to compare strings in a macro. For instance, you may need to compare what
a user typed with some pre-determined value. If you do this directly, you must take into
consideration that the user may not have typed his (or her) string in the same way as you
expected. Particularly vexing is the fact that the user may have mixed upper and lower case in
their response.
The quickest and easiest way around this is to use either the UCase() or LCase() function on their
input before you do the comparison. For instance, let's assume you prompt the user for the word
"yes" to verify they want an action done. The following code will check the input, regardless of
how the user typed it.
The trick is make sure your test string is either all upper or all lower case, and then convert the
user’s input to that same case.
A = Str(B)
In this syntax, if B is equal to 5, then when completed, A will be " 5"; if B is -4, then A would be
"-4". Notice the leading space when converting positive numbers. This may not provide
satisfactory results for some subroutines. Instead, you should create a function that returns a
stripped-down version of the string. The following function does just that:
A = Trim(Str(X))
ToNum = A
End Function
The reason that the value passed to the VBA function (X) is defined as a Variant is that you can
then pass any type of numeric value.
A = Val(MyString)
B = Val("-12345.67")
C = Val("9876")
D = Val(" 4 5 2 1")
The first line converts MyString into a value, placing it in A. The second line results in B being
set to –12345.67. The third places the value 9876 into C, and the final line sets D equal to 4521.
Notice that spaces are ignored in the conversion; this is why the final line works the way it does.
You should also note that trying to use formatted numbers in a conversion will confuse the Val()
function. Thus, Val(“1,234”) would not return a value of 1234 (as one might hope), but a value
of 1. The conversion stops at the first nonnumeric character, in this case the comma.
As your macro executes, information can be stored and restored in the elements of the array. At
some time, you may want to erase all the information in the array. One classic way of doing this
is using a For ... Next loop to step through each array element, as follows:
For J = 0 To 99
MyText(J) = ""
Next J
When the looping is complete, everything has been erased from the array. A quicker way of
accomplishing the same task is to use the ERASE function, as follows:
Erase MyText
Once executed, this single line sets each element of the MyText array back to an empty string. If
the array is numeric, then each element of the array is set to zero.
x = Abs(y)
x = Rnd()
where x is the result. The value returned will always be between 0 and 1. To translate this to
some other random value, all you need to do is multiply the result by the highest number you
want to consider. For instance, if you wanted a random number between 1 and 25, you could use
the following code line:
x = Int(25 * Rnd()) + 1
Since Rnd always returns a value between 0 and 1 (but never 1 itself), multiplying what it returns
by 25 and then using the Int function on that result will return a number between 0 and 24.
Finally, 1 is added to this result, so that x will be equal to a number between 1 and 25, inclusive.
x = Asc(y)
where x is the variable that the ANSI value should be assigned to, and y is the string to be
analyzed. The way in which the Asc function works is very similar to the CODE worksheet
function.
iThisHour = Hour(Now())
When executed, iThisHour will be equal to the current hour number, which ranges from 0 to 23.
Notice that this example uses the Now() function. If you want to determine the hour number for a
different date and time value, simply substitute that value in place of the Now() function.
iDay = Day(Date)
The Day function returns an integer value representing the day of the month of whatever date
you provide. In this example, the Date function represents today's date, and so Day returns
today's day of the month.
If you want to get fancier in your date calculations, you can use the DateDiff function. This
function allows you, for instance, to determine the number of weeks or months between two
dates. In order to use the function to find this type of information, you would do as follows:
The first line determines the number of weeks between the two dates, and the second determines
the number of months between them.
The first and second lines are functionally the same; they both produce a line of 25 spaces. In the
first example, the ANSI value of 32 is used, which is the character code for a space. In the third
line, sNew3 will be equal to 80 equal signs.
The fourth line produces a 20-character string of equal signs. This can be a bit frustrating to
programmers familiar with other implementations of BASIC, as to them the last example should
create a 40-character string of alternating equal signs and asterisks. (Under older versions of
BASIC, the String function concatenates whatever you designate, so one could expect this to
create a 40-character string made up of 20 iterations of “=*”. Not so; VBA does not implement
the String function as is done in other BASICs.)
Dissecting a String
If you have used BASIC before, you will be right at home with the string functions provided by
VBA. The following table details the most common string functions and what they return.
Function Comments
Left(Source, Count) Returns the left Count characters of Source text.
Mid(Source, Start [, Count]) Returns the portion of Source text beginning with the
Start character. If Count is supplied, then the result is
limited to that many characters.
Right(Source, Count) Returns the right Count characters of Source text.
Fortunately, VBA provides several different functions to remove spaces from a string. The
following are the three functions you could use:
MyVar = LTrim(MyVar)
MyVar = RTrim(MyVar)
MyVar = Trim(MyVar)
The first example ends up trimming all the spaces from the left end of the string, the second
removes them from the right end, and the third removes them from both ends. You can use the
function that you feel best fits your programming needs.
A = Len(MyString)
B = Len("This is a test")
The first line returns the length of the characters in the variable MyString. The second returns the
number of characters between the quote marks (in this case, 14—remember that spaces count as
characters).
If you want to determine the length of the information in a particular cell, you follow a bit
different approach:
C = Len(ActiveSheet.Range(ActiveWindow.Selection.Address))
When this line is executed, it returns the length of whatever is in the currently selected cell.
x = Int(y)
Sub Test()
Dim A As Double
Dim C As Double
Dim E As Double
A = 5908
C = 0
C = -C
E = 180 / WorksheetFunction.Pi
MsgBox E * WorksheetFunction.Atan2(C, A)
End Sub
When the code is executed, the error is generated on the line where ATAN2 is executed. Lars
was wondering what, exactly, caused the problem.
The problem is apparently related to how you are manipulating the C variable. You first define C
as zero, and then negate this value. There is no such thing as negative zero, and when you try to
negate the value, Excel apparently balks when that value is subsequently used in the formula.
One way to solve the problem is simply to change the way in which C is transformed to account
for zero values. Change the macro so that it looks like this:
Sub Test()
Dim A As Double
Dim C As Double
Dim E As Double
A = 5908
C = 0
If C <> 0 Then C = -C
E = 180 / WorksheetFunction.Pi
MsgBox E * WorksheetFunction.Atan2(C, A)
End Sub
Now the macro will work just fine because you are only doing the transform on C if it doesn’t
equal zero.
It also appears that the error is only generated if C is defined as a floating-point value. If you
dimension C as an Integer, then the original macro does not generate an error. This could
indicate that the problem is related to how a floating point representation of the non-existent
negative zero is internally represented. Since the Integer data type deals strictly with whole
numbers, that representation problem does not occur.
You also can get rid of the problem if you declare C as a Variant data type, or if you remove the
declaration line altogether (which means that VBA defaults to declaring C as a Variant when it is
first used).
That being the case, you have two options: you can either load the VBA equivalent of the
Analysis ToolPak, or you can create your own BIN2DEC function in VBA. To do the first, make
sure that in Excel you install the Analysis ToolPak – VBA add-in. If it is not listed in the
available add-ins, use Windows to search for the file ATPVBAEN.XLA. (If you are using a
language version of Excel other than English, then the “EN” portion of the file will be different.)
This is the actual add-in you want to enable.
Once you’ve enabled the add-in, display the VBA Editor and choose Tools | References to
display the References dialog box. Make sure the atpvbaen.xla reference is selected. Close the
dialog box, and you can then use BIN2DEC just like you would any other worksheet function.
The other option is to create your own BIN2DEC function. The following is an example of a
function that accepts a string that contains the binary digits and returns a numeric value that
represents the decimal value of that string.
iLen = Len(sMyBin) - 1
For x = 0 To iLen
Bin2Dec = Bin2Dec + _
Mid(sMyBin, iLen - x + 1, 1) * 2 ^ x
Next
End Function
This function actually doesn’t have the same limitations as the BIN2DEC worksheet function; it
will work with binary numbers containing more than 10 digits.
First, it is easy to use most worksheet functions (such as SUM) from within a macro. All you
need to do is to preface the function name with “Application.WorksheetFunction.” or simply
“WorksheetFunction.” Thus, if you know that each run of the macro will require summing
A1:A100, then A1:A300, and finally A1:A25, you could use a macro like this:
For Run = 1 To 3
Select Case Run
Case 1
myRange = Worksheets("Sheet1").Range("A1", "A100")
Case 2
myRange = Worksheets("Sheet1").Range("A1", "A300")
Case 3
myRange = Worksheets("Sheet1").Range("A1", "A25")
End Select
Results = WorksheetFunction.Sum(myRange)
Range("B" & Run) = Results
Next Run
End Sub
This macro uses a For . . . Next loop to specify different ranges of cells to be summed. It then
uses the SUM worksheet function to assign the sum to the Results variable, which is (finally)
stuffed into a cell in column B. The results of the first run are put in B1, the second in B2, and
the third in B3.
While this particular macro may not be that useful, it shows several helpful techniques, such as
how to define a named range, how to use the SUM function, and how to stuff the sum into a cell.
What the macro doesn’t do is to show how to select a variable number of cells to be summed. To
do this, it is best to rely upon the End method of the Range object. The following code line
shows how you can stuff the sum of the range starting at A1 and extending to just before the first
blank cell in the column:
Note that a range (myRange) is defined as beginning with A1 and extending through whatever
the End method returns. This is then summed and stuffed into B1.
The problem is actually bigger than what Cesarettin proposes. Microsoft knows about this
problem; it seems to stem from issues with the internal formulas used by MOD. You can find
more information about the error here:
http://support.microsoft.com/?kbid=119083
Basically, the MOD function returns an error if the divisor (the second argument in the MOD
function), multiplied by 134,217,728, is less than or equal to the number being evaluated (the
first argument in the MOD function).
Thus, the problem occurs when the number being evaluated is 268,435,456 and the divisor is 2,
the number being evaluated is 402,653,184 and the divisor is 3, the number being evaluated is
536,870,912 and the divisor is 4, etc.
The solution suggested by Microsoft is to simply not use the MOD function and instead rely
upon the following formula:
=number-(INT(number/divisor)*divisor)
This is not the only solution, however. There are other formulaic approaches you can use, as
well. For instance:
=MOD(MOD(number,134217728*divisor),divisor)
This will solve for larger numbers much larger than the limit for MOD, but theoretically will hit
the same problem when the number being evaluated reaches 134,217,728*134,217,728*divisor.
For most uses, this is limit is large enough that it will never be reached.
If you only need to find the modulus of a number divided by 2, then you can insert a check into
your formula in the following manner:
=MOD(IF(A1>=268435456,A1-268435456,A1),2)
This checks if the number being evaluated (in this case, in cell A1) is larger than the limit, and if
it is it subtracts the limit from the number before calculating the modulus. You could also
effectively remove the MOD limit by using this formula:
=MOD(MOD(number,2^16),2))
This takes the large number modulo 2 to the 16th power, then takes the resulting value modulo 2.
If the numbers are viewed as binary, it's easy to see what is happening. MOD(largenum,2^16)
just drops all bits to the left of the 16th binary digit. For modulo 2, only the right-most digit is
required to determine the result anyway, so the dropped bits never affect the result, regardless of
value.
Of course, you could simply create your own MOD function in VBA and use it in your formulas
instead of the built-in MOD function.
DblMod = D1 Mod D2
End Function
The function simply lets you pass two arguments to the VBA function. It then relies upon the
VBA Mod function, which doesn’t have the same limitation as the MOD worksheet function.
This problem with EOMONTH apparently occurs because of changes made in "native functions"
in Excel 2007. In previous versions of Excel, EOMONTH was part of the Analysis ToolPak. In
Excel 2007 and later versions the function has been moved out of the ToolPak and into Excel. In
other words, you don't need to have the Analysis ToolPak activated in order to use EOMONTH.
This leads to the problem. If you have a workbook created in a previous version of Excel and
you open it in Excel 2007's compatibility mode, the workbook formulas are evaluated and, under
some circumstances, the internal "tokens" used for functions are updated. When the workbook is
saved back out, the updated token is stored in the workbook and, when the workbook is reloaded,
the token now points to what Excel interprets as an invalid function.
Microsoft hasn't posted anything in their Knowledge Base about this error as of yet. The problem
seems to be intermittent (as Joe noted), affecting a workbook only after it has gone through
between four and eight open/edit/save cycles.
The solution is to open the workbook and, if the problem is not manifest, use Save As to save the
workbook in native Excel 2007 format. Since compatibility mode is not involved from that point
on, the problem should not occur again. If the problem is manifest, then you will need to correct
the problem (as Joe has done in the past) and then save the workbook in Excel 2007 format.
If it is not possible for you to save the workbook in Excel 2007 format (perhaps you need to use
the older format in order to work with others who have not updated their program), then you
should consider not relying on the EOMONTH function. Instead, use a formula such as either of
the following:
=A1+31-DAY(A1+31)
=DATE(YEAR(A1),MONTH(A1)+1,1)-1
If you prefer, you can create your own user-defined function to calculate the last day of a month.
The following is one approach:
You'll want to make sure that you pass the function a valid date, either by referencing a date in a
cell or by enclosing a literal date within quote marks. Assuming cell B7 contains the date
5/10/09, both of the following will return the same result:
=LastOfMonth(B7)
=LastOfMonth("5/10/2009")
There are a couple of ways you could approach this problem. First, you could use a conditional
formula to determine whether VLOOKUP will return a value or an error. If it will return an
error, then you can have the formula run a user-defined function (MyUDF), as shown here:
=IF(ISERROR(VLOOKUP(B2,CODES,1,FALSE)),MyUDF(),
VLOOKUP(B2,CODES,1,FALSE))
All you need to do is make sure that you put your actual VLOOKUP code in the formula (twice)
and replace MyUDF with the name of the user-defined function you want to trigger.
Another approach is to set up an event handler for the Calculate event. This can be rather simple,
as in the following:
This example assumes that the VLOOKUP formula is in cell A1 and that you want to run a
macro called Macro1 if the VLOOKUP returns an error. Your macro could then do whatever you
need it to do. Remember, as well, that the Calculate event handler should be placed in the
ThisWorksheet object.
You could also make the Calculate event handler a bit more robust, as shown here:
The VLOOKUP function doesn't have a way to check for the case of information; it is case
insensitive. There are several ways you can work around this shortcoming, however. One way is
to use the CODE function to create an intermediate column that can be searched by VLOOKUP.
Assuming that your original data is in column B, you could put the following formula in cell A1
and copy it down the column:
=CODE(LEFT(B1,1))&"."&CODE(MID(B1,2,1))&"."&CODE(RIGHT(B1,1))
This formula looks at the first three characters of whatever is in cell B1 and converts those
characters to decimal character codes separated by periods. Thus, if A1 contained "ABC" then
B1 would contain "65.66.67". Assuming that the value you want to locate is in cell C1, you
could use the following as your VLOOKUP formula:
=VLOOKUP(CODE(LEFT(C1,1))&"."&CODE(MID(C1,2,1))&"."&
CODE(MID(C1,3,1)), A:B,2,)
Another approach is to use the EXACT function to determine the location of what you are
looking for. This approach doesn't use VLOOKUP at all, instead relying on the INDEX function.
The formula assumes that the cells you want to compare are in column A and what you want to
return is the corresponding cell in column B.
=IF(MIN(IF(EXACT(C1,$A$1:$A$100),ROW($A$1:$A$100)))=0,NA(),
INDEX($B$1:$B$100,MIN(IF(EXACT(C1,$A$1:$A$100),ROW($A$1:$A$100)))))
This formula needs to be entered as an array formula (SHIFT+CTRL+ENTER). The first part of the
formula (the first instance of EXACT) compares C1 (what you are looking for) to each value in
the range A1:A100. Since this is an array formula, you end up with, in this case, 100 True/False
values depending on whether there is an exact match or not. If there is a match, then the first
ROW function returns the row of the match and the INDEX function is used to grab the value
from column B in that row.
In some instances you might want to create your own user-defined function that will do the
lookup for you. The following is an example of such a macro:
Application.Volatile
To use the macro, simply call the function with the value you want to find (say cell C1), the
range whose first column should be searched (such as A:B), and optionally the offset of the
column within that range, as here:
=CaseVLook(C1,A:B,2)
A few additional approaches can be found at the following Knowledge Base article:
http://support.microsoft.com/?kbid=214264
Requiring Input
When you are developing a worksheet that will be used by other people, you may want to make
sure that they fill in certain cells before they are allowed to close the workbook. There is no
built-in function in Excel to do this, but you can create a macro that will make the necessary
check and stop the user for proceeding. This can be a rather simple macro, tied to the
BeforeClose event.
The BeforeClose even is triggered whenever a workbook is closed by whatever means. The trick
is the setting of the Cancel property within the event handler. Setting Cancel to True will stop the
closing of the workbook and leaving it unchanged results in the workbook closing normally.
For example, the following macro checks whether cell A1 has anything in it; if it does, then the
workbook is closed. If it doesn't, then the user is informed that something is missing and the
closing is canceled.
More elaborate macros can be created, if desired. For instance, you might have several different
cells that need to be checked. The following version checks a range named "Mandatory" to see if
each cell in the range contains something. If any of the cells are empty, then the workbook
cannot be saved or closed. (This macro is triggered not only during the BeforeClose event, but
also during the BeforeSave event.) The two event handlers are put into the code for the
workbook and the ForceDataEntry macro is placed in a regular macro module.
CellCount = 0
For Each c In rng
If Len(c) > 0 Then
CellCount = CellCount + 1
End If
Next c
ForceDataEntry = False
If CellCount <> rngCount Then
ForceDataEntry = True
End If
End Sub
You should note that any implementation that requires macros (like this one does) suffers from
one potential problem—users can decide to not enable macros when the workbook is loaded. If
they run the workbook with the macros disabled, then they will still be able to save the workbook
without all the mandatory cells containing values.
The problem is that when someone activates the cell, it is possible for them to still leave it
empty. The only time that Excel won't allow the person to leave the cell blank is if they start to
edit the cell and try to leave it blank after the edit. Merle wants, once the cell is selected, for the
user to absolutely only be able to leave the cell if they choose Yes, No, or N/A.
Data Validation, by itself, can't take care of this. There are a couple of ways that you can work
around the problem, however. The first idea is to modify the options that you give the user. For
instance, let's say that you add a fourth choice of "Provide Answer." You could then change the
value in the cell to the same value and save your workbook. When the user opens it, the cell
contains "Provide Answer" and, once they select the cell, they won't be able to blank it out; they
will need to provide an answer.
Another option is to use a macro in conjunction with the Data Validation you have set up. The
easiest method is to set up an event handler for each time the selection changes in the worksheet.
The following example kicks into play if the cell selection is C22 (which is where your Data
Validation should be, as well).
There is no built-in way to do this in Excel, as the program determines its own order of choosing
which cell is next selected. You can modify which cell is selected next when you press Enter in a
worksheet, but you cannot modify what happens when you press Tab in a protected worksheet.
By default cells are selected left to right and then top to bottom in the worksheet.
If you want to modify what happens when the Tab key is pressed, then you'll need to resort to
using a macro to control the selection order. The following macro is an example; it moves to cell
D5 when leaving cell C10 and to E5 when leaving cell D10:
The problem with using a VBA solution like this is that it can make your spreadsheet—
particularly if it is a large one—a bit more sluggish. By their nature, macros also mean that the
Undo feature is disabled.
If your tab order needs are more complex, then you may be interested in the code discussed at
this web page:
http://www.ozgrid.com/forum/showthread.php?t=82272
As you can tell, the code can get rather complex at times. Of course, such an approach, since it
stipulates all cell-to-cell movement, makes it more difficult to make changes to the design of the
worksheet itself.
This cannot be done with any native configuration setting in Excel. Instead, you will need to
create a macro that will handle the entry for you. A natural choice for the macro is to use the
Change event for the worksheet, so that any time a value is entered into a cell, the entry is
"pulled apart" and stuffed in cells in the row.
This macro checks, first, to see if what was entered is numeric. If it is, then the digits are
extracted from the value and placed in consecutive cells in the row.
The drawback to such a macro, of course, is that you still need to press ENTER to trigger the
event. If you want to get away from pressing ENTER entirely, then you will need to rely upon a
different approach. This technique relies upon the OnKey function to assign macros to specific
keystrokes. Place the following code into a standard macro module.
Sub Assigns()
Dim i As Variant
With Application
For i = 0 To 9
.OnKey i, "dig" & i
Next
End With
End Sub
Sub ClearAssigns()
Dim i As Variant
With Application
For i = 0 To 9
.OnKey i
Next
End With
End Sub
Sub dig0()
ActiveCell.Value = 0
ActiveCell.Offset(1, 0).Select
End Sub
Sub dig1()
ActiveCell.Value = 1
ActiveCell.Offset(1, 0).Select
End Sub
Sub dig2()
ActiveCell.Value = 2
ActiveCell.Offset(1, 0).Select
End Sub
Sub dig3()
ActiveCell.Value = 3
ActiveCell.Offset(1, 0).Select
End Sub
Sub dig4()
ActiveCell.Value = 4
ActiveCell.Offset(1, 0).Select
End Sub
Sub dig5()
ActiveCell.Value = 5
ActiveCell.Offset(1, 0).Select
End Sub
Sub dig6()
ActiveCell.Value = 6
ActiveCell.Offset(1, 0).Select
End Sub
Sub dig7()
ActiveCell.Value = 7
ActiveCell.Offset(1, 0).Select
End Sub
Sub dig8()
ActiveCell.Value = 8
ActiveCell.Offset(1, 0).Select
End Sub
Sub dig9()
ActiveCell.Value = 9
ActiveCell.Offset(1, 0).Select
End Sub
To start the macro, run the Assigns macro. Thereafter, every time a digit is typed the digit is
stuffed into the current cell and the next cell to the right selected. If you type in text, then nothing
happens. (Of course, if you try to enter a mixed value, such as B2B, then when you press "2" that
is what will end up in the cell.) When you are done with this type of data entry, run the
ClearAssigns macro to finish up.
There is no way to do this directly in Excel without (as Craig mentions) using Data Validation.
There are a few things you can try to achieve the desired effect, however. First, you can using a
formula to check the length of any cell, and then display an error message, if desired. For
instance, if the cells you want to check are in column C, you could use a formula such as the
following:
Place the formula in the cell to the right of the cell being checked (such as in cell D1), and then
copy it down as many cells as necessary. When an entry is made in C1, and if it is more than 15
characters, then the message is displayed.
If such a direct approach is undesirable, then you’ll need to use macros to do the checking. The
following is a simple example that is triggered whenever something is changed in the worksheet.
Each cell in the worksheet is then checked to make sure it is not longer than 15 characters. If
such a cell is discovered, then a message box is displayed and the cell is cleared.
A more robust approach is to check in the event handler to see if the change was made
somewhere within a range of cells that need to be length-limited.
ExitHandler:
Application.EnableEvents = True
Set rCell = Nothing
Set rng = Nothing
Exit Sub
ErrHandler:
MsgBox Err.Description
Resume ExitHandler
End Sub
To use this macro, you simply need to change the value assigned to iChars (represents the
maximum length allowed) and the range assigned to rng (currently set to A1:A10). Because the
macro checks only for changes within the specified range, it is much faster with larger
worksheets than the macro that checks all the cells used.
There are several different ways that this can be handled, depending on how you want the data
treated once it is entered. One approach is to change the formatting of the cell into which the
values are being entered. You could, for instance, use the following custom format for the cell:
#,##0",000"
Whenever you enter a value in the cell, Excel follows the value with ",000". Thus, enter 27 in the
cell and Excel displays 27,000.
The drawback to this approach is that the underlying number is still considered the smaller value.
If you later try to add 1 to the cell contents, you don't get 27,001, you get 28,000. You also won't
be able to enter decimal values. This means that if you enter 1.23, you don't get 1,230; you
instead get 1,000 because the value is treated as an integer before displaying.
A better approach is, perhaps, to change a configuration setting in Excel itself. Follow these steps
if you are using Excel 2007 or a later version:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
2. At the left side of the dialog box, click Advanced.
3. Check the Automatically Insert a Decimal Point check box and change the value for the
setting to -3.
4. Click OK.
If you are using an earlier version of Excel, follow these steps, instead:
1. Choose Options from the Tools menu. Excel displays the Options dialog box.
2. Make sure the Edit tab is displayed.
3. Check the Fixed Decimal Places check box and change the value at the right of the
option to -3.
4. Click OK.
Now, whenever you enter any information into a cell, Excel automatically multiplies the value
by 1,000, provided you don't include a decimal point in what you are entering. This means that if
you enter the value 3, Excel actually enters 3,000 into the cell. If you instead enter 1.23, then
Excel enters 1.23; it doesn't multiply by 1,000.
If you choose this approach, remember that it is not only data entry on the current worksheet that
is affected. This setting affects all data entry on all worksheets from this time forward. If this is
not what you want, then you'll need to remember to turn off the setting (clear the check box)
when you want to return to normal data entry.
You could, as well, use a macro approach to the problem. For instance, if you are entering only
numeric data into the worksheet, you could create a macro that will multiply the contents of a
cell by 1,000 every time the cell changes. Right-click the worksheet tab and choose View Code
from the resulting Context menu. Then enter the following macro into the code window:
Perhaps the best solution, though, is to keep things simple. Have a column where you input your
values as you want. Then, in another column, use a formula to modify whatever values you
entered. For example, you could enter 1.23 into cell A1. In cell B1, then, you could multiply this
value by 1,000. The value in cell B1 could then be used within other formulas, elsewhere in your
workbook.
Unfortunately, they don’t provide one. There are ways around this, however. One way is to just
add a tool to the Quick Access Toolbar that pastes values for you. All you need to do is follow
these steps if you are using Excel 2007 or later:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
2. At the left side of the dialog box click Customize (Excel 2007) or Quick Access
Toolbar (Excel 2010 or Excel 2013).
The Quick Access Toolbar area of the Excel Options dialog box
3. Use the Choose Commands From drop-down list to choose All Commands.
4. In the list of commands, choose Paste Values.
5. Click the Add button. The command is copied to the right side of the screen.
6. Click OK.
If you are using an earlier version of Excel you can create a toolbar button that pastes values for
you:
1. Choose Customize from the Tools menu. Excel displays the Customize dialog box.
2. Make sure the Commands tab is selected.
Now, whenever you want to paste just the values, you can click on the new tool button.
If you don’t want to use the mouse to paste values, then you can use the tried-and-true keyboard
sequence to paste values: ALT+E, S, V, ENTER (for versions of Excel prior to Excel 2007) or
ALT, H, V, S, V, ENTER (for Excel 2007 and later). This sequence selects the menus and
dialog box options necessary to paste values.
If you want a shorter keyboard shortcut, the best way to do it is to create a macro that does the
pasting for you, and then make sure that you assign a keyboard shortcut to the macro. For
instance, create the following simple macro:
Sub PasteVal()
Selection.PasteSpecial Paste:=xlValues
End Sub
2. From the list of available macros, select the PasteVal macro you just created.
3. Click on Options. Excel displays the Macro Options dialog box.
4. In the Shortcut Key area, indicate the key you want used with the CTRL key as your
shortcut. For instance, if you want CTRL+G to execute the macro, then enter a G in the
Shortcut Key area.
5. Click on OK to close the Macro Options dialog box.
6. Click on Cancel to close the Macro dialog box.
Now, whenever you want to paste values, all you need to do is press CTRL+G, the macro is run,
and the values in the Clipboard are pasted to the selected cell.
What if you want to allow cells to be edited, but you want them to become protected right after
someone enters information in the cell? For instance, you have cells in which a user could enter
information, but once entered, you don’t want them to have the ability to change the information
they entered.
There is no inherent ability in Excel to protect your input after entry, but you can create the
ability through the use of a macro. The following macro is an example of how you can do this:
This macro assumes that the worksheet has already been protected and that all the cells where
you want input to be possible are unlocked. What it does is check to see if the input was done in
the proper range of cells, in this case somewhere in the range of A1:D100. If it was, then the
worksheet is unprotected, the cell in which information was just entered is locked, and the
worksheet is again protected.
If you are using this approach in your own workbook, you will need to modify the potential input
range and you will want to change the password used to unprotect and protect the worksheet.
This is rather easy to do manually. There is an important item to keep in mind, however: All the
cells in the worksheet are "locked," by default. In other words, you don't need to look for a way
to lock the non-empty cells; you only need to look for a way to unlock the empty ones. (There is
one exception to this, addressed shortly.)
With this in mind, you can follow these steps to get your empty cells unlocked:
That's it. You can now lock your worksheet and only those blank cells that were selected at the
end of step 5 will be accessible.
One interesting thing to note is that you don't really have to select a range in step 1. If, instead,
you select a cell within the main body of your worksheet's entries, Excel assumes that you want
to operate on the used area of your worksheet. In other words, when you get to step 5 what will
be selected are all the empty cells in the used area of you worksheet.
One more thing to be aware of is that once you set the locking status of a cell (step 8), the cell
retains that status until you specifically change it. This means that if you've previously made
changes to the locking status of the cells, it may be beneficial to explicitly lock the cells prior to
unlocking the empty ones. You can do this by following these modified steps:
In this case you must perform step 1—you have to select a range to affect. Excel won't assume
which range you want to affect as in the earlier comment.
If you prefer, you can use a macro to protect your cells and your worksheet:
Sub UnlockEmptyCells()
Dim myCell As Range
ActiveSheet.Protect DrawingObjects:=True, _
Contents:=True, Scenarios:=True
myCell.Select
End Sub
This macro makes sure that all the cells in the worksheet are locked, then it unlocks the blank
cells in the used range, and finally it protects the worksheet.
For some workbooks, however, you may want Excel to make the active cell in the selected
worksheet the same as the active cell in the previous worksheet. There is no setting to
automatically do this in Excel, but there are a couple of things you can try. One thing is to follow
these steps:
1. Hold down the CTRL key as you click on the tab of the worksheet you want to go to.
Two worksheet tabs should now be selected; the one with the bold name is the one that
is actually displayed on the screen.
2. Click on the tab for the worksheet you want to go to. Both tabs should still be selected,
but just the one you clicked on should have its name in bold.
3. Hold down the CTRL key as you click on the tab of the worksheet you just left.
These steps work because you are “grouping” worksheets. When you do, Excel makes the
selected cells the same for all worksheets in the group.
These three steps cannot be automated with macros, but you can take a different approach with a
macro to accomplish the same task. The first thing you need to do is declare a public variable
anywhere within a module of the workbook, as shown here:
This variable, sAddress, will be used to store the current address of the active cell. In the
“ThisWorkbook” module of the workbook, add these two macros:
End Sub
The first macro is run automatically by Excel any time that the selected cell changes. All it does
is retrieve the address of whatever cell is active, and then store that address in the sAddress
variable.
The second macro is automatically run whenever a workbook is activated. It checks to see if
there is anything stored in sAddress. If there is, it selects whatever cell address is stored there.
The error code is necessary in case you select a sheet that doesn’t use cells, such as a chart sheet.
This macro approach works great if you only want to make this navigational change in a single
workbook or two. If you prefer to make the change “system wide” (so to speak), you must be a
little more complex in your approach to the macro. In this case, you need to place your code in
the Personal workbook so that it is loaded every time you start Excel. Specifically, place the
following code into a new class module of the Personal workbook. This class module should be
named something descriptive, such as ClassXLApp:
Next, open the "ThisWorkbook" module of the Personal workbook and copy the following code
to it:
Once you save the Personal workbook and restart Excel, the range in the first workbook that
opens will be selected in the next worksheet that is selected.
For instance, if you type "George" into cell A8, and then type "George" into A9, regular data
validation will generate an error, as one would expect, indicating that the value you are trying to
enter is not unique. However, if you type "George" into cell A8, copy that cell and paste it into
cell A9, no data validation error is triggered—the paste is allowed.
There is no direct way around this in Excel. You can, however, cause Excel to do some checking
whenever you try to do a paste. Consider the following macro:
This macro is only run when the selection changes in a worksheet. (This code needs to be in the
code window for a worksheet.) It examines the target cells (the ones being selected), and if the
user is trying to paste into a cell that has validation active, it will not allow it. Further, the user
will see a dialog box that indicates the error.
You should note that this routine just checks to see if pasting into a data-validated cell is being
done. If it is, then an error is generated. The routine does not check to see if what is being pasted
is actually permissible under the validation rules in the target cells; that would be much more
complex and require quite a bit more coding.
Of course, the appearance of the radical (or even whether it appears at all) depends on the font
used in the cell. The ALT+251 method works for most normal fonts, but some fonts may not
include the radical symbol (in which case it won’t appear) or may have the symbol mapped to a
different character in the font. In that case, the best way to insert the symbol is to use the Symbol
dialog box to search through the desired font and find the radical.
You can also use the Windows Character Map program to find the radical, copy it to the
Clipboard, and then paste it into Excel.
All of the methods described so far are great if the only thing you want in the cell is the radical.
You can, however, format a cell so that the radical symbol is displayed just to the left of
whatever value is in the cell. Perhaps the easiest way to apply this format to a cell is to use a
macro, as shown here:
Sub Radical()
ActiveCell.NumberFormat = ChrW(8730) & "General"
End Sub
Select the cell you want to format, then run the macro. (You can see how this custom format is
handled by Excel if you run the macro and then display the Format Cells dialog box.)
There is no intrinsic or formulaic method of doing this in Excel. This means that you need to turn
to a solution that is based on a macro. Fortunately, VBA offers several different ways you can
approach this problem. One approach is to simply use a formula to make sure that each formula
within a selection is actually an array formula.
Sub MakeCSE1()
Dim rCell As Range
This macro assumes that you’ll select the cells to be “converted” before actually running the
macro. If you prefer, you could define a range of cells (give the range a name) and then run a
similar macro that always does its work on that range.
Sub MakeCSE2()
Dim rng As Range
This macro looks for a range named CSERange and then checks every cell in the range. If it
doesn’t contain an array formula, then the formula is converted to an array formula.
Note the use of the HasArray property to check if a cell contains an array formula. This property
can actually be helpful in other ways. For instance, you could create a simple user-defined
function, such as this:
This function returns True if the cell being pointed to doesn’t contain an array formula. If it does
contain one, then False is returned. You could then use this function as the basis for a conditional
format. All you need to do is create a format that uses it in this way:
=NoCellArray1(A5)
Since NoCellArray returns True if the cell doesn’t contain an array formula, your conditional
format could set the color of the cell to red or set some other visible sign that the cell doesn’t
have the requisite array formula. You could also use the following function to accomplish the
same task:
An entirely different approach is to add something to your formulas that allows them to easily be
recognized as array formulas. For instance, you could add the following to the end of any of your
array formulas:
+N("{")
This doesn't affect the computation in any way, but can be easily checked to see if it is there. The
checking can be done by an event handler, such as the following:
Selection.FormulaArray = ActiveCell.Formula
End If
End Sub
Note that the handler checks to see if the formula ends with ("{") and, if it does, forces the
formula to be treated as an array formula. The great thing about this approach is that you'll never
have to press CTRL+SHIFT+ENTER on the worksheet again—the event handler takes care of it for
you. If, at some point, you want to convert the formula back to a regular (non-array) version,
simply modify the formula so it doesn't include +N("{").
There are several ways that the desired results can be achieved. The first is to simply move
“past” the desired target, and then move back to it, as in the following macro:
Sub GotoCol1()
With Application
ActiveWindow.FreezePanes = False
Range("H1").Select
ActiveWindow.FreezePanes = True
.Goto Range("IV1")
.Goto Range("Z1")
End With
End Sub
The important code lines are those that use the Goto method. The first jump is to the last cell of
the first row, and the second jump moves back to the true target, Z1. By moving in this way,
column Z ends up just to the right of the frozen range, A:G.
While this works just fine, a better solution would be to use the Scroll parameter with the Goto
method. Consider the following example:
Sub GotoCol2()
With Application
ActiveWindow.FreezePanes = False
Range("H1").Select
ActiveWindow.FreezePanes = True
.Goto Reference:=Range("Z1"), Scroll:=True
End With
End Sub
The Scroll parameter is optional with the Goto method; it defaults to False. If you set it to True,
then Goto scrolls through the window so that the upper-left corner of the target range (Z1)
appears in the upper-left corner of the window.
Moving to the first cell in row 252 is easy, provided there is data in all the cells in A1:A251. But
if there can be empty cells in column A, then jumping to A252 can be a bit more difficult. In that
case, you might be interested in a macro that makes jumping to the first cell of the empty row
after your data quite easy:
Sub FindFirstCellNextRow()
Dim x As Integer
x = ActiveSheet.UsedRange.Rows.Count
ActiveCell.SpecialCells(xlLastCell).Select
ActiveCell.EntireRow.Cells(1, 1).Offset(1, 0).Activate
End Sub
The first two lines effectively recompute the “last cell” in the worksheet and then the next two
lines select that cell and jump to the cell in column A that is one row down.
Assign the macro to a keyboard shortcut, and you’ll always be just one keystroke away from
jumping to the first truly empty row in the worksheet.
There are any number of macro-based solutions you can try. Essentially, you need a macro to
detect when information has been deleted and then check to see if the person deleting the
information has permission to do so. The following is just one possible approach to the issue:
sPassword = "Password"
sTemp = "You must enter the password to delete data"
Application.EnableEvents = True
End Sub
The macro, which is actually an event handler triggered whenever something in the worksheet is
changed, checks to see if the information in a cell (or top-left cell in a range) was deleted. If so,
then the user is asked for a password. If the person doesn't have the password, then the Undo
method is invoked to "undo" the person's deletion. (You'll want to change the password, assigned
to the sPassword variable) to the actual password you want people to use.)
Another option is to use an Excel add-in that can take care of the security issues for you. Some
subscribers suggest using A-Tools, which comes in either a free or pro (paid) edition. You can
find more information about this add-in here:
http://www.atoolspro.com/
A-Tools, among other things, apparently allows you to apply various security features to Excel
data that resides on a network. (Chances are good that Jim is sharing his workbook on a network,
as it is used by many people in his company.)
There is no built-in method in Excel to accomplish this selective method of zooming, but there
are a couple of workarounds you can use. One such workaround is to use a macro that displays
the value in the active cell in a message box. Such a macro is easy to add to the worksheet
module:
Every time you select a different cell in the worksheet, the macro pops up a message box that
shows the contents of that cell. This solves the problem, but it can get tiresome to continually
close message boxes every time you change which cell is selected.
You could also create a macro that simply changed the font size of whatever cell is currently
selected. The following simple macro, added to the worksheet module, looks at the currently
selected cell and increases its font size by 500%.
The utility of such a macro will depend, of course, on how you have the height and width of the
selected cell formatted. If they are static heights and widths, it is possible that increasing the font
size will make the cell contents unreadable. If the height and width are dynamic, then the
contents should still be quite readable.
Still another approach is to create your own zoomed-in picture of each cell as it is selected:
In order to use the macro, you need to call it each time the selection in the worksheet changes.
To do this, you add a small macro to the worksheet module:
In this case, every time the cell selection is changed, the ZoomCell macro is run to create a
picture that is six times the size of the original. If it gets bothersome to have the picture
automatically change every time you select a different cell, you could do away with the trigger
macro in the worksheet module and modify the ZoomCell macro so that it runs whenever you
initiate it, perhaps with a shortcut key that you set up.
Sub ZoomCell()
Dim s As Range
Dim ZoomIn As Single
Set s = Selection
ZoomIn = 6
p.Delete
Exit For
End If
Next
In order to return the address of the currently selected cell, you must resort to using macros. The
following macro will return the value of the cell selected at the time it is run:
The inclusion of the Application.Volatile method means that every time the worksheet is
recalculated, this function (macro) is again run. To use the macro you can place the following in
any cell desired, including A1:
=CurrentCell
You should note that this macro doesn’t result in the contents of A1 changing every time you
move to a different cell. Again, the contents of A1 will change only when the workbook is
recalculated, either by changing something in the worksheet or by pressing F9.
If, instead, you need to have a “real time” version that automatically updates A1 as the selected
cell is changed, you can follow these steps:
Now, as you move about this single sheet, the contents of A1 should be constantly updated to
reflect your location.
Range("A4").Select
This is great if you always want to go to cell A4, but terrible if you want to go to the first cell of
whatever row you are on.
As with many tasks in VBA, there are several ways you can approach a solution to this dilemma.
The first method is actually a variation on what the macro recorder returns, as shown above. All
you need to do is change the row designator so it represents the current row, as in the following:
VBA figures out what the current row is, slaps it together with the “A” designator, and comes up
with a cell reference that works with the Range method.
Another technique you can use is to put the Cells property to work, as follows:
Cells(Application.ActiveCell.Row, 1).Select
This approach, of course, can be modified so that you actually select any given cell in the current
row. All you need to do is change the column designation (1, in the above example) to a number
representing the column desired.
Another approach (which produces the same result) is to use the Range object in conjunction
with the Cells property, as shown here:
Range(Cells(Selection.Row, 1).Address).Select
Selection.Row gives the row number of the current selection. The Address property of the Cells
method returns the address of a particular cell in A$1$ format. This address is then used as the
parameter for the Range object, and the actual cell is selected by the Select method.
The Cells property returns an object that represents a specific row and column (individual cell)
of a worksheet. In this usage, Cells is used twice to determine a specific range of cells. The first
instance returns the first cell of the current row, while the second returns the third cell of the
current row. Thus, the range becomes the first through third cells of the current row.
Instead of using the Cells property to specify a location, you can use the Offset property to
accomplish much of the same task. Consider the following code:
This uses the Offset property of the ActiveCell object to specify a range relative to the currently
selected cell. The Offset property takes an argument that represents the row and column of the
offset. A negative value represents up (for the row) and left (for the column). A positive value is
down (for the row) and right (for the column). You can also use a value of 0, which represents
the current row or column.
Cells(ActiveWindow.RangeSelection.Row, 1).Select
Once executed, the selected cell becomes the first cell (in column A) of the current row. If you
run this line while a range of cells is selected, then the cell in column A of the first row of the
selection is selected.
Sub CellSelect1()
Workbooks("pwd.xls").Sheets("Sheet3").Select
ActiveSheet.Range("A18").Select
End Sub
You might think that this macro will select Sheet3!A18 in the pwd.xls workbook. It does, with
some caveats. If you have more than one workbook open, this macro results in an error, if
pwd.xls isn’t the currently active workbook. This occurs even if pwd.xls is already open, but
simply not selected.
The same behavior exists even when you condense the selection code down to a single line:
Sub CellSelect2()
Workbooks("pwd.xls").Sheets("Sheet3").Range("A18").Select
End Sub
You still get the error, except when pwd.xls is the active workbook. The solution is to entirely
change how you perform the jump. Instead of using the Select method, use the Goto method and
specify a target address for the method:
Sub CellSelect3()
Application.Goto _
Reference:=Workbooks("pwd.xls").Sheets("Sheet3").[A18]
End Sub
This code will work only if pwd.xls is already open, but it doesn’t need to be the currently active
workbook. If you want the target workbook to be scrolled so that the specified cell is in the
upper-left corner of what you are viewing, then you can specify the Scroll attribute to be True, as
shown here:
Sub CellSelect4()
Application.Goto _
Reference:=Workbooks("pwd.xls").Sheets("Sheet3").[A18] _
Scroll:=True
End Sub
The easiest way to do this in Excel is to press SHIFT+SPACE BAR. The entire row is highlighted,
and the selected cell remains the same. If you want to move to another cell in the same row
(without changing the highlight), you can use TAB to move to the right and SHIFT+TAB to move
to the left.
If you prefer to have Excel automatically highlight the row, you must rely upon a macro. The
following one will do the trick:
r = Selection.Row
c = Selection.Column
rr = r
cc = c
With Columns(c).Interior
.ColorIndex = 20
.Pattern = xlSolid
End With
With Rows(r).Interior
.ColorIndex = 20
.Pattern = xlSolid
End With
End Sub
Make sure you attach the macro to the worksheet you are using at the time. All the code does is
highlight the row and column the active cell is at. When moving to another cell, the code
remembers the previous cell (by using variables declared as Static) and removes the highlighting
from the previous rows and columns. This code highlights both the current row and column. For
just highlighting the row, remove the chunks of code with r and rr in them. The only real
problem with this method is that if your sheet has any previous color-filled cells, these will be
changed to NoFill, erasing any color that was there.
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
2. At the left of the dialog box click Advanced.
3. Under Editing Options, make sure that the checkbox for “After pressing Enter, move
selection” is checked (it should be by default).
4. Using the Direction drop-down list, change the direction as desired. Changing the
direction affects how Excel behaves in all workbooks.
1. Choose Options from the Tools menu. Excel displays the Options dialog box.
2. Click on the Edit tab.
3. Adjust the Move Cursor After Enter setting. Changing the direction affects how Excel
behaves in all workbooks.
If you have a need to vary the ENTER key behavior on a workbook-by-workbook basis, you might
think you are out of luck. You can, however, use a little creative macro code to specify which
direction you want to go after ENTER, and have that code run whenever a workbook is activated.
For instance, let’s say that you had a particular workbook, and you always want to move the
selection up after pressing Enter. In this particular workbook, you can add the following code to
the thisWorkbook object in the VBA editor:
Application.MoveAfterReturn = True
Application.MoveAfterReturnDirection = xlUp
End Sub
There are two separate subroutines here. The first one runs whenever the window for the
workbook is activated. In this case, it stores the settings associated with the MoveAfterReturn
and MoveAfterReturnDirection properties into variables. (You will learn about these variables
shortly.) The macro then sets the MoveAfterReturn property to True and sets the direction to
xlUp. If you want to go a different direction by default in this particular workbook, simply use a
different Excel constant, such as xlDown, xlToLeft, or xlToRight.
The second subroutine runs whenever the workbook window is deactivated. In this case, the
values of the MoveAfterReturn and MoveAfterReturnDirection properties are reset to what they
were before the workbook was first activated.
The two variables used in these routines, lMoveDirection and bMove, need to be defined in the
declaration portion of any module. This allows the variables to be accessed from both of the
above routines.
Excel does not provide this type of data entry as an option. You can, however, create a macro to
handle the data entry. One way is to us a simple macro that prompts the user for a string of
characters. When the user presses Enter (to signify that the string is complete), then the macro
takes each successive four-character chunk and puts them in consecutive cells.
Sub FourCharEntry1()
Dim str As String
Dim x As Integer
Dim y As Integer
Notice that the macro, as it is putting four-character chunks into cells, makes sure that each
chunk is preceded by an apostrophe. The reason for this is to handle those instances when the
four-character chunk may consist of only numbers and those numbers begin with one or more
zeroes. Adding the apostrophe makes sure that Excel treats the cell entry as text and the leading
zeroes won't be wiped out.
You could, as well, avoid the use of an InputBox by simply allowing someone to enter text into a
cell in the worksheet. The person could type away as much as desired (thousands of characters, if
necessary). Then, with the cell selected, you could run a macro that will pull the information
from the cell and perform the same task—breaking it up into four-character chunks. The
following macro does just that:
Sub FourCharEntry2()
Dim str As String
Dim x As Integer
Dim y As Integer
str = ActiveCell.Value
y = 0
For x = 1 To Len(str) Step 4
ActiveCell.Offset(0, y) = "'" & Mid(str, x, 4)
y = y + 1
Next
End Sub
Another approach is to use a custom user form for the user input. The form provides a much
richer interaction with VBA, so you can actually have it stuff information into cells after every
fourth character is entered.
Start by creating a user form (as described in other issues of ExcelTips) that contains two
controls—a text box and a button. Name the text box vText and associate the following code
with it:
This simply runs every time the contents of the text box change (i.e., when you type each
character) and then checks the length of whatever it contains. When the length reaches 4 the code
takes those characters and stuffs them into a cell. The contents of vText are then emptied.
The name of the button you create in the user form doesn't really matter. It will be used as a way
to close the user form, and should have the following code associated with it:
When you are ready to use the user form, simply select the cell where you want input to start and
then run the following macro:
Sub Start()
UserForm1.Show
End Sub
The user form appears and you can start typing away. When you are done, just click the button
and the user form is closed.
Defined tables are something introduced with Excel 2007. You create them (as Mike mentions)
by using the Table tool on the Insert tab of the ribbon. I normally find it best to enter my column
headings and my data, put any summary formulas in the last column of the table, but don’t put
them in the last row. I then select a cell in the table and use the Table tool to define the entire
area of the table.
Once a table is defined in this manner, you can then use the Design tab of the ribbon to modify
how Excel sees your table. Click the tab and make sure, in the Table Style Options group, that
you specify your table has a Header Row, Total Row, First Column (for your column headers),
and Last Column (for your summary formulas). You can then use a macro such as the following
to figure out and select the table body:
Sub SelectTableBody()
Dim rTableData As Range
With ThisWorkbook.Worksheets(1)
Set rTableData = .ListObjects("Table1").DataBodyRange
Set rTableData = rTableData.Offset(0, 1) _
.Resize(, rTableData.Columns.Count - 2)
End With
rTableData.Select
End Sub
The first Set statement sets the rTableData range equal to what Excel considers the body of the
data table. This includes everything except the header and the total row. (Why Excel includes the
first column and the last column when you’ve designed those columns to be special beats me.)
The next Set statement then adjusts the range inward by one column on the left and one column
on the right. The result is that rTableData represents just the data range that you want.
This approach is dynamic in nature, meaning that it will adjust automatically (well, each time
you run it) whenever you add or delete table rows or column. It will not adjust properly if you
happen to delete either the first or last columns of your data table; it assumes those columns will
never be part of the body range you want.
Cells(1, 1).Value = 23
For entering information in a cell, the Value property is applicable to any object that resolves to a
range. Thus, you could use the following to place a text value ("Address") into the cell at C4:
Range("C4").Value = "Address"
Range("A1") = "Test1"
Range("B1") = "Test2"
Range("C1") = "Test3"
He wonders if there is a way to fill them in a single statement, similar to the following:
Range("A1:C1") = ("Test1","Test2","Test3")
Jonathan's desired syntax is close, but it won't work. Here's how it will work:
Range("A1:C1") = Array("Test1","Test2","Test3")
Note the use of the Array statement, which tells VBA that what follows should be considered a
sequence of values to be used in the sequence of cells at the left of the operator. Interestingly
enough, you could stuff values into variables and also use the Array statement, as shown here:
sOne = "Apples"
sTwo = "Oranges"
sThree = "Artichokes"
Range("A1:C1") = Array(sOne, sTwo, sThree)
Finally, if you wanted to have the values placed into a single column rather than in a row, you
would need to use the Transpose function, in this manner:
Range("A1:A3") = Application.Transpose(Array("Test1","Test2","Test3"))
The easiest way to do this is with a macro that steps through each of your defined names and
copies the name definition to the target workbook. Here's an example:
Sub CopyNames()
Dim Source As Workbook
Dim Target As Workbook
Dim n As Name
Note that the majority of the work in the macro is done in the For Each loop that steps through
all the defined names. It creates the name in the target workbook and gives it the same
assignment as it had in the source workbook (contained in the Value property).
It should be noted that, by default, named ranges include the name of the worksheet in the Value
property. If the source workbook has a named range that refers to, say, Sheet4 and there is no
Sheet4 in the target workbook, then the addition of the name fails. The macro doesn't generate an
error; it simply doesn't create the new named range. The solution is to either (a) make sure that
there target workbook contains the same sheet names as the source workbook or (b) modify the
macro so that it recognizes that there are missing sheets and takes whatever action is appropriate.
If you prefer to not create a macro, then the easiest method may be to copy your worksheets from
the source workbook to a target workbook. Excel generally copies the named ranges along with
the worksheets. The only time this would not be a satisfactory approach is if the target workbook
already has worksheets with the same names as those worksheets you might want to copy. In that
case, you'd be best to use the macro approach.
Using the mouse to select large ranges of cells is cumbersome, at best. There are much easier
ways to select large ranges, and these selection methods can be used to easily copy values to
those large ranges.
Let's say that you have a value in cell A3 and you want to copy it to a large range, such as
C3:C55000. The easiest way to do the copy is to follow these steps:
Easy, huh? A similar approach to selecting large ranges could also be used with the Go To box,
in this manner:
If you ever find yourself needing to copy to very large ranges using a macro, you can do so using
a single command. To copy only the value from A3 to the range C3:55000, you would use the
following:
Range("C3:C55000") = Range("A3").Value
If you instead wanted to copy both values and formats to the large range, then you could use this
command:
Range("A3").Copy Destination:=Range("C3:C55000")
Regardless of how you perform your copying task, make sure you are patient. Depending on
what you are copying, it can take quite a while for the operation to complete. If you are copying
a formula to such a large range, then it can take very long as Excel performs the thousands of
new calculations you've required of it.
If you want to do the clearing manually, you can follow these steps:
2. Click the Special button. Excel displays the Go To Special dialog box.
3. Select the Constants radio button. The four check boxes under the Formulas option then
become available. (This is a bit confusing. Why Microsoft made the Constants radio
button control some check boxes under a different radio button is not immediately
clear.)
4. Make sure that all the check boxes under the Formulas radio button are selected. (They
should have been selected by default.)
5. Click OK. Excel selects all the constants (cells that don’t contain formulas) in the
worksheet.
6. Press the DEL key.
This works great if you only need to clear out the non-formula contents of a worksheet once in a
while. If you need to do it more often, then you can simply use the macro recorder to record the
above steps. Or, if you prefer, you can create your own macro from scratch, such as the
following one:
Sub ClearAllButFormulas()
Dim wks As Worksheet
This macro is particularly useful if you need to clear out all the non-formula cells in an entire
workbook. The reason is because it does the clearing on every worksheet in the entire workbook,
without you needing to do the clearing manually.
If you want to quickly convert large ranges of text without the need to retype the text in the cells
of the range, you can use the following macro.
Sub MakeLower()
Dim MyText As String
Dim MyRange As Range
Dim CellCount As Integer
This macro steps through the cells in a range you select, converts the contents of any cell that
does not contain a formula to uppercase. You can easily modify the macro so that it converts to
uppercase by changing the LCase function (used near the bottom of the macro) to UCase.
Another nifty modification is if you want to use title case instead of uppercase or lowercase.
(Title case is where only the first letter of each word is uppercased.) To do this, replace
LCase(MyText) with Application.Proper(MyText).
If you find yourself in this situation, the MakeProper macro may do the trick for you. It will
examine a range of cells, which you select, and then convert any constants to what Excel refers
to as “proper case.” This simply means that when you are done, the first letter of each word in a
cell will be uppercase; the rest will be lowercase. If a cell contains a formula, it is ignored.
Sub MakeProper()
Dim rngSrc As Range
Dim lMax As Long, lCtr As Long
There are several ways you can approach this problem. One is to use a rather long formula to do
the conversion:
=SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(
SUBSTITUTE(SUBSTITUTE(PROPER($B$13);" A ";" a ");
" An ";" an ");" In ";" in ");" And ";" and ");
" The ";" the ");" With ";" with ")
Remember, this is all a single formula. It does the case conversion, but then substitutes the
desired lowercase words (a, an, in, and, the, with). While this is relatively easy, the utility of the
formula becomes limited as you increase the number of words for which substitutions should be
done.
Perhaps a better approach is to use a user-defined function macro to do the case conversion for
you. The following function checks for some common words that should not have initial caps,
making sure they are lowercase.
Application.Volatile
str = StrConv(str, vbProperCase)
For i = LBound(vExclude) To UBound(vExclude)
str = Application.WorksheetFunction. _
Substitute(str, " " & _
StrConv(vExclude(i), vbProperCase) _
& " ", " " & vExclude(i) & " ")
Next
MyProper = str
End Function
Words can be added to the array, and the code automatically senses the additions and checks for
those added words. Notice, as well, that the code adds a space before and after each word in the
array as it does its checking. This is so that you don’t have the code making changes to partial
words (such as “and” being within “stand”) or to words at the beginning of a sentence. You can
use the function within a worksheet in this way:
=MyProper(B7)
This usage returns the modified text without adjusting the original text in B7.
If you prefer, you can use a function that takes its list of words from a named range in the
workbook. The following function uses a range of cells named MyList, with a single word per
cell. It presumes that this list is in a worksheet named WordList.
Dim c As Range
Dim sTemp As String
sTemp = Application.WorksheetFunction.Proper(cX.Value)
For Each c In Worksheets("WordList").Range("MyList")
sTemp = Application.WorksheetFunction.Substitute( _
sTemp, Application.WorksheetFunction.Proper( _
" " & c.Value & " "), (" " & c.Value & " "))
Next c
ProperSpecial = sTemp
End Function
If there is one and only one comma that separates the surname from the first name, you can
create a formula to do the conversion. Assuming the name is in A1, the formula would be:
=UPPER(LEFT(A1,FIND(",",A1)-1))&MID(A1,FIND(",",A1),LEN(A1))
If you prefer to not use a formula (which may mess up the look of your worksheet), you could
also use a macro to convert the names, in place. Consider the following:
Sub CapitalizeSurnames()
Dim rCell As Range
Dim iComma As Integer
For Each rCell In Selection
iComma = InStr(rCell, ",")
If iComma > 0 Then
rCell = UCase(Left(rCell, iComma - 1)) & _
Mid(rCell, iComma)
End If
Next
Set rCell = Nothing
End Sub
Simply select the cells that you want to convert (such as those in column A) and then run the
macro. It makes the conversion to the names in the cells.
The following macro will check the cells in a selected range. If the cells contain text, and that
text ends in a minus sign, the macro will move the minus sign to the beginning of the text and
stuff it back into the cell. The result is that the cell is converted from a text value to the proper
numeric value.
Sub ConvToNum()
Dim MyText As Variant
Dim MyRange As Range
Dim CellCount As Integer
There are several ways to approach this problem. The first is to understand the source of the
problem. The text file is probably created on a system that is following a metric standard. Some
countries, following the metric standard, use a space for a thousands separator instead of a
comma. Thus, you could import the file properly into Excel if you change your regional settings
in Windows before starting Excel and doing the import. You can change the regional settings by
using the Control Panel.
If you don't want to change the regional settings on your system, there are other approaches you
can take. After Excel imports the information, you can select the range of cells that contain
numbers and simply do a search and replace. You are searching for a single space and replacing
it with nothing. This does away with the space completely, and Excel will then treat the contents
of the cell as a number.
You can also use a formula, if desired, to modify the imported data. For instance, if the imported
number (containing a space) is in cell A3, you could use this formula to strip out the space:
=1*SUBSTITUTE(A3," ","")
Note that there is a space between the first set of quotes and nothing between the second set of
quotes.
If you have quite a bit of data to convert, or if you have text interspersed with the "numbers-
only" cells, then you may decide to use a macro to do the conversion. The following macro
works on a selection you make before calling it. It also checks to make sure that the cell—after
removing the spaces—contains a numeric value. If it doesn't, then no conversion is done.
Sub ClearSpacesIfNumeric()
Dim c As Range 'Cell under examination
Dim tmpText As String 'Cell contents without spaces
Dim i As Integer 'Simple counter
There are a couple of things you can try. First, you can use the CLEAN worksheet function to get
rid of non-printable characters. Just use the function in this manner:
=CLEAN(A1)
The result is "cleaned" text, without the non-printables. If you want to replace foreign characters
with regular ASCII characters, that will need to be done with a macro. Here's an example of a
relatively straightforward approach:
Sub StripAccent()
Dim sAcc As String
Dim sReg As String
Dim sA As String
Dim sR As String
Dim i As Integer
sAcc = "ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïðñòóôõöùúûüýÿ"
sReg = "SZszYAAAAAACEEEEIIIIDNOOOOOUUUUYaaaaaaceeeeiiiidnooooouuuuyy"
For i = 1 To Len(sAcc)
sA = Mid(sAcc, i, 1)
sR = Mid(sReg, i, 1)
Selection.Replace What:=sA, Replacement:=sR, _
LookAt:=xlPart, MatchCase:=True
Next
End Sub
The macro steps through the characters in the sAcc variable and, one at a time, uses Find and
Replace to replace them with the corresponding character in the sReg variable. You can adjust
the contents of sAcc and sReg to reflect your conversion needs; the key is to make sure that they
are both the same length.
There are a few ways that you can approach this problem, depending on the characteristics of the
data that you are starting with. Assuming that you only have 8-bit characters in your worksheet,
then the only character codes that could be used for characters is 0 through 255. If you want to
limit your data to only 7-bit characters, then that means you only want things in the character-
code range of 0 through 127. Thus, you could use a macro to easily search for any characters in
the range of 128 to 255 and simply delete them. This macro takes this approach:
Sub Remove8Bit1()
Cells.Select
For i = 128 To 255
X = Chr(i)
Selection.Replace What:=X, Replacement:="", _
LookAt:=xlPart, SearchOrder:=xlByRows, _
MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
Next
End Sub
The approach finds only those values in your worksheet that are in the 8-bit range. It won’t touch
anything that is in the 8-bit range that is actually created by a formula. (In most instances that
shouldn’t be a problem. If it is a problem, the proper fix is to modify the formulas creating the
offending results.)
If your data contains Unicode characters, then you’ll want to use a different approach.
Technically, Unicode characters are not 8-bit characters; they are 16-bit characters and can have
character code values in the range of 0 to 65,535. Because you want to ignore anything with a
value over 127, using the search-based approach discussed earlier becomes unwieldy—you
would end up doing over 65,000 searches instead of only 128.
A better approach is to simply look at all the characters in all the selected cells and if they have a
character code over 127, ignore them. That is the approach taken in the following macro:
Sub Remove8Bit2()
Dim rngCell As Range
Dim intChar As Integer
Dim strCheckString As String
Dim strCheckChar As String
Dim intCheckChar As Integer
Dim strClean As String
Note that the macro uses the Ascw function instead of the traditional Asc function so that it looks
at Unicode values.
If you would like a “button” that does this in Excel, you’ll quickly find that there is none built
into the program. You can quickly create one, however, by using a macro:
Sub PlusMinus()
Dim cell As Range
If Application.IsNumber(cell) Then
cell.Value = cell.Value * -1
End If
Next cell
End Sub
Note that the macro simply steps through whatever range of cells you selected when the macro
started. Each cell is checked to see if it contains a number. If it does, then the value of that
number is multiplied by -1. The result is a switch in sign for the number.
You can assign this macro to a shortcut key or to a toolbar button to make it easy to use at any
time.
=SMALL(A1:A100,2)
If you wanted to know the two lowest numbers in the range, then use two formulas containing
the SMALL function—one with 1 as the second parameter (for the lowest number) and one with
2 as the second parameter (for the second lowest number).
There are situations, of course, where the two smallest numbers in the range could actually be the
same number. For instance, if the lowest number is 3 and there is a second 3 in the list, then both
the lowest numbers will be the same. If you want the two lowest unique numbers then you will
need to use a macro to determine them.
End If
End If
Next i
End Function
=SMALLn(A1:A100,2)
When called like this, the function returns the second lowest unique value in the specified range.
There are a couple of ways you can approach this problem. One method you can try is to use the
DMIN function. All that you need is to make sure that you have a header on your data column
(such as “MyData”) and then create a small criteria field in some out-of-the-way place. For
instance, you might want to create the criteria field by placing a header (such as “Min Even”) in
cell F1 and place the formula =ISEVEN(MyData) in cell F2. Cell F2 evaluates to an #VALUE!
error, but that is fine in this case. You can then use the following formula in a different cell:
=DMIN(A1:A100, 1, F1:F2)
If you prefer, you can use an array formula to figure out the lowest even value. Because your
data range can contain text as well as numbers, not all array formulas will work, however. For
instance, the following will generate an error if there is anything but numbers in the data range:
=MIN(IF(MOD(A1:A100,2)=0,A1:A100))
To make sure you don’t get the errors, you need to do some checking in the formula:
=MIN(IF(ISNUMBER(A1:A100),IF(NOT(MOD(A1:A100,2)=0),"",A1:A100)))
Again, remember that this is an array formula, so you need to enter it using SHIFT+CTRL+ENTER.
If you prefer, you can create a user-defined function that will return the desired value:
Application.Volatile
MinEven = 9.99 * 10 ^ 307
bNotFound = True
To use this macro, simply use the following with a cell of your worksheet:
=MinEven(A1:A100)
If there are no even numbers in the range, the function will return a #Num error.
There are a few ways you can go about counting and displaying, but it is important to understand
that these are different tasks. Perhaps the best way to display those values that fit the criteria is to
use conditional formatting. You can add a conditional formatting rule to each cell that will make
bold or otherwise highlight the desired values. Follow these steps:
8. Use the controls in the dialog box to modify the formatting, as desired.
9. Click OK to close the Format Cells dialog box.
10. Click OK to close the New Formatting Rule dialog box. The formatting is applied to the
range of cells you selected in step 1.
If you prefer, you could also use the following formula in step 6:
=AND(ISODD(A1),A1>50)
To get the count of cells that fit the criteria, you could use an array formula:
=SUM(--((MOD(MyCells,2))*(MyCells>50)))
This formula assumes that the range of cells you want to analyze are named MyCells. Don’t
forget to enter the cell using CTRL+SHIFT+ENTER. If you don’t want to use an array formula, you
could use the following:
=SUMPRODUCT(--(MOD(MyCells,2)*(MyCells>50)))
You could also use a macro to derive both the cells and the count. The following is a simple
version of such a macro; it places the values of the cells matching the criteria into column M and
then shows a count of how many cells there were:
Sub SpecialCount()
Dim c As Range
Dim i As Integer
i = 0
For Each c In Range("A2:J101")
If c.Value > 50 And c.Value Mod 2 Then
i = i + 1
Range("L" & i).Value = c.Value
End If
Next c
MsgBox i & " values are odd and greater than 50", vbOKOnly
End Sub
By using the ConvertFormula method available in VBA, you can easily convert a formula from
relative to absolute addressing. The following short macro uses this method to change the
addressing method used in a range of cells:
Sub Relative2Absolute()
For Each c In Selection
If c.HasFormula = True Then
c.Formula = Application.ConvertFormula(c.Formula, _
xlA1, xlA1, xlAbsolute)
End If
Next c
End Sub
The key to how this macro works is, of course, in the ConvertFormula method. The last
parameter used by the method is—in this case—xlAbsolute. If you want to adapt the macro so
that it changes to other types of addressing, you can change xlAbsolute to xlRelative,
xlAbsRowRelColumn, or xlRelRowAbsColumn. (I’m sure you can figure out the purpose of
each constant by its name.)
Sub Reverse()
If Not ActiveCell.HasFormula Then
sRaw = ActiveCell.Text
sNew = ""
For J = 1 To Len(sRaw)
sNew = Mid(sRaw, J, 1) & sNew
Next J
ActiveCell.Value = sNew
End If
End Sub
This macro only affects a single selected cell, and it will not make any changes to a cell that
contains a formula.
No, there isn’t a built-in command to do it. You can, however, create a macro that will do the
switching for you. This macro could then be assigned to a shortcut key or placed on a toolbar so
it can be easily accessed. Here’s a simple macro that will do the switching:
Sub ReverseNames()
Dim x As Integer
Dim sCell As String
Dim sLast As String
Dim sFirst As String
Dim rCell As Range
To use the macro, just select the range of cells you want to affect and then run it. The macro
searches for a space within the cell and considers everything in front of the space to be the first
name and everything after the space to be the last name. These two elements are reversed, a
comma put between them, and stuffed back into the cell.
Many people use the ampersand operator in preference to the CONCATENATE function
because it requires less typing, but CONCATENATE would become immensely more valuable if
it would handle a range of cells. Unfortunately it does not, but you can create your own user-
defined function that will concatenate every cell in a range very nicely. Consider the following
macro:
Application.Volatile
For Each r In myRange
Concat = Concat & r & myDelimiter
Next r
If Len(myDelimiter) > 0 Then
Concat = Left(Concat, Len(Concat) - Len(myDelimiter))
End If
End Function
This function requires a range and provides for an optional delimiter. The last "If" statement
removes the final trailing delimiter from the concatenated string. With the CONCAT1 function,
cells can be added and deleted within the range, without the maintenance required by
CONCATENATE or ampersand formulas. All you need to do is call the function in one of the
following manners:
=CONCAT1(C8:E10)
=CONCAT1(C8:E10,"|")
The second method of calling the function uses the optional delimiter, which is inserted between
each of the concatenated values from the range C8:E10. There is a problem with this, however: If
a cell in that range is empty, then you can end up with two sequential delimiters. If you prefer to
have only a single delimiter, then you need to make one small change to the function:
Application.Volatile
For Each r In myRange
If Len(r.Text) > 0 Then
The easiest way to accomplish this is to use a macro, which can be created as a user-defined
function. Here's an example:
This function basically takes a value that is passed to it (a cell reference) and verifies that the cell
reference is for column C. If it is, then it starts to concatenate values from column B based on the
values in column A. It only returns the string of concatenated values if the value is column A is
different than the value in the row above it. Assuming your identifiers are in column A and your
values to be concatenated are in column B, you could place the following in column C:
=CatSame(C1)
Copy this down as far as necessary in column C and you end up with exactly what Pam wanted.
A more versatile function would be one that would function somewhat like VLOOKUP, but
bring back a concatenated list of values that match whatever you are looking up. Consider the
following function:
Application.Volatile
Set rng = Intersect(rngAll, rngAll.Columns(1))
For Each rCell In rng
If rCell.Value = vValue Then _
VLookupAll = VLookupAll & sSep & _
rCell.Offset(0, iCol).Value
Next rCell
This function takes up to four arguments. The first is the value you want to match in your
lookup. In Pam's instance, this would be the identifier you want, such as A, B, or C. The second
argument is the range of cells in which to look for the matches (column A in this case). The third
argument is an offset (from the range in the second argument) that represents the values you
want concatenated. You can use the function in this manner:
=VLookupAll("B",A1:A99,1)
If you want to specify a different delimiter between values, you can do it using the optional
fourth argument. For instance, the following returns a string where a dash separates each value:
=VLookupAll("B",A1:A99,1,"-")
The solutions so far have focused on using macros. The reason for this is relatively simple: There
isn't a formula-based solution that can do what Pam needs. Using nested IF statements to
evaluate what is in column A won't work well because you are limited in how deeply IF
statements can be nested.
You could use a formula and an intermediate result if you don't mind having the concatenated
values be at the last instance of an identifier in column A. Start by putting this formula in cell
C1:
=B1
Copy this formula down as many rows as necessary. What you end up with is an increasingly
long series of concatenated values in column C, with the longest in each run being on the same
row as the last sequential identifier in column A. You can then put the following in all the
applicable cells of column D:
=IF(LEN(C2)>LEN(C1),"",C1)
This formula only displays the longest strings from column C, which is what Pam needed to
begin with.
There are two primary ways to get the value you want. The first is to use an array formula to
calculate the position. The following array formula (entered by using CTRL+SHIFT+ENTER) will
work in the majority of cases:
=MATCH(TRUE,ISERROR(VALUE(MID(A1,ROW(INDIRECT("1:"&LEN(A1))),1))),0)
The only instances where this array formula won’t work is if cell A1 is either empty or contains a
strictly numeric value. If your list may contain this type of data (or no data at all), then you
should consider using a slightly longer array formula:
=IF(LEN(A1)=0,0,MIN(IF(1*ISNUMBER(1*MID(A1,ROW(INDIRECT("A1:A"&
LEN(A1))),1))=0,ROW(INDIRECT("A1:A"&LEN(A1))),LEN(A1)+1)))*
(ISNUMBER(A1)=FALSE)
Remember that that is a single array formula, entered by using CTRL+SHIFT+ENTER. It will
properly handle instances where A1 contains no non-digit characters (as in a blank cell or a value
such as “123”).
Of course, the other way you can handle finding out the position of the first non-digit character is
to create a user-defined function. There are many different ways that such a macro can be
implemented. One of the easiest ways to implement the macro is to simply step through each
character in whatever is passed to the macro. When a character is found that is outside the ASCII
code range for digits (48 to 57), then you know you’ve found the first position. The following
macro shows a way to do this type of technique:
Application.Volatile
iPos = 0
For J = 1 To Len(str)
iChar = Asc(Mid(str, J, 1))
If iChar <= 47 Or iChar >= 58 Then
iPos = J
Exit For
End If
Next J
FirstNonDigit = iPos
End Function
To use the function, simply use a formula such as this in your worksheet:
=FirstNonDigit(A1)
If the cell you reference is empty or if it only contains digits, then the function returns a 0 value.
The easiest way to figure out if a given cell contains only the allowable characters and digits is to
use a formula that removes the non-digit permissible characters and then sees if the resulting
value is numeric. All of the following formulas can do the trick quite nicely:
You could also use a simple macro to figure out if a cell contains only digits and your allowed
characters. While there are any number of ways that such a macro could be approached, here's a
rather easy method:
Application.Volatile
For X = 1 To Len(sRaw)
If InStr(sAllowed, Mid(sRaw, X, 1)) = 0 Then Exit For
Next X
DigitsOnly = False
If X > Len(sRaw) Then DigitsOnly = True
End Function
The macro examines whatever is passed to it, comparing each character in the string to a list of
allowed characters (in the constant sAllowed). If an disallowed character is detected, the loop is
exited early and a False value is returned. Thus, if you wanted to evaluate the cell at A1, you
could use the following in your macro:
=DigitsOnly(A1)
Since they return either True or False values, any of these approaches (formula or user-defined
function) could be used in conjunction with conditional formatting to make formatting changes
to your part numbers.
Actually, the SEARCH function could be used to find the desired occurrence, in the following
manner:
=SEARCHB("b",G20,(SEARCHB("b",G20,(SEARCHB("b",G20,1)+1))+1))
Notice how the SEARCHB function is used in a nested manner. The formula specifies what is
being searched for (the letter “b”) and the number of nesting levels indicates which occurrence
within the cell you want to find. The formula returns the position of the desired character within
the cell.
The problem with such a formula, of course, is that it is difficult to maintain and can quickly get
unusable if you want to find, say, the seventh occurrence.
=FIND(CHAR(1),SUBSTITUTE(A1,"B",CHAR(1),3))
This formula examines the value in A1. It substitutes the CHAR(1) code for the third occurrence
of “B” within the cell. The FIND function then looks within the resulting string for the position
where CHAR(1) occurs. If the desired occurrence does not exist, then the formula returns a
#VALUE error.
If you prefer, you could create a user-defined function that will look for the Nth position of a
character. The following is a very simple macro that takes three arguments: the string to be
searched, the text to match, and the position desired.
Application.Volatile
FindN = 0
For J = 1 To N
FindN = InStr(FindN + 1, sInputString, sFindWhat)
If FindN = 0 Then Exit For
Next
End Function
The function is case sensitive in what it searches for, and it returns the position within the
specified string at which the sFindWhat value occurs. If there is no occurrence at the specified
instance, then the function returns a 0. The following shows how the function can be used in a
worksheet:
=FindN("b",C15,3)
There are several ways that you can approach this problem, and the correct solution for your
needs will depend on the characteristics of the data with which you are working. If you know
that the only place in your data that you will have a dash is within your pattern, then you can key
off of the presence of the dash by using a formula such as the following:
=MID(A1,FIND("-",A1)-2,8)
This finds the dash and then grabs the eight characters beginning two characters to the left of the
dash. This obviously will not work if there are dashes in other places in the text or if it is possible
to have "patterns" that include non-digits (such as 12-34B32) and you want those excluded. In
that case you'll need a much more complex formula:
This includes an error checking component that finds out if the characters just before the dash
and just after the dash contain anything other than digits. If they do, then nothing is returned.
The one thing that these formulaic approaches don't do is handle those situations where there
may be more than one occurrence of the pattern within the same cell. In that case, a macro is the
best approach. The following will extract the valid patterns and place them in a new worksheet
called "Results".
Sub ExtractPattern()
On Error Resume Next
Set SourceSheet = ActiveSheet
Set TargetSheet = ActiveWorkbook.Sheets("Results")
If Err = 0 Then
Worksheets("Results").Delete
End If
Worksheets.Add
ActiveSheet.Name = "Results"
Set TargetSheet = ActiveSheet
Cells(1, 1).Value = "Found Codes"
Cells(1, 1).Font.Bold = True
iTargetRow = 2
SourceSheet.Select
Selection.SpecialCells(xlCellTypeLastCell).Select
Range(Selection, Cells(1)).Select
Note that the macro uses the Like function in two places. The first instance determines if the
pattern occurs anywhere in the cell, and the second instance is used to determine if the extracted
characters exactly match the desired pattern.
Unfortunately, Excel doesn’t include such a shortcut. You can, however, create one using a
macro. The following is a simple macro to merge whatever cells you’ve selected:
Sub MergeCells1()
Selection.Merge
End Sub
After you create the macro, you can assign it to a keyboard shortcut and you are set to go. If you
instead want a macro that is a shortcut for the Merge and Center tool, then you can use the
following:
Sub MergeCells2()
With Selection
.HorizontalAlignment = xlCenter
.Merge
End With
End Sub
One method for identifying the cells is to use Excel's searching capabilities. Follow these steps:
1. Press CTRL+F. Excel displays the Find tab of the Find and Replace dialog box.
2. If necessary, click the Options button to make sure the Find and Replace dialog box is
expanded to show all options.
6. Make sure the Merge Cells check box is selected (there should be a check in the check
box).
7. Click OK to close the Find Format dialog box.
8. Click Find All.
Excel searches for any merged cells and if they are located, the cells are displayed in the bottom
of the Find and Replace dialog box. You can then select one of the found ranges and the
corresponding range is selected in the worksheet.
If you prefer, you can a macro to find the various merged cells in the worksheet. The following
macro shows perhaps the simplest method of doing this:
Sub FindMerged1()
Dim c As Range
For Each c In ActiveSheet.UsedRange
If c.MergeCells Then
MsgBox c.Address & " is merged"
End If
Next
End Sub
This particular macro steps through all the cells in the worksheet (well, at least those that are in
the UsedRange) and, if the cell is part of a merged cell, a message box is displayed. Note that the
pertinent property being checked is the MergeCells property. This is set to True if the cell is
merged with another cell.
Of course, a macro such as this can take quite a long time to run if the worksheet has lots of cells
and even longer if a good number of those cells are merged. Your macro would run faster if it
didn't stop at each merged cell and display a dialog box. The following version takes a different
approach, filling each merged cell with a yellow color:
Sub FindMerged2()
Dim c As Range
For Each c In ActiveSheet.UsedRange
If c.MergeCells Then
c.Interior.ColorIndex = 36
End If
Next
End Sub
A variation on this approach could be to create a user-defined function that simply returns True
or False if the cell is merged:
With this simple function you could then use conditional formatting to somehow highlight cells
if they are merged. (If the function returns True, then conditional formatting applies whatever
formatting you specify to the cell.)
Finally, if you want a list of cells that are merged in the worksheet, you can simply have your
macro put together the list instead of coloring the cells:
Sub FindMerged4()
Dim c As Range
Dim sMsg As String
sMsg = ""
For Each c In ActiveSheet.UsedRange
If c.MergeCells Then
If sMsg = "" Then
sMsg = "Merged worksheet cells:" & vbCr
End If
sMsg = sMsg & c.Address & vbCr
End If
Next
If sMsg = "" Then
sMsg = "No merged worksheet cells."
End If
MsgBox sMsg
End Sub
This variation displays a single message box at the end of the macro, indicating the addresses of
any merged cells located in the worksheet.
There are a couple of approaches you can use. The first is to use the Find and Replace
capabilities of Excel. Follow these steps:
1. Press CTRL+H to display the Replace tab of the Find and Replace dialog box.
2. In the Find What box, enter two spaces.
3. Make sure the Replace With box is empty.
4. Select the Match Entire Cell Contents check box.
5. Click on Replace All.
6. Repeat steps 2 through 5, but this time use only one space in step 2.
7. Close the Find and Replace dialog box.
Another option is to use the Trim worksheet function. This approach is handy if the cells you
want to modify are all in a particular area of the worksheet, such as a single column. For
instance, if you want to get rid of the spaces from the cells in column D, you could use the
following formula:
=Trim(D1)
The Trim function returns the contents of cell D1 without any leading or trailing spaces. You
could then copy the results of this formula and use Paste Special to paste the values back into
whatever cells you desire.
Of course, if you have lots of worksheets you need to process, or if you routinely get workbooks
that contain the extra spaces in cells, a better way would be to create a macro that could get rid of
the spaces. Perhaps the fastest way would be to examine all the cells in the worksheet and get rid
of any extra spaces:
Sub CleanSheet1()
For Each cell In ActiveSheet.UsedRange
cell.Value = Trim(cell)
Next cell
End Sub
The macro steps through each cell and uses the Trim function to get rid of any leading or trailing
spaces. This works on all the cells, but it may produce undesired results, depending on the
characteristics of your data. If you have cells that have leading spaces—and you want those
spaces—then you’ll need to use a different macro. This version will give more satisfactory
results:
Sub CleanSheet2()
Dim rCell As Range
Dim rText As Range
It only checks those cells containing constants (which includes all text in the worksheet) and then
checks to see if using the Trim function would result in an empty cell. If so, then the cell is
cleared. If the Trim function wouldn’t result in an empty cell, then no change is made to the cell.
Sub DelDups()
Dim rngSrc As Range
Dim NumRows As Integer
Dim ThisRow As Integer
Dim ThatRow As Integer
Dim ThisCol As Integer
Dim J As Integer, K As Integer
Application.ScreenUpdating = False
Set rngSrc = ActiveSheet.Range(ActiveWindow.Selection.Address)
NumRows = rngSrc.Rows.Count
ThisRow = rngSrc.Row
ThatRow = ThisRow + NumRows - 1
ThisCol = rngSrc.Column
The macro works on a selection you make before calling it. Thus, if you need to remove
duplicate cells from the range C15:C59, simply select that range and then run the macro. If you
select more than a single column in the range (for instance, C15:E59), then only the first column
in the range is affected. When the macro is complete, the duplicate cells are removed, as are any
blank cells.
Manually, you can use data filtering to determine the unique values. Make sure the column has a
label at the top of it, then select a cell in the column. Choose Data | Filter | Advanced Filter or, in
Excel 2007 (and later), display the Data tab of the ribbon and click Advanced in the Sort & Filter
group. Use the controls in the resulting dialog box to specify that you want to copy the unique
values to another location which you specify.
You can also use a formula to manually determine the duplicates in the list. Sort the values in the
column, and then enter the following formula in cell B2:
=IF(A2=A1,"Duplicate","")
Copy the formula down to all the cells in column B that have a corresponding value in column
A. Select all the values in column B and press CTRL+C. Use the Paste Special dialog box to paste
just the values into the same selected cells. You’ve now converted the formulas into their results.
Sort the two columns according to the contents of column B, and all of your duplicate rows will
be in one area. Delete these rows, and you have your finished list of unique values.
Either of these manual approaches are fast and easy, but if you routinely have to delete duplicate
values from a column, a macro may be more your style. The following macro relies on the
advanced data filtering, much like the earlier manual method:
Sub CreateUniqueList()
Dim rData As Range
Dim rTemp As Range
rTemp.EntireColumn.Copy _
rData.EntireColumn
Application.CutCopyMode = False
rTemp.EntireColumn.Delete
Set rData = Nothing
Set rTemp = Nothing
End Sub
The macro creates a temporary column, uses advanced filtering to copy the unique values to that
column, then deletes the original data column. The result is just unique values in column A. If
you don’t want your macro to use the data filtering feature of Excel, then the following macro
will do the trick:
Sub DelDups()
Dim rngSrc As Range
Dim NumRows As Integer
Dim ThisRow As Integer
Dim ThatRow As Integer
Dim ThisCol As Integer
Dim J As Integer, K As Integer
Application.ScreenUpdating = False
Set rngSrc = ActiveSheet.Range(ActiveWindow.Selection.Address)
NumRows = rngSrc.Rows.Count
ThisRow = rngSrc.Row
ThatRow = ThisRow + NumRows - 1
ThisCol = rngSrc.Column
The macro works on a selection you make before calling it. Thus, if you need to remove
duplicate cells from the range A2:A974, simply select that range and then run the macro. When
the macro is complete, the duplicate cells are removed, as are any blank cells.
The simplest solution is to further split the addresses into separate columns, such that the suite
number is in its own column. You can do that by following these steps:
1. Make sure there is a blank column to the right of the address column.
2. Select the cells that contain addresses.
3. Start the Text to Columns wizard. (In Excel 2007 or later display the Data tab of the
ribbon and click the Text to Columns tool in the Data Tools group. In older versions of
Excel choose Text to Columns from the Data menu.)
4. In the first step of the Wizard, make sure the Delimited option is selected, then click
Next.
5. In the second step of the Wizard, make sure the Comma check box is selected, then
click Next.
6. In the third step of the Wizard click Finish.
The street address should now reside in the original column and the previously blank column
should now contain everything that was after the comma in the original addresses. In other
words, the suite number is in its own column. With your data in this condition it is an easy step
to use filtering to display or extract the unique street addresses.
If you don’t want to permanently split up the addresses into two columns, you could use a
formula to determine duplicates. Assuming that the address list is sorted, you could use a
formula similar to the following:
=IF(OR(ISERROR(FIND(",",A3)),ISERROR(FIND(",",A2))),
"",IF(LEFT(A3,FIND(",",A3))=LEFT(A2,FIND(",",A2)),
"Duplicate",""))
This formula assumes that the addresses to be checked are in column A and that this formula is
placed somewhere in row 3 of a different column. It first checks if there is a comma in either the
address in the current row or the address in the row before. If there is no comma in either of the
addresses, then it assumes there is no possible duplicate. It there is a comma in both of them, the
formula checks the portion of the addresses before the comma. If they match, then the word
“Duplicate” is returned; if they don’t match, then nothing is returned.
The result of copying the formula down the column (so that one formula corresponds to each
address) is that you will have the word “Duplicate” appear next to those addresses which match
the first part of the previous address. You can then figure out what you want to do with those
duplicates that are found.
Another option is to use a macro to determine your possible duplicates. There are any number of
ways that a macro to determine duplicates could be devised; the one shown here simply checks
the first X characters of a “key” value against a range and returns the address of the first
matching cell.
For instance, let’s assume that your addresses are in the range A2:A100. In column B you can
use this NearMatch function to return addresses of possible duplicates. In cell B2 enter the
following formula:
=NearMatch(A2,A3:A$100,12)
The first parameter for the function (A2) is the cell you want to use as your “key.” The first 12
characters of this cell are compared against the first 12 characters of each cell in the range
A3:A$100. If a cell is found in that range in which the first 12 characters match, then the address
of that cell is returned by the function. If no match is located, then the #N/A error is returned. If
you copy the formula in B2 down, to cells B3:B100, each corresponding address in column A is
compared to all the addresses below it. You end up with a list of possible duplicates in the
original list.
=XX=. There may be multiple instances of these characters in each cell, but Steven only wants to
delete everything before the first occurrence.
One way to do this is to use a formula. For instance, the following formula will evaluate
whatever is in cell A1 and simply return everything up to the =XX= characters. If the characters
are not found in the cell, then the entire cell is returned:
=RIGHT(A1,IF(ISERROR(FIND("=XX=",A1,1)),
LEN(A1),LEN(A1)-FIND("=XX=",A1,1)+1))
If you want, instead, to not return the first occurrence of =XX=, all you need to do is change the
+1 near the end of the formula to -3.
If you prefer a macro-based solution you could use a routine like the following. It examines all
the cells that are currently selected and then deletes everything before the =XX= sequence.
Sub DeleteToSequence()
Dim rCell As Range
Dim sSeq As String
Dim x As Long
sSeq = "=XX="
For Each rCell In Selection
x = InStr(rCell.Value, sSeq)
If x > 0 Then
rCell.Value = Mid(rCell, x)
End If
Next
You should be aware that this macro can cause some errors, particularly when what you are
searching for begins with an equal sign (as in =XX=). When a string beginning with an equal
sign is stuffed back into the cell, you’ll get a #NAME? error because Excel tries to parse the cell
as if it contains a formula.
If you want to delete everything up through the character sequence, use this line in the middle of
the routine:
You can extract both words using formulas. Extracting the first word is relatively
straightforward. All you need to do is find the location of the first space in the phrase, then
extract whatever is to the left of it. If one presumes that the phrase is in A1, one can use the
formula:
=LEFT(A1,FIND(" ",A1)-1)
In principle, to get the last word can be accomplished the same, it is just more complicated to
find the last space in the string. A way to do this is to:
The “different character” one can use is the first ASCII character (i.e., char(1)), which is non-
printing and very unlikely to be in the phrase. The number of spaces can be found by taking the
difference between the length of the phrase with the length of the phrase with no spaces (by
using SUBSTITUTE to replace all spaces with the null string):
LEN(A1)-LEN(SUBSTITUTE(A1," ",""))
Then you can substitute char(1) for the last occurrence of the space:
FIND(CHAR(1),SUBSTITUTE(A1," ",CHAR(1),LEN(A1)-
LEN(SUBSTITUTE(A1," ",""))))
1+ FIND(CHAR(1),SUBSTITUTE(A1," ",CHAR(1),LEN(A1)-
LEN(SUBSTITUTE(A1," ",""))))
You can then use the MID function to extract the part of the string starting at this location until
the end of the string. (You don’t have to calculate the exact length. If you pick a number larger
than the length of the last word, only the last word will be chosen. Thus, you can start at the
location above and extract the number of characters in the string to ensure you have enough.):
=MID(A1,1+FIND(CHAR(1),SUBSTITUTE(A1," ",CHAR(1),
LEN(A1)-LEN(SUBSTITUTE(A1," ","")))),LEN(A1))
You can also, if you prefer, create user-defined functions to grab the words you want. Grabbing
the first word is easy:
The function uses the Split function to pull apart whatever is in the specified cell, using the
second parameter (" ") as the delimiter. Each element in the array (arr) then contains a portion of
the original string. In this case what is being returned is the first element (specified by LBound)
of the array—the first word.
Since the words from the phrase are being placed in an array, you can use just a slight variation
on the function to return the last word:
Note that, essentially, the only real change in the function is the use of UBound instead of
LBound. The UBound function specifies the last element of the array. You can use both of these
functions in a worksheet in this manner:
=FirstWord(A1)
=LastWord(A1)
There is no way to change this behavior within Excel itself. Instead, you need to turn to other
solutions. One is to use a macro, such as the following:
Sub UnSelectSomeCells()
Dim rSelect As Range
Dim rUnSelect As Range
Dim rNew As Range
Dim rCell As Range
To use the macro, select the entire range you want to start with, such as A7:R182. Then run the
macro. You are asked to choose the cells to be unselected. You can do so by simply selecting the
cells with the mouse, holding down the SHIFT key as you click on each one. When you dismiss
the input box, the selection you started with is modified to exclude the cells you selected.
If you prefer to not use your own macros, you can find help for deselecting cells in a selected
range by using third-party tools, such as the ASAP Utilities. You can find their Excel tools at this
Web page:
http://www.asap-utilities.com/asap-utilities-excel-tools.php
This can be rather easily done with a macro. All you need to do have the macro step through the
data and compare the date in each row to today’s date. If the date is less than today, then the
Delete method is used on the EntireRow object.
Sub DeleteRows1()
Dim x As Long
Dim iCol As Integer
In this example, the macro checks column G (in the iCol variable) for the date. If your date is in
a different column, then you should make the change to the variable. Depending on the number
of rows of data in your worksheet, the macro may also take quite a while to run.
If you notice a lag in performance, then you may want to use a different approach. The following
example uses the AutoFilter capabilities of Excel to first filter the data to show only the old data,
and then deletes those rows.
Sub DeleteRows2()
Dim Dates As Range
Dim nRows As Double
Dim currDate As Variant
This macro presumes that you have taken the step of assigning a name to your data range. Select
all the cells in your data table—including any heading row—and give it the name “Dates.” When
you run the macro, it uses this range as the target for the AutoFilter.
Sub StyleKill()
Dim styT As Style
Dim intRet As Integer
The macro needs just a little user input. Whenever the macro detects a user-defined style, you are
asked if you want to delete it. Clicking on the Yes button causes the style to be removed from the
workbook.
This is rather easy to do with the built-in Subtotals feature of Excel. All you need to do is follow
these steps:
1. Make sure your table contains column labels. For instance, if column A contains the
department names, then cell A1 could contain a label such as “Department.” Make sure
all the columns have labels.
2. Sort the data in your table, using the department column as the key.
3. With any cell within the table still selected, choose Subtotals from the Data menu or, in
Excel 2007 and later versions, display the Data tab of the ribbon and click Subtotal in
the Outline group. Excel displays the Subtotal dialog box.
If, for some reason, you don’t want to use the Subtotals feature, you can always write a macro
that will remove all the page breaks in your worksheet, then add new page breaks at the
appropriate places. The following macro will do the trick:
Sub PageBreak()
Dim CellRange As Range
Dim TestCell As Range
To use the macro, simply select the cells you want to use as your key for doing the splits, minus
the top cell. For instance, if the departments are in column A, rows 2 through 37, you would
select the range in A3 through A37. Run the macro, and any old page breaks are removed and
new ones added.
Moving Subtotals
David was adding subtotals to large worksheets, and looking for a way to move the subtotal cells
to different cells. For instance, assume that when Excel added the automatic subtotals, they were
added in column S, and the SUBTOTAL formula added by Excel referred to ranges of cells in
column S. David wanted to move the SUBTOTAL formulas (and only those formulas) out of
column S to column T, and have the formulas still refer to detail in column S.
One option is to go through and move the SUBTOTAL formulas, one at a time, to the desired
locations. (You would use CTRL+X and CTRL+V to move the cells, rather than CTRL+C and
CTRL+V to merely create copies of the cells.) If the worksheets are large, with many subtotals,
this can become very tedious very quickly.
Tedium in Excel is often the primary impetus for creating a macro. This case is no exception. It
is possible to create a macro that will do the actual move of the SUBTOTAL formulas. Consider
the following example:
Sub MoveSubtotals()
Dim rCell As Range
This example works by examining each cell selected in column S. If the formula in the cell
contains the word SUBTOTAL, then the formula is copied one column to the right, in column T,
and deleted from the cell in column S. You can change the distance left or right that the subtotals
are moved by simply changing the value assigned to the iOffset variable.
If you use subtotals sparingly, and only want to apply a different format for one or two
worksheets, you can follow these general steps:
If you will be repeatedly adding and removing subtotals to the same data table, you may be
interested in using conditional formatting to apply the desired subtotal formatting. Follow these
steps if you are using a version of Excel prior to Excel 2007:
12. Using the controls in the dialog box, set the formatting as you want it applied to the
Total row.
13. Click OK to dismiss the Format Cells dialog box.
14. Click OK to dismiss the Conditional Formatting dialog box.
When following the above steps, make sure that you replace A1 (steps 4 and 10) with the column
in which your subtotals are added. Thus, if your subtotals are in column G, you would use G1
instead of A1.
If you are using Excel 2007 or a later version, then the steps required to create the conditional
formats are different:
1. Make sure the Home tab of the ribbon is displayed, then click Conditional Formatting in
the Styles group. Excel displays a drop-down list of options.
2. Click the Manage Rules option. Excel displays the Conditional Formatting Rules
Manager dialog box.
3. Click the New Rule option. Excel displays the New Formatting Rule dialog box.
4. At the top of the dialog box, choose Use a Formula to Determine Which Cells to
Format. The bottom portion of the dialog box should change.
5. In the formula area of the dialog box, enter the following formula:
=ISNUMBER(FIND("Grand Total",$A1))
6. Click the Format button. Excel displays the Format Cells dialog box.
7. Change the formatting for the cells in any way desired.
8. Click OK to dismiss the Format Cells dialog box.
9. Click OK to dismiss the New Formatting Rule dialog box. The Conditional Formatting
Rules Manager dialog box should again be visible.
10. Click New Rule. Excel again displays the New Formatting Rule dialog box.
11. At the top of the dialog box, choose Use a Formula to Determine Which Cells to
Format. The bottom portion of the dialog box should change.
12. In the formula area of the dialog box, enter the following formula:
=ISNUMBER(FIND("Total",$A1))
13. Click the Format button. Excel displays the Format Cells dialog box.
14. Change the formatting for the cells in any way desired.
15. Click OK to dismiss the Format Cells dialog box.
16. Click OK to dismiss the New Formatting Rule dialog box.
17. Click OK to dismiss the Conditional Formatting Rules Manager dialog box.
When following the above steps, make sure that you replace A1 (steps 5 and 12) with the column
in which your subtotals are added. Thus, if your subtotals are in column G, you would use G1
instead of A1.
If you need to do formatting of subtotals on quite a few worksheets, then you may want to create
a macro that will do the formatting for you. The following macro examines all the cells in a
selected range, and then applies cell coloring, as appropriate.
Sub FormatTotalRows()
Dim rCell as Range
The macro colors the subtotal rows yellow and the grand total row orange. The macro, although
simple in nature, is not as efficient as it could be since every cell in the selected range is
inspected. Nevertheless, on a 10 column 5000 row worksheet this macro runs in under 5 seconds.
What if you want to distribute, as evenly as possible, a combination of the alphabet letters into
four groups based on the third column (hours)? For example, if the sum of all the hours for each
letter of the alphabet is 4,000 hours, you want to come up with a combination that would
segregate the alphabet so that each one of the four groups would have around 1,000 hours per
group.
In this case, however, a simple approach is best, and that involves using a macro. Let’s assume
that you have your data in columns A through C. The following macro will analyze the range
you specify and return a combination of values that fulfill your requirements.
Application.Volatile
ReDim lCells(sRaw.Rows.Count)
ReDim sRet(sRaw.Rows.Count)
ReDim lBk(iBuckets)
ReDim sBk(iBuckets)
lGTotal = 0
For J = 1 To sRaw.Rows.Count
lCells(J) = sRaw(J, iTCol)
lGTotal = lGTotal + lCells(J)
sRet(J) = sRaw(J, iRetCol)
Next J
For J = 1 To sRaw.Rows.Count - 1
For K = J + 1 To sRaw.Rows.Count
If lCells(J) < lCells(K) Then
lTemp = lCells(J)
lCells(J) = lCells(K)
lCells(K) = lTemp
sTemp = sRet(J)
sRet(J) = sRet(K)
sRet(K) = sTemp
End If
Next K
Next J
For J = 1 To iBuckets
If Right(sBk(J), 2) = ", " Then
sBk(J) = Left(sBk(J), Len(sBk(J)) - 2)
End If
sBk(J) = sBk(J) & " (" & lBk(J) & ")"
Next J
DoDist = sBk(iWanted)
End Function
Notice that this function is passed five parameters. The first is the range that you want evaluated,
the second is the offset of the column within that range that should be totaled, the third is the
number of “buckets” you want to use in the evaluation, the fourth is the number of the bucket
that you want to return, and the fifth is the offset of the column (in the specified range) that
contains the values you want returned.
What the macro does is to grab all the values in the column you want totaled, and then sort them
in descending order. These values, from largest to smallest, are then distributed among however
many “buckets” you specified that there should be. The number is always added to the bucket
that contains the smallest total. The string that is returned by the function represents the return
values (whatever is in each cell of the column specified by the fifth parameter) and the total of
the bucket.
For instance, if you wanted to evaluate the range A1:C:26, you wanted the distribution to be
based on the values in the third column of the range (column C), you wanted there to be four
buckets in the analysis, you wanted the third bucket returned, and you wanted to have the
function return whatever is in column A of the range, then you would use the following to call
the function:
=DoDist(A1:C26,3,4,3,1)
There is no way to do this with any of the built-in Excel functions or wizards. This is probably a
reflection of the fact that most people leave the data on a single worksheet, and then use various
Excel tools (such as filtering) to display only a subset of the overall data.
If you want to copy state-specific information to separate sheets, however, you can do so
manually by using AutoFilter. This works particularly well if you have only a few states in your
sources data. Just apply an AutoFilter and display only those rows that are in the state you want
to copy. Select the visible rows, copy them, and paste them to a new worksheet. Repeat the
process with each of the other states in your original data set.
If you have data for quite a few sheets, you can copy it by automating this process. The
following macro uses the AutoFilter capabilities of Excel to copy the information to a new
worksheet. It does this for each unique value in the state column, which is specified by the iCol
variable. (In this example, iCol is set to 5, which means that the states are in column E.)
Sub NewSheetEachAutofilter()
Dim wOri As Worksheet
Dim wks As Worksheet
Dim wPT As Worksheet
Dim bAutoFilter As Boolean
Dim PT As PivotTable
ExitHandler:
Application.DisplayAlerts = True
Application.ScreenUpdating = True
'remove filter if no previous one
If Not bAutoFilter Then
wOri.AutoFilterMode = False
End If
Set rCell = Nothing
Set rPT = Nothing
Set PT = Nothing
Set wOri = Nothing
Errhandler:
MsgBox Err.Number & ":" & Err.Description
Resume ExitHandler
End Sub
The code may look complex because of its length, but it isn’t particularly difficult. It makes sure
that the AutoFilter is turned on, and then it creates a PivotTable based on your original data. This
PivotTable is used to gather the list of states from the data. Each state is then used on the original
data as a filtering criteria. The filtered information is then copied to a new worksheet that is
named using the state.
The macro does not modify the original data. If you prefer to have the original data deleted after
it is moved to a worksheet, then all you need to do is add a single line of code. Add this line right
after the line that deletes the PivotTable (wPT.Delete):
wOri.Delete
There are a couple of different ways that a solution to this problem can be approached. One
solution is to use Access as your first importing step. Access will easily handle the thousands of
records you want to import—even if there are more records than what you can import into Excel.
You could import the file into Access, filter out the unwanted records, and then export the
resulting table as an Excel workbook.
The best solution, however, may be to bypass Excel’s import filters entirely. You can easily
write an import routine in VBA, and allow it to process the import file. For instance, consider the
following macro:
Sub Import()
Dim sFile As String
Dim sUnwanted As String
Dim sDelim As String
Dim iRow As Integer
Dim iCol As Integer
Dim bBadRecord As Boolean
Dim iTemp As Integer
sFile = "d:\data.txt"
iRow = 1
While Not EOF(1) 'Scan file line by line
iCol = 1
Line Input #1, sBuffer
This macro opens a data file and reads each record in the file. It checks the record to make sure it
is OK to import, and then pulls the record apart, based on a delimiter, and stuffs the information
into the current worksheet. You can change the name of the data file (the sFile variable), the text
that indicates a bad record (sUnwanted variable) and the delimiter (sDelim variable).
As an example, let’s assume that you have a data file named Customers.txt. This file contains all
your customer records, but you don’t want to import the records for customers with addresses
inside the United States. Further, the records in the data file use a tab character between each
field. In this case, you would only need to make the following changes to the variables at the
beginning of the macro:
sFile = "d:\Customers.txt"
sUnwanted = "United States"
sDelim = Chr(9)
Once you run the macro, the current worksheet contains just the desired data.
One possibility is to make copies of the raw text file (the one you want to import) and then cut
the size of each file down. For instance, if you have a total of 110,000 rows you need to import
into Excel, and you are operating under the 65,535-row limit, you could make two copies of the
raw text file. Delete the second half of the first text file and the first half of the second. Thus, you
can import the first file (now 55,000 rows) into one worksheet and the second file (also 55,000
rows) into the second.
If you don’t want to break up your input files, you might consider importing the file into Access.
Unlike Excel, Access has virtually no limit on the number of rows you can import. You could
then either work with the file in Access, or export portions of the file to use in Excel.
Finally, you could use a macro to import the records in the large source file. There are many
ways you can do this, but the basic idea behind any approach is to fetch each row from the
source file and place it in a new row of a worksheet. The macro must keep track of how many
rows it’s placed, and switch to a new worksheet, if necessary.
sDelim = Chr(9)
MaxSize = 65000
I = 0
Open "C:\MyDir\MyFile.txt" For Input As #5
Do While Not EOF(5)
iSh = (I / MaxSize) + 1
lL = I Mod MaxSize
Line Input #5, strLine
If Right(strLine, 1) <> sDelim Then
strLine = Trim(strLine) & sDelim
End If
J = 0
Do While Len(strLine) > 1
iLen = InStr(strLine, sDelim)
Worksheets("Sheet" & iSh).Offset(lL, J).Value = _
Trim(Left(strLine, iLen - 1))
strLine = Trim(Right(strLine, Len(strLine) - iLen))
J = J + 1
Loop
I = I + 1
Loop
Close #5
End Sub
The macro assumes you have enough worksheets already in your workbook to contain the data,
and that they are numbered Sheet1, Sheet2, Sheet3, etc. Two variables you’ll want to check in
the program are the settings of sDelim and MaxSize. The first specifies what character is used as
a field delimiter in the information that is being read. The second specifies the maximum number
of rows you want on each worksheet. (Don’t set MaxSize greater than whatever your version of
Excel will allow.)
Finally, note that the macro opens the text file MyFile.txt. You’ll want to change this Open
statement so that it opens the real source file you want to import.
The easiest way to do merges of this magnitude—particularly if you have to do it often—is with
a macro. The following macro displays a dialog box asking you to select the files to merge. (You
can select multiple workbooks by holding down the CTRL key as you click each one.) It loops
thru the list you select, opening each one and moving all its worksheets to the end of the
workbook with the code.
Sub CombineWorkbooks()
Dim FilesToOpen
Dim x As Integer
FilesToOpen = Application.GetOpenFilename _
(FileFilter:="Microsoft Excel Files (*.xls?), *.xls?", _
MultiSelect:=True, Title:="Files to Merge")
x = 1
While x <= UBound(FilesToOpen)
Workbooks.Open FileName:=FilesToOpen(x)
Sheets().Move After:=ThisWorkbook.Sheets _
(ThisWorkbook.Sheets.Count)
x = x + 1
Wend
ExitHandler:
Application.ScreenUpdating = True
Exit Sub
ErrHandler:
MsgBox Err.Description
Resume ExitHandler
End Sub
In the process of adding the worksheets to the end of the workbook, Excel will automatically
append a (2), (3), etc. when duplicates worksheet names are detected. Any formulas in the book
referring to other sheets will also be updated to reflect the new names.
One way to do this is to manually add the desired worksheets, and then individually import each
of the text files. This, as you can imagine, would quickly get tedious. A much better solution is to
use a macro to do the importing, such as the following one.
Sub CombineTextFiles()
Dim FilesToOpen
Dim x As Integer
Dim wkbAll As Workbook
Dim wkbTemp As Workbook
Dim sDelimiter As String
sDelimiter = "|"
FilesToOpen = Application.GetOpenFilename _
(FileFilter:="Text Files (*.txt), *.txt", _
MultiSelect:=True, Title:="Text Files to Open")
x = 1
Set wkbTemp = Workbooks.Open(FileName:=FilesToOpen(x))
wkbTemp.Sheets(1).Copy
Set wkbAll = ActiveWorkbook
wkbTemp.Close (False)
wkbAll.Worksheets(x).Columns("A:A").TextToColumns _
Destination:=Range("A1"), DataType:=xlDelimited, _
TextQualifier:=xlDoubleQuote, _
ConsecutiveDelimiter:=False, _
Tab:=False, Semicolon:=False, _
Comma:=False, Space:=False, _
Other:=True, OtherChar:="|"
x = x + 1
ExitHandler:
Application.ScreenUpdating = True
Set wkbAll = Nothing
Set wkbTemp = Nothing
Exit Sub
ErrHandler:
MsgBox Err.Description
Resume ExitHandler
End Sub
This macro allows you to select which files you want to import, and then it places the data from
those files onto the separate worksheets in the workbook. The macro assumes that the data being
imported uses the pipe character (|) as a delimiter between fields.
If you know that the files to be imported are always in the specific folder, and that you want to
import all the files in that folder, then you can simplify the macro a bit. The following example
assumes that the files are in the folder c:\temp\load_excel, but you could change that folder name
by making a simple change to fpath variable in the macro code.
Sub LoadPipeDelimitedFiles()
Dim idx As Integer
Dim fpath As String
Dim fname As String
idx = 0
fpath = "c:\temp\load_excel\"
fname = Dir(fpath & "*.txt")
While (Len(fname) > 0)
idx = idx + 1
Sheets("Sheet" & idx).Select
With ActiveSheet.QueryTables.Add(Connection:="TEXT;" _
& fpath & fname, Destination:=Range("A1"))
.Name = "a" & idx
.FieldNames = True
.RowNumbers = False
.FillAdjacentFormulas = False
.PreserveFormatting = True
.RefreshOnFileOpen = False
.RefreshStyle = xlInsertDeleteCells
.SavePassword = False
.SaveData = True
.AdjustColumnWidth = True
.RefreshPeriod = 0
.TextFilePromptOnRefresh = False
.TextFilePlatform = 437
.TextFileStartRow = 1
.TextFileParseType = xlDelimited
.TextFileTextQualifier = xlTextQualifierDoubleQuote
.TextFileConsecutiveDelimiter = False
.TextFileTabDelimiter = False
.TextFileSemicolonDelimiter = False
.TextFileCommaDelimiter = False
.TextFileSpaceDelimiter = False
.TextFileOtherDelimiter = "|"
.TextFileColumnDataTypes = Array(1, 1, 1)
.TextFileTrailingMinusNumbers = True
.Refresh BackgroundQuery:=False
fname = Dir
End With
Wend
End Sub
Notice that Excel adds extra quotation marks, first around the entire cell contents, and then an
extra set around any previously "quoted" text within the cell.
One solution for handling the problem is to simply load the text file created by Excel into
another program, such as Word, and use the Find and Replace feature to remove the undesired
quotes. A better solution, however, is to create your own macro that creates the output text file.
Consider the following macro:
Sub Export()
Dim r As Range, c As Range
Dim sTemp As String
All you need to do is select the cells you want to export, and then run the macro. The cells in the
selection are extracted from the worksheet and placed in the file c:MyOutput.txt. (This filename
can be changed in the macro to whatever your needs dictate.)
The only way to approach this problem is through the use of macros. If you want to simply strip
out the characters, in place, then you can do so by selecting the cells you want to affect and then
running a macro that examines each cell and deletes the offending characters. There are many
ways you could do this; the following macro is a straightforward approach.
Sub CleanText1()
Dim rngCell As Range
Dim intChar As Integer
Dim strCheckString As String
Dim strCheckChar As String
Dim intCheckChar As Integer
Dim strClean As String
'Do nothing
Case 153 To 154 'special language chars
'Do nothing
Case 159 To 165 'special language chars
'Do nothing
Case Else
strClean = strClean & strCheckChar
End Select
Next intChar
rngCell.Value = strClean
Next rngCell
End Sub
The nice thing about this approach to stripping out the characters is that you can easily get rid of
other characters by simply modifying what is checked (and what actions are taken) in the Select
Case structure.
If you don’t want to modify the original cells, a good approach is to put together a user-defined
function that will return a “clean” version of a string. This can be achieved by making a few
modifications to the previous macro.
Application.Volatile
strClean = ""
For intChar = 1 To Len(sRaw)
strCheckChar = Mid(sRaw, intChar, 1)
intCheckChar = Asc(strCheckChar)
Select Case intCheckChar
Case 65 To 90 'upper case chars
'Do nothing
Case 97 To 122 'lower case chars
'Do nothing
Case 128 To 151 'special language chars
'Do nothing
Case 153 To 154 'special language chars
'Do nothing
Case 159 To 165 'special language chars
'Do nothing
Case Else
strClean = strClean & strCheckChar
End Select
Next intChar
CleanText2 = strClean
End Function
In order to use the function, you could put a formula such as the following in a cell:
=CleanText2(A1)
The result is that the formula returns a “clean” version of whatever is in cell A1 without
disturbing the contents of cell A1.
There is a drawback to using Paste Special, however—it changes the actual value, which you
might not want to happen. Why? Because four months after making the adjustment to the values,
you might not remember exactly what you did, or what the starting values were.
For this reason, you may find it more desirable to replace values with formulas that indicate what
was done with your adjustment. For instance, you may have the value of 100 in cell B3, and you
want to increase it by 10%. Using Paste Special you can easily change it to 110, but you may
instead want to replace the value with the formula =100*1.1. With such a formula, there would
be no question four months from now about the starting value or what you did to it.
The only way to adjust values with formulas is to use a macro, such as the following one:
Sub Adjust()
Dim Target As Range
Dim J As Integer
Dim sForm As String
Dim sMod As String
To use this macro, select the cells you want to adjust, and then run it. You are asked for a
formula to add to the cells. As an example, if you wanted to multiply the cells by 1.1, you would
enter *1.1 (the asterisk multiplication symbol, followed by 1.1). The macro then steps through
each selected cell and makes the adjustments. If the cell contains a formula, then the formula is
adjusted as you specified. If the cell contains anything else, then it is turned into a formula that
includes your adjustment.
This seems to be related to the idea that it is impossible for a formula to return a blank value,
when "blank" is used synonymously with "empty." You can, however, expand your formula a bit
so that it returns an empty string. Instead of using =A3 as your formula, you would use the
following:
=IF(ISBLANK(A3),"",A3)
This formula uses ISBLANK, which returns either True or False, depending on whether the
referenced cell (A3) is blank or not. The IF function then returns an empty string ("") if A3 is
blank, or it uses the value in A3 if A3 is not blank.
Regardless of what the formula returns, you can still use its result in other formulas, and it will
work fine. Even if it returns an empty string, it is still treated by other formulas as if it contained
zero. In areas where treating the cell as if it contained zero might be problematic (such as when
you are charting the results of the formula), then you can modify the formula a bit, as shown
here:
=IF(ISBLANK(A3),NA(),A3)
This formula returns the #N/A error if A3 is blank. This error propagates through other formulas
that reference the formula, but the #N/A error is ignored completely when charting.
While the above solutions are satisfactory for most people, some people would really like to see
a target cell be truly blank if the source cell is blank. For instance, you might want cell B7 to be
blank if cell A3 is blank. If you put a formula in cell B7 (as already discussed), then cell B7 is
not truly blank—it contains a formula.
If this is your goal—true "blankness"—then you can only achieve it through the use of a macro.
The macro will need to check to see if the source cell was changed. If it was, then whatever is in
the source needs to be copied to the target cell.
While there is no worksheet function that will produce the desired count, there is a formula or
two you can use. If you just want to know the number of cells that have at least one comma in
them, the following formula will work just fine:
=COUNTIF(A1:A10,"*,*")
If you, instead, need to figure out the number of commas in the range when there could be
multiple commas per cell, then you need to use a different formula:
=SUM(LEN(A1:A10))-SUM(LEN(SUBSTITUTE(A1:A10,",","")))
This formula should be entered as an array formula, which means that you should use
CTRL+SHIFT+ENTER to enter the formula. If you need to derive the count for a different range,
just change the range in two places in the formula.
If you prefer, you could also create a user-defined function to count the number of commas.
There are multiple ways to approach such a task; the following is just one example.
Application.Volatile
iCount = 0
For Each rCell In rng
sTemp = Application.WorksheetFunction. _
Substitute(rCell.Value, ",", "")
iCount = iCount + _
(Len(rCell.Value) - Len(sTemp))
Next
CountComma = iCount
Set rCell = Nothing
Set rng = Nothing
End Function
In order to use the function in the worksheet, enter the following into a cell:
=CountComma(A1:A10)
All of these methods described so far will count commas that are actually in the cell. They will
not count commas that appear to be in the cell because of formatting. For instance, if a number
appears as “1,234” in a cell, chances are good that the comma is there because of the way that
the cell is formatted; it is not really in the cell itself. Such commas are not counted.
Of course, if all you need to do is know the number of commas and you don’t need the value in
your worksheet, you can bypass the use of formulas and macros all together. Follow these
general steps:
Excel does the replacement and displays a dialog box that shows how many replacements were
made.
We’ve looked high and low and can’t find a single formula that will do what is wanted. You can,
however, do it with an intermediate column. For instance, if you have your numbers in column A
(beginning in A1), then you could put the following formula in cell B1:
=IF(A1<0,1,0)
=IF(A2<0,B1+1,0)
Copy this down to all the other cells in column B for which there is a value in column A. Then,
in a different cell (perhaps cell C1) you can put the following formula:
=MAX(B:B)
This value will represent the largest number of consecutive negative values in column A.
If you don’t want to create an intermediate column to get the answer, you could create a user-
defined function that will return the value.
Dim c As Range
Dim lCounter As Long
Dim lMaxCount As Long
Application.Volatile
lCounter = 0
lMaxCount = 0
On Error Resume Next
For Each c In rng.Cells
If c.Value < 0 Then
lCounter = lCounter + 1
If lCounter > lMaxCount Then
lMaxCount = lCounter
End If
Else
lCounter = 0
End If
Next c
MaxNegSequence = lMaxCount
End Function
To use the function, just place a formula similar to the following in your worksheet:
= MaxNegSequence(A1:A512)
There are several ways you can approach this task. (Doesn’t that always seem the way in Excel?)
The first is to use a formula that relies on several functions:
This regular formula will sum the digits in any integer value (in cell A1) in a simple, elegant
manner. This is not the only possible formula, however. The following is an array formula
(terminated by pressing CTRL+SHIFT+ENTER) version of the same formula:
=SUM(1*MID(A1,ROW(INDIRECT("1:"&LEN(A1))),1))
Either of these formulas work fine if the value in A1 is a positive whole number. If there are any
non-digit characters in the number (such as a negative sign or a decimal point), then the formulas
return a #VALUE! error.
These are not the only formulas possible for this type of calculation. You can find some other
examples of formulas in the Microsoft Knowledge Base:
http://support.microsoft.com/?kbid=214053
You can also use a user-defined function to return the desired sum. The following macro steps
through each digit in the referenced cell and calculates a total. This value is then returned to the
user:
sNumber = CStr(Number)
For i = 1 To Len(sNumber)
Sum = Sum + Mid(sNumber, i, 1)
Next
AddDigits = Sum
End Function
To use this function, just use a formula such as =AddDigits(A1) in a cell. An even more compact
user-defined function (invoked in the same manner) is the following:
Unlike the earlier macro, this version doesn’t convert the cell contents to a string in order to
process it. Instead, it steps through each digit of the value, stripping off the last digit and adding
it to the total.
There is no intrinsic function you can use to create the desired sum, but you can create a formula
to perform the task. One method is to use the SUMIF function, in the following manner:
=SUMIF(A1:A10,">0")-SUMIF(A1:A10,"<0")
The first SUMIF sums all the values that are greater than zero, and the second sums all those less
than zero. Thus, with the four values -33, 14, -5, 42, the first SUMIF would result in a sum of 56
(14 + 42) and the second would result in a sum of -38 (-33 + -5). When you subtract the second
sum from the first (56 - -38) you get a final answer of 94, which is the sum of all the absolute
values.
Another approach is to use the SUMPRODUCT function. The following formula will produce
the desired result:
=SUMPRODUCT(ABS(A1:A10))
The function is typically used to multiply different elements of arrays by each other, and then
sum those products. Since only one array (A1:A10) is provided, there is no multiplication done,
but a sum of the desired absolute values is returned.
You can also get the desired result by using an array formula, a convenient but seldom used
feature of Excel. Assuming your values are in the range A1:A10, type this formula:
=SUM(ABS(A1:A10))
Don’t press ENTER; instead press CTRL+SHIFT+ENTER, which signifies this is an array formula. If
the formula is entered correctly, you’ll see braces around the formula in the Formula bar:
{=SUM(ABS(A1:A50))}
What the formula does is internally create the intermediate column (which is an array of values)
which are the individual absolute values of A1:A10. It then sums this array and displays the
result.
Finally, if you prefer you could create your own user-defined function (a macro) that will return
the sum of the absolute values in a range. The following is a macro that will accomplish this
task:
You can use the function by entering a simple formula in your worksheet:
=SumAbs(A1:A10)
There are numerous macros available on the Internet (including at ExcelTips) that allow you to
do conditional summing based on the color or other format of a cell. This need is different,
however, in that it is not the color of the cell at issue, but the color of the cell one column to the
left. This can still be done using a macro, as shown here:
For Each c In r
If c.Offset(0, -1).Interior.ColorIndex = 6 Then 'Yellow
a = a + c.Value
End If
Next c
SumNextYellow = a
End Function
The function can be used in a worksheet formula, and accepts a range reference as an argument.
It then steps through each cell in the range, and if the cell just to the left is yellow, then the value
is included in the sum. (You should note that the ColorIndex used in the macro should be tested
with your version of Excel to make sure that it is applicable; it may be different in different
versions.)
A much more robust example is shown in the following listing. This function accepts one or
more ranges of cells, along with an argument that represents a sample of the formatting you want
to use.
ColorConditionSum = False
If Not TypeOf cSample Is Excel.Range Then Exit Function '>>>
lColorIndex = cSample.Interior.ColorIndex
MySum = 0
Set rngCol2 = Nothing
ColorConditionSum = MySum
End Function
=ColorConditionSum(A10, A12:B22)
In this case, is a cell that has the interior color you want to match and A12:B22 is the range of
cells to be evaluated. The values are pulled from the second column in the range and the
formatting is checked on the cells in the first column.
needs a way to sum the quantity column related to parts 12345 ABC, 12345 DEF, 123456 GHI,
etc. She needs a way to do this without splitting the location code to a different column.
There is more than one way to get the desired answer. For the sake of the examples in this tip,
assume that the part numbers are in column A (as Kathy indicated) and that the quantities for
each part are in column B. It is these quantities that need to be summed, based upon just a
portion of what is in each cell in column A. Further, you can put the part number (minus the
location code) desired in cell D2.
The first potential solution is to use the SUMPRODUCT function, in this manner:
=SUMPRODUCT(--(VALUE(LEFT(A2:A49,FIND(" ",A2:A49)))=D2),B2:B49)
This formula checks the values in the range A2:A49. You should make sure that this range
reflects the range of your actual data. If you generalize the formula so that it looks at all of
columns A and B (as in A:A and B:B), you'll get a #VALUE error, since it tries to apply the
formula to empty cells in the columns.
You can get a similar result by using an array formula such as this:
=SUM(B:B*(LEFT(A2:A49,5)=TEXT(D2,"@")))
Remember, again, that this is an array formula, so you need to enter it by pressing
SHIFT+CTRL+ENTER. Note, as well, that this formula converts the value in D2 to text for the
comparison. This wasn't done in the previous formula because there the substring picked out of
column A was converted to a numeric value using the VALUE function.
You can also use the DSUM function to construct a working formula. Let's assume that the part
numbers (column A) have a column header in cell A1. Copy this column header (such as "Part
Num") to another cell in the worksheet, such as cell D1. In cell D2, enter the part number,
without its location code, followed by an asterisk. For example, you could enter "12345*"
(without the quote marks) into cell D2. With that specification set up, you can then use this
formula:
=DSUM($A$1:$B$49,$B$1,D1:D2)
This formula uses the specification in cell D2 (the characters 12345 followed by anything) as a
key to which values from column B should be summed.
Finally, if you had the same specification in cell D2 as you used with the DSUM approach, you
could use a very simple SUMIF function, in this manner:
=SUMIF(A:A,D2,B:B)
Note that this approach allows you to use the full column ranges (A:A and B:B) in the formula.
If your part numbers (in column A) are not as consistent in their format as you might like, then
you may be better creating a user-defined function to find your quantities. For instance, if your
part numbers aren't always the same length or if the part numbers can contain both digits and
letters or dashes, then a UDF is the way to go. The following example works great; it keys on the
presence of at least one space in the value. (Kathy indicated that a space separated the part
number from the location code.)
PC = Parts.Count
If PartsQty.Count <> PC Then
MsgBox "Parts and PartsQty must be the same length", vbCritical
Exit Function
End If
For i = 1 To PC
Pos = InStr(1, Parts(i), " ")
Pos2 = InStr(Pos + 1, Parts(i), " ")
AddPrtQty = tmpSum
End Function
To use the function, in your worksheet call it using two ranges and the part number you want:
=AddPrtQty(A2:A49,B2:B49,"GB7-QWY2")
The SUM function is pretty simplistic in how it does its work; it simply sums a range. You can
change the function you use and get the desired results, however. For instance, let’s assume that
you want to sum the range of A3:A45, and that you don’t want any hidden values to be included
in the sum. You should use the SUBTOTAL function in the following manner:
=SUBTOTAL(109,A3:A45)
The first parameter of the function (109) indicates how you want SUBTOTAL to do its work. In
this case, it means you want SUBTOTAL to sum the range, using the SUM function, and you
don’t want any hidden values included in the value returned. (You can find out more about the
controlling SUBTOTAL parameters if you look in the online Help for the SUBTOTAL
function.)
If you don’t want to use the SUBTOTAL function for some reason, you can create your own
user-defined function (a macro) that will only sum the visible values in a range. Consider this
macro:
Application.Volatile
vTotal = 0
For Each cell In Cells_To_Sum
If Not cell.Rows.Hidden Then
If Not cell.Columns.Hidden Then
vTotal = vTotal + cell.Value
End If
End If
Next
Sum_Visible = vTotal
End Function
To use the function, simply use a formula like this wherever you want your sum to appear:
=Sum_Visible(A1:A1000)
Unfortunately there is no way to modify the default functions available on the status bar. There
are, however, some workarounds that you can consider. The obvious is to use a formula in a cell
to evaluate the number of zeros in a range:
=COUNTIF(A1:E52,0)
You could also select the desired range and use the Find tool (CTRL+F) to search for the number
0. If you click on Find All, the dialog box reports the number of occurrences in the selected
range—the number of zeros.
If you prefer, you can create a short macro that will do the calculation and display it on the status
bar. The following is an example of a macro that is run every time the selection is changed in the
worksheet.
All you need to do is make sure that you place this code within the code module for the
worksheet you want affected. (Just right-click the worksheet’s tab and choose View Code from
the resulting Context menu. That’s where the code should be placed.)
There are several ways you can perform this task. If the structure of your product codes is
consistent, then inserting the dashes is a snap. For instance, if there will always be a single letter
followed by numbers, then you could use a formula such as this:
Chances are good that your data won't be structured, meaning you could have one or two letters
followed by up to three digits. Thus, both A4 and QD284 would both be valid product codes. In
this case, a solution formula takes a bit more creativity.
One way to handle it is with an array formula. Consider the following formula:
=REPLACE(A1,MATCH(FALSE,ISERROR(1*MID(A1,ROW(INDIRECT("1:100")),1)),0),0,"-")
If values are in A1-A10, you can put this formula into B1, and then copy it down the column.
Since it is an array formula, it must be entered by pressing CTRL+SHIFT+ENTER. The formula
finds the location of the first number in the cell and inserts a dash before it.
Assume, for the sake of example, that cell A1 contains BR27. The innermost part of the formula,
INDIRECT("1:100"), converts the text 1:100 to a range. This is used so that inserting or deleting
rows does not affect the formula. The next part of the formula, ROW(INDIRECT("1:100")),
essentially creates an array of the values 1-100: 1,2,3,...,99,100. This is used to act on each
character in the cell.
The next step is to apply the ISERROR function to the results of the multiplication. This
converts the errors to TRUE and the non-errors to FALSE, yielding TRUE, TRUE, FALSE, and
FALSE. The MATCH function looks in the array of TRUE and FALSE values for an exact
match of FALSE. In this example, the MATCH function returns the number 3, since the first
FALSE value is in the third position of the array. At this point, we essentially know the location
of the first number in the cell.
The final function is REPLACE, which is used to actually insert the dash into the source string,
beginning at the third character.
As you can tell, the formula to perform the transformation can be a bit daunting to decipher. For
those so inclined, it may be easier to just create a user-defined function. The following macro is
an example of one that will return a string with the dash in the proper place:
Application.Volatile
myLength = Len(myText)
For i = 1 To myLength
myCharCode = Asc(Mid(myText, i, 1))
If myCharCode >= 48 And myCharCode <= 57 Then
Exit For
End If
Next i
If i = 1 Or i > myLength Then
DashIn = myText
Else
DashIn = Left(myText, i - 1) & "-" _
& Mid(myText, i, myLength - 1)
End If
End Function
The macro examines each character in the original string, and when it finds the first numeric
character, it inserts a dash at that point. You would use the function in this way:
=DashIn(A1)
Removing Spaces
Do you have a lot of data that contains spaces, and you need to remove those spaces? Perhaps
you imported it from another program, or the spaces were entered by mistake. For example, you
may have a large number of policy numbers in a worksheet, and there are spaces in the policy
numbers. If you want to remove those spaces, there are two approaches you can use.
The first approach is to use the SUBSTITUTE function. Let’s say that a policy number is in cell
A5. In cell B5 you could use this formula:
=SUBSTITUTE(A5," ","")
The result is that cell B5 contains the policy number with all the spaces removed.
The second approach works well if you have a lot of cells containing spaces, and you want to
remove them in one step. Create the following macro:
Sub NoSpaces()
Dim c As Range
Select the cells you want to modify, and then run the macro. It examines each cell in the selected
range, removing any spaces in that range. The result is then placed back in the same cell.
If your random numbers were to really be numbers (digits only), then generating them would be
easy. All you would need to do is use the RANDBETWEEN function (in the Analysis ToolPak)
in this manner:
=RANDBETWEEN(10000000,99999999)
This is not what Nancy wants, however. Her random “numbers” can contain upper- and
lowercase letters, as well. This becomes a bit stickier. There are, however, several approaches
you can use.
One approach is to put all your possible characters into an individual cell, such as B7:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
Name this cell something snazzy, such as MySource. You could then use a formula such as the
following to return the random string of characters:
=MID(MySource,RANDBETWEEN(1,LEN(MySource)),1)
& MID(MySource,RANDBETWEEN(1,LEN(MySource)),1)
& MID(MySource,RANDBETWEEN(1,LEN(MySource)),1)
& MID(MySource,RANDBETWEEN(1,LEN(MySource)),1)
& MID(MySource,RANDBETWEEN(1,LEN(MySource)),1)
& MID(MySource,RANDBETWEEN(1,LEN(MySource)),1)
& MID(MySource,RANDBETWEEN(1,LEN(MySource)),1)
& MID(MySource,RANDBETWEEN(1,LEN(MySource)),1)
The formula is long; it has been broken into individual lines for clarity, but it is still a single
formula. It concatenates eight characters pulled from the source you entered into cell B7.
Another approach is to create a table that contains all the characters you would want in your
random text string. Start by placing the numbers 1 through 62 in a column, one number in each
row. To the left of these numbers place your characters—A, B, C, D, etc. (This should be the
same characters you placed in cell B7 in the previous technique.) Select both columns of the 62
rows and give it a name, such as MyTable. You can then use the following formula to generate
the random characters:
=VLOOKUP(RANDBETWEEN(1,62),MyTable,2)
& VLOOKUP(RANDBETWEEN(1,62),MyTable,2)
& VLOOKUP(RANDBETWEEN(1,62),MyTable,2)
& VLOOKUP(RANDBETWEEN(1,62),MyTable,2)
& VLOOKUP(RANDBETWEEN(1,62),MyTable,2)
& VLOOKUP(RANDBETWEEN(1,62),MyTable,2)
& VLOOKUP(RANDBETWEEN(1,62),MyTable,2)
& VLOOKUP(RANDBETWEEN(1,62),MyTable,2)
Again, remember that this is a single formula, although it is a bit shorter than the previous
formula.
Each of the approaches presented so far has one drawback: they are regenerated each time your
worksheet is recalculated. Thus, it is hard to have a single generated random string that won’t
change on a regular basis. The best way around this is to use a macro, but you don’t necessarily
want to use a user-defined function. Why? Because it, too, would change its result every time the
worksheet was recalculated. Instead, you need a macro that will put the random strings into your
workbook starting at a specific cell location. The following is an example of such a macro:
Sub MakeRandom()
Dim J As Integer
Dim K As Integer
Dim iTemp As Integer
Dim sNumber As String
Dim bOK As Boolean
Range("D4").Activate
Randomize
For J = 1 To 50
sNumber = ""
For K = 1 To 8
Do
Run the macro, and whatever is in cells D4:D53 is overwritten by the random values. If you want
the values written into a different location, change the Range statement near the beginning of the
macro.
This sort of repetitive task just cries out for a macro. (They are great for doing boring, dull,
repetitive tasks that you don't want to do manually.) The following is a simple macro that can do
the required grunt work:
Sub CreateNames()
Dim i As Integer
Dim x As Integer
Dim y As Integer
Dim z As Integer
i = 1
For x = 97 To 122
For y = 97 To 122
For z = 97 To 122
Cells(i, 1) = "Name" & Chr(x) _
& Chr(y) & Chr(z)
i = i + 1
Next
Next
Next
End Sub
The macro uses three counter variables (x, y, and z) to serve as "counter variables" that control
which letter of the alphabet is appended to the "name" stuffed into a cell. Notice that the For ...
Next loops range from 97 to 122, which are the ASCII codes for lowercase a through z.
If you don't want to use a macro for some reason, type the following formula into cell A1 of a
blank worksheet:
This is a single formula, and it results in "Nameaaa" being displayed. Copy the formula down
through row 17,576 and you'll have your fake names.
There are a couple of ways that this can be done. Perhaps the easiest, though, is to simply assign
a random number to each item in column A. Assuming that the first item is in cell A1, put the
following in cell B1:
=RAND()
Double-click the fill handle in cell B1, and you should end up with a random number (between 0
and 1) to the right of each item in column A.
Now, select all the cells in column B and press CTRL+C to copy them to the Clipboard. Use Paste
Special to paste values right back into those cells in column B. (This converts the cells from
formulas to actual static values.)
Sort columns A and B in ascending order based on the values in column B. If you look across the
rows, you'll now have items (column A) associated randomly with a name (column G).
Even though it is not necessary, you could also follow these same steps to add a random number
to the right of each name and then sort the names. (I say it isn't necessary because randomizing
the items should be enough to assure that there are random items associated with each name.)
The technique discussed so far works great if you have to do the random pairing only once in a
while. If you need to do it quite often, then a macro may be a better approach. There are, of
course, many different macro-based approaches you could use. The following approach assumes
the item list is in column A and the name list in column G. It also assumes that there are header
cells in row 1 for each column.
Sub AssignNames()
Set srItems = Range("A2").CurrentRegion
Set srNames = Range("G2").CurrentRegion
NameCount = srItems.Rows.Count - 1
ItemCount = srNames.Rows.Count - 1
'Randomize Names
ReDim tempArray(NameCount, 2)
For x = 0 To NameCount - 1
tempArray(x, 0) = Range("G2").Offset(x, 0)
tempArray(x, 1) = Rnd()
Next x
'Bubble Sort
For i = 0 To NameCount - 2
For j = i To NameCount - 1
If tempArray(i, 1) > tempArray(j, 1) Then
tempItem = tempArray(j, 0)
tempName = tempArray(j, 1)
tempArray(j, 0) = tempArray(i, 0)
tempArray(j, 1) = tempArray(i, 1)
tempArray(i, 0) = tempItem
tempArray(i, 1) = tempName
End If
Next j
Next i
'AssignNames
Range("B2") = "Assigned"
AssignCount = NameCount
If NameCount > ItemCount Then AssignCount = ItemCount
For x = 0 To AssignCount
Range("B2").Offset(x, 0) = tempArray(x, 0)
Next x
End Sub
If there are more names than items the macro randomly assigns names to items. If there are more
items than names it randomly assigns some items to names and randomly leaves "holes" (items
without names). It stores them in column B, overwriting whatever was there.
There are a couple of ways that you can accomplish this task. It is quite easy to do through the
use of a simple formula. For instance, if your first text string is in cell A1, put the following
formula to its right, in cell B1:
=MID($A1,COLUMN()-1,1)
This formula uses Excel worksheet functions to pull apart the text string. The COLUMN
function, in this case, returns the value 2 since the formula is in column B and that is the second
column in the worksheet. This value is decremented by 1, and then used as a pointer into the
string in cell A1, marking where the extracted character should come from. When you copy this
formula right, for however many cells desired, you end up with individual characters from the
string, in consecutive order.
Of course, if you have quite a few strings in the worksheet (as John does), then copying this
formula over 249 columns and down, say, several hundred rows can make for a very sluggish
worksheet. In such situations it may be desirable to use a macro to split apart the strings instead
of a formula. The following macro, SplitUp, is one approach to doing the actual tearing apart.
Sub SplitUp()
Dim c As Range
Dim r As Range
Dim sTemp As String
Dim z As Integer
The macro starts by defining a range (r) that consists of all the cells in column A that contain
values. The c variable is then used to represent each cell in the range, and the first 249 characters
pulled from each cell. A For … Next loop is then used to pull each character from the string and
stuff it into a cell to the right of the string.
The factor that complicates dividing the part number into segments is that there is no set length
for each component of the combined part number. If the components were of standard lengths,
then you could use the Text to Columns function in Excel. Since they aren’t, and there is no
delimiter between the components, then that potential avenue for solution is closed.
If you want to use formulas to pull apart the part numbers, then you will need three of them, one
for each component you want to extract. Assuming that the part numbers follow the pattern
indicated (text, digits, text) and that the first part number is in cell A1, you could use the
following in cell B1:
=LEFT(A1,MATCH(FALSE,ISERROR(1*MID(A1,ROW(INDIRECT("1:100")),1)),0)-1)
This needs to be entered as an array formula, meaning that you need to enter it using
CTRL+SHIFT+ENTER. The formula finds the first numeric digit in the part number, and then
returns everything before that digit. It will work on any part number that isn’t over 100
characters in length.
To extract the second component of the part number, you can put the following formula in cell
C1:
=MID(A1,MATCH(FALSE,ISERROR(1*MID(A1,ROW(INDIRECT("1:100")),
1)),0),COUNT(1*MID(A1,ROW(INDIRECT("1:100")),1)))
=VALUE(MID(A1,MATCH(FALSE,ISERROR(1*MID(A1,ROW(INDIRECT("1:100")),
1)),0),COUNT(1*MID(A1,ROW(INDIRECT("1:100")),1))))
To get the last component of the part number, you need to use the following formula, again
entered as an array formula:
=MID(A1,MATCH(FALSE,ISERROR(1*MID(A1,ROW(INDIRECT("1:100")),
1)),0)+COUNT(1*MID(A1,ROW(INDIRECT("1:100")),1)),100)
While this approach works very well, array formulas are notoriously calculation intensive,
especially when you have a lot of the formulas in your worksheet. If you need to pull apart a
thousand part numbers, that means that you end up with 3,000 array formulas, which can be
very, very slow in recalculating.
If you find yourself in this situation, you may want to use a macro to actually pull apart the part
numbers. The following macro should work on part numbers that follow the pattern of text,
digits, text, as already described.
Sub Split1()
Dim C As Range
Dim sNew As New
Dim i As Integer
i = i + 1
If i > Len(C) Then Exit Do
Loop
C.Offset(0, 1).Value = sNew
i = i + 1
If i > Len(C) Then Exit Do
Loop
C.Offset(0, 2).Value = sNew
To use the macro, just make a selection of part numbers and run it. The macro uses the concept
of looking for changes between numeric/nonnumeric values in string of characters in the cell.
When one of these boundaries is reached, the part of the original string before the boundary is
stuffed into a new cell. This concept can be shortened a bit, as is done in the following example.
Sub Split2()
Dim C As Range
Dim j As Integer
Dim k As Integer
C.Offset(0, 1) = Left(C, j - 1)
C.Offset(0, 2) = Mid(C, j, k - j)
C.Offset(0, 3) = Mid(C, k, Len(C) - (k - 1))
Next C
End Sub
The difference between this version of the macro and the previous one, of course, is that this
version steps through the original cell and determines the boundaries all at once. When they are
known, then the components of the original part number are stuffed into the cells.
An interesting approach to pulling apart the part numbers is to use a couple of short user-defined
functions that determine where the boundaries are between the components. Consider the
following two functions:
Function pNumber(X)
i = 1
Do Until Mid(X, i, 1) Like "#": i = i + 1: Loop
pNumber = i
End Function
Function pAlpha(X)
X = UCase(X)
i = pNumber(X)
Do Until Mid(X, i, 1) Like "[A-Z]": i = i + 1: Loop
pAlpha = i
End Function
These are much shorter than the previous macros, and all they do is return the boundary where
the numbers start (in the case of pNumber) and the boundary where the second group of text
starts (in the case of pAlpha). To use the functions, you use the following three formulas to
return, respectively, the first, second, and third components of the original part number:
=MID(A1,1,pNumber(A1)-1)
=MID(A1,pNumber(A1),pAlpha(A1)-pNumber(A1))
=MID(A1,pAlpha(A1),LEN(A1)-pAlpha(A1)+1)
You might think you could use the Text to Columns tool in Excel, but it is not suited well for the
job. If you set the tool to split text based on delimiters such as a space, then you end up with a
single word in each cell. If you set the tool to split the text as “fixed width,” then it doesn’t split
words at spaces; it just makes sure that each chunk is whatever size you specify.
You could use a formula to get the desired results, but doing so would result in formulas that are
amazingly long. For instance, if the too-long text is in cell A3, the following formula could be
used to split out the first chunk of that text, at the space before the 12th character:
=IF(LEN($A3)>12,IF(ISERROR(FIND(" ",MID($A3,12,1)
&MID($A3,11,1)&MID($A3,10,1)&MID($A3,9,1)&MID($A3,8,1)
&MID($A3,7,1)&MID($A3,6,1)&MID($A3,5,1)&MID($A3,4,1)
&MID($A3,3,1)&MID($A3,2,1))),MID($A3,1,1),MID($A3,1,
13-FIND(" ",MID($A3,12,1)&MID($A3,11,1)&MID($A3,10,1)
&MID($A3,9,1)&MID($A3,8,1)&MID($A3,7,1)&MID($A3,6,1)
&MID($A3,5,1)&MID($A3,4,1)&MID($A3,3,1)&MID($A3,2,1))))
,RIGHT($A3,LEN($A3)))
Remember—this is all a single formula, just to get the first chunk. The formulas to get the
second, third, fourth, and later chunks are even longer. Clearly, using a formula may not be the
best approach.
This leaves using a macro. A macro can examine the text string and easily chop it up into pieces
of the desired length. Consider the following user-defined function:
iSegments = 0
sRest = sSentence
sTemp = Left(sRest, iLen + 1)
Do Until Len(sTemp) <= iLen
iSpace = 0
For J = Len(sTemp) To 1 Step -1
If Mid(sTemp, J, 1) = " " And iSpace = 0 Then iSpace = J
Next J
If iSpace > 0 Then
sTemp = Left(sRest, iSpace - 1)
sRest = Mid(sRest, iSpace + 1)
Else
sRest = Mid(sRest, Len(sTemp) + 1)
End If
iSegments = iSegments + 1
ReDim Preserve sSegments(1 To iSegments)
sSegments(iSegments) = sTemp
sTemp = Left(sRest, iLen + 1)
Loop
iSegments = iSegments + 1
ReDim Preserve sSegments(1 To iSegments)
sSegments(iSegments) = sTemp
If iPos <= iSegments Then
SplitMe = sSegments(iPos)
Else
SplitMe = ""
End If
End Function
The function takes either two or three parameters. The first parameter is the string to be split up,
the second is which chunk you want from the string, and the third (and optional) parameter is the
desired length of each chunk. If you leave off the third parameter, then the function assumes you
want each chunk to be a maximum of 12 characters. As an example, assuming that the text is in
cell A5, the following will return the second chunk from the text where each chunk is up to 12
characters long:
=SplitMe(A5,2)
The function will return good results, provided each word in the text string is no longer than the
specified target length for each chunk. If it is, then you’ll get some strange results, including
some chunks that don’t contain full words.
This can be accomplished using either a formula for a macro. Regardless of which approach you
use, the key is to figure out where the text switches from lower- to uppercase. This can only be
done by examining each character in the string. So, if you want to use a formulaic approach, then
you'll need to use an array formula. The following array formula returns the last name of
whatever is in cell A1:
=MID(A1,MATCH(1,(CODE(MID(A1,ROW($1:$255),1))>=65)
*(CODE(MID(A1,ROW($2:$255),1))<90),)+1,255)
Remember, since this is an array formula, you should enter it by pressing CTRL+SHIFT+ENTER. It
returns everything in the cell starting with the first uppercase letter it finds. Thus, in
"mikeDAVIS" it would return "DAVIS" and in "mikeDavis" it would return "Davis". Assuming
that you use the array formula in cell B1, you could then determine the first name by using the
following:
=SUBSTITUTE(A1,B1,"")
There are many similar array formulas that can accomplish much the same task. For example,
this array formula will return the first name (all the characters up to the first uppercase character)
of whatever is in cell A1:
=LEFT(A1,MAX((CODE(MID(A$1,ROW(INDIRECT("1:"&
LEN(A1))),1))>96)*ROW(INDIRECT("1:"&LEN(A1)))))
You can then use the same regular formula (the one that uses the SUBSTITUTE function) to
derive the last name.
If you want to use a macro approach to finding the names, all you need to do is come up with a
formula that will return the location of the first capital letter in the text. The following code
returns this "change point" in the text:
Application.Volatile
sCellValue = Trim(MyCell.Value)
i = 1
Do While (Asc(Mid(sCellValue, i, 1)) > 90 _
Or Asc(Mid(sCellValue, i, 1)) < 65) _
And i < Len(sCellValue) + 1
i = i + 1
Loop
If i > Len(sCellValue) Then
GetFirstUpper = 99
Else
GetFirstUpper = i
End If
End Function
To use the function, let's assume that the name is in cell A1. You could find the first and last
names using these formulas in your worksheet:
=LEFT(A1,GetFirstUpper(A1)-1)
=MID(A1,GetFirstUpper(A1),LEN(TRIM(A1))-GetFirstUpper(A1)+1)
If you prefer for your macro to return the actual names, you could use the following one to return
everything before the first capital letter:
Application.Volatile
sCellValue = Trim(MyCell.Value)
i = 1
Do While (Asc(Mid(sCellValue, i, 1)) > 90 _
Or Asc(Mid(sCellValue, i, 1)) < 65) _
And i < Len(sCellValue) + 1
i = i + 1
Loop
If i > Len(sCellValue) Then
GetFirstName = sCellValue
Else
GetFirstName = Left(sCellValue, i - 1)
End If
End Function
To use the macro, all you need to do is use the following in a worksheet cell. (This assumes that
the text string to be evaluated is in cell A1.)
=GetFirstName(A1)
A minor variation on the macro will allow you to similarly fetch the last name, which is assumed
to be everything starting with the first capital letter encountered.
Application.Volatile
sCellValue = Trim(MyCell.Value)
i = 1
Do While (Asc(Mid(sCellValue, i, 1)) > 90 _
Or Asc(Mid(sCellValue, i, 1)) < 65) _
And i < Len(sCellValue) + 1
i = i + 1
Loop
If you prefer, you could combine the macros into a single function that would, based upon what
you specify, return either the first or last name:
Application.Volatile
sCellValue = Trim(MyCell.Value)
i = 1
Do While (Asc(Mid(sCellValue, i, 1)) > 90 _
Or Asc(Mid(sCellValue, i, 1)) < 65) _
And i < Len(sCellValue) + 1
i = i + 1
Loop
If i > Len(sCellValue) Then
GetName = sCellValue
Else
If LCase(sWanted) = "first" Then
GetName = Left(sCellValue, i - 1)
Else
GetName = Mid(sCellValue, i)
End If
End If
End Function
To use this combined function you simply need to specify which name you want:
=GetName(A1, "First")
The word "First" passed as a parameter in this manner returns the first name (everything before
the first capital letter). Any other string passed as the second parameter (such as "Last" or "xxx"
or "Rest" or even "") results in the last name being returned.
This can be done with a formula, but it quickly becomes unwieldy. For instance, the following
formula can be used to put dashes between the letters of whatever you type into cell A1:
This particular example of a formula will only work on text up to six characters in length. Thus,
it would work properly for "house", but not for "household". The formula could be lengthened
but, again, it would quickly become very long.
A better approach is to use a macro to do the conversion. If you want to insert the dashes right
into the cell, you could use a macro such as this:
Sub AddDashes1()
Dim Cell As Range
Dim sTemp As String
Dim C As Integer
This macro is designed to be used on a selected range of cells. Just select the cells you want to
convert, and then run the macro. The dashes are added between each letter in the cells.
If you prefer to not modify the original cell values, you could create a user-defined function that
would do the job:
Application.Volatile
sTemp = ""
For C = 1 To Len(Src)
sTemp = sTemp + Mid(Src, C, 1) + "-"
Next
AddDashes2 = Left(sTemp, Len(sTemp) - 1)
End Function
To use this function you would use the following in your worksheet:
=AddDashes2(A1)
If you want to make sure that the function is a bit more robust, you could modify it so that it
handles multiple words. In such an instance you would not want it to treat a space as a "dashable
letter." For example, you would want the routine to add dashes to "one two" so it came out as "o-
n-e t-w-o" instead of "o-n-e- -t-w-o". The following variation on the function will do the trick:
Application.Volatile
sTemp = ""
For C = 1 To Len(Src)
sTemp = sTemp + Mid(Src, C, 1)
If Mid(Src, C, 1) <> " " And
Mid(Src, C + 1, 1) <> " " And
C < Len(Src) Then
sTemp = sTemp + "-"
End If
Next
AddDashes3 = sTemp
End Function
What is happening is that when you edit the cells, Excel is parsing the cell contents as numbers
instead of as text. In this case, the best solution is to make sure that your cell contents are
preceded with an apostrophe before you do the Find and Replace to get rid of the dashes. If you
have a worksheet that contains a lot of ISBN numbers in column A, you can add the apostrophes
with a formula such as the following:
= "'" & A1
You can then copy the results of the formulas and then use Paste Special to paste values back
into column A. Each value in column A will then include the apostrophe. When you later
perform the Find and Replace, the leading zeroes will still be present and you won’t get any
attempts at scientific notation.
The reason this works is because the apostrophe is an indicator to Excel that the cell contents
should be treated as text. The apostrophe isn’t displayed in the worksheet, but it is part of the cell
contents, as you can tell by looking at the Formula bar.
Another approach is to bypass using Find and Replace to get rid of the dashes. Instead use the
SUBSTITUTE function to remove them, in this manner:
=SUBSTITUTE(A1,"-","")
The SUBSTITUTE worksheet function returns a text value, so any leading zeroes are maintained
and Excel doesn’t try to convert the numbers to use a numeric format.
If you routinely need to remove the dashes from a range of cells containing ISBNs, you might be
better served to use a macro to do the operation. The following macro works upon whatever cells
you’ve selected before running it.
Sub RemoveDashes()
Dim c As Variant, sISBN As String
Basically the macro does three things: It removes the dashes, it formats the cell as text, and it
places the stripped ISBN back in the cell with an apostrophe before it.
The short answer is that you can’t do this using a search and replace, either wild card or regular.
You can, however, use a formula to add any missing brackets. The following is just one example
of the type of formula you can use:
=IF(AND(NOT(ISERROR(SEARCH("[",A1))),NOT(RIGHT(A1,1)="]")),A1&"]",A1)
The trick is to check to see if the cell (A1 in this case) has a left bracket in it and, if it does, check
for the right bracket. If the right bracket isn’t found, then you append one to the contents of the
cell. Here’s another variation on the same formulaic theme:
=IF(ISERROR(FIND("[",A1)),A1,IF(ISERROR(FIND("]",A1)),A1&"]",A1))
If you have to check large numbers of cells for missing brackets on a regular basis, you may
want to create a macro that will examine a range of cells and add a right bracket if one is needed.
Here’s an example of how such a macro could be formulated:
Sub Close_Bracket()
Dim c As Range
To use the macro, simply select the range of cells you want to affect, and then run it. The cells
are examined in-place and modified, if needed.
There are a couple of ways that this task can be approached. It is assumed, to begin with, that
you don't want to modify the structure of your worksheet by adding intermediate columns. This
assumption precludes, as well, the use of the Text to Columns feature to split the original string
into individual words.
The key to the problem is making sure that your formula can determine where the spaces are in
the original string. You might think that a formula such as the following will do the job:
=LEFT(A1,1)&MID(A1,FIND(" ",A1,1)+1,1)&MID(A1,
FIND(" ",A1,FIND(" ",A1,1)+1)+1,1)
This formula works partially. It works just fine if the original string has two spaces separating
three words. If there are any fewer words then the formula returns an error. If there are any more
words, then it returns only the first letters of the first three words (it ignores anything after the
third word).
This means that the formula needs to not only check for spaces, but handle errors if there are no
spaces or if there are too few spaces. The error checking means that the formula becomes much
longer:
=IF(ISERR(LEFT(A1,1)&MID(A1,SEARCH(" ",A1)+1,1)
&MID(A1,SEARCH(" ",A1,SEARCH(" ",A1)+1)+1,1)
&MID(A1,SEARCH(" ",A1,SEARCH(" ",A1,SEARCH(" ",A1)+1)+1)+1,1)
&MID(A1,SEARCH(" ",A1,SEARCH(" ",A1,SEARCH(" ",A1,SEARCH(" ",
A1)+1)+1)+1)+1,1)),IF(ISERR(LEFT(A1,1)&MID(A1,SEARCH(" ",A1)+1,1)
&MID(A1,SEARCH(" ",A1,SEARCH(" ",A1)+1)+1,1)
&MID(A1,SEARCH(" ",A1,SEARCH(" ",A1,SEARCH(" ",A1)+1)+1)+1,1)),
IF(ISERR(LEFT(A1,1)&MID(A1,SEARCH(" ",A1)+1,1)
&MID(A1,SEARCH(" ",A1,SEARCH(" ",A1)+1)+1,1)),
IF(ISERR(LEFT(A1,1)&MID(A1,SEARCH(" ",A1)+1,1)),
IF(ISERR(LEFT(A1,1)),"",LEFT(A1,1)),LEFT(A1,1)
&MID(A1,SEARCH(" ",A1)+1,1)),LEFT(A1,1)&MID(A1,SEARCH(" ",A1)+1,1)
&MID(A1,SEARCH(" ",A1,SEARCH(" ",A1)+1)+1,1)),
LEFT(A1,1)&MID(A1,SEARCH(" ",A1)+1,1)
&MID(A1,SEARCH(" ",A1,SEARCH(" ",A1)+1)+1,1)
&MID(A1,SEARCH(" ",A1,SEARCH(" ",A1,SEARCH(" ",A1)+1)+1)+1,1)),LEFT(A1,1)
&MID(A1,SEARCH(" ",A1)+1,1)&MID(A1,SEARCH(" ",A1,SEARCH(" ",A1)+1)+1,1)
&MID(A1,SEARCH(" ",A1,SEARCH(" ",A1,SEARCH(" ",A1)+1)+1)+1,1)
&MID(A1,SEARCH(" ",A1,SEARCH(" ",A1,SEARCH(" ",A1,SEARCH(" ",A1)+1)
+1)+1)+1,1))
This formula will handle, properly, anything from 0 to 5 words in a string. It also assumes that
the string doesn't start or end with a space and that it doesn't contain multiple numbers of spaces
between words. If you want to handle a larger number of words or other potential complications
(such as the number of spaces between words), then it is best to use a user-defined function.
There are any number of ways that a user-defined function could pull the leading characters from
the words of a string. In fact, I received quite a few variations that accomplish the same thing.
The following example, however, is perhaps the most concise code that I ran across:
Application.Volitile
Temp = Split(Trim(Raw))
For J = 0 To UBound(Temp)
Initials = Initials & Left(Temp(J), 1)
Next J
End Function
The Split function "tears apart" a string based on where spaces occur within it. The individual
words in the string are placed into an array (in this case, Temp) where you can then access
individual words. To use the function in your worksheet, simply use something like this:
=Initials(A1)
One way to approach this is to use the SUBSTITUTE worksheet function in conjunction with the
PROPER function. For instance, if you wanted to find instances of the word "The" with "the",
you could use the following:
Note the inclusion of the space before and after what you are searching for and what you are
replacing. This insures that only full words are modified. It also makes sure that no changes are
made at the beginning of the cell value or at the end.
If you wanted to search for other words that needed replacing, you can simply increase the
number of instances of SUBSTITUTE in the formula:
This can obviously get a bit awkward if you have a lot of words you want to exclude from being
modified. In that case you'll need to resort to using a macro. The following macro, written as a
user-defined function, can be used to convert all words in a cell to initial caps (just like
PROPER), but make sure that certain defined words are lowercase.
c = StrConv(ref, 3)
'split the words into an array
vaArray = Split(c, " ")
For i = 2 To UBound(vaArray)
For J = LBound(vaLCase) To UBound(vaLCase)
' compare each word in the cell against the
' list of words to remain lowercase. If the
' Upper versions match then replace the
' cell word with the lowercase version.
If UCase(vaArray(i)) = UCase(vaLCase(J)) Then
vaArray(i) = vaLCase(J)
End If
Next J
Next i
Title = Trim(str)
End Function
To use the macro, all you need to do is use the following in your worksheet:
=Title(A1)
If you get an error when you try to run this macro, chances are good that you are using Excel 97.
The Split function was not added until Excel 2000, so Excel 97 users will get an error. If you do,
then add the following macro which emulates what the Split function does.
Indx = 0
sTemp = Raw
J = InStr(sTemp, Delim)
While J > 0
Indx = Indx + 1
ReDim Preserve vAry(1 To Indx)
vAry(Indx) = Trim(Left(sTemp, J))
sTemp = Trim(Mid(sTemp, J, Len(sTemp)))
J = InStr(sTemp, Delim)
Wend
Indx = Indx + 1
ReDim Preserve vAry(1 To Indx)
vAry(Indx) = Trim(sTemp)
Split = vAry()
End Function
You can also find an additional approach on accomplishing the desired conversion at this site:
http://dmcritchie.mvps.org/excel/proper.htm
Assuming that the month and year listed in column A is really an Excel date value (and not text),
you can easily create a formula to return the desired information. If you have row 1 occupied
with headings for your columns, enter the following in cell C2:
Remember that this is a single formula, and should be entered all on one line. You can copy the
formula down as many rows as necessary in column C, and it should provide the desired
information. It only makes a notation in column C if the value in column B is greater than the
maximum or less than the minimum of all the foregoing values in column B.
If you have quite a bit of data in your worksheet, you could notice that the formula results in long
recalculation times. If this is the case, then you may want to consider using macro that will do
the desired analysis and provide the appropriate information. The following macro provides
looks backward through the information in column B and provides both a “lowest since” and
“highest since” result in columns C and D.
Sub FindHiLow()
Dim orig_cell As Range
Dim orig_val As Integer
Dim orig_row As Integer
Dim rownum As Integer
Dim newcell As Range
Dim new_val As Integer
Dim lowrow As Integer
Dim hirow As Integer
If you had only a few workbooks to change, the task is pretty easy: Load each workbook and, in
turn, make the change to each of them. If you have a couple hundred workbooks in which the
change needs to be made, then the task becomes more formidable.
If you anticipate only needing to do this task once, then the easiest solution is to create a text file
that contains the path and filename of each of the workbooks, one workbook per line. For
instance, you might end up with a file that had entries such as this:
c:\myfiles\first workbook.xlsx
c:\myfiles\second workbook.xlsx
c:\myfiles\third workbook.xlsx
The file could have as many lines in it as necessary; it doesn’t really matter. The important thing
is that each line be a valid path and file name, and that there be no blank lines in the file.
You could most easily create such a file by displaying a command-prompt window, navigating to
the directory containing the workbooks, and issuing the following command:
Each file in the directory ends up in the myfilelist.txt file. You will need to load the text file into
a text editor and check it out so you can delete extraneous entries. (For instance, myfilelist.txt
will end up in the listing.) You will also need to add the path name to the beginning of each line
in the file.
Once the file is complete, you can start Excel and use a macro to read the text file, load each
workbook listed in the text file, step through each worksheet in that workbook, make the
appropriate change, and save the workbook. The following macro will perform these tasks
nicely.
Sub ChangeFiles1()
Dim sFilename As String
Dim wks As Worksheet
With ActiveWorkbook
For Each wks In .Worksheets
' Specify the change to make
wks.Range("A1").Value = "A1 Changed"
Next
End With
ActiveWorkbook.Close SaveChanges:=True
Loop
Close #1
End Sub
While this approach works great if you only have to process a single batch of workbook files, it
can be made much more flexible if you anticipate needing to make such changes in the future.
The biggest hassle, of course, is putting together the myfilelist.txt file each time you want to
process a batch of files. Flexibility is added if the macro could simply use a directory and then
load each workbook from that directory.
Sub ChangeFiles2()
Dim MyPath As String
Dim MyFile As String
Dim dirName As String
Dim wks As Worksheet
Workbooks.Open MyFile
With ActiveWorkbook
For Each wks In .Worksheets
' Specify the change to make
wks.Range("A1").Value = "A1 Changed"
Next
End With
ActiveWorkbook.Close SaveChanges:=True
MyFile = Dir
If MyFile > "" Then MyFile = dirName & MyFile
Loop
End Sub
This macro uses whatever directory you specify for the dirName variable. Any workbook file
(ending with the .Xlsx extension) is loaded and processed.
Another approach is to have the macro ask the user which directory should be processed. You ca
use the standard Excel File dialog box to do this, in the manner shown in the following macro.
With Application.FileDialog(msoFileDialogFolderPicker)
Workbooks.Open MyFile
With ActiveWorkbook
For Each wks In .Worksheets
' Specify the change to make
wks.Range("A1").Value = "A1 Changed"
Next
End With
ActiveWorkbook.Close SaveChanges:=True
MyFile = Dir
If MyFile > "" Then MyFile = dirName & MyFile
Loop
End Sub
Three-Dimensional Transpositions
As a former heavy-duty Lotus 1-2-3 user at a prior job, Patti got VERY attached to a feature that
is sorely lacking in Excel: the ability to transpose data in three dimensions. Two-dimensional
transposition is supported in Excel, but Patti has not figured out a way to take a row or column or
table and spread it through a stack of worksheets. This was a function that was used daily by
everyone in her finance office, and she really misses it.
Patti is right; there is no built-in function to do this in Excel. The closest option is to use a
PivotTable and the “Show Pages” capabilities it includes. In general, you follow these steps if
you are using Excel 2007 or later:
5. Choose Show Report Filter Pages. Excel asks you to confirm that you want to show the
pages.
6 Click OK.
If you are using an older version of Excel, follow these steps, instead:
What you end up with is a series of worksheets, one for each entry the column you specified in
step 2. Those worksheets each contain a “page” of the PivotTable.
If this still doesn’t quite do what you want, then you’ll need to resort to using a macro to
transpose the data. Such a macro can get quite complex, but basically all it needs to do is step
through your data table and move each row (or column) of data to its own worksheet.
As an example, the following macro (Transpose3D) will take each row from a selected range of
cells and place that row on its own, newly created worksheet.
Sub Transpose3D()
Dim rngTbl As Range
Dim wsName As String
Dim R As Integer
Dim C As Integer
Dim i As Integer
Dim j As Integer
Dim Killit As Integer
Dim RCount As Integer
Dim CCount As Integer
Dim Table1() As Variant
Dim Row1() As Variant
RCount = Selection.Rows.Count
CCount = Selection.Columns.Count
If RCount < 2 Then
MsgBox ("Error; Select a range with more than one row.")
GoTo EndItAll
End If
wsName = ActiveSheet.Name
R = ActiveCell.Row
C = ActiveCell.Column
For i = 1 To RCount
If SheetExists(wsName & "_Row_" & i) Then
Killit = MsgBox("Sheet " & wsName & "_Row_" & i & _
" Already Exists!" & vbCrLf & _
" Cancel: Stop Transposition" & vbCrLf & _
" OK: Delete Sheet and Continue", vbOKCancel)
If Killit = vbCancel Then GoTo EndItAll
Application.DisplayAlerts = False
Sheets(wsName & "_Row_" & i).Delete
Application.DisplayAlerts = True
End If
Sheets.Add
ActiveSheet.Name = wsName & "_Row_" & i
Cells(R, C).Select
For j = 1 To CCount
Row1(1, j) = Table1(i, j)
Next j
Range(ActiveCell, ActiveCell.Offset(0, CCount - 1)) = Row1()
Sheets(wsName).Select
Next i
GoTo EndItAll
Abend:
MsgBox ("Error in Routine Transpose3D.")
EndItAll:
Application.DisplayAlerts = True
End Sub
Listing Combinations
Ron knows he can use the COMBIN function to determine the number of combinations that can
be made from a number of digits. He's wondering, however, if there is a way to list out all the
combinations themselves.
There is no built-in way to list combinations in Excel. You can, however, create a macro to do
the listing for you. If you want to find the unique combinations in a set of sequential numbers
starting at 1, then the following set of macros will do the trick. All you need to do is run the
function TestCNR and you will end up with a "matrix" of cells that represent the number of 4-
digit combinations in the sequential set of values ranging from 1 to 10.
Sub TestCNR()
Cnr 10, 4
End Sub
Sub Cnr(n, r)
i = 1
For j = 1 To r
Cells(i, j).Value = j
Next
Do Until Finished(n, r, i)
j = FindFirstSmall(n, r, i)
For k = 1 To j – 1
Cells(i + 1, k).Value = Cells(i, k).Value
Next
Cells(i + 1, j).Value = Cells(i, j).Value + 1
For k = j + 1 To r
Cells(i + 1, k).Value = Cells(i + 1, k - 1).Value + 1
Next
i = i + 1
Loop
End Sub
Function Finished(n, r, i)
Temp = True
For j = r To 1 Step -1
If Cells(i, j).Value <> j + (n - r) Then
Temp = False
End If
Next
Finished = Temp
End Function
Function FindFirstSmall(n, r, i)
j = r
Do Until Cells(i, j).Value <> j + (n - r)
j = j - 1
Loop
FindFirstSmall = j
End Function
The macro overwrites whatever is in your worksheet, so make sure you run the test with a blank
worksheet displayed. If you want to change the size of the set or the number of elements in the
subset, just change the values passed in the TestCNR routine.
If you want to pull unique combinations from a string of characters (for instance, the letters of
the alphabet), then you need to use a different set of macros. The following will work fine; it
assumes that the characters you want to use as your "universe" is in cell A1 and the number you
want in each unique combination is in cell A2.
Sub FindSets()
Dim iA() As Integer
Dim sUniv As String
Dim iWanted As Integer
Dim j As Integer
Dim k As Integer
ReDim iA(iWanted)
For j = 1 To iWanted
iA(j) = j
Next j
Run the FindSets macro and the different combinations desired end up in column 2. Be careful
when running the macro, however. The number of combinations can get very large very quickly.
For instance, if you put 26 letters (A through Z) in cell A1 and the value 5 in cell A2, the macro
will crash if you aren't using Excel 2007 or a later version. Why? Because there are 65,780
possible five-character combinations and only 65,536 rows in which to place them.
There is no event that Excel can recognize as a “click” on a cell. Perhaps the closest event is the
SelectionChange event, which is triggered every time the cell selection changes. The event
handler could then check to make sure that the cell selection is within the C3:P312 range, and
then place an “x” in the cell if it is. The event handler shown here will do that:
There is a problem with this approach, however: Not only will the SelectionChange event trigger
when you click on a different cell, it also triggers if you use the keyboard to move from one cell
to another in the worksheet. This means that if you use the keyboard to move about the
worksheet you will leave a tail of “x” characters in each cell you transit.
One way around this is to change the event that triggers the check and change of the cells. While
Excel has no “click” event, there is a “double click” event. This means that you can change the
cell on which you double click, as shown here:
Actually, there is a very quick way to manually delete all the non-formula information in a
worksheet. Follow these steps:
If you have a lot of worksheets in a workbook and you want to delete all the constants from each
of the worksheets, you might want to use a macro that automates the above steps for the entire
workbook.
Sub DelAllConstants()
Dim wks As Worksheet
Dim rng As Range
On Error GoTo 0
If Not rng Is Nothing Then
rng.ClearContents
End If
Set rng = Nothing
Next
Set wks = Nothing
End Sub
There are a few ways you can approach this problem. Before proceeding with any solution,
however, you should make sure that you aren’t trying to change something that isn’t really
broken. For instance, you’ll want to make sure that the “E” that appears in the number isn’t part
of the format of the number—in other words, a designation of exponentiation. If it is, then you
don’t really want to remove the character because it will end up changing the nature of the
underlying number.
If you determine that the characters aren’t part of the number’s format, then you can first try
using formulas to remove the alpha characters. If the values you want to change are in column A,
you could enter the following (very long) formula in column B:
=MID(A1,MATCH(TRUE,ISERROR(1*MID(A1,ROW(INDIRECT
("1:"&LEN(A1))),1)),0),-MATCH(TRUE,ISERROR(1*MID
(A1,ABS(ROW(INDIRECT("1:"&LEN(A1)))-LEN(A1)-1),1))
,0)+LEN(A1)+2-MATCH(TRUE,ISERROR(1*MID(A1,ROW
(INDIRECT("1:"&LEN(A1))),1)),0))
Make sure you enter this as an array formula by pressing CTRL+SHIFT+ENTER. Then enter the
following into column C:
=SUBSTITUTE(A1,B1,"")
The result is that column C contains the values from column A, without the alpha characters.
You could use Paste Special to copy the information from column C to another column so that
you end up with actual values instead of formula results.
This approach may work great for short-term use on a single workbook, but if you need to do
this sort of data processing more often then you will want to create a user-defined function to do
the processing. Here’s an example:
sTemp = ""
For x = 1 To Len(sWord)
sChar = Mid(sWord, x, 1)
If Asc(sChar) >= 48 And _
Asc(sChar) <= 57 Then
sTemp = sTemp & sChar
End If
Next
OnlyNums = Val(sTemp)
End Function
=OnlyNums(A1)
The function returns a numeric value. If you want to create an even shorter macro to do the
processing, consider the following:
StripChar = ""
For I = 1 To Len(aText)
aChar = Mid(aText, I, 1)
Select Case aChar
Case "0" To "9"
StripChar = StripChar & aChar
End Select
Next
End Function
=STRIPCHAR(A1)
=VALUE(STRIPCHAR(A1))
The first returns a text string consisting of the digits, the second returns the numeric version of
that string.
There is no built-in function you can use in Excel to garner this information. Thus, the cleanest
approach would be to create your own function, such as the following:
Application.Volatile
vOps = Array("+", "-", "*", "/", "^")
TermsInFormula = iCount
Set AWF = Nothing
End Function
The function checks the formula in the referenced cell to see how many of the five mathematical
operators it contains. The number of terms in the formula is always one more than the number of
operators, since each term is separated by an operator.
In order to use the function, you would enter the following formula into a cell, assuming that you
want to know how many terms are in the formula in cell A1:
=TermsInFormula(A1)
The function will work on formulas, numbers, and text that looks like a formula. It will not,
however, consider the "/" in dates as an operator since the display of the date is not part of the
Formula property that the function examines. (The display of dates is part of the Text or Value
property, not the Formula property.)
This question is not as simple as it seems. For some people, finding the number of digits in a
value, less any decimal points or negative signs. If that is all you need, then something like this
formula will work just fine:
=IF(A1<0,IF(A1=INT(A1),LEN(A1)-1,LEN(A1)-2),IF(INT(A1)=A1,LEN(A1),LEN(A1)-1))
The reason that this isn't that simple, however, is because what constitutes the number of
significant digits in a value depends on many things. The bottom line is that you can't always tell
by looking at a value how many significant digits it has.
For instance, the value 100 could have 1, 2, or 3 significant digits. It is presumed that the value
1.00 has 3 significant digits, but that may not be the case if the value displayed is the result of
formatting imposed by Excel—for instance, the value in the cell could be 1.0000437, which
Excel formats as 1.00. You can discover more about the topic of significant digits here:
http://excel.tips.net/T001983
There are some generally accepted ways to identify significant digits in a number, but any
attempt to codify a set of rules is always open to debate. One such set of rules has been noted at
Wikipedia, in the "Identifying Significant Digits" section of this article:
http://en.wikipedia.org/wiki/Significant_figures
With at least a rudimentary set of rules in mind (such as the one in the Wikipedia article) it is
possible to develop a user-defined function that will give you the most likely number of
significant digits for a value.
Application.Volatile
Set rCell = rng.Cells(1)
sText2 = Trim(rCell.Text)
sText = ""
'find position of decimal point (it matters)
iDec = InStr(sText2, ".")
sText = Mid(sText, 2)
Wend
iMax = Len(sText)
=SigFigs(A1, x)
You can replace x with either 1 or 2. If you specify 1, then the function returns the minimum
number of significant digits. If you specify 2, then the function returns the maximum number of
significant digits. In most cases the two possible return values will be the same, except with
values that are whole numbers, without a trailing decimal point, that have trailing zeroes. In other
words, if you use the function to evaluate the number 1234000, then the minimum (x is 1)
returns 4 and the maximum (x is 2) returns 7.
The function takes into consideration how the number appears in the worksheet, meaning that it
matters how the number is formatted. It strips out any formatting characters, such as negative
signs, parentheses, and commas.
The IF function can take up to three parameters. The first parameter is the comparison that is to
be made, the second parameter is what should be returned if the comparison is true, and the third
is what should be returned if the comparison is false. It is possible to leave off the last parameter,
but if you do then Excel will return the value 0 if the comparison is false. (This is what Vineet is
seeing returned by his IF function usage.)
The obvious solution, then, is to make sure that you provide the IF function with something that
should be returned when the comparison is false. For instance, let's say that your formula is in
cell B1 and you are comparing something in cell A1. The formula you use may look like this:
=IF(A1<10,"under ten",B1)
Note that the words "under ten" are returned if the value in A1 is less than 10. If this condition is
not met, then the value in B1 is returned. Since this formula is in cell B1, this means that the
previous value in the cell is returned if the condition is false.
It also means that the formula contains a circular reference. For circular references to work OK
you need to let Excel know that it is OK for them to occur in your worksheet. Choose Tools |
Options | Calculation tab and make sure the Iteration check box is selected. Excel will now allow
the circular reference without complaint.
If you don't want to allow a circular reference in your worksheet, then the only recourse is to
create a macro that updates the value in cell B1 based upon any changes to cell A1:
This simple macro, when added to the ThisWorkbook module, is executed every time there is a
change in the workbook. If the value is cell A1 is changed (and only that cell), then the value is
checked to see if it is less than 10. If it is, then the value in cell B1 is changed. If it isn't, then the
value in cell B1 is left alone.
There is one "gottcha" that you need to keep in mind with any of the approaches discussed thus
far, formula or macro. If the value in cell A1 is (let's say) 15, then cell B1 will contain what was
there before, whatever it was. If you change the value in cell A1 to (let's say) 7, then B1 will
change to "under ten." That's fine, but from that point on cell B1 will never appear to change.
Why? Because if you then change cell A1 to a value greater than 10, cell B1 will contain (as just
explained) what was there before. And, as you now understand, the value that was there before is
the result of the previous true result, which was "under ten." Thus, true or false, the formula or
macro from this point on displays the text "under ten."
1. Select the cell that you want to have the drop shadow.
2. Make sure the Drawing toolbar is displayed. (If you don't see it, click View | Toolbars |
Drawing.)
3. On the Drawing toolbar, click the Shadow Style tool. (It is the second from the right.)
You'll see a palette of various shadows you can apply.
4. Select the shadow desired.
That's it. What Excel technically does is to add a text box, the exact same size as the cell you
selected in step 1, over the top of the cell. This text box is transparent so that the cell contents
show through, but it has borders applied so that you see the drop shadow.
If you are using Excel 2007 or a later version, which don't have a Drawing toolbar, then you are
pretty much out of luck as far as a simple answer goes. Even searching through the tools
available for the Quick Access Toolbar, there is no command that functions the same as the
Shadow Style tool on the old Drawing toolbar. The best you can do is to follow these general
steps:
1. Add an AutoShape to the worksheet that is the same size as the cell.
2. Turn off the fill for the AutoShape.
3. Add a drop shadow to the AutoShape.
This, of course, could get a bit tedious over time. For this reason, you may want to use a macro
to actually perform the steps. The following macro will add a drop shadow to whatever range
you have selected in the worksheet.
Sub AddDropShadows()
Dim ar As Range
For Each ar In Selection.Areas
AddDrop ar
Next ar
End Sub
With rng.Parent.Shapes.AddShape( _
msoShapeRectangle, rng.Left, rng.Top, _
rng.Width, rng.Height)
.Fill.Visible = msoFalse
.Shadow.Obscured = msoTrue
.Shadow.Type = vShadowType
.Shadow.ForeColor.SchemeColor = vSchemeColor
.Shadow.Visible = msoTrue
.ThreeD.Visible = msoFalse
End With
End Sub
There is one thing that you should know about adding drop shadows to cells in Excel 2007 and
later versions: The shadow, itself, is not as “smart” as in earlier versions of Excel. When adding
a drop shadow in Excel 2003, for instance, the shadow is appears at the bottom and right side of
the shape added, as if the shape is filled. In Excel 2007 and later versions the drop shadow is for
all four sides, so it looks a bit unprofessional when applied to a cell.
There are a couple of approaches you can take. First, you could use conditional formatting to do
the highlighting. Set the conditional formatting to “Cell Value Is” “Not Equal To” and then enter
the formula as the comparison. This will tell you when the value in the cell does not equal
whatever the formula is, but a potential “gottcha” is if the person overrides the formula with the
result of that formula. For instance, if the formula would have produced a result of “27” and the
user types “27” into the cell.
Another possibility is to define a formula in an named constant, and then use that named constant
in a conditional format. Follow these steps:
1. Choose Name from the Insert menu, then choose Define. Excel displays the Define
Name dialog box. If you are using Excel 2007 or a later version, display the Formulas
tab of the ribbon and, within the Defined Names group, click Define Name. Excel
displays the New Name dialog box.
2. In the Names in Workbook box (the Name box in Excel 2007 and later versions), enter
the name you want assigned to this formula. For this example, use CellHasNoFormula.
3. Select whatever is in the Refers To box, at the bottom of the dialog box, and press DEL.
This gets rid of whatever Excel had there before.
4. Enter the following formula in the Refers To box:
=NOT(GET.CELL(48,INDIRECT("rc",FALSE)))
5. Click OK.
Now you can set up some conditional formats and use this named formula in the format. Simply
set the condition to “Formula Is” (versions before Excel 2007) or set the conditional formatting
rule type to Use a Formula to Determine which Cells to Format (Excel 2007 and Excel 2010) and
enter the following formula in the condition:
=CellHasNoFormula
The formula returns True or False, depending on whether there is a formula in the cell or nor. If
there is no formula, then True is returned and whatever format you specify is applied to the cell.
Another approach is to use a user-defined function to return True or False, and then set up the
conditional format. You could use a very simple macro, such as the following:
You can then set up a conditional format to use a formula. You can use the following formula if,
for instance, you are conditionally formatting cell C1:
=NOT(IsFormula(C1))
The formula returns True if there is no formula in the cell, so the conditional format is applied.
The only downside of using any of these formulas to determine if a formula is in the cell is that it
cannot determine if the formula in the cell has been replaced with a different formula. This
applies to both the macro approach and the defined formula approach.
A totally different approach is to rethink your worksheet a bit. You can separate cells for user
input from those that use the formulas. The formula could use an IF function to see if the user
entered something in the user input cell. If not, your formula would be used to determine a value;
if so, then the user’s input is used in preference to your formula. This approach allows you to
keep the formulas you need, without them being overwritten by the user. This results in great
integrity of the formulas and the worksheet results.
If you miss the old Center Across Selection button, you may wonder if you can ever get it back.
(You probably know that you can do the same thing by displaying the Alignment tab of the
Format Cells dialog box and then use the Horizontal drop-down list to choose Center Across
Selection.) There is no built-in Center Across Selection tool that you can use, but you can create
a simple macro that will do the same thing:
Sub CenterAcrossColumns()
With Selection
.HorizontalAlignment = xlCenterAcrossSelection
.MergeCells = False
End With
End Sub
Once you have the macro, you can assign it to a shortcut key or a toolbar button.
For instance, if you wanted to make the contents of cell A1 bold, you could use the following in
your macro:
Likewise, if you wanted to make the currently selected cell bold, you could use the following
code:
Selection.Font.Bold = True
If you wanted to explicitly turn off the bold attribute of a particular cell, all you need to do is
change True to False in the foregoing examples.
It is just as easy to format cell contents as italics, the only difference is that you use the Italic
property of the Font object. All you need to do is replace the Bold property in the above
examples with the Italic property, as shown here:
This is best done by developing a user-defined function that can do the counting for you. The
following example steps through a range of cells and counts for whatever color index value you
specify.
iCount = 0
For Each rCell In rng
v = rCell.Font.ColorIndex
If IsNull(v) Then
For x = 1 To Len(rCell.Value)
If rCell.Characters(x, 1).Font.ColorIndex _
= iColor Then
iCount = iCount + 1
Exit For
End If
Next
ElseIf v = iColor Then
iCount = iCount + 1
End If
Next
CountColorIndex = iCount
End Function
The function first looks at the font color of the cell as a whole. If the cell color is Null, that
means that the color of individual characters has been changed and so the function starts looking
through each character. If it finds the matching color, the count (iCount) is incremented and the
function stops looking through each character.
If the cell color is not Null, then the function determines if the font color of the cell as a whole
matches the desired color. If it does, then the count is incremented.
This process is repeated for each cell in the specified range, and the function then returns the
value of the count. You use the function in the following manner:
=CountColorIndex(B7:D42,3)
This formula checks the range B7:D42 to see if there are instances of the color red. The count is
then returned by the formula.
It is worth mentioning that the function relies on color index values. The normal, default value
for red is 3 and the value for blue is 5, but these values can be modified by the user, and they
may vary based on the version of Excel you are using. For the function to return the desired
results, you’ll need to modify the color index value, specified in the second parameter of the
formula, so that it represents the color indexes used in your particular workbook.
Unfortunately, Excel’s VBA doesn’t have constants defined for each of the 56 colors in the
palette. The only colors defined, by name, are members of the ColorConstants class, and there
are eight members of the class: vbBlack, vbWhite, vbRed, vbGreen, vbBlue, vbYellow,
vbMagenta, and vbCyan.
In VBA you can use the ColorIndex property to define which color you want to use from Excel’s
palette. The problem is that ColorIndex is not a color; it is an index into the palette. Thus, a
ColorIndex of 1 is the first color in the palette, 2 is the second, and so on. You can see this in
action by looking at the sample code at this URL:
http://www.ozgrid.com/VBA/ReturnCellColor.htm
This code examines the ColorIndex property for a cell and returns a color name. The name
returned, however, is not a constant for the color; it is only a description of what color the palette
at that index appears to be.
If you want to set the color of a cell, you actually should use the Color property. This property
allows you to use the eight VBA color constants mentioned earlier. It just so happens that if you
use these Color property to set the interior color of a cell, you’ll find that the eight named colors
correspond to ColorIndex values of 1 through 8. The following macro illustrates this nicely:
Sub CheckColors()
Dim arr8Colors As Variant
Dim i As Integer
arr8Colors = Array( _
vbBlack, vbWhite, vbRed, vbGreen, _
vbBlue, vbYellow, vbMagenta, vbCyan)
For i = 0 To 7
Selection.Offset(i, 0).Interior.Color = arr8Colors(i)
Selection.Offset(i, 1).Value = Selection.Offset(i, 0).Interior.ColorIndex
Next i
End Sub
This correspondence for the first eight values between Color and ColorIndex should only be
taken as an artifact of history, dating back to the days when Excel only allowed you to use eight
colors—the eight colors defined with VBA constants. If you want to specify some other color for
a cell, you should use the RGB function to specify the Color property, as shown here:
The RGB function allows you to specify the red, green, and blue components of any color. Each
component can range in value from 0 to 255.
You aren't alone, Jerri. The new color schemes introduced in Excel 2007 seem to baffle many
people. The simple approach to colors used in older versions of Excel is gone, and the new
approach doesn't seem to have any humanly discernable rhyme or reason. Doing simple searches
at Microsoft doesn't turn up any helpful information. For instance, a search of Microsoft online
support for PatternTintAndShade (which should get right to the heart of the matter) returns
nothing—and this is four years after Excel 2007 was released! A wider search for the same word,
looking at all of Microsoft.com, returns something only marginally more useful; it returns a page
that describes the PatternTintAndShade property as a property that "returns or sets a tint and
shade pattern." The reaction that comes to mind is "well, duh!"
In searching around the web there are lots of questions that crop up very similar to yours and
precious few answers. There was one site that has a bit of useful information and is well worth
reading:
http://www.databison.com/index.php/excel-color-palette-and-color-index-change-
using-vba/
Sorry about the long URL; you'll want to make sure you get it all. Perhaps, as time goes on, more
information will come available.
The easiest way to do this is to use a macro that grabs the values in A1:A3 and then modifies the
color of cell C1 based on those values. Ideally, the macro should check to make sure that the
values in the source cells are in the range of 0 through 255. The following macro works great for
this purpose:
Range("C1").Interior.Color = _
RGB(lRed, lGreen, lBlue)
End If
End Sub
Note that this macro should be added to the code for the worksheet on which the cells exist. (Just
right-click the sheet tab and choose View Code, then add the macro there.) It is an event handler
that is automatically run every time there is a change in cell A1, A2, or A3. The values in those
cells are ensured to be between 0 and 255 by taking the absolute value of the cell contents and
using the remainder (modulo) of dividing it by 256.
Excel doesn't include a function to do this, but if you only need to check out the RGB values for
a single cell and you are using Excel 2007 or a later version, the easiest way is to follow these
steps:
1. Select the cell that is formatted with the color you want to check.
2. Display the Home tab of the ribbon.
3. Click the down-arrow at the right side of the Fill Color tool, in the Font group. Excel
displays a small palette of colors and some other options.
4. Choose More Colors. Excel displays the Colors dialog box.
5. Make sure the Custom tab is displayed.
6. At the bottom of the dialog box you can see the individual values for the red, green, and
blue components of the color in the cell.
7. Click OK when done.
If you have a need to get the values more often or if you are using an older version of Excel, then
creating your own user-defined function is the way to go. The function you use depends on what
you want to actually have returned to your worksheet. For instance, if you want to have the
traditional six-character hex code for RGB colors returned, you would use the following very
simple macro:
This macro looks at the interior color for any cell you reference, puts the hex values for the color
in the right order, and returns the string to Excel. To use the function you simply invoke it, in
your worksheet, with a cell referenced in this manner:
=getRGB1(B4)
You may not want the traditional hex codes for the RGB colors, however. If you want to get the
decimal values for each of the colors, then the following macro returns that:
C = rcell.Interior.Color
R = C Mod 256
G = C \ 256 Mod 256
B = C \ 65536 Mod 256
getRGB2 = "R=" & R & ", G=" & G & ", B=" & B
End Function
Invoked the same way as the getRGB1 macro, this version returns a string such as "R=255,
G=204, B=0". You can also modify the macro even further so that it returns a single value, based
upon a parameter you set:
C = rcell.Interior.Color
R = C Mod 256
G = C \ 256 Mod 256
B = C \ 65536 Mod 256
If opt = 1 Then
getRGB3 = R
ElseIf opt = 2 Then
getRGB3 = G
ElseIf opt = 3 Then
getRGB3 = B
Else
getRGB3 = C
End If
End Function
To use the macro, simply add a second parameter to the function used in your worksheet,
specifying what you want:
=getRGB3(B4,1)
If the second parameter is 1, then the function returns just the red value. If you specify a second
parameter of 2, then the green value is returned, and 3 returns the blue value. Any other value for
the second parameter (or if you omit it entirely) returns the full decimal value of the interior
color.
If you don't want to go the route of creating a macro, or if you want to determine colors in more
than just your Excel worksheet, you might consider a third-party utility. One that looks
interesting is Instant Eyedropper, which is free. You can find more information about it here:
http://instant-eyedropper.com
It's fairly obvious that you can change the background colors of any cells manually, so there is
no need to go into that option for making the changes. What you need is a way to make changes
to all the cells at once. If you are using Excel 2007 or a later version, follow these steps:
1. Press CTRL+H to display the Replace tab of the Find and Replace dialog box.
2. Expand the dialog box by clicking the Options button.
3. Click the Format button at the right side of the Find What box. Excel displays the Find
Format dialog box.
4. Make sure the Fill tab is selected.
5. Use the controls in the dialog box to specify the background color you want to replace.
6. Click OK.
7. Click the Format button at the right side of the Replace With box. Excel displays the
Replace Format dialog box.
8. Make sure the Fill tab is selected.
9. Use the controls in the dialog box to specify the background color you used when
changing the cells.
10. Click OK.
11. Click Replace All.
If you are using Excel 2002 or 2003 you can follow these steps:
1. Press CTRL+H to display the Replace tab of the Find and Replace dialog box.
2. Expand the dialog box by clicking the Options button.
3. Click the Format button at the right side of the Find What box. Excel displays the Find
Format dialog box.
4. Make sure the Patterns tab is selected.
5. Use the controls in the dialog box to specify the background color you want to replace.
6. Click OK.
7. Click the Format button at the right side of the Replace With box. Excel displays the
Replace Format dialog box.
8. Make sure the Patterns tab is selected.
9. Use the controls in the dialog box to specify the background color you used when
changing the cells.
10. Click OK.
11. Click Replace All.
If you are using an older version of Excel, these steps won't work. Instead you'll need to use a
macro to do the changes. The following is an example of one that should work. (You can also
use this macro in later versions of Excel, as well.)
Sub ChangeColor()
Dim rCell As Range
If Selection.Cells.Count = 1 Then
MsgBox "Select the range to be processed."
Exit Sub
End If
For Each rCell In Selection
If rCell.Interior.Color = RGB(255, 0, 0) Then 'red
rCell.Interior.Color = RGB(0, 0, 255) 'blue
End If
Next rCell
End Sub
Excel, however, doesn’t allow you to create your own formatting tools and have them accessible
while editing a cell. This is because Excel “deactivates” all user-defined macros while you are
doing the editing. You are left with formatting the cell contents via the Format Cells dialog box
(CTRL+1).
There is a sneaky way you can use to create your own formatting tools, however. This involves
the use of user forms and VBA to create your own formatting “dialog box.” (I know—this is not
really a dialog box, but a form.) Creating your own user form is not terribly difficult, but it isn’t
for the faint-of-heart when it comes to macros. Follow these steps to create your own form:
3. Using the controls in the form toolbox, add three CommandButton controls across the
top of the form.
4. Change the properties for the left CommandButton control so its Name is btnSuper and
its Caption is Superscript.
5. Change the properties for the center CommandButton control so its Name is btnSub and
its Caption is Subscript.
6. Change the properties for the right CommandButton control so its Name is btnNormal
and its Caption is Normal.
7. Just under the three buttons, add a TextBox control. You don’t need to change any
properties for this control.
8. Just under the TextBox control, add a fourth CommandButton control.
9. Change the properties for this last CommandButton control so its Name is btnExit and
its Caption is Exit.
That’s it; you’ve created your user form, and you are ready to associate macro code with the
controls you just placed. With the user form selected, press F7 to display the Code window for
the form. The window may contain a line or two of automatically generated code. Replace this
with the following code:
Close the Code window for the user form, and close the form window itself. You now need to
create a very short macro that will display the actual user form. This macro is created the same as
any other Excel macro, and should look like this:
Sub DoForm()
UserForm1.Show
End Sub
You can now close the VBA Editor window. In order to use the macro, select the cell you want
to edit, and then run the DoForm macro. Excel displays your user form, which contains the text
in the selected cell. You can then select text within the user form and use the buttons
(Superscript, Subscript, and Normal) to change the formatting of the actual cell contents. The
macro affects the contents of the cell, not the contents of the user form. Thus, it is helpful to be
able to see both the selected cell and the user form on the screen at the same time.
=C7
This copies the contents from cell C7 to the current cell, and updates whenever the contents of
cell C7 change. What if you are not just interested in copying cell values, but also want to copy
formatting from one cell to another?
Unfortunately, there is no intrinsic way to do this in Excel. There are two workarounds you can
try, however. First, you can create a macro that will find out whenever cell C7 changes, and if it
does, the macro copies the contents of the cell (including formatting) to the target cell. For
instance, the following macro will run every time there are changes in the worksheet. When the
change is in cell C7, then the contents of C7 are copied to cell E3 on Sheet1.
There are some downsides to this approach. First, it can be slow, particularly if you have quite a
few cells that you want to copy in this manner. In addition, the macro only runs if the contents of
cell C7 are actually changed, not if the formatting alone of C7 is changed. (There is no way to
trigger an automatic event whenever formatting is changed.)
An alternative to the macro approach is to use the Camera tool in Excel. This has been covered
in other issues of ExcelTips, but essentially the camera is a way to copy a dynamic image of a
range of cells from one place to another. It is the image of the source cells that is shown, and it is
shown as a graphic, not as the contents of any target cells. Since the graphic is dynamic,
whenever the source cells are changed (including formatting), the image is also updated to reflect
the change.
To use the Camera tool, you must customize a toolbar so that the tool is available; it is not
available by default. When you are doing your customizing, the Camera tool is easiest to find if
you choose to display all commands. The Camera tool has a small camera icon next to it.
With the Camera tool in place, follow these steps to use it:
1 : 0.3333333333
The reason that the resulting C1 doesn’t match what is shown in B1 (0.33) is because the value
in B1 isn’t really 0.33. Internally, Excel maintains values to 15 digits, so that if cell B1 contains a
formula such as =1/3, internally this is maintained as 0.33333333333333. What you see in cell
B1, however, depends on how the cell is formatted. In this case, the formatting probably is set to
display only two digits beyond the decimal point.
There are several ways you can get the desired results in cell C1, however. One method is to
simply modify your formula a bit so that the values pulled from cells A1 and B1 are formatted.
For instance, the following example uses the TEXT function to do the formatting:
In this case, A1 is formatted to display only whole numbers and B1 is formatted to display only
two decimal places.. You could also use the ROUND function to achieve a similar result:
Another possible solution is to change how Excel deals with precision in the workbook. Follow
these steps if you are using a version of Excel prior to Excel 2007:
1. Choose Options from the Tools menu. Excel displays the Options dialog box.
2. Make sure the Calculation tab is displayed.
If you are using Excel 2007 or a later version, then you should follow these steps:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
2. Click the Advanced option at the left of the dialog box.
3. Scroll through the available options until you see the When Calculating This Workbook
section.
Now, Excel uses the precision shown on the screen in all of its calculations and concatenations
instead of doing calculations at the full 15-digit precision it normally maintains. While this
approach may be acceptable for some users, for others it will present more problems than it
solves. You will need to determine if you can live with the lower precision in order to get the
output formatted the way you expect.
Still another approach is to create your own user-defined function that will return what is
displayed for the target cell, rather than what is stored there. The following macro will work
great in this regard:
To use this macro, you would use a formula like this in your worksheet:
It is important to understand that these two types of formatting are separate and distinct from
each other. For instance, if you explicitly format a cell as bold red, that is the way it appears. If
you later apply a conditional format to it, and that format causes the cell to appear in green, that
is exactly what is happening—the cell is appearing in green, but it is still formatted as red.
What does this have to do with VBA? If you test the formatting of a cell in VBA, then the
formatting you are testing is the explicit formatting. In the above scenario, this means that the
test will always indicate that the cell is bold red, and never report that it is green, regardless of
what the conditional formatting is doing to the cell. This is because conditional formatting
affects the cell's display, not its underlying (explicit) formatting.
The other upshot of all this is that if you want to find out what conditional formatting is being
displayed, you may need to recreate all your conditional tests within VBA. This can get rather
complex rather quickly. For more information on this topic, there is a great page you can refer to.
Check out one of Chip Pearson's pages, here:
http://www.cpearson.com/excel/CFColors.htm
The best way to go about this task depends on the version of Excel you are using. If you are
using Excel 2003 or a later version of the program you can simply use Excel's Find and Replace
tool to make the change. Follow these steps:
1. Press CTRL+H. Excel displays the Replace tab of the Find and Replace dialog box.
2. Click the Options button, if necessary, to enlarge the dialog box.
3. Click the Format button to the right of Find What line. Excel displays the Find Format
dialog box.
4. Make sure the Number tab is displayed.
5. Use the controls in the dialog box to specify the format you want to find.
6. Click OK to close the Find Font dialog box.
7. Click the Format button to the right of Replace With line. Excel displays the Replace
Format dialog box.
8. Make sure the Number tab is displayed.
9. Use the controls in the dialog box to specify the format you want to use as your
replacement.
10. Click OK to close the Replace Font dialog box.
11. Use the Within drop-down list to choose Workbook.
12. Click Replace All.
If you are using an older version of Excel, then the Find and Replace tool doesn't allow you to
search or replace formatting. Instead, you must use a macro to do the changes. Here's an example
of a macro that simply goes through all the used cells in the workbook and sets all the formats to
General.
Sub FormatGeneral()
Dim iSht As Integer
Dim rng As Range
If you wanted to get a bit more selective in which formats were replaced, then you'll need to
check the existing format of the cells as you go through them. For instance, the following macro
checks for any cells formatted as Currency and then changes only those cells to a General
format.
Sub CurrencyToGeneral()
Dim iSht As Integer
Dim rng As Range
Dim c As Range
If you want to make the macro even more flexible, you can have it ask you to click on a cell that
uses the format you want to find and then click on a cell that uses the format you want to change
those cells to.
There is nothing built-in to Excel that will allow this formatting information to be grabbed. You
can, however, create a very simple macro that will do the trick. The following macro takes, as
arguments, a cell reference and optionally an indicator of what data you want returned.
=FontInfo1(A1,1)
The second parameter (in this case 1) means that you want the font name. If you change the
second parameter to 2 then the font size is returned. (Actually you could have the second
parameter be anything other than 2—or leave it off entirely—and it returns the font name.)
If you want to return both values at once, you can apply a lesser-known way of returning arrays
of information from a user-defined function. Try the following:
Select two horizontally adjacent cells (such as C7:D7) and type the following formula:
=FontInfo(A1)
Because the function returns an array, you need to terminate the formula entry by pressing
SHIFT+CTRL+ENTER. The font name appears in the first cell (C7) and the font size appears in the
second cell (D7).
changed to a new font specified by corporate mandate. (You know how corporate mandates can
be!)
The manual way to approach this task is to load each workbook, go through each worksheet,
select the cells, and change the fonts in those cells. To make Hamish’s task even more complex,
he needs to change multiple fonts in each workbook. In other words, given fonts A, B, C, and D,
Hamish needs to change font A to C and font B to D.
The best way to approach this problem is through the use of a macro. There is so much loading,
searching, and changing that is necessary that it only makes sense to relegate the work to a
macro. The following macro should do the job:
Sub ChangeFontNames()
Dim vNamesFind
Dim vNamesReplace
Dim sFileName As String
Dim Wkb As Workbook
Dim Wks As Worksheet
Dim rCell As Range
Dim x As Integer
Dim iFonts As Integer
Dim sPath As String
Application.ScreenUpdating = False
iFonts = UBound(vNamesFind)
If iFonts <> UBound(vNamesReplace) Then
MsgBox "Find and Replace Arrays must be the same size"
Exit Sub
End If
sFileName = Dir(sPath & "*.xls")
Do While sFileName <> ""
Set Wkb = Workbooks.Open(sPath & sFileName)
For Each Wks In Wkb.Worksheets
For Each rCell In Wks.UsedRange
For x = 0 To iFonts
With rCell.Font
If .Name = vNamesFind(x) Then _
.Name = vNamesReplace(x)
End With
Next
Next
Next
Wkb.Close(True)
sFileName = Dir
Loop
Application.ScreenUpdating = True
Set rCell = Nothing
Set Wks = Nothing
Set Wkb = Nothing
End Sub
To use the macro with your own workbooks, there are a couple of things you need to do. First,
make sure that all the workbooks you want to change are stored in a single folder and that you
know the name of the folder. Then, within the macro, change the variables defined near the
beginning of the macro. Change the elements of the vNamesFind and vNamesReplace arrays to
match the names of the fonts you want to respectively find and replace. You should then change
the sPath variable so it contains the full path to the folder containing your workbooks. (Don’t
forget a trailing backslash on the path.)
When you run the macro, it loads each workbook in the folder, in turn. Then, it goes through
each worksheet in each workbook, and examines every cell. If the cell has one of the fonts to be
found, then it is replaced with the respective replacement font. When the macro is done with the
workbook, it is saved and the next workbook is processed.
Those interested in avoiding this type of problem on new worksheets should explore how to use
styles in Excel. You can define any number of styles and use them throughout a workbook. If
you later need to change the formatting for specific cells, all you need to do is change the
underlying styles. (Styles have been covered in other issues of ExcelTips.)
The answer depends, in part, on the version of Excel you are using. If you are using Excel 2002
or a later version of Excel, the answer is easy—you do it virtually the same way that you do in
Word. Follow these steps:
1. Press CTRL+H to display the Replace tab of the Find and Replace dialog box.
2. Click the Options button to expand the dialog box.
The expanded Replace tab of the Find and Replace dialog box.
3. In the Find What box, enter the word you want to make bold.
4. Enter the same word in the Replace With box.
5. Click the Format button to the right of the Replace With box. Excel displays the
Replace Format dialog box.
6. Click the Font tab.
While this appears quite easy, you need to remember that these steps change the formatting of
entire cells, not just words within a cell. Thus, if you were searching and replacing the word
“brown,” then any cell that contained the word “brown” would be made bold—the entire cell,
not just the word.
If you are using an older version of Excel (Excel 97 or Excel 2000) or you want to only affect
words within the cell, then these steps won’t work. Instead you’ll need to resort to a macro to do
the bolding. Basically, you’ll need a macro that looks through a worksheet and determines what
can be changed. (You cannot make individual words or digits in formulas or numeric values
bold; you can only make changes to the word-level formatting for text constants.)
Once the macro finds cells it can process, it needs to search through the cells for the desired
word, and then make that text bold. The following macro implements this very strategy:
Sub FindAndBold()
Dim sFind As String
Dim rCell As Range
Dim rng As Range
Dim lCount As Long
Dim iLen As Integer
Dim iFind As Integer
Dim iStart As Integer
sFind = InputBox( _
Prompt:="What do you want to BOLD?", _
Title:="Text to Bold")
If sFind = "" Then
MsgBox "No text was listed"
GoTo ExitHandler
End If
iLen = Len(sFind)
lCount = 0
If lCount = 0 Then
MsgBox "There were no occurrences of" & _
vbCrLf & "' " & sFind & " '" & _
vbCrLf & "to bold."
ElseIf lCount = 1 Then
MsgBox "One occurrence of" & _
vbCrLf & "' " & sFind & " '" & _
vbCrLf & "was made bold."
Else
MsgBox lCount & " occurrences of" & _
vbCrLf & "' " & sFind & " '" & _
vbCrLf & "were made bold."
End If
ExitHandler:
Set rCell = Nothing
Set rng = Nothing
Exit Sub
ErrHandler:
MsgBox Err.Description
Resume ExitHandler
End Sub
The macro first sets the search range to those cells that contain text constants. It then prompts the
user for a word that needs to be changed. Once entered, the macro then starts looking through all
the cells in the range. Each cell is checked to see if it contains the target word. If so, then the
.Bold property for those characters is set and the macro continues searching.
The macro also keeps track of how many changes were made, displaying the total changes at the
end of its work.
There are a couple of ways you can approach this issue. Perhaps the easiest approach is to simply
sort or filter the column that has the text values in it. Excel allows you to filter and sort based on
text color, which means that either you could see just the red-text cells or put all the red-text cells
into a contiguous range. Then it is an easy task to cut the red-text cells and paste them into the
next column.
Now your cells are sorted, by color, with the color specified in step 5 at the beginning of the cell
range. You can easily copy it or move it to a different column. You could use the same general
steps if you wanted to filter your text values based on font color.
If you prefer, you could also use a macro to move your red-text cells. Here's an example that
copies the cell value and font color one cell to the right.
Sub MoveRedText1()
Dim c As Range
To use the macro, just select the cells you want to analyze and then run the macro. It doesn't
copy all formatting of the cells it is moving; if that is critical you can actually use a much simpler
macro to do the moving.
Sub MoveRedText2()
Dim c As Range
If you use either of these macros and your red-text cells don't move, it could be because the cells
don't actually use red text. There are many different shades of red that can be displayed in Excel,
so you'll need to tweak the macros to make sure that you are checking for the proper font color.
In step 4 you could also press SHIFT+F10 rather than ALT+E. The first displays the Context
menu for the cells and the other uses the regular menu system.
If you are using Excel 2007 you can follow these steps:
If you are using Excel 2010 or Excel 2013 then you should follow these steps:
If you prefer a real shortcut key (only a single key press), then you will need to develop your
own macro to paste only the format. The following is an example of such a macro.
Sub PasteSpecialFormats()
If Application.CutCopyMode = False Then
Beep
MsgBox "No formatting in Clipboard"
Else
Selection.PasteSpecial Paste:=xlFormats
End If
End Sub
To use the macro, follow steps 1 through 3 as previously outlined, and then run the macro. Since
the macro can be assigned to a shortcut key, you end up with an easier shortcut than what has
already been discussed.
1. Press CTRL+F to display the Find tab of the Find and Replace dialog box.
5. From the colors available, choose the color you want to find.
6. Click OK to close the Find Format dialog box.
7. Click Find All. The Find and Replace dialog box expands to show the addresses of all
the cells formatted with the color you specified in step 5.
8. Click one of the cell addresses in the bottom of the dialog box. Excel selects the cell
within the actual worksheet.
9. Press CTRL+A. All of the addresses within the dialog box are selected.
10. Click Close. All the cells of the desired color are selected.
If you are using Excel 97, Excel 2000, or Excel 2002 the only way to select cells of a particular
color is to use a macro. Consider the macro shown here:
Sub SelectColoredCells()
Dim rCell As Range
Dim lColor As Long
Dim rColored As Range
Next
If rColored Is Nothing Then
MsgBox "No cells match the color"
Else
rColored.Select
MsgBox "Selected cells match the color:" & _
vbCrLf & rColored.Address
End If
Set rCell = Nothing
Set rColored = Nothing
End Sub
To use the macro, select a range of cells before running it. The macro then steps through each
selected cell and compares its color with whatever color you specify in lColor. If a match is
found, then the cell is added to a selection set. When completed, the macro selects only those
matching cells, and then exits.
If you would like to find out other macro-based solutions, you can refer to the following article at
the Microsoft Knowledge Base:
http://support.microsoft.com/?kbid=142122
To use the function, all you need to do is use a formula such as the following in a cell of your
worksheet:
=CountYellow(A1:A99)
This example returns the number of cells in the range of A1:A99 that use the yellow fill color.
Notice in the CountYellow function that the cells are examined to see if the ColorIndex property
is equal to 6. In other VBA coding you may be used to seeing near-English constants that define
colors. In this case, the normal color constants don’t work. Instead, the ColorIndex property
works based on a set of index values into a particular palette of colors. If you are interested in
seeing the various index values used for the different colors, take a look at the VBA online help
file for the ColorIndex property.
Once you know how to walk through the cells in a range in this manner, it is easy to perform
other types of operations based on the color used to fill cells in the range. For instance, instead of
simply counting the number of cells, you could add up the values of the cells in the range, or you
could find the average of the values in the range. All you need to do is to make the appropriate
changes in the code in the innermost If … End If structure.
There are a few ways you can get the information you need. One way is to go through these
steps:
1. Figure out which color it is that you want to use in your count.
2. Press F5. Excel displays the Go To dialog box.
3. Click the Special button. Excel displays the Go To Special dialog box.
8. Click the Format button. Excel displays the Find Format dialog box.
9. Make sure the Fill tab is displayed.
10. Click on the color you want to search for. (This is the color you determined in step 1.)
11. Click OK to close the Find Format dialog box.
12. Click Find All.
When you perform these steps, Excel shows, at the bottom of the Find and Replace dialog box,
how many cells it found that match your color. Since you started the search with only blank cells
selected, the resulting count is all those cells that are blank that are filled with the color.
Of course, if you need to determine this count quite a few times, then these steps can get very
tedious very quickly. In such cases it is a better idea to use a macro. The following macro steps
through each blank cell in whatever range you have selected and checks to see if it contains a
pattern or a color and is empty. If the conditions are fulfilled, then a counter for that color is
incremented.
Sub CountBlankColors1()
Dim c As Range
Dim J As Integer
Dim ColorCount(56) As Long
ActiveSheet.Range("a1").CurrentRegion.SpecialCells(xlCellTypeBlanks).Select
sTemp = "These are the color counts" & vbCrLf & vbCrLf
For J = 0 To 56
If ColorCount(J) > 0 Then
sTemp = sTemp & "Color " & J & ": " & ColorCount(J) & vbCrLf
End If
Next J
MsgBox sTemp
End Sub
Of course, you might not want to count different colors individually. Instead, you might want to
know simply how many blank cells are filled with any color, in aggregate. In that case the macro
becomes much simpler.
Sub CountBlankColors2()
Dim c As Range
Dim x As Long
x = 0
ActiveSheet.Range("a1").CurrentRegion.SpecialCells(xlCellTypeBlanks).Select
It should be noted that these approaches don't take into consideration if the cell is colored
through the use of a conditional format or not. (In fact, they don't take conditional formats into
account at all.)
Flashing Cells
Many people use the conditional formatting features of Excel to draw attention to specific values
or areas of their worksheets. For instance, a cell might be formatted so that its contents are
displayed in red or in boldface if above or below a certain threshold.
What is missing, however, is a way to make the contents of a cell flash, or blink on and off. For
such a feat, you are left to your own devices and the miracle of macros. By utilizing these tools,
you can make cells blink by first designing a special style for the blinking cells, and then running
a simple macro.
To create the special style, follow these steps if you are using Excel 2007 or a later version:
5. Using the controls in the dialog box, modify any attributes for the style, as you desire.
6. Click on OK.
If you are using an older version of Excel, follow these steps instead:
2. Choose Style from the Format menu. Excel displays the Style dialog box.
3. In the Style Name box, enter a new style name, such as Flashing.
4. Using the controls in the dialog box, modify any attributes for the style, as you desire.
5. Click on OK.
You can now apply the style to any other cells you desire in your workbook. Now create the
macros (there are two of them), as follows:
Sub StartFlash()
NextTime = Now + TimeValue("00:00:01")
With ActiveWorkbook.Styles("Flashing").Font
If .ColorIndex = xlAutomatic Then .ColorIndex = 3
.ColorIndex = 5 - .ColorIndex
End With
Application.OnTime NextTime, "StartFlash"
End Sub
Sub StopFlash()
Application.OnTime NextTime, "StartFlash", schedule:=False
ActiveWorkbook.Styles("Flashing").Font.ColorIndex = xlAutomatic
End Sub
To start the items flashing, simply run StartFlash. The cells formatted with the Flashing style will
alternate between red and white text approximately once a second. When you want to turn the
flashing off, simply run the StopFlash macro.
There is one important thing to note about this macro: the variable NextTime is declared outside
of the actual procedure in which it is used. This is done so that NextTime maintains its value
from one invocation of StartFlash to the next.
Unfortunately this cannot be done with a custom format. Why? Because custom formats are for
the display of numbers, not text. There is one text format, designated by an ampersand, but that
is it; there are no others and no others can be defined.
Since custom formats cannot be used, one is left to figure out a workaround. One way to do it is
to examine your part numbers and see if the text portion of the number can be removed and the
part number still be usable. For instance, Justin’s number is 660501C016971. If the format for
the part number always calls for the letter C at the same point in the part number (and no other
possible letters there), then you could simply delete the C and be left with the number
660501016971. Since it is a number, you can develop a custom format for it that includes dashes
in the proper places and the letter C in the proper place. The custom format would look like this:
0000-00-C00-0000
With the format applied to a cell that contains the number 660501016971, you would end up
with a correctly formatted part number displayed: 6605-01-C01-6971. This approach does have
drawbacks, however. The biggest drawback is that if you ever want to export the part numbers to
another program, perhaps as a CSV file, what ends up exporting is the original number without
the formatting or the letter C.
Another workaround is to use a formula to display the part numbers in the format you desire.
You could enter them into a cell without dashes, and then use the formula to add the dashes at
the appropriate places. When creating reports, then, you would simply hide the column that
contains the part numbers without the dashes. Here’s a formula that will work, provided the part
number without dashes is in cell A1:
=LEFT(A1,4) & "-" & MID(A1,5,2) & "-" & MID(A1,7,3) & "-" & RIGHT(A1,4)
If you work with the part numbers quite a bit, you might want a way to both add and remove the
dashes easily. The best way to do this is with a macro. You can develop a macro that will allow
you to add and remove the dashes from a part number in a selected range of cells. The following
is an example of such a macro.
Sub DashesIn()
DoDashes ("In")
End Sub
Sub DashesOut()
DoDashes ("Out")
End Sub
Dim J As Integer
Note that there are actually three macros in this listing. The first (DashesIn) adds dashes to a part
number, while the second (DashesOut) removes them. Simply select the cells containing the part
numbers and then run the macro that will perform the operation you want done.
Both DashesIn and DashesOut call the common routine, DoDashes, to actually do the work. The
macro examines all the cells in the selection and then performs whatever operation needs to be
done on the contents of those cells.
The short answer is that no, there is no way to do this. The formats that appear in the non-custom
categories are hard-coded by Excel. The only thing you could do to make the application of the
custom formats faster (if that is your goal) is to use a macro that applies the format. The
following is an example of a macro that applies a custom format to whatever cells are selected:
Sub MyNumberFormat()
Selection.NumberFormat = "_(* #,##0_);_(* (#,##0);_(* ""-""??_);_(@_)"
End Sub
You can assign the macro to a shortcut key or to the Quick Access Toolbar, thereby making it
very easy to apply.
At this point, every cell in the worksheet that contains formulas is selected, and you can add
color to those cells or format them as desired. This approach can be automated, if desired, by
using a macro like the following:
Sub ColorFormulas()
ActiveSheet.Cells.SpecialCells(xlCellTypeFormulas, 23).Select
With Selection.Interior
.ColorIndex = 6
.Pattern = xlSolid
End With
End Sub
You can run this macro as often as necessary in order to highlight the various cells that contain
formulas. The only problem is that if a formula is deleted from a cell that was previously
highlighted, the highlighting remains; it is not removed automatically. In this case, a different
macro approach is mandated. This macro acts on a range of cells you select before running the
macro.
Sub ColorFunction()
For Each cell In Selection
If cell.HasFormula Then
With cell.Interior
.ColorIndex = 6
.Pattern = xlSolid
End With
Else
cell.Interior.ColorIndex = xlNone
End If
Next cell
End Sub
The macro checks each cell in the range. If the cell contains a formula, then it is highlighted. If
the cell does not contain a formula, then the highlight is turned off.
Another potential solution is to use a user-defined function along with the conditional formatting
capabilities of Excel. Create the following function in the VBA Editor:
With this function in place, you can use the conditional formatting capabilities of Excel (detailed
elsewhere in ExcelTips) to check what the formula returns. In other words, you would set a
conditional format that checked the result of this formula:
=CellHasFormula(A1)
If the result is true (the cell contains a formula), then your conditional format is applied.
It is interesting to note that you don't have to create a VBA macro to use the conditional
formatting route, if you don't want to. (Some people have a natural aversion to using macros.)
Instead, you can follow these steps if you are using a version of Excel prior to Excel 2007:
4. Click OK.
If you are using Excel 2007 or later, follow these steps instead:
3. In the Name field (at the top of the dialog box), enter a name such as FormulaInCell.
4. In the Refers To field (at the bottom of the dialog box), enter the following:
=GET.CELL(48,INDIRECT("rc",FALSE))
5. Click OK. The New Name dialog box disappears, the Name Manager dialog box
reappears, and the name you defined is listed in the dialog box.
6. Click Close.
Now you can follow the techniques previously outlined for setting up the conditional formatting.
The only difference is that the conditional format should check for the following formula,
instead:
=FormulaInCell
as a different color, such as blue or green). Connie isn't sure how to set this up or if it can even
be done with conditional formatting.
There is a way to mark duplicates using conditional formatting; just follow these general steps:
6. Click the Format button and change the formatting to reflect how you want duplicate
company names to appear.
7. Click OK to close the New Formatting Rule dialog box.
At this point all your duplicates should match whatever formatting you selected in step 6. The
only problem is that all duplicates are formatted the same way. In other words, if you have two
companies (ABC Company and DEF Company) and there are duplicates for those companies,
they are all formatted the same way—you won't see different formatting for the two companies.
Of course, you could easily use Excel's filtering capabilities to single out duplicate companies,
non-duplicate companies, or individual company names. This might be the easiest way to "zero
in" on the companies you want to locate.
The only way to use conditional formatting to apply different colors to different groups of
duplicate company names requires that you identify, up front, the actual duplicates. With that list
in hand, you could create a series of conditional formatting rules that use formulas similar to the
following:
In this formula "ABC Company" is the company name, B1 is the first cell of the range, and
B1:B99 is the full range of cells. For each formatting rule you could apply different formatting
appropriate to that particular company. That means that if you knew, up front, that there were 24
different company names that had duplicates, you would need to set up 24 conditional formatting
rules to handle those 24 names.
Complex, indeed. Unfortunately, there is not an easier way using conditional formatting. You
could, however, forego the conditional formatting and use a macro to make your duplicates stand
out. The simplest "automatic" macro we could come up with (where you don't need to know the
duplicate names ahead of time) is one that examines a range of cells and sets the internal cell
color based on duplicate company names.
Sub ColorCompanyDuplicates()
Dim x As Integer
Dim y As Integer
Dim lRows As Long
Dim lColNum As Long
Dim iColor As Integer
Dim iDupes As Integer
Dim bFlag As Boolean
lRows = Selection.Rows.Count
lColNum = Selection.Column
iColor = 2
For x = 2 To lRows
bFlag = False
For y = 2 To x - 1
If Cells(y, lColNum) = Cells(x, lColNum) Then
bFlag = True
Exit For
End If
Next y
If Not bFlag Then
iDupes = 0
For y = x + 1 To lRows
If Cells(y, lColNum) = Cells(x, lColNum) Then
iDupes = iDupes + 1
End If
Next y
If iDupes > 0 Then
iColor = iColor + 1
If iColor > 56 Then
MsgBox "Too many duplicate companies!", vbCritical
Exit Sub
End If
Cells(x, lColNum).Interior.ColorIndex = iColor
For y = x + 1 To lRows
If Cells(y, lColNum) = Cells(x, lColNum) Then
Cells(y, lColNum).Interior.ColorIndex = iColor
End If
Next y
End If
End If
Next x
End Sub
To use the macro, simply select the cells that contain the company names and then run it. The
macro makes three passes through the cells. The first pass looks backward through the cells from
the current one being examined; it is used to determine if there are any "backward" duplicates,
because if there are than no further processing on that particular cell is needed. The second pass
looks forward through the cells to determine if there are any duplicates to the current company
name. If there are, then a third pass increments the cell color value and then applies it to the
duplicates.
Note that the macro sets the ColorIndex property of any duplicates it finds, and it increments the
variable used to set the property when it finds a new set of duplicate company names. For all
those company names for which there are no duplicates, the ColorIndex property of the cell is
not changed. This means there is a limit on how many companies can be marked, however—the
ColorIndex can only range between 0 and 56. The values actually assigned by the macro range
from 3 to 56, so it is only possible to format 54 groupings of companies.
Colors in an IF Function
Steve would like to create an IF statement (using the worksheet function) based on the color of a
cell. For example, if A1 has a green fill, he wants to return the word “go”, if it has a red fill, he
wants to return the word “stop”, and if it is any other color return the word “neither”. Steve
prefers to not use a macro to do this.
Unfortunately, there is no way to acceptably accomplish this task without using macros, in one
form or another. The closest non-macro solution is to create a name that determines colors, in
this manner:
=IF(GET.CELL(38,Sheet1!A1)=10,"GO",IF(GET.CELL(38,Sheet1!A1)
=3,"Stop","Neither"))
6. Click OK.
With this name defined, you can, in any cell, enter the following:
=mycolor
The result is that you will see text based upon the color of the cell in which you place this
formula. The drawback to this approach, of course, is that it doesn’t allow you to reference cells
other than the one in which the formula is placed.
The solution, then, is to use a user-defined function, which is (by definition) a macro. The macro
can check the color with which a cell is filled and then return a value. For instance, the following
example returns one of the three words, based on the color in a target cell:
Function CheckColor1(range)
If range.Interior.Color = RGB(256, 0, 0) Then
CheckColor1 = "Stop"
ElseIf range.Interior.Color = RGB(0, 256, 0) Then
CheckColor1 = "Go"
Else
CheckColor1 = "Neither"
End If
End Function
This macro evaluates the RGB values of the colors in a cell, and returns a string based on those
values. You could use the function in a cell in this manner:
=CheckColor1(B5)
If you prefer to check index colors instead of RGB colors, then the following variation will
work:
Function CheckColor2(range)
If range.Interior.ColorIndex = 3 Then
CheckColor2 = "Stop"
ElseIf range.Interior.ColorIndex = 14 Then
CheckColor2 = "Go"
Else
CheckColor2 = "Neither"
End If
End Function
Whether you are using the RGB approach or the color index approach, you’ll want to check to
make sure that the values used in the macros reflect the actual values used for the colors in the
cells you are testing. In other words, Excel allows you to use different shades of green and red,
so you’ll want to make sure that the RGB values and color index values used in the macros
match those used by the color shades in your cells.
One way you can do this is to use a very simple macro that does nothing but return a color index
value:
=GetFillColor(B5)
The result is the color index value of cell B5 is displayed. Assuming that cell B5 is formatted
using one of the colors you expect (red or green), you can plug the index value back into the
earlier macros to get the desired results. You could simply skip that step, however, and rely on
the value returned by GetFillColor to put together an IF formula, in this manner:
You’ll want to keep in mind that these functions (whether you look at the RGB color values or
the color index values) examine the explicit formatting of a cell. They don’t take into account
any implicit formatting, such as that applied through conditional formatting.
For some other good ideas, formulas, and functions on working with colors, refer to this page at
Chip Pearson’s website:
http://www.cpearson.com/excel/colors.aspx
Before you can use conditional formatting, however, you need to create a user-defined function
that will return True or False, depending on whether there is a formula in a cell. The following
macro will do the task very nicely:
To use this with conditional formatting, select the cells you want checked, and then follow these
steps if you are using a version of Excel prior to Excel 2007:
1. Choose Conditional Formatting from the Format menu. Excel displays the Conditional
Formatting dialog box.
2. Make sure the first drop-down list is “Formula Is.”
3. In the formula area, enter “=HasFormula(A1)” (without the quote marks). If the active
cell in the range that you selected is not A1, you’ll need to modify the formula slightly
to reflect whatever cell is active.
4. Click the Format button. Excel displays the Format Cells dialog box.
5. Use the controls in the Format Cells dialog box to specify how you want the cells
formatted.
6. Click OK to close the Format Cells dialog box.
7. Click OK to close the Conditional Formatting dialog box.
If you are using Excel 2007 or a later version then you should follow these steps, instead:
1. With the Home tab of the ribbon displayed, click the Conditional Formatting option in
the Styles group. Excel displays a palette of options related to conditional formatting.
2. Choose Highlight Cells Rules and then choose More Rules from the resulting submenu.
Excel displays the New Formatting Rule dialog box.
3. In the Select a Rule Type area at the top of the dialog box, choose Use a Formula to
Determine Which Cells to Format.
4. In the Format Values Where This Formula Is True box, enter “=HasFormula(A1)”
(without the quote marks). If the active cell in the range that you selected is not A1,
you’ll need to modify the formula slightly to reflect whatever cell is active.
5. Click Format to display the Format Cells dialog box.
6. Use the controls in the Format Cells dialog box to specify how you want the cells
formatted.
7. Click OK to close the Format Cells dialog box.
8. Click OK.
There are several different solutions to this predicament. One solution is to apply a conditional
format that uses two conditions. The first condition checks for the blanks, and the second checks
for zero values. The condition that checks for blanks doesn’t need to adjust any formatting, but
the one that checks for zero values can. This works because if the first condition is satisfied (the
cell is blank), the second condition is never tested. Do the following if you are using Excel 2007
or later:
1. Select the range you want conditionally formatted. (For this example, I’ll assume that
you’ve selected the range A2:A99.)
2. With the Home tab of the ribbon displayed, click the Conditional Formatting option in
the Styles group. Excel displays a palette of options related to conditional formatting.
3. Click Manage Rules. Excel displays the Conditional Formatting Rules Manager dialog
box.
4. Click New Rule. Excel displays the New Formatting Rule dialog box.
5. In the Select a Rule Type area at the top of the dialog box, choose Format Only Cells
That Contain.
6. Using the first drop-down list for the rule, choose Blanks.
7. Click OK. Excel closes the New Formatting Rule dialog box and again displays the
Conditional Formatting Rules Manager dialog box, this time with your new rule visible.
(Note that you didn’t specify any formatting for this rule; that’s fine.)
8. Make sure the Stop If True check box is selected for the rule.
9. Click New Rule. Excel again displays the New Formatting Rule dialog box.
10. In the Select a Rule Type area at the top of the dialog box, choose Format Only Cells
That Contain.
11. Using the first drop-down list for the rule, choose Cell Value.
12. Using the second drop-down list for the rule, choose Equal To.
13. In the value box for Condition 2, enter 0.
14. Click the Format button. Excel displays the Format Cells dialog box.
15. Use the controls in the dialog box to modify the formatting, as desired.
16. Click OK to close the Format Cells dialog box.
17. Click OK to close the New Formatting Rule dialog box. Excel again displays the
Conditional Formatting Rules Manager, and the rule you just defined is the first one in
the list. (It should also be selected.)
18. Click the down arrow to move the rule you just created to the second position in the list
of rules.
19. Click OK to close the Conditional Formatting Rules Manager dialog box. The
formatting is applied to the range of cells you selected in step 1.
If you are using an older version of Excel, follow these steps, instead:
1. Select the range you want conditionally formatted. (For this example, I’ll assume that
you’ve selected the range A2:A99.)
2. Choose Conditional Formatting from the Format menu. Excel displays the Conditional
Formatting dialog box.
3. Using the first drop-down list for Condition 1, choose Formula Is.
4. In the formula box for Condition 1, enter the formula =ISBLANK(A2).
9. Click the Format button for Condition 2. Excel displays the Format Cells dialog box.
10. Use the controls in the dialog box to modify the formatting, as desired.
11. Click OK to close the Format Cells dialog box.
12. Click OK to close the Conditional Formatting dialog box. The formatting is applied to
the range of cells you selected in step 1.
Another solution is to combine your two conditions into a single condition. Follow these steps in
Excel 2007 or later:
1. Select the range you want conditionally formatted. (For this example, I’ll assume that
you’ve selected the range A2:A99.)
2. With the Home tab of the ribbon displayed, click the Conditional Formatting option in
the Styles group. Excel displays a palette of options related to conditional formatting.
3. Click New Rule. Excel displays the New Formatting Rule dialog box.
4. In the Select a Rule Type area at the top of the dialog box, choose Use a Formula To
Determine Which Cells to Format.
5. In the formula box enter the formula =AND(A2=0,A2<>"").
6. Click the Format button. Excel displays the Format Cells dialog box.
7. Use the controls in the dialog box to modify the formatting, as desired.
8. Click OK to close the Format Cells dialog box.
9. Click OK to close the New Formatting Rule dialog box. The formatting is applied to the
range of cells you selected in step 1.
Here are the steps to accomplish the same task in older versions of Excel:
1. Select the range you want conditionally formatted. (For this example, I’ll assume that
you’ve selected the range A2:A99.)
2. Choose Conditional Formatting from the Format menu. Excel displays the Conditional
Formatting dialog box.
3. Using the first drop-down list for Condition 1, choose Formula Is.
4. In the formula box for Condition 1, enter the formula =AND(A2=0,A2<>"").
5. Click the Format button for Condition 1. Excel displays the Format Cells dialog box.
6. Use the controls in the dialog box to modify the formatting, as desired.
7. Click OK to close the Format Cells dialog box.
8. Click OK to close the Conditional Formatting dialog box. The formatting is applied to
the range of cells you selected in step 1.
The formula used in step 4 checks to make sure that the value is 0 and that the cell is not blank.
The AND function makes sure that only when both criteria are satisfied will the formula return
True and the format be applied.
There are any number of other formulas that could also be used. For instance, each of the
following formulas could be substituted in either step 5 or 4:
• =AND(COUNT(A2)=1,A2=0)
• =AND(A2=0,NOT(ISBLANK(A2)))
• =AND(A2=0,LEN(A2)>0)
• =NOT(ISBLANK(A2))*(A2=0)
If you wanted an even faster way to highlight zero values while ignoring blanks, you might
consider using a macro. The macro would be faster because you could just import and run it; you
don’t have to select a range of cells and enter the formula (or formulas) for the conditional
formatting. The following macro is an example of one you could use:
Sub FormatRed()
TotalRows = 65000
ColNum = 1
End If
Next
End Sub
The macro checks the cells in column A. (It checks the cells in rows 1 through 65,000; you can
modify this, if desired.) If the cell contains a numeric value and that value is zero, then the cell is
filled with red. If the cell contains something else, then the cell is set back to its normal color.
You can, however, use a macro to examine cell contents and make changes in the appearance of
a cell. Consider the following macro, which examines any cells you have selected when you run
the macro. If any of the cells have a length of more than two characters or a value of more than
10, then the cell’s font is changed.
Sub DoReformat()
Dim rCell As Range
To use the macro, just select the cells you want changed and then run the macro. If you want the
formatting to change more automatically, then you can have the macro check to see if a change
was made within a certain range of cells:
This macro, when added to the worksheet object, will run every time the worksheet is
recalculated. It checks the range A1:A10, applying the same tests as in the previous macro. The
result is that the formatting of the cells is checked and changed continuously. To have the macro
check a different range, just change the addresses assigned to the rng variable near the beginning
of the macro.
One drawback of this macro is that it can get sluggish if you have a very large range for it to
check. It will go very quickly if you are checking A1:A10 (ten cells), but may go much slower if
you are continually checking B2:N465 (over 6,000 cells). In that case, you may want to design
the macro so it runs whenever the worksheet is changed, but only takes action if the change was
done to a cell in your target range. The following version is also added to the worksheet object:
If Union(Target, Range("A1:A10")).Address = _
Range("A1:A10").Address Then
Application.EnableEvents = False
For Each rCell In Target
If Len(rCell.Text) > 2 Or _
Val(rCell.Value) > 10 Then
rCell.Font.Name = "Arial"
rCell.Font.Size = 16
Else
rCell.Font.Name = "Times New Roman"
rCell.Font.Size = 12
End If
Next
Application.EnableEvents = True
End If
End Sub
The macro uses the Union function to check whether the cells changed (passed to the event
handler in the Target variable) have any overlap with the range you want checked. If they do,
then the checking is done on the cells in the Target range.
One thing to keep in mind with macros that affect formatting is that if you have conditional
formatting applied to a cell that is also checked by a macro, the formatting in the conditional
formatting takes precedence over the formatting in the macro. If your macro is changing font
name and font size, this isn’t a big concern because conditional formatting won’t affect these
attributes. However, if you change your macro to also change a different format attribute—such
as cell color—and that attribute is also changed by the conditional format, then it won’t look like
the macro did anything because Excel uses the conditional formatting in preference to what the
macro does.
There is no direct way to do this when setting up a conditional format—Excel simply won’t
allow you to use diagonal borders with a conditional format. That means that you may want to
look for and use an acceptable workaround. Here are a few ideas for the conditional format:
• Set the conditional format to use a font color that is the same as cell background color.
That way the contents will seem to disappear if your condition is met.
• Set the conditional format to use one of the cell patterns. There a some that look like
multiple diagonal lines through the cell.
• Set the conditional format to use strikethrough formatting for any text that appears in
the cell.
If you actually want to use the diagonal borders, then the only way to do it is to apply an explicit
format to the cell and not rely on a conditional format. This can be done through the use of a
macro, such as the following:
You should right-click on a worksheet tab, display the code window from the resulting Context
menu, and then paste this macro into the code window. The macro is executed any time a cell is
changed in the worksheet. It checks the cells in C12:C20, and if any of them contain a zero
value, then the diagonal border is set for that cell.
You can easily change the macro to apply to a different range of cells or to check for a different
condition when applying the borders. If you prefer, you can change the xlDiagonalUp constant to
xlDiagonalDown, depending on which diagonal border you want applied.
When you copy a protected cell from one sheet to another, if the formulas in the source cell were
hidden in the protection process, then the results of the formulas are pasted, unprotected, into the
target cells. This is probably no big deal, as you wanted the formulas—not the results—
protected.
Excel is not as protective about conditional formats, however. The conditional formats of the
cells that you paste, since they are in an unprotected worksheet, can be viewed and modified, as
desired. This can be a problem if the conditional formats contain formulas that you want to also
keep private.
The only way around this problem is to disable the ability to copy anything from your protected
worksheet. You do this through the use of a macro, added to the worksheet object, that would
disable copying.
This macro works because anytime the worksheet is deactivated (meaning, the target worksheet
is selected), then CutCopyMode is set to False. This results in the “marching ants” that appeared
around the source cells when the user pressed CTRL+C being removed, and pasting therefore no
longer possible. Copying and pasting on the same worksheet is still fine; just not to a different
(unprotected) worksheet.
that condition. This has made Allan concerned that there are other instances of conditional
formats that have become corrupted since originally being set up. He wonders if there is any
simple way of checking all conditional formatting so that these errors can easily be found.
The best way is to use a macro to step through all the conditional formats defined for a
worksheet. The following macro does just that, looking for any #REF! errors in the formulas.
Sub FindCorruptConditionalFormat()
Selection.SpecialCells(xlCellTypeAllFormatConditions).Select
For Each c In Selection.Cells
For Each fc In c.FormatConditions
If InStr(1, fc.Formula1, "#REF!", _
vbBinaryCompare) > 0 Then
MsgBox Prompt:=c.Address & ": " _
& fc.Formula1, Buttons:=vbOKOnly
End If
Next fc
Next c
End Sub
If an error is found, then a message box displays both the address of the cell and the formula
used in the conditional formatting rule.
There is no intrinsic way to do this in Excel; none of the Paste Special options will do the task, as
desired. You can, however, use a macro to accomplish the task:
Option Explicit
Sub PasteFC()
Application.ScreenUpdating = False
Dim rWhole As Range
Dim rCell As Range
Dim ndx As Integer
Dim FCFont As Font
Dim FCBorder As Border
Dim FCInt As Interior
Dim x As Integer
Dim iBorders(3) As Integer
iBorders(0) = xlLeft
iBorders(1) = xlRight
iBorders(2) = xlTop
iBorders(3) = xlBottom
If rng.FormatConditions.Count = 0 Then
ActiveCondition = 0
Else
For ndx = 1 To rng.FormatConditions.Count
Set FC = rng.FormatConditions(ndx)
Select Case FC.Type
Case xlCellValue
Select Case FC.Operator
Case xlBetween
If CDbl(rng.Value) >= CDbl(FC.Formula1) And _
CDbl(rng.Value) <= CDbl(FC.Formula2) Then
ActiveCondition = ndx
Exit Function
End If
Case xlGreater
If CDbl(rng.Value) > CDbl(FC.Formula1) Then
ActiveCondition = ndx
Exit Function
End If
Case xlEqual
If CDbl(rng.Value) = CDbl(FC.Formula1) Then
ActiveCondition = ndx
Exit Function
End If
Case xlGreaterEqual
If CDbl(rng.Value) >= CDbl(FC.Formula1) Then
ActiveCondition = ndx
Exit Function
End If
Case xlLess
If CDbl(rng.Value) < CDbl(FC.Formula1) Then
ActiveCondition = ndx
Exit Function
End If
Case xlLessEqual
If CDbl(rng.Value) <= CDbl(FC.Formula1) Then
ActiveCondition = ndx
Exit Function
End If
Case xlNotEqual
If CDbl(rng.Value) <> CDbl(FC.Formula1) Then
ActiveCondition = ndx
Exit Function
End If
Case xlNotBetween
If CDbl(rng.Value) <= CDbl(FC.Formula1) Or _
CDbl(rng.Value) >= CDbl(FC.Formula2) Then
ActiveCondition = ndx
Exit Function
End If
Case Else
Debug.Print "UNKNOWN OPERATOR"
End Select
Case xlExpression
If Application.Evaluate(FC.Formula1) Then
ActiveCondition = ndx
Exit Function
End If
Case Else
Debug.Print "UNKNOWN TYPE"
End Select
Next ndx
End If
ActiveCondition = 0
End Function
There are three procedures in this solution. The last procedure, ActiveCondition, is designed to
return a number indicating which of the conditions in a conditional format is currently in effect.
This routine was found at Chip Pearson’s site, as indicated in the first comment of the function.
(No sense in re-inventing the wheel. <g>)
The center function, NewFC, is simply used to determine which of two values is valid. The
procedure you actually run, however, is PasteFC. Simply select the cells you want to convert to
explicit formatting, then run the procedure. It checks each cell you selected for which formatting
condition is active, determines the formatting of that condition, and then applies it to the cell.
Finally, the conditional formatting for the cell is removed.
For instance, let’s say that the first cell of each row contains a part number. What if you want to
delete any rows that have duplicate part numbers in the first cell? If you need this solution, the
following macro is for you:
Sub DelDupRows()
Dim rngSrc As Range
Dim NumRows As Integer
Dim ThisRow As Integer
Dim ThatRow As Integer
Dim ThisCol As Integer
Dim RightCol As Integer
Dim J As Integer, K As Integer
Application.ScreenUpdating = False
Set rngSrc = ActiveSheet.Range(ActiveWindow.Selection.Address)
NumRows = rngSrc.Rows.Count
ThisRow = rngSrc.Row
ThatRow = ThisRow + NumRows - 1
ThisCol = rngSrc.Column
RightCol = ThisCol + rngSrc.Columns.Count - 1
End If
Next J
Application.ScreenUpdating = True
End Sub
The macro works on a selection you make before calling it. Thus, if you need to remove
duplicate rows from the range D7:G85, simply select that range and then run the macro. It
removes the duplicates from the range D7:D85, and then removes all rows in D7:G85 (four
columns per row) for which the cell in column D is blank.
The first method is to use Excel’s AutoFilter feature. This works particularly well if you have a
rather simple criteria by which to delete rows. When you turn on the AutoFilter, Excel places
pull-down buttons at the right side of each cell in the data table’s header row. Using these pull-
down buttons you can specify the records you want displayed. You should select a filter value
that will result in displaying only those rows you want to delete. With those rows displayed, you
can select them and use the menus or ribbons to get rid of the rows. When you turn AutoFilter
off, then you are left with only the rows you wanted.
Another method involves the use of macros to do the deleting for you. This approach works well
if you have to perform the deletions on lots of data, or if you do it quite often. The following
macro can delete rows based on a key value:
Sub DeleteRows()
Dim strToDelete As String
Dim rngSrc As Range
Dim NumRows As Integer
Dim ThisRow As Integer
Dim ThatRow As Integer
Dim ThisCol As Integer
Dim J As Integer
Dim DeletedRows As Integer
NumRows = rngSrc.Rows.Count
ThisRow = rngSrc.Row
ThatRow = ThisRow + NumRows - 1
ThisCol = rngSrc.Column
End If
Next J
MsgBox "Number of deleted rows: " & DeletedRows
End Sub
To use the macro, select the range the key range that covers the rows you want checked. For
instance, if the key to be checked is in column G, and you want to check rows 5 through 73, then
you would select the range G5:G73. When you run the macro, it asks you what value it should
check for. If any cells in the range G5:G73 contain the value you specify, the corresponding row
for that cell will be deleted.
There are obviously other ways to delete rows based on a value. For a good selection of different
methods, take a look at this page by Dave Hawley at Ozgrid:
http://www.ozgrid.com/VBA/VBACode.htm
The following macro, DeleteRows, will remove every X rows from your worksheet. All you
have to do is select the rows you want it applied to. The macro, as written, will remove every
second row. So, if you wanted to delete the first, third, fifth, and seventh rows beginning with
row 10, you would select rows 10 through 16 and then run this macro. It results in rows 10 (the
first row), 12 (the third row), 14 (the fifth row), and 16 (the seventh row) being deleted.
Sub DeleteRows()
Dim iStart As Integer
Dim iEnd As Integer
Dim iCount As Integer
Dim iStep As Integer
Dim J As Integer
End Sub
If you want to delete some other multiple of lines, simply change the setting for the iStep
variable. For instance, if you want to delete every fifth row, change iStep from 2 to 5. (You only
need to make the single change, in the iStep = 2 declaration.)
Sub HideRows()
BeginRow = 1
EndRow = 100
ChkCol = 3
You can modify the macro so that it checks a different beginning row, ending row, and column
by simply changing the first three variables set in the macro. You can also easily change the
value that is checked for within the For … Next loop.
You should note that this macro doesn’t unhide any rows, it simply hides them. If you are
checking the contents of a cell that can change, you may want to modify the macro a bit so that it
will either hide or unhide a row, as necessary. The following variation will do the trick.
Sub HURows()
BeginRow = 1
EndRow = 100
ChkCol = 3
There are a couple of ways you can approach this problem. The first is to use filtering
capabilities. Just create another column that contains a formula such as this:
=AND(B2=0,C2=0)
The value returned by the formula will be True only if both the second (B) and third (C) columns
contain a zero value. Copy the formula to the other appropriate cells in the column, and you can
then filter the data based on that column. When you display only those rows containing a False in
the column, then you have effectively hidden the rows in which there is a zero value in columns
two and three.
You can also use a macro to check out the rows for you. The following macro steps through each
row in the worksheet, beginning with row 1. As long as there is something in column A, then the
macro checks to make sure that there is a zero value in columns B and C. If there is, then the
.Hidden property for the row is set.
Sub Hide()
Dim Criteria as Boolean
Dim i As Integer
i = 1
Do Until Trim(Cells(i, 1).Value) = ""
Criteria = True
Criteria = Criteria And (Cells(i, 2).Value = 0) _
And Cells(i, 2).Value <> ""
Criteria = Criteria And (Cells(i, 3).Value = 0) _
And Cells(i, 3).Value <> ""
If Criteria Then Rows(i).EntireRow.Hidden = True
i = i + 1
Loop
End Sub
The macro runs until such time as it encounters a row where there is nothing in column A. This
means that you need to make sure there is actually something in the rows before your data table.
If your data table starts in row 4 of the worksheet, and cells A1 through A3 have nothing in
them, then the macro will never run satisfactorily. You can, of course, adjust the macro in this
situation so that it starts checking in row 4; simply change the initial assignment of the i variable
to 4 instead of 1.
One way you can identify hidden rows is to follow these general steps:
1. In a column that has nothing in it, select all the cells that will cover the area you want to
check. (You can select the entire column, if you desire, but that may be overkill.)
2. Press ALT+; (that's a semicolon). Excel selects only the unhidden cells in the selected
range.
3. Press X (or some other viewable character) and press CTRL+ENTER. This puts the
character (X) into all the visible cells.
Unhide all the rows, and you'll be able to easily see which cells in that column don't have the
character (X) in them. These are the rows that were previously hidden. You could also, if
desired, use the same general approach, but after step 2 (instead of step 3) you could apply some
pattern or color to the cells. Once you unhide all the rows, those cells without any pattern or
color are the ones that were previously in hidden rows.
If you don't want to unhide rows at all, perhaps the best way to find out the information is to use
a macro. The following simple macro steps through the first 1,000 rows of a worksheet and then
lists, in a message box, the rows that are hidden.
Sub ShowRows()
Dim rng As Range
Dim r As Range
Dim sTemp As String
Note that the heart of the macro—where it determines whether a row is hidden or not—is in
checking the Hidden property of the EntireRow object. If this property is True, then the row is
hidden.
The way you accomplish this is to check the Hidden property of each row. If the property is
True, then the row is hidden; if False, then row is visible.
As an example of how this works, assume that you have a worksheet that you use to track clients.
Some of these clients are considered active and others inactive. To mark a client as inactive, you
hide the row containing the client. At some point, you want to number the active clients, and you
want to do it using a macro. The following macro will do the trick for you:
Sub NumberClients()
Dim c As Range
Dim j As Integer
j = 0
For Each c In Selection
If Not c.Rows.Hidden Then
j = j + 1
c.Value = j
Else
c.Clear
End If
Next c
End Sub
To use the macro, simply select the cells in which the numbering will be done. The macro
checks, first of all, to make sure you have only selected cells in a single column. Then, it steps
through each cell in the selected range. If the row containing the cell is not hidden, then the
counter (j) is incremented and stored in the cell. If the row containing the cell is hidden, then the
contents of the cell are cleared. The key to this macro is the If … End If structure that tests the
value of the Hidden attribute.
2. Put the value 1 in the first table cell of the new column and the value 2 in the second
cell.
3. Select the two cells entered in step 2 and use the Fill handle to drag down to the last cell
in the table. You should now have a column filled with consecutive numbers, 1 through
however many rows there are in the table. These filled cells should still be selected.
4. Press CTRL+C. This copies the cells to the Clipboard.
5. In the new column, just below the last cell, paste the copied cells. You should now have
another range of cells below the table filled with the same consecutive numbers you
created in step 3.
6. Select any cell in the original table.
7. Display the Data tab of the ribbon and click Sort in the Sort & Filter group (Excel 2007
and later versions) or choose Sort from the Data menu (older versions of Excel). Excel
selects the table, including the rows added in step 5, and displays the Sort dialog box.
8. Specify that you want to sort in ascending order by the new column that contains your
numbers.
9. Click on OK. The table is resorted.
10. Delete the column you added in step 1.
The above steps work because of the way in which Excel does its sorting. If, for some reason,
you end up with two blank rows next to each other (in other words, the sorting does not work
exactly as it should have), then you can modify the process slightly. In step 2, enter the numbers
1 and 3 in the top two cells. This results in odd numbers being filled down the new column.
Instead of doing steps 4 and 5, you would simply fill a like area with even cells (simply fill the
first cell with 2 and the second one with 4). When you then sort in steps 6 through 9, the
resulting table has the rows interleaved in the proper order.
If you are not adverse to using macros, inserting the blank rows is even easier. Simply select the
rows you want to affect, and then execute this macro:
Sub AddBlankRows()
Dim J As Integer
Dim MySelection As Range
Of course, you should remember that if your only purpose in adding rows is to “space out” your
information, you can achieve the same thing by simply increasing the height of each row in the
table. You should only physically add blank rows if you need those rows in order to insert
additional information in your data table.
In such a situation, it would be nice to have a quick way to enter a blank row after the current
row, and copy the data in the current row to the new blank row. There are no intrinsic commands
in Excel to do this, but a macro can do it very handily. Consider the following example:
Sub InsertCopyRow1()
ActiveCell.EntireRow.Select
Selection.Copy
Selection.Insert Shift:=xlDown
End Sub
In order to use the macro, all you need to do is select a cell in any row. When the macro is run, a
duplicate of the current row is inserted just below the row you are in.
The only problem with this solution is that it leaves the Excel interface a bit “messy” (for lack of
a better word). When completed, a complete row is still selected, and the new row has the
“marching ants” marquee around it.
This problem can be overcome by including commands to collapse the selection and move it to a
desired location. Another way is to simply use a different macro that relies on different VBA
commands. The following macro will also insert and copy a row, but it leaves the cell that you
selected active:
Sub InsertCopyRow2()
ActiveCell.Offset(1, 0).EntireRow.Insert
ActiveCell.EntireRow.Copy ActiveCell.Offset(1, 0).EntireRow
End Sub
If you do a lot of this type of moving about, however, you would probably be more interested in
a macro that combines the two steps into a single step that can be initiated by a shortcut key. The
following macro will work:
Sub SelectRowDown1()
If ActiveCell.Row < 65536 Then
ActiveCell.Offset(1, 0).Select
ActiveCell.EntireRow.Select
End If
End Sub
If you assign this to a shortcut key, such as CTRL+D, then every time you press the shortcut key,
you move down a row and it is selected. The problem with this approach, however, is that after
the macro has been run, the first cell in the row is always the active cell. This is different than if
you use the DOWN ARROW, SHIFT+SPACE BAR method of moving and selecting.
It is apparently the EntireRow.Select method that results in the first cell being activated. To get
around this problem, all you need to do is determine which column you were in, and then
activate that cell. The following version of the macro does just that:
Sub SelectRowDown2()
If ActiveCell.Row < 65536 Then
ActiveCell.Offset(1, 0).Select
iCP = ActiveCell.Column
ActiveCell.EntireRow.Select
ActiveCell.Offset(0, iCP - 1).Activate
End If
End Sub
If you are interested in a macro that moves up, you can use this macro:
Sub SelectRowUp()
If ActiveCell.Row > 1 Then
ActiveCell.Offset(-1, 0).Select
iCP = ActiveCell.Column
ActiveCell.EntireRow.Select
ActiveCell.Offset(0, iCP - 1).Activate
End If
End Sub
You can assign this macro to the CTRL+U shortcut key, and then your movement macros will be
complete.
If you need something that is more “high powered” than these macros, check out the RowLiner
add-in from Pearson Software Consulting Services:
http://www.cpearson.com/excel/RowLiner.htm
For instance, the following code snippet steps through the rows in a selection and sets the height
of each row to 36 points (one-half inch):
By default, when you wrap text within a cell, Excel automatically adjusts row height so that all
the text in the cell is visible. There are only two exceptions to this default:
• The cell in which you are wrapping text is actually merged with another cell.
• The height of the row in which the cell is located was previously changed.
In Jordan’s case, there are no merged cells in the problem row. This leaves us with the second
exception—it would appear that the height of the row in which the cell is located was explicitly
set before wrapping was turned on in some of the row’s cells.
In this case, the solution is simple: Reset the row height. There are actually a couple of ways you
can do this. First, you could select the row and then double-click the “boundary” between the
row and an adjacent row. With the row selected, take a look at the row header, to the left of
column A. This area contains a row number, and the “boundary” you need to double-click is
between this row number and the next row number.
It can be a bit tricky to get the mouse pointer in the correct location to do the double-clicking, so
an approach I prefer—when using Excel 2007 or later—is to select the row and display the
Home tab of the ribbon. In the Cells group there is a Format tool; I click it and then choose
AutoFit Row Height. When using older versions of Excel I select the row and simply choose
Format | Row | Autofit. Regardless of my Excel version, these steps allow Excel to determine the
appropriate row height based on the contents of the row. If a cell in the row has wrapping turned
on, then the row height will automatically adjust to display the information in the cell.
You can find additional information about this issue in the Microsoft Knowledge Base:
http://support.microsoft.com/?kbid=149663
If you have quite a few rows that contain cells with wrapping turned on, and the height of none
of the rows is adjusting, then you may be interested in a quick little macro that can do the
adjustment for you:
Sub AutofitRows()
For Each CL In UsedRange
If CL.WrapText Then CL.Rows.AutoFit
Next
End Sub
The macro steps through all the cells in a worksheet, and if the cell has wrapping turned on, it
sets the AutoFit property of the row in which the cell is located.
The trick to this operation is to simply make sure that you select all the worksheets you want to
affect. Take a look at the worksheet tabs at the bottom of the program window. You should see
one for each worksheet in your workbook. If you want to affect the rows in a series of
consecutive worksheets, click the tab for the first worksheet in the series and hold down the Shift
key as you click the tab for the last. If the worksheets you want to affect are not consecutive,
click the tab for one of the worksheets and then hold down the Ctrl key as you click on the tabs
for each of the others.
With all the worksheets you want to affect selected, select the rows within the worksheet you can
see. As you adjust the row height for those rows, Excel automatically adjusts the row height for
the same rows in each of the other selected worksheets.
When you are done, click on a single worksheet tab. This cancels the selected set of worksheets,
and you can continue to work as you desire. (If you don't cancel the selection set, then any
changes you make on the screen continue to be made in all the selected worksheets.)
If you need to adjust row heights quite a bit, and your formatting is always the same, then you
might benefit from having a macro to affect the sheets. The following macro steps through each
selected worksheet and adjusts the height of rows 1 through 5. (You should obviously change the
row height in the macro and the row numbers to reflect what you really need.)
Sub row_hts()
For Each wksht In Worksheets
Set sht = wksht
sht.Rows("1:5").RowHeight = 25
Next
End Sub
You can easily assign the macro to a shortcut key or the Quick Access Toolbar so it can quickly
be executed.
Excel provides a handy way to split data into separate columns using the Text to Columns tool.
This can be used to split the data based on the presence of the ASCII 10 character, which is what
Excel inserts when you press ALT+ENTER. The problem is that while this successfully splits the
data into separate columns, it doesn’t get it into separate rows, like James requested.
That means that the solution to this problem must include the use of a macro. One approach is
shown in the following code. In this example, the macro assumes that you want to “expand”
everything in the worksheet, and that the data in the worksheet starts in row 1.
Sub CellSplitter1()
Dim Temp As Variant
Dim CText As String
Dim J As Integer
Dim K As Integer
Dim L As Integer
Dim iColumn As Integer
Dim lNumCols As Long
iColumn = 4
iTargetRow = 0
With wksSource
lNumCols = .Range("IV1").End(xlToLeft).Column
lNumRows = .Range("A65536").End(xlUp).Row
For J = 1 To lNumRows
CText = .Cells(J, iColumn).Value
Temp = Split(CText, Chr(10))
For K = 0 To UBound(Temp)
iTargetRow = iTargetRow + 1
For L = 1 to lNumCols
If L <> iColumn Then
wksNew.Cells(iTargetRow, L) _
= .Cells(J, L)
Else
wksNew.Cells(iTargetRow, L) _
= Temp(K)
End If
Next L
Next K
Next J
End With
End Sub
Note that in order to run the macro, you will need to specify, using the iColumn variable, the
column that contains the cells to be split apart. As written here, the macro splits apart info in the
fourth column. In addition, the split-apart versions of the cells are stored in a new worksheet, so
that the original worksheet is not affected at all.
The macro relies upon the use of the Split function to tear apart the multi-line cells. This function
is only available beginning in Excel 2000, and isn’t available in Excel for the Mac at all. In
addition, you might want to only run the macro on a particular selection of cells. To overcome all
these potential problems, you will want to consider the following macro, instead:
Sub CellSplitter2()
Dim iSplitCol As Integer
Dim iEnd As Integer
Dim sTemp As String
Dim iCount As Integer
Dim i As Integer
Dim wksNew As Worksheet
Dim wksSource As Worksheet
Dim lRow As Long
Dim lRowNew As Long
Dim lRows As Long
Dim lRowOffset As Long
Dim iTargetRows As Integer
Dim iCol As Integer
Dim iCols As Integer
Dim iColOffset As Integer
Dim AWF As WorksheetFunction
Application.ScreenUpdating = False
iCols = Selection.Columns.Count
lRows = Selection.Rows.Count
iColOffset = Selection.Column - 1
lRowOffset = Selection.Row - 1
lRowNew = lRowOffset
ExitRoutine:
Set wksSource = Nothing
Set wksNew = Nothing
Set AWF = Nothing
Application.ScreenUpdating = True
Exit Sub
ErrRoutine:
MsgBox Err.Description, vbExclamation
Resume ExitRoutine
End Sub
The macro still relies upon the use of a variable to indicate the column to be split apart. In this
instance, the variable is iSplitCol, and it is set to column 4. The macro only works on the cells
selected when it is first run, and the split-apart cells are transferred to a new worksheet. The
address of the upper-left cell in the new worksheet is the same as the upper-left cell selected
when the macro is run.
What green-bar paper did (for those who don't know) was provide a visual cue for your eyes so
that you could easily follow a row of numbers across the width of the paper, without the human
tendency of skipping from one row to another. You may still use some device to help you read
rows of numbers—for instance, a ruler held under the row to guide your eyes.
If you find yourself pulling out the ruler more often or wishing for the return of green-bar paper,
then you may be interested in a little macro I whipped up to help. The following macro,
ShadeRows, will shade every fifth row in the rows you select. This means that the first, sixth,
eleventh, and so on rows will be shaded.
Sub ShadeRows()
Dim iStart As Integer
Dim iEnd As Integer
Dim iStep As Integer, J As Integer
To run the macro, just select the rows you want to affect, and then run it. If you want to change
the interval at which rows are shaded, change the iStep value from 5 to some other value. For
instance, if you wanted every other row shaded, you would change iStep = 5 to iStep = 2.
There are several ways you can approach this problem. One way is to add some additional
information to the worksheet to designate which cells should be included in the sum. For
instance, in the example you are interested in summing cells in row 6 of the worksheet. If you
can add some indicators in row 5, these could then be used a “triggers” in a formula. Put the
number 1, for example, above each cell you want included in the sum (columns A, E, I, M, etc.).
Then, you can use a formula such as the following:
=SUMPRODUCT(A5:X5, A6:X6)
The formula basically multiples whatever is in row 5 against row 6, and then sums the results.
Since there are only 1s in the columns you want summed, these are all that are included in the
final sum.
If you don’t want to add an indicator row to your worksheet, then you need to look at different
solutions. You could still use the SUMPRODUCT function in a formula such as the following:
=SUMPRODUCT((MOD(COLUMN(6:6),4)=1)*(6:6))
This formula relies on the MOD function to return the remainder of a division. In this case, what
is being divided is the column number of a cell by the value 4. This will result in a remainder of
either 0, 1, 2, or 3. Every fourth cell in a row will have the same remainder. Thus, column A
(also known as column 1) will have a MOD value of 1 (1 divided by 4 is 0, with 1 left over), as
will columns E, I, M, etc.
Note that the formula compares whether the MOD value is 1 or not. If it is, then the comparison
returns True (1); if it isn’t, then it returns False (0). This is then multiplied against the cell in the
sixth row. Finally, SUMPRODUCT sums all these multiplications and gives the desired result.
While this formula provides the sum of every fourth cell in the sixth row, it could easily be
changed to provide the sum for every third cell, fifth cell, or whatever interval you want. Simply
change the 4 in the MOD function to the interval desired.
If you wanted to select a different cell in each “cluster” of four cells to be summed, then all you
need to do is change the value being compared in the MOD function. In this example, only the
first cell in each cluster of four will have a MOD of 1 (A, E, I, M, etc.). If you instead want to
sum every fourth cell starting with, say, cell C, then you would change the comparison value
from 1 to 3. Why? Because C is the third cell in the cluster and will have a MOD of 3, as will
each fourth cell thereafter (G, K, O, etc.).
The only “gottcha” to this general rule is if you want to sum the fourth cell in each four-cell
cluster. For instance, you might want to sum cells D, H, L, P, etc. In this case the comparison
value used wouldn’t be 4 since there will never be a remainder of 4 when doing a MOD
operation that involves dividing by 4. Instead, the comparison value would be 0, as in the
following:
=SUMPRODUCT((MOD(COLUMN(6:6),4)=0)*(6:6))
If you prefer to work with array formulas, you can use a slightly shorter variation on the above
formula:
=SUM(IF(MOD(COLUMN(6:6),4)=1,6:6))
Note that the formula should be entered by pressing CTRL+SHIFT+ENTER. It will then appear in
the Formula bar with braces ({ }) around the formula. The same modification notes relative to
the MOD divisor and comparison value apply here as they did with the SUMPRODUCT
function.
Both of these formulaic approaches (SUMPRODUCT and the array formula) sum every fourth
cell in the entire row. If you instead want to limit the cells from which the sum is derived to a
portion of the row, then simply replace 6:6 (both instances) with the proper range. Thus, if you
wanted to only sum every fourth cell in the range of A6:Z6, you would use that range in the
formula.
If you do a lot of summing in this manner, and you apply it not only to ranges in a row but
ranges in a column, you may want to consider creating a user-defined function to do the
summing. The following simple function will do the trick:
The function examines the range passed to it, and then sums every fourth cell starting with the
first cell in the range. If you prefer to have it sum every second cell in the range, then change the
comparison value in the If statement, as discussed earlier in this tip. (Since the Mod operation is
used in this function, and it operates the same as the MOD worksheet function, then the same
comparison values come into play for determining which cell in each cluster should be summed.)
The user-defined function will work just fine on either cells in a row or cells in a column. You
simply need to make sure that you pass it the range you want, as demonstrated here:
=SumEveryFourth(C3:C57)
There are a couple of ways to go about this—with or without macros. On the “without macros”
side of the fence, there are a number of different approaches, and all of them involve the use of
additional columns to hold intermediate results.
For example, let’s assume that you have your data in column A, starting in cell A2, and that cell
A1 is empty (it doesn’t even have header text in it). In this case you could enter the following
formula in cell B2:
=IF(NOT(A2-A1=1),A2,IF(A3-A2=1,B1,A2))
=IF(NOT(A3-A2=1),IF(A2-A1=1,TEXT(B1,"00000")
&" - "&TEXT(B2,"00000"),TEXT(A2,"00000")),"")
Now you can copy the formulas in cells B2:C2 down their respective columns. What you end up
with in column C is the condensed series of ZIP Codes. You can copy these values, using Paste
Special to ignore blank cells, to anyplace else you want.
If you want to use a macro approach, then there are no intermediate columns necessary. A macro
can be written that essentially collapses the list of ZIP Codes in place. The following macro
loops through whatever range of cells you selected and creates the condensed list:
Sub CombineValues()
Dim rng As Range
Dim rCell As Range
Dim sNewArray() As String
Dim x As Long
Dim y As Long
Dim sStart As String
Dim sEnd As String
Next
rng.ClearContents
For x = 1 To y
rng.Cells(x) = "'" & sNewArray(x)
Next
Set rng = Nothing
Set rCell = Nothing
End Sub
If you don't have to do the hiding too often, the easiest method is the following, provided there is
something in every cell of column A:
If you don't have data in all the cells of column A, then the following variation is probably the
fastest method:
1. In the Name Box (top-left corner of the worksheet, above column A), enter
A31:A65536. (If you are using Excel 2007 or later, enter A31:A1048576) Excel selects
the range you entered.
2. Hide your rows as you normally would. (Either right-click and hide that way or use the
menus/ribbon. You can also just press CTRL+9.)
If you need to hide rows like this quite often, you could use the macro recorder to record any of
the above techniques, or you could use a more flexible macro, like this one:
Sub HideRows()
Dim r As Variant
On Error GoTo Canceled
r = InputBox("Rows to Hide:")
Rows(r).EntireRow.Hidden = True
Canceled:
End Sub
The only caveat is that you need to remember to include a colon in the rows you specify for the
macro. Thus, if you wanted to hide rows 31 through 543, you would enter 31:543.
There are a number of ways you can approach this problem. The easiest way may be to simply
sort the imported data by the column of your choice. All the rows that contain nothing in that
column end up at either the end or beginning of the data (depending on if you sort in ascending
or descending order) and you can easily delete those rows.
Obviously, when you do a sort in this manner you could end up with your data out of the
original, imported order. If you need your data to be in the original order—but with the blank
rows removed—just insert a column to the left or right of your data, fill it with sequential
numbers, perform the sort by any column except that added column, and then delete the rows
that are blank (with only something in the numbering column). You can then sort a second time
based on the numbering column and your data will be back in its original order.
4. Choose Blanks and then click OK. Excel selects only those cells in the column that are
blank.
5. Choose Delete from the Edit menu. Excel displays the Delete dialog box.
6. Choose Entire Row and then click OK.
If you prefer to use a macro to get rid of the blank rows, you can use something similar to the
following:
Sub DeleteEmptyRows()
Dim LastRow As Long
Dim J As Long
LastRow = ActiveSheet.UsedRange.Rows.Count + _
ActiveSheet.UsedRange.Rows(1).Row - 1
Application.ScreenUpdating = False
For J = LastRow To 1 Step -1
If Application.WorksheetFunction.CountA(Rows(J)) = 0 Then
Rows(J).Delete
End If
Next J
Application.ScreenUpdating = True
End Sub
Why would you want to use a macro? Because you may need to delete empty rows week after
week. Just put the macro into your Personal workbook and you can then access it whenever you
need.
http://www.cpearson.com/Excel/deleting.htm#DeleteBlankRows
http://dmcritchie.mvps.org/excel/lastcell.htm
The easiest way to do this is to make sure that the title is in cell A1. Since you have one column
and two rows frozen, as you scroll to the right cell A1, containing the title, will always be visible
on the screen.
If you want something a bit more fancy with your title, then you need to do a bit of work with
text boxes and macros. If you place the title in a text box positioned in the first row, then you can
use some macros to make sure that the text box is always centered on the screen in that row.
Let’s assume, for the sake of this example, that the text box containing the title is called
“TitleTextBox.” As you scroll left and right in the worksheet, a macro could automatically check
to make sure that the left edge of the text box is always equal to the left edge of the visible screen
area. The following code needs to be added to the worksheet code for the worksheet containing
the text box:
This macro, because it is part of the worksheet code, will run every time the selection is changed
in the worksheet. Thus, when you use the arrow keys to move left or right, use the tab keys, or
select a cell with the mouse, the macro will run and make sure that the left edges of the text box
and the visible area always match up.
When this macro won’t kick in is when you scroll left and right by using the horizontal scroll bar
at the bottom of the screen. There is no “scroll event” that is triggered automatically when the
scroll bars are used. Until a selection is made somewhere within the new visible range, thereby
triggering the SelectionChange event, the textbox location will not be moved.
The only workaround to this limitation is to use Visual Basic’s timer capabilities to update the
textbox periodically. The following code does it every second, but you can adjust it to run less
often, if desired. This code gets added to a regular VBA module:
Sub UpdateTB()
If ActiveSheet.Name = "Sheet1" Then
ActiveSheet.Shapes("TitleTextBox").Left = _
ActiveWindow.VisibleRange.Left
End If
Application.OnTime Now + TimeSerial(0, 0, 1), "UpdateTB"
End Sub
And this gets added to the workbook object to start the timer when the workbook is first opened:
If you use the timer-based approach to positioning the text box, you won’t need to use the one
that is tied to the SelectionChange event. The timer version simply adjusts the title after every
interval.
Rows in a PivotTable
When working with PivotTables, you may have a need to determine how many rows the
PivotTable contains. There are a couple of ways you can go about this. If you want to use a
worksheet formula, you can create a formula that will return the count of cells.
The first thing you need to do is to determine which column of your PivotTable you want to
count. For the sake of this example, let's say that you want to count column C. Display the New
Name dialog box and specify a name for your data in the Name field. In the Refers To field enter
the following formula:
=OFFSET($C$1,0,0,COUNTA($C:$C,1))
Click OK, and you have given a name to a range of data defined by the formula. Assuming that
the name you used was PTRows, you could then use the following formula in a regular cell:
=ROWS(PTRows)
What is returned is the count of the rows in the data range, which represents your PivotTable.
If you want to determine the row count in a macro, the following line will assign the value to the
lRowCount variable:
lRowCount = ActiveSheet.PivotTables("Pivottable1").TableRange2.Rows.Count
This code returns a count of all the rows in the PivotTable, including the page fields. If you want
to omit the page fields and just return the count of the rows in the main PivotTable, you can use
this code instead:
lRowCount = ActiveSheet.PivotTables("Pivottable1").TableRange1.Rows.Count
=COLUMN()
What if you want an alphabetic value, rather than a numeric value? This can be done in any of
several different ways. For instance, the following formula will work very nicely for the first 26
columns, A through Z:
=CHAR(COLUMN()+64)
This works because the letters A through Z use character codes 65 through 90. When COLUMN
returns a value for columns A through Z (1 through 26), this can be added to 64 to get the letters
of those columns, 65 through 90.
Of course, this solution won’t work if you want to know the letter designations of columns
beyond Z. Versions of Excel prior to Excel 2007 can go up to column IV and later versions can
go to column XFD. This formula will work for single- and double-character columns:
=IF(COLUMN()<27,CHAR(COLUMN()+64),CHAR((COLUMN()/26)+64)&
CHAR(MOD(COLUMN(),26)+64))
As you can tell, when you get into multiple characters for a column, the formula gets long rather
quickly. You can make the formula shorter by using a function other than COLUMN, however.
Consider this formula, which relies primarily upon the ADDRESS function:
=LEFT(ADDRESS(1,COLUMN(),4),(COLUMN()>26)+1)
The ADDRESS function returns the address of a specific cell. In this case, it returns the address
for the cell in the first row of the current column. Thus, if the formula is in cell BF27, it returns
BF1. The formula uses the LEFT function to return the correct number of left-most characters in
the address, minus the number 1 for the row.
Again, this approach will only work for columns that have up to two characters in them. If you
are using Excel 2007 or a later version, you need a different formula that will handle one, two, or
three characters. This formula will work just fine:
=LEFT(ADDRESS(1,COLUMN(),4),LEN(ADDRESS(1,COLUMN(),4))-1)
An even shorter version of the formula relies upon the SUBSTITUTE function instead of the
LEFT function:
=SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),"")
This version uses the ROW function to put the address together, and then strips the ROW value
out of the result of the ADDRESS function.
Of course, you can also use a macro-based solution, if you want to. The follow macro will work
with one, two, or three character columns:
Application.Volatile
J = Selection.Column
iDiv = 26 ^ 2
sTemp = ""
While J > 0
K = Int(J / iDiv)
If K > 0 Then sTemp = sTemp & Chr(K + 64)
J = J - (K * iDiv)
iDiv = iDiv / 26
Wend
AlphaCol = sTemp
End Function
The macro is a user-defined function, which means that you can use it in your worksheets by
simply adding this to any cell:
=AlphaCol()
However, I find that a single column can be unhidden very easily using a macro. Consider the
following:
Sub UnhideSingleColumn()
Dim Col As String
Dim rng As Range
StartHere:
Col = InputBox("Enter column to unhide.", "Unhide Column")
If Col = "" Then Exit Sub
On Error Resume Next
' if not a valid range, an error occurs
Set rng = ActiveSheet.Columns(Col)
If Err.Number <> 0 Then
On Error GoTo 0
Err.Clear
MsgBox "Invalid input! Please input a valid column."
GoTo StartHere
End If
rng.EntireColumn.Hidden = False
The macro prompts the user for which column should be made visible, and then tries to select
that column. If the column cannot be selected, then an error is generated and the user is again
asked for input. If the column can be selected, then its .Hidden property is turned off, thereby
making the column visible.
For instance, the following code snippet steps through the columns in a selection and sets the
width of each column to 10 characters:
The first step, of course, is to figure out if two columns are identical or not. This can be
determined rather easily with an array formula such as the following:
=AND(A1:A100=B1:B100)
If you want something that is a bit more automatic, meaning that the duplicate column is deleted,
then you’ll need to use a macro. The following steps through all the columns in the worksheet
and, starting with the right-most column, compares all the columns. If any are the same—
regardless of their order in the worksheet—then the macro asks if you want the duplicate column
deleted.
Sub DeleteDuplicateColumns()
Dim rngData As Range
Dim arr1, arr2
Dim i As Integer, j As Integer, n As Integer
n = rngData.Columns.Count
For i = n To 2 Step -1
For j = i - 1 To 1 Step -1
If WorksheetFunction.CountA(rngData.Columns(i)) <> 0 And _
WorksheetFunction.CountA(rngData.Columns(j)) <> 0 Then
arr1 = rngData.Columns(i)
arr2 = rngData.Columns(j)
If AreEqualArr(arr1, arr2) Then
With rngData.Columns(j)
'mark column to be deleted
.Copy
If MsgBox("Delete marked column?", vbYesNo) _
= vbYes Then
rngData.Columns(j).Delete
Else
'remove mark
Application.CutCopyMode = False
End If
End With
End If
End If
Next j
Next i
End Sub
The first approach works very well if your data is sorted by column. In other words, the data that
you import is in ascending order, or you want it in sorted order. In this case, follow these steps:
1. Select the columns that represent your data. Make sure you select, as part of the range,
all the blank columns as well.
2. Display the Sort dialog box. (In Excel 2007 or later display the Data tab of the ribbon
and click the Sort tool. In older versions of Excel choose the Sort option from the Data
menu.)
3. Click the Options button. Excel displays the Sort Options dialog box.
7. Click on OK.
When sorting in this manner, all the empty columns end up “pushed” to the right, and your data
is in a sorted order.
If you don’t want your data sorted, then you can use a nifty macro that will check for blank
columns in a selected range and then delete those columns. The following macro will do the
trick:
Sub DeleteEmptyColumns()
first = Selection.Column
last = Selection.Columns(Selection.Columns.Count).Column
For i = last To first Step -1
If WorksheetFunction.CountBlank(ActiveSheet.Columns(i)) = 65536 Then
Columns(i).Delete
End If
Next i
End Sub
To use the macro, select the range of columns in which you want blank columns deleted. The
macro steps through the columns and if the column is truly blank, it is deleted. You should note
that this macro will delete only columns that are truly empty. If cells within a column include a
formula that returns a zero value (and you have the display of zeros values turned off) or that
returns an empty string, then the column isn’t empty—it contains formulas. In this case, the
column won’t be deleted.
Combining Columns
There may be times when you have a need to concatenate cells together. For instance, you may
have information in three columns, but you want it combined together into the first column of
each row. The following macro, StuffTogether, will do just that. It examines the range of cells
you select, and then moves everything from each cell in a row into the first cell of the row.
Sub StuffTogether()
Dim FirstCol As Integer, FirstRow As Integer
Dim ColCount As Integer, RowCount As Integer
Dim ThisCol As Integer, ThisRow As Integer
Dim J As Integer, K As Integer
Dim MyText As String
FirstCol = ActiveWindow.RangeSelection.Column
FirstRow = ActiveWindow.RangeSelection.Row
ColCount = ActiveWindow.Selection.Columns.Count
RowCount = ActiveWindow.Selection.Rows.Count
For J = 1 To RowCount
ThisRow = FirstRow + J - 1
MyText = ""
For K = 1 To ColCount
ThisCol = FirstCol + K - 1
MyText = MyText & Cells(ThisRow, ThisCol).Text & " "
There are a couple of ways this can be done. If you don’t have to do this too often, a formulaic
approach may be best. Just use the ampersand (&) to concatenate the contents of the rows you
want to combine:
=C6 & " " & C7 & " " & C8 & " " & C9
The result is all the text combined into a single cell. You can copy this result to the Clipboard,
and then use Paste Special to put it into the final cell where you need it. Finally you can delete
the original multiple rows that are no longer needed.
If you need to perform this type of concatenation more than a few times, a simple macro may
help:
Sub Combine()
Dim J As Integer
To use this macro, select the cells you want to concatenate and then run the macro. The contents
of all the cells are combined into the first cell in the selection, then whatever is in the other cells
is cleared. The macro doesn’t delete any rows; that is left for you to do. It does, however,
combine the contents quickly—even more quickly if you assign a shortcut key to the macro.
For instance, the first five rows may have the same part number, so Doug wants those rows to be
shaded green. The next two rows have a different part number, so he wants those to have no
green shading. The next three rows have the next part number, so those should be green again,
and so on. Every time the part number changes, the shading of the row (green or not green)
should change.
One easy way to accomplish this task is to create a helper column that displays either a 0 or a 1
depending upon the part number in column A. For instance, let's say you wanted to put your
helper column in column Z. You could put the following formula in cell Z2:
=IF(A2=A1,Z1,1-Z1)
Copy the formula down column Z for each row in your data table. When done, column Z will
contain either 1 or 0, switching only when the part number in column A changes. You can then
use the value in column Z as a controlling value for your conditional formatting. All you need to
do is set the formula in the format so that if column Z contains 1, then your cells are green.
You should note that once your conditional formatting is set up and working properly, you can
hide column Z so that it isn't a distraction to anyone using your data table.
If you can't use a helper column for some reason, then there is a pretty cool formula you can use
in the conditional format itself. Just make sure your data table is sorted by column A (the part
numbers) and then select all the cells in the table, with the exception of any column headers.
Then define a conditional format that uses this formula:
=MOD(SUMPRODUCT(--(($A$1:INDIRECT(ADDRESS(ROW()-1,1,3,1))
=$A$2:INDIRECT(ADDRESS(ROW(),1,3,1)))=FALSE)),2)
Remember that this is a single formula, entered in the conditional formatting rule, all on one line.
This formula assumes that the part numbers are in column A and that the data table begins in cell
A2. Further, if you delete any rows in the data table, you'll want to reapply the conditional
format to all the cells in the data table.
Finally, there are any number of macros that you could write to apply the formatting. All you
need to do is have the macro step through the cells in column A, determining whether the part
number changes, and then apply the correct formatting based on what it finds out. Here is an
example:
Sub ShadeRows()
Dim ThisOrder As Long
Dim PrvOrder As Long
For R = 2 To LastRow
ThisOrder = Cells(R, 1).Value
PrvOrder = Cells(R - 1, 1).Value
If ThisOrder <> PrvOrder Then Clr = 1 - Clr
Apparently VBA trails somewhat behind the behavior of the user interface, as selecting the entire
column B also ends up selecting all the columns, A through F:
Sub TestMacro1()
Range("B3").EntireColumn.Select
End Sub
There seems to be no way around this behavior. Even if you eliminate the EntireColumn method
and simply select column B, you still get all the columns, A through F:
Sub TestMacro2()
Range("B:B").Select
End Sub
It is probably a better programming approach to not select the column preparatory to doing some
action upon that column, but to do the action directly. For instance, let’s assume that you want to
make all of the cells in column B bold. You can do so in this manner:
Sub TestMacro3()
Range("B3").EntireColumn.Font.Bold = True
End Sub
This affects only the cells in column B, and nothing in A or C through F. You could similarly use
an iterative approach to processing the cells in the desired column:
Sub TestMacro4()
Dim rCell As Range
Dim X As Long
X = 1
For Each rCell In Range("B:B")
rCell.Value = X
X = X + 1
Next
End Sub
This stuffs a value into each cell in column B, and conveniently ignores any merges that include
a cell in column B.
If it is mandatory that you be able to select an entire column, without any columns added because
of merged cells, then you may be tempted to use the MergeCells property to check for the
merged cells. According to the VBA online help, the following should detect the merged cells in
the selection and then dump out of the macro:
Sub TestMacro5()
Range("B3").EntireColumn.Select
If Selection.MergeCells Then
Exit Sub
End If
'
' Perform rest of macro
'
End Sub
Unfortunately, testing shows that this code will not work. The MergeCells property apparently
only returns True if the entire selection is made up of merged cells, not if the selection only
contains a few merged cells. That means that you are left to some other way to determine if
merged cells have modified the intended selection, such as the following:
Sub TestMacro6()
Range("B3").EntireColumn.Select
If Selection.Columns.Count > 1 Then
Exit Sub
End If
'
' Perform rest of macro
'
End Sub
This approach examines the number of columns in the selection, and then dumps out if Excel
reports that there is more than one.
You can, however, achieve the desired effect by using a macro to analyze the cell and adjust the
Hidden attribute of the row you want to conditionally hide. The following simple macro, for
instance, examines the contents of cell B4 and, if the cell contains 0, hides column H. If cell B4
does not contain 0, then column H is displayed.
Sub HideColumn1()
If Range("B4").Value = 0 Then
Columns("H").EntireColumn.Hidden = True
Else
Columns("H").EntireColumn.Hidden = False
End If
End Sub
If you want the hiding and unhiding of the column to be done in real time, you can use the
following version of the macro. Just make sure that you put this version in the code window for
the worksheet on which you want it to work.
Notice that the guts of the two macros are the same. The only difference is that the second
version is triggered by an event within Excel—the changing of which cell is currently selected.
This means that every time you move from one cell to another, the value in B4 is checked and
column H is either hidden or unhidden.
If it is possible that the contents of cell B4 could be empty, then it is possible that Excel will
interpret that emptiness as a zero value. In that case, you can modify the macro just a bit so that it
checks for an empty cell.
Sub HideColumn2()
Dim rCell As Range
Set rCell = Range("B4")
Columns("H").EntireColumn.Hidden = False
If (Not IsEmpty(rCell))
And (IsNumeric(rCell)
And (rCell.Value = 0) Then
Columns("H").EntireColumn.Hidden = True
End If
End Sub
This version of the macro actually checks three conditions: that B4 is not empty, that it contains
a numeric value, and that the value is 0. If all three of these conditions are met, then column H is
hidden.
Toggling AutoFilter
One of the handy features of Excel is AutoFilter. It allows you to quickly filter any list by the
contents of a particular column. You can add, to the Quick Access Toolbar or a regular toolbar,
an AutoFilter tool. This tool uses an image of a funnel and an equal sign.
The tool is a bit deceptive, however; it is not the same as the AutoFilter option available from the
Data menu. The menu option is a toggle condition. If you have a cell selected in a list and you
choose the menu option, then the AutoFilter controls appear at the top of each column in the
list—there are no other changes to the list. If you use the AutoFilter tool, not only do the controls
appear, but Excel filters the list based on the cell you had selected when you used the tool.
Another difference between the two is that the AutoFilter menu option functions like a toggle—
choose it once, and the AutoFilter is applied; choose it again and it is removed. The AutoFilter
tool doesn’t do that; it only applies the AutoFilter.
What if you want a toolbar option that is a real toggle, just like the menu option? There are two
approaches you can use to solve this problem. The first involves the use of a simple macro:
Sub ToggleAutoFilter()
On Error GoTo errMessage
Selection.AutoFilter
Exit Sub
errMessage:
MsgBox "Select a cell in the range to be filtered.", vbOKOnly
End Sub
All you need to do is assign the macro to a toolbar button or to a shortcut key and you can turn
AutoFilter on and off, just as if you selected the option from the menus.
The second option may be even simpler. Just follow these steps if you are using Excel 2007 or
later:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
2. At the left of the dialog box, click Customize (Excel 2007) or Quick Access Toolbar
(Excel 2010 and Excel 2013).
3. Using the Choose Commands From drop-down list, choose Data Tab.
4. In the list of commands, select Filter.
5. Click the Add button. The icon for the command appears at the right of the dialog box.
6. Click OK.
It is interesting that the icon for the Filter command (step 4) looks exactly the same as the icon
for the AutoFilter command. Regardless, they are not the same, as already discussed. These steps
add the tool to the Quick Access Toolbar, and it works exactly the same as the Filter tool on the
Data tab of the ribbon.
1. Choose Customize from the Tools menu. Word displays the Customize dialog box.
2. In the actual Excel menus (not the Customize dialog box), select the Filter option from
the Data menu.
3. As you hold down the CTRL key, drag the AutoFilter option from the menus and drop it
someplace in a toolbar. (If you don’t hold down the CTRL key, the menu option is
moved. You don’t want to do this, so hold down the CTRL key.)
4. Click on the Close button on the Customize dialog box.
The result is that the menu option is now accessible from the toolbar. You can click on it to turn
AutoFilter on and off, at will.
The lack of contrast between the black and blue drop-down arrows in a filtered column is not an
uncommon complaint. In fact, this very issue was addressed in a past issue of ExcelTips. (You
can search at the ExcelTips Web site for the phrase “drop-down arrow colors” for a handy tip in
this regard.)
If you actually want to know what criteria are being applied to a column, then you’ll be
interested in a small macro that will place the criteria into another cell:
Filter = ""
On Error GoTo Done
With Rng.Parent.AutoFilter
If Intersect(Rng, .Range) Is Nothing Then GoTo Done
With .Filters(Rng.Column - .Range.Column + 1)
If Not .On Then GoTo Done
Filter = .Criteria1
Select Case .Operator
Case xlAnd
Filter = Filter & " AND " & .Criteria2
Case xlOr
Filter = Filter & " OR " & .Criteria2
End Select
End With
End With
Done:
DispCriteria = Filter
End Function
This is actually a user-defined function that you can use in your worksheet. For instance, if you
wanted to know the filtering criteria that was applied to column C, you could use the following
in a cell:
=DispCriteria(C:C)
If you prefer, you could simply reference the header cell for the column being filtered. For
example, if the header (the one to which AutoFilter adds the drop-down arrow) is cell C3, you
could use the following:
=DispCriteria(C3)
The criteria displayed by the function are those actually used by AutoFilter. For instance, if you
use a filtering criteria that says “Top 10,” then Excel translates that at the time it is applied into
something like “>=214.3281932” (the value will vary, depending on your data). It is the
formulatic filter that is returned by the DispCriteria function, not the “Top 10” wording.
The function is based on one created by Microsoft MVP Stephen Bullen. The macro has been
published in various places, and you can find it on John Walkenbach’s Web site, here:
http://www.j-walk.com/ss/excel/usertips/tip044.htm
The filtering capabilities of Excel don’t provide a way that you can automatically check for the
presence of comments, but there are a couple of ways you can approach a solution. One possible
solution is to follow these general steps:
If you prefer, you can create a user-defined function that will let you know if a particular cell has
a comment associated with it. The following is a simple way to make such a determination:
Now you can use a formula such as the following within a worksheet:
=CellHasComment(B2)
When the formula is executed, it returns either True or False, depending on whether cell B2 has a
comment or not. You can then use Excel’s filtering capabilities to display only those rows that
have a True returned by the formula.
This was an interesting problem to research, and it took a bit of poking and prodding. It appears
that Excel allows you to define sorting settings for the filters you apply to a data set. To see this
in action, follow these steps:
1. Open a workbook that has some data in it, or create a workbook that has data you can
sort and filter.
2. Select a cell within the data.
3. Display the Data tab of the ribbon.
4. Click the Filter tool, within the Sort & Filter group. Excel places drop-down arrows at
the top of each column in your data.
5. Click the drop-down arrow at the top of one of the column.
Note that the drop-down menu that appears allows you to select which data is filtered in the
column. This is where people normally stop looking, though. It is interesting that at the top of the
drop-down menu there are some sorting controls. If you use these controls, then the filtered
results that Excel displays are filtered according to your specifications.
If you turn on the macro recorder at this point (after applying a filter that includes sorting) and
click the Clear tool, this is the macro that is recorded by Excel:
Sub Macro1()
'
' Macro1 Macro
'
'
ActiveWorkbook.Worksheets("Sheet1").AutoFilter.Sort.SortFields.Clear
ActiveSheet.ShowAllData
End Sub
Note that there are two lines in the recorded macro. The first line clears the sorting settings and
the second clears all the filtering settings. If you record the same steps without having first
chosen a sorting setting in the drop-down filtering menu at the top of a column, then Excel
doesn't include the first line.
The upshot of this is that you can easily create your own single-line macro that removes any
filtering but retains any sorting settings made through the filtering drop-down. The simple macro
would look like this:
Sub ClearFilter()
ActiveSheet.ShowAllData
End Sub
It should be noted that if you turn off filtering (by clicking a second time on the Filter tool),
Excel automatically clears any filtering and sorting settings you may have applied. If you want to
retain sorting settings—particularly complex sorting settings—outside of the filtering
framework, then it would be best to record, as a macro, the steps you go through for sorting your
data.
Unfortunately, it appears that the color of the drop-down arrows is hard-coded into Excel and
cannot be changed. You can try a workaround, if you desire, that would instead color the first
cell in each of the filtered columns. Add the following macro to a regular module in the
workbook:
Sub ColorDisplayFilter()
Dim flt As Filter
Dim iCol As Integer
Dim lRow As Long
iCol = 0
lRow = ActiveSheet.AutoFilter.Range.Row
Application.EnableEvents = False
For Each flt In ActiveSheet.AutoFilter.Filters
iCol = iCol + 1
If flt.On Then
Cells(lRow, iCol).Interior.Color = vbYellow
Else
Cells(lRow, iCol).Interior.ColorIndex = xlColorIndexNone
End If
Next flt
Application.EnableEvents = True
End Sub
The code steps through the filters for a worksheet and, if the filter is active for a column, colors
the first cell yellow. If the filter is not active, then it gets rid of the yellow color.
To trigger the routine so that it runs automatically, there are two things you need to do. First of
all, you need to add the following macro to the thisWorkbook object:
This triggers every time the worksheet is calculated. If the AutoFilterMode property is True, then
the coloring macro is executed.
The second thing you need to do is add a SUBTOTAL formula to your worksheet. Assuming
that column A is one of the columns in the filter, you could add the following to the worksheet:
=SUBTOTAL(9,A:A)
The SUBTOTAL function is recalculated every time a filter is changed, so this helps ensure that
the coloring macro is executed. The formula can be hidden, if desired, but it must be on the
worksheet that has the filter to ensure that the sheet triggers the event.
Perhaps the easiest way to do this would be to add a single column to the source data for your
PivotTable. The column could contain a simple formula that designates whether the row is
within the desired range for inclusion in the PivotTable. For instance, if column A contains the
transaction date for the row, then you could include the following in the added column:
=YEAR(A2)>YEAR(NOW())-3
The result of the formula is either True or False, depending on whether the transaction is within
the previous three years or not. Thus, if this formula is evaluated in 2013, then any transactions
within 2011, 2012, and 2013 would return True; all others would be False. Then, within your
PivotTable definition you could filter based on the contents of this particular column, thereby
ensuring that only those True rows are included in the PivotTable.
If you prefer a macro-based solution, you could easily develop one that examined each of the
PivotTables and changed the PivotField named "Year" so that it was equal to a desired year. The
following shows how easy it is to make such a change:
Sub ChangePivotYear()
Dim sht As Worksheet
Dim pvt As PivotTable
Dim iYear as Integer
The macro sets the field to 2012; if you want to use a different year, just change what is assigned
to the Year variable. Note, as well, that the macro affects all the PivotTables in the entire
workbook.
The best solution to this problem is to make sure that the macro turns off filtering. This can be
easily done by adding the following line of code near the beginning of the macro:
ActiveWorksheet.AutoFilterMode = False
This ensures that filtering is off and removes the problems that filtered data may present for your
macro.
Most of the time this isn’t a problem. It can be a problem, however, for those entering dates that
“wrap around” to last year. For instance, many people enter dates for the previous month or two
in their worksheets. In most months this isn’t a problem, because the past month or two is in the
same year as the current month. It can be a problem during January and February, however,
when you may be entering dates from November and December of the previous year.
One solution is to always enter the year when entering a date. It is unarguably faster to leave the
year off during data entry and allow Excel to add it to your entry. Thus, it would be nice to come
up with a way to enter dates during the first two months of the year and have the previous year’s
year appended to them.
One way to handle this is to change the system date on your computer. Within Windows,
decrement the system date by one year. Then, all dates that you enter will change to last year.
This has ramifications in other programs, however, unless you remember to change the system
date back. It also can mess up your data entry if, during the latter part of January and early
February, you start entering dates from this year, and Excel automatically appends last year’s
year to them.
Doing anything more complex necessarily involves the use of a macro. Consider the following
example, which should be added to the code window for a worksheet:
This macro automatically runs whenever there is a change in the worksheet. If the change being
made is to a single cell in column A, and what is being entered is a date that is greater than
today’s date, then a year is subtracted from what is being entered.
This works great, provided you are routinely entering dates that are within either the last year or
the months so far in the current year. If you specifically add a far-future date (such as entering
6/11/09 on 3/2/08), then the year is still incremented by one. The macro could, of course, be
modified to check to see if the date being entered is in the last two months of a year, and that the
date is actually being entered during the first two months of a year, before doing the decrement
of the year.
Unfortunately Excel cannot. Why? Because you have given it no indication that this should be a
date. (Excel keys on separators, not on numeric values.) If you or your data entry people cannot
change their input habits so that separators are also entered, then you will need some sort of a
workaround to convert the entered information to an actual date value.
Your first thought might be that you could use a custom format to display the information.
Consider the following custom format:
##"/"##"/"####
This format would display the number 08152008 as 8/15/2008. The only problem is that it only
changes the display of the number—if you want to use the date as a real Excel date, you cannot
do so because you haven’t converted the value into something that Excel recognizes as a date.
If the values input were very consistent in their format, and if they were input as text instead of
as numeric values, then there is an easy way you can convert them to dates. By very consistent, I
mean that the input always used two digits for the month, two for the day, and four for the year.
In addition, the cells containing the values must be formatted as text. In this instance, you can
follow these steps:
If all went well, Excel should have parsed the text values as dates, and you can delete the original
column. If this did not work, then it means that either the original values were not formatted as
text, or eight digits were not used to enter all the dates.
Another possible solution is to use a formula to convert the entered values into actual dates. The
following is one such formula:
=DATE(RIGHT(A1,4),LEFT(A1,IF(LEN(A1) = 8,2,1)),LEFT(RIGHT(A1,6),2))
This formula assumes that the entered date (the one without separators) is in cell A1. The
formula will work with either seven- or eight-digit dates.
If you prefer custom functions, you can create one in VBA that examines the data being passed,
converts it to a date/time format, and then returns the result. The following function is very
versatile in this regard; it will work with both American and European date formats:
If bAmerican Then
DateTime = DateSerial(iYear, iMonth, iDay)
Else
DateTime = DateSerial(iYear, iDay, iMonth)
End If
This macro function assumes that the data being passed to it is a numeric value, as would
normally happen when inputting dates without separators. (Refer back to the logic on this at the
beginning of the tip.)
As you can tell, there are a number of workarounds, but none of them is as simple as just
entering separators when entering the dates. If training yourself or your data input people to do
this is difficult, you might consider setting up some data validation rules for the input cells.
These rules can check to make sure that you are entering information using a specific format
(such as a date with separators), and stop you if you are not. (How you create data validation
rules has been covered in other issues of ExcelTips.)
For this reason, you may wonder if there is a way to skip entering the colon and either have them
entered automatically or entered all at once. Entering them automatically takes a bit more doing,
requiring the use of a macro, and will be covered shortly. Entering the colons all at once can be
done with a formula, as in the following:
=TIMEVALUE(REPLACE(A1,3,0,":"))
This formula assumes that the time value (without a colon) is in cell A1, and that it is comprised
of four digits. Thus, if cell A1 contains a value such as 1422, then the formula returns 14:22 as
an actual time value. (You may need to format the cell as a time value.)
If your original entry cell might contain a time that uses only three digits, such as 813 instead of
0813, then you need to use a slightly different formula:
=TIME(LEFT(A1,LEN(A1)-2),RIGHT(A1,2),0)
If you prefer for the insertion of the colons to happen automatically, you can use a macro. You
can create a macro that will examine a range of cells where you plan on adding dates to the
worksheet, and then insert the colon in the entry. This is done by creating a macro that is
triggered by the SheetChange event. The following macro is one such:
Application.EnableEvents = False
With Target
If .HasFormula = False Then
Select Case Len(.Value)
Case 1 ' e.g., 1 = 00:01 AM
TimeStr = "00:0" & .Value
Case 2 ' e.g., 12 = 00:12 AM
TimeStr = "00:" & .Value
Case 3 ' e.g., 735 = 7:35 AM
TimeStr = Left(.Value, 1) & ":" & _
Right(.Value, 2)
Case 4 ' e.g., 1234 = 12:34
TimeStr = Left(.Value, 2) & ":" & _
Right(.Value, 2)
Case Else
Err.Raise 0
End Select
.Value = TimeValue(TimeStr)
End If
End With
Application.EnableEvents = True
Exit Sub
EndMacro:
MsgBox "You did not enter a valid time"
Application.EnableEvents = True
ActiveCell.Offset(-1, 0).Select
End Sub
The first thing the macro does is to check to see if the data that was just entered was in the range
C7:D15. If it wasn’t, then the macro exits. It also checks to make sure that there is only a single
cell selected and that the cell isn’t empty. If all these criteria are met,, then the macro checks the
length of the value in the cell and pads it out with leading zeroes, as necessary. This macro is
based on a macro found at Chip Pearson’s site, here:
http://cpearson.com/excel/DateTimeEntry.htm
If the dates are in the same sequence format that you use in your regional settings, then
converting is a snap. For instance, if your regional settings use the date format MDY (month
followed by day followed by year), and the date you are importing is in the same format, then
you can simply select the cells and replace the periods with a slash. When Excel changes
1.15.2011 to 1/15/2011, it automatically parses the result as a date.
If the format you are importing doesn’t match your regional settings, then you need to shuffle
around the date into the same format. For instance, if the date you are importing is 01.10.11
(January 10, 2011), and your system would interpret this as October 1, 2011, then the easiest way
is to separate the date into individual components, and then put them back together. Follow these
general steps:
=DATE(C1,A1,B1)
Another solution is to simply use a macro to do the conversion. The following is a user-defined
function that takes the non-standard date and converts it to a properly formatted date value. The
macro also switches around the position of the month and day, as done in the Text to Columns
technique.
K = Len(A)
K1 = InStr(1, A, ".")
K2 = InStr(K1 + 1, A, ".")
Convert_Date = DateSerial(Val(Mid(A, K2 + 1, _
K - K2 + 1)), Val(Mid(A, K1 + 1, K2 - K1)), _
Val(Mid(A, 1, K1 - 1)))
End Function
When it comes to dates and times, however, not all programs speak in a way that Excel can
understand. For instance, if your other program stores dates in the format "Mon Jan 10 14:33:03
2011", then Excel won’t be able to parse the date and you will need to do the conversion in some
other manner.
Fortunately, most programs generate their dates and times in a format that follows a pattern.
Assuming, for instance, that "Mon Jan 10 14:33:03 2001" represents the format followed by all
dates, you can do the conversion using a simple formula:
=DATEVALUE(MID(A1,9,2)&MID(A1,5,3)&RIGHT(A1,4)) + TIMEVALUE(MID(A1,12,8))
This formula assumes that the foreign date/time format is in cell A1. Simply format the result of
the formula using one of Excel’s date/time formats, and you’ll have no problem.
If you prefer, you can use the Text to Columns function to break the foreign date/time format
into its integral parts:
1. Make sure there are four empty columns to the right of the date/time. This is where
Excel will place the various parts of the date/time.
2. Choose all of the cells containing the foreign dates/times.
3. Choose Text to Columns from the Data menu. (In Excel 2007 and later versions, display
the Data tab of the ribbon and click the Convert Text to Columns tool in the Data Tools
group.) Excel starts the Convert Text to Columns Wizard.
4. Make sure that Delimited is selected, then click Next. Excel displays the second step of
the wizard.
5. Make sure the Space check box is selected.
6. Click Finish.
The dates and times are now separated into five individual columns. You can now use a formula
to put a valid date/time back together. For instance, assuming that the exploded version of the
date/time is in cells A1:E1, you could use the following:
=(C1&B1&E1)+D2
Again, format the result using a date/time format, and you are all set.
If you prefer to use a macro to do the conversion, then the following macro will step through all
the selected cells and do the conversion:
Sub ConvDate()
Dim c As Range
The macro converts the text string to an acceptable date/time (using DateValue) and then formats
the cell to display the value property.
There are actually several ways you could go about solving this problem. One way is to set up
"equivalence tables" within a worksheet, where the left column includes a code character and the
right indicates the numeric value that is associated with that character. You could then fashion a
formula that uses VLOOKUP to find the values and convert the results into a date.
As an example, create you equivalence table in some unused cells to the right of your data. In my
case, I put the table in columns P and Q. In column P I put the code characters, 1 through 9 and a
through z. (Make sure you precede the digits 1 through 9 with an apostrophe so they are stored as
text rather than as numbers.) In column Q I put the numbers 1 through 35. This entire range
(P1:Q35) I then gave a name of DateTable. Here is the formula, then, that will return a decoded
date for a coded date stored in cell A1:
=DATE(2000+VLOOKUP(LEFT(A1,1),DateTable,2,0),
VLOOKUP(MID(A1,2,1),DateTable,2,0),VLOOKUP(RIGHT(A1,1),
DateTable,2,0))
Remember that this is a single, continuous formula. Another technique is to bypass the
equivalence tables altogether and instead use a formula to do the conversion. The following is an
example that will decode a date in cell A1:
=DATE(2000+FIND(LEFT(A1,1),"123456789abcdefghijklmnopqrstuvwxyz"),
FIND(MID(A1,2,1),"123456789abc"),FIND(MID(A1,3,1),
"123456789abcdefghijklmnopqrstuv"))
This formula uses the FIND function to translate from the code character to a value, and then
these values are used in the DATE function to return the desired date. Another possible formula
relies, instead, on character code values to create the date:
=DATE(2000+CODE(MID(A1,1,1))-87+(CODE(MID(A1,1,1))<58)*39,CODE(MID(A1,2,1))-
87+(CODE(MID(A1,2,1))<58)*39,CODE(MID(A1,3,1))-87+(CODE(MID(A1,3,1))<58)*39)
Finally, you could create a user-defined function to return the decoded date. The following is just
a simple example; it looks at each character and converts it to a numeric value that is then used
with the DateSerial function to create an Excel date serial number:
Application.Volatile
D = InStr(X, Right(datecode, 1))
M = InStr(X, Mid(datecode, 2, 1))
Y = 2000 + InStr(X, Left(datecode, 1))
DecodeDate = DateSerial(Y, M, D)
End Function
It should be pointed out, as well, that regardless of the approach you use, there is an inherent
flaw in your date codes. The year uses the code values 1 through 9 and a through z. This means
that the date code can be one of 35 possible values. When added to the year 2000 (the base year
for how you described the code), that means that the maximum year value that can be coded is
2035. Any date after that year will not work with this coding.
2. Choose Validation from the Data menu. If you are using Excel 2007 or a later version,
click the Data tab of the ribbon then click Data Validation in the Data Tools group.
Excel displays the Data Validation dialog box.
3. Make sure the Settings tab is displayed.
Now, anytime you try to enter a date in cell A2 that is earlier than the date in cell A1, Excel
displays an error message and will not allow the date to be entered.
What happens, however, when you want to limit the dates that can be entered in cell A1? For
instance, if you put the date 4/1/11 in cell A1, and you want to make sure that the next date
entered in A1 is not earlier than 4/1/11. If you put a date such as 4/15/11 in cell A1, that would
be OK, but then the next time you enter a date in cell A1 you don’t want it earlier than 4/15/11.
In other words, you want to make sure that cell A1 can only accept dates later than the date
currently in A1.
This is a bit stickier. If you follow the above steps but select cell A1 in step 1, then data
validation won’t work. Why? Because the date you enter in cell A1 will always be greater than
or equal to the date you enter in A1—Excel doesn’t compare to the previous date in A1 when
doing data validation.
The only way to work through this problem is through the use of two macros. First, place the
following macro in a regular module:
Sub Date_Validation()
Dim dteDate As Date
Dim strDate As String
With Range("A1")
' Memo original date
dteDate = CDate(.Text)
' Create date string
strDate = Format(dteDate, "m\/d\/yy")
With .Validation
' Delete old settings
.Delete
' Set new data validation
.Add _
Type:=xlValidateDate, _
AlertStyle:=xlValidAlertStop, _
Operator:=xlGreaterEqual, _
Formula1:=strDate
.IgnoreBlank = False
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = "Invalid Date Entry"
.InputMessage = ""
.ErrorMessage = _
"Date is older than the previous date (" & _
dteDate & ")."
.ShowInput = True
.ShowError = True
End With
End With
End Sub
This macro needs to be called by another macro, this one placed in the worksheet’s code
window, so that it is triggered every time there is a change in the worksheet:
The way these macros work is really quite interesting. Because you place the latter one in the
worksheet’s code window, it triggers every time there is a change to the worksheet. If the cell
being changed is A1, then the Date_Validation macro is run.
The Date_Validation macro grabs the date from cell A1 and constructs a data validation rule for
the cell. That’s all it does—sets a data validation rule that won’t allow a date to be entered in the
cell that is earlier than the date currently in the cell.
The beauty of the macro is that once the data validation rule is in effect, then the next time cell
A1 is changed, the data validation rule is triggered before the Worksheet_Change event is fired.
Thus, the data validation rule makes sure that only a date greater than the current date can be
entered. Once data validation is cleared, then the macro takes care of resetting the data validation
rule, so it compares to the newly entered date.
This is a harder problem to approach than one might assume, particularly in Excel. Since an
action needs to be taken upon the pressing of a particular key (in this case, the plus or minus
keys), one would naturally assume that the OnKey method could be used. Consider the following
examples:
Sub Start_OnKey()
Application.OnKey "{+}", "Plus1"
Application.OnKey "-", "Minus1"
End Sub
Sub End_OnKey()
Application.OnKey "{+}"
Application.OnKey "-"
End Sub
Sub Plus1()
If IsDate(ActiveCell) And Not ActiveCell.HasFormula Then
ActiveCell.Value = ActiveCell.Value + 1
End If
End Sub
Sub Minus1()
If IsDate(ActiveCell) And Not ActiveCell.HasFormula Then
ActiveCell.Value = ActiveCell.Value - 1
End If
End Sub
According to all the VBA documentation, the above should work just fine, once you run the
Start_OnKey macro. Every time a plus or minus key is pressed, the appropriate procedure is run
to either increment the date or decrement the date. The problem is, it won't work on some
versions of Excel. Why? Because the plus key, when pressed, apparently puts some versions of
Excel into a special "formula entry" mode that bypasses the normal keyboard buffer relied upon
by OnKey. So while pressing the minus key while a cell containing a date is selected produces
the desired result, pressing the plus key does not.
For those versions of Excel where the plus key is a problem, the only solution is to change the
keystrokes to something else. For instance, you could change the keypresses so that CTRL+U is
used to increment the date and CTRL+D is used to decrement the date:
Sub Start_OnKey()
Application.OnKey "^u", "Plus1"
Application.OnKey "^d", "Minus1"
End Sub
Sub End_OnKey()
Application.OnKey "^u"
Application.OnKey "^d"
End Sub
For the purposes of providing an answer, I’m going to assume that the range you want displayed
will be equal to the 13 weeks (one quarter) immediately preceding the current fiscal week. With
this in mind, there are a couple of things that must be done by the macro. First, it must determine
what fiscal week it currently is. Then, it must hide all weeks not in the 13 weeks prior to this
current fiscal week and unhide all those that are.
This is all relatively easy to do, with the exception of figuring out which fiscal week it currently
is. The method of determining fiscal weeks can vary wildly from company to company. For
simplicity’s sake, however, I’m going to assume that the determination is fairly straightforward:
divide the day of the year by seven and see what we have.
Sub HideWeeks()
Dim BeginYear As Date 'start of fiscal year date
Dim FirstWeekCol As Integer 'first fiscal week column
Dim FirstShowWkCol As Integer 'first column to show
Dim CurrWkCol As Integer 'current week column
Dim J As Integer
Application.ScreenUpdating = False
Application.ScreenUpdating = True
End Sub
Note that there is one value that must be grabbed from the worksheet in this macro: the last day
of the prior year. It is assumed that this is in cell A1, and it is grabbed and placed in the
BeginYear variable. This value is used to determine the day of the current year.
Working with Outlook is a bit "higher level" than your run-of-the-mill Excel macro because you
need to understand not only how to access Excel data in the macro, but also how to manipulate
Outlook data. Without knowing exactly what data you need to transfer from the worksheet to the
Outlook appointment, let's examine a short scenario.
Let's assume that you have a worksheet that contains a series of rows, each of which represents a
single appointment you want to create. Each appointment contains information in seven columns,
as follows, from left to right:
• Subject. Text that describes the event/appointment (for example, "Yellow Pages
Reminder")
• Location. Text that describes the location of the event, such as a meeting room or a
conference call number (this is optional)
• Start Date/Time. Enter the date and time the event should start using a standard Excel
date format (you can display any way you like)
• Duration. Integer that represents a number of minutes for the appointment
• Busy Status. Integer that represents an optional value indicating if the time should
show as Free (0), Tentative (1), Busy (2), or Out of Office (3)
• Reminder Time. Integer that represents a number of minutes before the appointment
that a reminder should pop-up (as in 4320 which is the number of minutes in 3 days)
• Body. Text that describes any detail you might want to place in the body of the
appointment
With this data in place, you can use a macro to loop through all the rows (starting with the
second row, assuming the first row has headings) and create an appointment for each row.
Sub AddAppointments()
' Create the Outlook session
Set myOutlook = CreateObject("Outlook.Application")
The macro continues to loop through the rows until the Subject column is empty.
The only other option is to use some sort of conversion formula. These are easy enough to put
together, but the resulting cell will not contain a true Excel date, but text. This precludes the cell
contents from being used in other date-related functions. The following is an example of the type
of conversion formula you can use:
=DAY(A1)&IF(OR(DAY(A1)={1,2,3,21,22,23,31}),
CHOOSE(1*RIGHT(DAY(A1),1),"st","nd ","rd "),"th")
&TEXT(A1,"mmmm, yyyy")
There are others, but they all essentially do the same thing—pull the various parts of a date apart
and put them back together with the proper suffix.
If you prefer, you can also create a macro function that would return a properly formatted date,
with the ordinal suffix. The following is one such macro:
dDate = Day(myDate)
mDate = Month(myDate)
You use the macro by simply invoking it within a cell formula. For example, if you have a date
stored in cell B7, you can use the following in any other cell:
=OrdinalDate(B7)
There is a way to do this, but it doesn't involve the use of regular worksheet functions. While
Excel includes a rich assortment of worksheet functions that allow you to manipulate dates, the
"basis date" for Excel is January 1, 1901; this is the date from which all dates are calculated.
(You can change the basis date, but only by three years, to 1904. This capability is provided for
compatibility with Excel on the Mac.) This means that older dates—such as those you would
find in the cemetery for genealogy purposes—can't be directly calculated in Excel.
Fortunately, VBA doesn't have this limitation. This means that you can easily create a user-
defined function (a macro) that will do the math for you. Start by placing the starting date (either
birth or death date) in cell B1. Then, in cells B2:B4 enter the number of years, months, and days
by which you want to adjust the starting date. Thus, if B1 contains a birth date, then cells B2:B4
should be positive (you want to add them to the starting date). If B1 contains a death date, then
B2:B4 should be negative (you want to subtract them from the starting date).
Application.Volatile
Dim D As Date
In whatever cell you want to display the calculated date you can enter the following formula:
=FindDate(B1,B2,B3,B4)
The result of the function is a formatted date that represents the start date adjusted by the years,
months, and days you specify. So if cell B1 contains 1/18/1801, cell B2 contains 81, cell B3
contains 11, and cell B4 contains 17, then the function will return 1/4/1883. Similarly, if cell B1
contains 3/18/1901, cell B2 contains -93, cell B3 contains -8, and cell B4 contains -22, then the
result returned will be 6/26/1807.
There are a number of ways that this problem can be approached. All of the methods presume
that the dates in column A are in ascending order and that the readings in column B are not in
any type of discernable order. (In other words, the readings could bounce above and below 0 on
any given date.)
Provided that you have some control over the layout of the worksheet, you could add an
intermediate work column in column C, used to indicate when a value is negative. Simply place
a formula like this in column C, to the right of each reading:
=IF(B1<0,A1,"")
This formula returns the date in column A if the value in B is below 0 (negative), otherwise it
returns nothing. All you then need to do is look for the minimum value in column C:
=MIN(C:C)
Format the result as a date, and it represents the date at which the readings first became negative.
Another approach is to forego the use of the intermediate column and use an array formula to
determine the date. Assuming the data is in the range A1:B42, you can use any of the following
formulas:
=MIN(IF(B1:B42<0,A1:A42,""))
=OFFSET($A$1,MATCH(TRUE,$B$1:$B$42<0,0)-1,,,)
=INDEX(A:A,MIN(IF(B1:B42<0,ROW(B1:B42))))
=INDEX(A1:A42,MATCH(TRUE,B1:B42<0,0))
=INDIRECT("A"&MIN(IF(B1:B42<0,ROW(B1:B42))),TRUE)
Remember that these are all array formulas, so you need to enter whichever one you choose by
pressing SHIFT+CTRL+ENTER. Format the result as a date, and it is the answer you seek.
If you prefer, you could also use a simple macro to determine the date:
Function GetFirstNegative(rngdata)
Dim c As Variant
In your worksheet, you would use this user-defined function in this manner:
=GetFirstNegative(B1:B42)
As with many problems in Excel, the answer depends on the nature of the data involved and
exactly what you want to do. If the date in the cell is today’s date, and you simply want to have
the cell display the current month up through the 14th, and then next month after that, then you
can use a formula such as the following:
=CHOOSE(IF(DAY(NOW())>14,MONTH(NOW())+1,MONTH(NOW())),
"January","February","March","April","May","June",
"July","August","September","October","November",
"December","January")
This formula returns the name of a month, not a date. If you prefer to have a date returned, you
can use this formula:
=IF(DAY(NOW())>14,DATEVALUE(IF(MONTH(NOW())=12,1,
MONTH(NOW())+1) & "/" & DAY(NOW()) & "/" & IF(MONTH(
NOW())=12,YEAR(NOW())+1,YEAR(NOW()))),NOW())
Both of these formulas account for the “end of year wrap-around” when you advance from
December to January. A shorter version of this last formula can be created if you use the DATE
function instead of the DATEVALUE function:
=DATE(YEAR(NOW()),MONTH(NOW())+((DAY(NOW())>14)*1),1)
This formula, unlike the DATEVALUE example, always returns a date that is the first day of any
given month.
If you really want to advance the value of a particular date in a cell, then you must use a macro to
do the task. Further, you must make sure that the macro only runs once a month, at a particular
time on a particular day. For instance, if you wanted the macro to run at 00:00:00 on the 15th of
each month, you would need to set up the macro so that it checked the date and time, and then
ran at that particular date and time. You would also need to make sure that the workbook
containing the macro was open over that date and time.
The following macro will fetch the date from a cell and increase it by a month. The macro
assumes that you have a named range, DateCell, which refers to the cell to be updated.
Sub IncreaseMonth()
Dim dDate As Date
dDate = Range("DateCell").Value
Range("DateCell").Value = _
DateSerial(Year(dDate), _
Month(dDate) + 1, Day(dDate))
End Sub
To make sure that the macro runs at the appropriate time, you would need another macro. The
following macro is designed to be run whenever the workbook is opened:
Notice that this particular macro sets the OnTime method so that it runs the IncreaseMonth
macro at 23:59:59 on the 14th. This date and time was chosen because it is easier to catch than is
00:00:00 on the 15th.
Remember that the IncreaseMonth macro will only run if you open the workbook on the 14th,
and then leave the workbook open until the 15th.
Weekdays in a Month
Ever wonder how many of a particular weekday occur within a given month? For some people, it
is important to know how many Tuesdays there are in a month. And who doesn't want to know
whether a particular month will have four or five Saturdays?
Excel does not include an intrinsic function that you can use to determine the number of times a
particular weekday occurs within a given month. You can, however, create your own formulas
and functions to accomplish the task.
=4+N((WEEKDAY(DATE(YEAR($A$1),MONTH($A$1),1)))+
(DAY(DATE(YEAR($A$1),MONTH($A$1)+1,0))-28)>(7*((
WEEKDAY(DATE(YEAR($A$1),MONTH($A$1),1)))>(1+ROW()-
ROW($A$2)))+(1+ROW()-ROW($A$2))))
The formula relies on a date in A1. This date should be from the month you want "tested." The
formula is meant to be copied into a cell in row 2, and then copied to the six cells directly
beneath that. For instance, you could copy this formula to the range of cells B2:B8. The first
response (B2) is the number of Sundays in the month, the second (B3) is the number of
Mondays, and so on.
The drawback to this formula is that it uses the position of the cell containing the formula as part
of the formula. This means that the formula must be placed somewhere beginning in the second
row.
Another drawback is that the formula is quite long and complex. If you want a shorter formula,
then you need to turn to an array formula. One handy formula you can use assumes that you
provide three arguments: the year (cell C2), the month (cell D2), and a weekday (cell E2). With
these three items, the following formula works great:
Remember that this is an array formula, which means that you must enter it by pressing
SHIFT+CTRL+ENTER. In addition, the weekday value you enter in cell E2 must be in the range of
1 through 7, where 1 is Sunday, 2 is Monday, etc.
If your worksheet design doesn't allow for you to enter the year, month, and weekday in different
cells, a clean solution is to create a user-defined function to return the count. The following
macro is an example of this type of function.
=MonthWeekDays(A1,4)
In this usage, the first argument (cell A1) contains a date in the month being evaluated. The
second argument is a numeric value representing the weekday that you want to count. This value
must be in the range of 1 to 7, where 1 is Sunday, 2 is Monday, and so on.
If you have a list of dates listed in a column, you can use several different worksheet functions to
determine whether those dates are Fridays or not. The WEEKDAY function returns a number, 1
through 7, depending on the weekday of the date used as an argument:
=WEEKDAY(A2)
This usage returns the number 6 if the date in A2 is a Friday. If this formula is copied down next
to a column of dates, you could then use the AutoFilter feature of Excel to show only those dates
where the weekday is 6 (Friday).
You could also use the conditional formatting feature of Excel to simply highlight all the Fridays
in a list of dates. Follow these steps if you are using a version of Excel prior to Excel 2007:
=WEEKDAY(A2)=6
If you are using Excel 2007 or a later version, the steps to define the conditional format are
different:
6. In the formula area enter the following formula, replacing A2 with the address of the
active cell selected in step 1: =WEEKDAY(A2)=6
7. Click Format to display the Format Cells dialog box.
8. Set the formatting options to highlight the Fridays as desired.
9. Click OK to dismiss the Format Cells dialog box.
10. Click OK.
If you want to determine a series of Fridays based on a beginning and ending date, you can set up
a series of formulas to figure them out. Assuming that the beginning date is in A2 and the ending
date is in A3, you can use the following formula to figure out the date of the first Friday:
=IF(A2+IF(WEEKDAY(A2)<=6,6-WEEKDAY(A2),6)>A3,"",A2+IF(WEEKDAY(A2)<=6,6-
WEEKDAY(A2),6))
If you place this formula in cell C2 and then format it as a date, you can use the following
formula to determine the next Friday in the range:
=IF(C2="","",IF(C2+7>$A$3,"",C2+7))
If you copy this formula down for a bunch of cells, you end up with a list of Fridays between
whatever range of dates is specified by A2 and A3.
If you actually want to "pull" Fridays in a specific date range, then you will need to use a macro.
There are several ways you can go about this. This simple macro will examine all the dates in the
range A2:A24. If they are Fridays, then the date is copied into column C, beginning at C2. The
result, of course, is that the list starting at C2 will only contain dates that are Fridays.
Sub PullFridays1()
Dim dat As Range
Dim c As Range
Dim rw As Integer
If desired, you can change the range examined by the macro simply by changing the A2:A24
reference, and you can change where the dates are written by changing the value of rw (the row)
and the value 3 (the column) in the Cells function.
If you would rather work with a beginning date and an ending date, it you can modify the macro
so that it will step through the dates. The following macro assumes that the beginning date is in
cell A2 and the ending date is in cell A3.
Sub PullFridays2()
Dim dStart As Date
Dim dEnd As Date
Dim rw As Integer
dStart = Range("A2").Value
dEnd = Range("A3").Value
rw = 2
While dStart < dEnd
If Weekday(dStart) = vbFriday Then
Cells(rw, 3).Value = dStart
Cells(rw, 3).NumberFormat = "m/d/yyyy"
rw = rw + 1
End If
dStart = dStart + 1
Wend
End Sub
The macro still pulls the Fridays from the range and places them into a list starting at C2.
Another macro approach is to create a user-defined function that returns specific Fridays within a
range. The following does just that:
Application.Volatile
If dStartDate > dEndDate Then
PullFridays3 = CVErr(xlErrNum)
Exit Function
End If
PullFridays3 = ""
If iIndex = 0 Then
PullFridays3 = iMaxDays
ElseIf iIndex <= iMaxDays Then
PullFridays3 = dFirstday + (iIndex - 1) * 7
End If
End Function
You use this function in a cell in your worksheet in the following manner:
=PULLFRIDAYS3(A2,A3,1)
The first argument for the function is the starting date and the second is the ending date. The
third argument indicates which Friday you want returned from within the specified range. If you
use 1, you get the first Friday, 2 returns the second Friday, etc. If you use a 0 for the third
argument, then the function returns the number of Fridays in the specified range. If the specified
beginning date is greater than the ending date, then the function returns a #NUM error.
=DATE(YEAR(A1),MONTH(A1)+1,0)-(MAX(0,WEEKDAY
(DATE(YEAR(A1),MONTH(A1)+1,0),2)-5))
This formula returns a date that is only a Monday through Friday, and always the last such day in
the month represented by the date in A1. For some purposes, you may need to know what the last
Friday of any given month is. This is easily determined with this formula:
=DATE(YEAR(A1),MONTH(A1)+1,0)-WEEKDAY(DATE
(YEAR(A1),MONTH(A1)+1,0))+(WEEKDAY(DATE
(YEAR(A1),MONTH(A1)+1,0))>5)*7-1
This formula calculates the last day of the month for the date in cell A1 and, based on what day
of the week that date is, subtracts the appropriate number of days to return the previous Friday.
If you want to take business holidays into account, then the complexity of the formula gets quite
high, quite quickly. Because of that, it is best to create a user-defined function (a macro) that will
determine the last business day and compensate for holidays.
The following macro returns a date, Monday through Friday, that represents the last business
day. The date is compared against a holiday list (HolidayList), which should be a named range in
your workbook. If the date is found to be a holiday, then the ending business day is decremented
until a suitable day is located.
LastWorkDay = DateSerial(Year(lRawDate), _
Month(lRawDate) + 1, 0) - 0
If bFriday Then
LastWorkDay = MakeItFriday(LastWorkDay)
Else
LastWorkDay = NoWeekends(LastWorkDay)
End If
Notice that there are three private functions that are included. These functions are called from
within the main LastWorkDay function. The first one, myMatch, is a “wrapper” for the regular
Match method. This usage is included because of the required error handling.
The second function, NoWeekdends, is used to back a date up to the previous Friday if it just
happens to be a Saturday or Sunday. The MakeItFriday function is used to ensure that a date will
always be a Friday.
To use this user-defined function from your worksheet, you use it in a formula, like this:
The first parameter (A1) is the date to be evaluated. The second parameter (HolidayList) is an
optional list of holiday dates. As shown here, it is assumed that HolidayList is a named range in
the worksheet. If this parameter is provided, then the function makes sure that any date it returns
is not on the list of dates in HolidayList.
The final parameter is also optional; it can be either TRUE or FALSE. (The default, if it is not
specified, is FALSE.) If this parameter is set to TRUE, then the function always returns the last
Friday of the month. If this parameter is TRUE and the HolidayList is provided, then the
function returns the last non-holiday Friday of the month.
If you do program development in Excel, you may be wondering if there is a way to write your
program so that it will no longer work after a specific date. Fortunately, this is rather easy. One
solution is to use something like the following as an Auto_Open macro:
Sub Auto_Open()
Dim exdate As Date
exdate = "04/30/2013"
If Date > exdate Then
MsgBox ("You have reached end of your trail period")
ActiveWorkbook.Close
End If
MsgBox ("You have " & exdate - Date & "Days left")
End Sub
If the date on the system running the program is greater than the date specified in the exdate
variable, the user will see a message box indicating that their trial period has expired. When the
user clicks on the OK button, the workbook closes. If the trial period is not over, then the
message box indicates how many days are left in the period.
Of course, if you put a macro such as this in your application, it may stop you from opening the
workbook to make program changes. The obvious way around this, of course, is to hold down
the SHIFT key as you open the workbook. Doing so stops the Auto_Open macro from running. If
your users know this, they can bypass the expiration check just as easily as you, however. The
solution is to place similar checks within other macros that cannot be bypassed, and that are
essential to your program.
If you want to insert the current time and have it include the seconds, the best way is to use a
macro. You can then assign the macro to a keyboard shortcut or a toolbar button (or both) so it
can be immediately popped into place. The following macro will do the trick nicely:
Sub TimeStamp()
ActiveCell.Value = Time
ActiveCell.NumberFormat = "h:mm:ss AM/PM"
End Sub
Notice that the time is placed in the cell, and then the cell is formatted to show hours, minutes,
and seconds.
The interesting thing is that when a cell is formatted for elapsed time using [h]:mm:ss, the cell
can easily display elapsed times that have more than 10000 hours. Thus, you can sum a range of
cells to result in a value more than 10000 hours, but you cannot enter a larger value.
Unfortunately, there seems to be no way around this in Excel. The best solution, however, might
be to rethink how the data is entered. After all, 10000 hours is equal to 416 days and 16 hours—
well over a year. You could easily create a column for entering days, and use another for partial
days. A third column could then use a formula to return the elapsed hours based on the other two
columns.
Another solution is to simply not rely on Excel to do the parsing of your input. If you have a
huge number of hours to enter (such as 32315), then you could enter the following in the cell:
=32315/24
Excel maintains what you enter as a formula, but displays the proper number of hours, minutes,
and seconds. If you want to get more precise, you can enter a fractional amount that represents
the portion of an hour represented by your time. For instance, 37 minutes and 15 seconds is
0.620833 of an hour. Thus, you could enter the hours as follows:
=32315.620833/24
Of course, entering times in this manner can get tedious, particularly when you have calculate
the fractional portion of an hour represented by minutes and seconds. To overcome this, you
could create a custom function that allows you to enter hours, minutes, and seconds, and returns
a value that is easily formatted using the elapsed time format. The following function will do the
trick:
Application.Volatile
hr1 = hr / 24
min1 = min / 24 / 60
sec1 = sec / 24 / 60 / 60
RealBigTime = hr1 + min1 + sec1
End Function
After creating the function, enter something like =RealBigTime(32341,30,45) in a cell. The
result is a value that can be formatted with the elapsed time format to 32341:30:45.
The short answer is no, you cannot—Excel contains no function to indicate if a cell contains a
time. The reason is quite simple: Times and dates in Excel are technically nothing but numbers.
A date is any number in the range 1 to 2958465, which represent the dates of 1/1/1900 through
12/31/9999. If you add a decimal portion to the number, then that represents a time (0 is
midnight, 0.25 is 6:00 am, 0.5 is noon, etc.).
Knowing the range of values that can be used for dates and times, along with the fact that a cell
containing a time should be formatted properly to display a time, you can create a formula that
will indicate if a cell contains a time:
=IF(AND(CELL("format",B2)>="D6",CELL("format",B2)<="D9"),
"Time Format","Not Time Format")
This formula checks the formatting applied to cell B2. If the formatting is one of the commonly
used formats for times, then it returns the text “Time Format.” If a different formatting is used,
then the formula returns “Not Time Format.”
A different approach is to check whether the value in cell B2 is a valid time value. You can do
that by using a formula such as the following:
The function works fine as long as cell B2 contains only a time. If the cell contains both a date
and time, then the function always returns “Not a Time Entry.”
To get the best of both worlds—checking formats and the value in the cell—consider making a
user-defined function in VBA. The reason is simple: VBA includes the IsDate function which
not only looks at the current range of the number, but also checks to see that the cell is formatted
as a date. The following macro provides an example as to how you could create such a function:
=IsTime(B2)
The function reads how the value is displayed (using the text property of the cell object) and then
tries to convert it with the TIMEVALUE function. If that is a date (determined by the IsDate
function) then the display is a valid time. If it is not a date, VBA generates an error, which the
code is programmed to ignore.
You may have a need to convert a local time to GMT in your worksheet. If you always know that
the time will be entered in local time, this can be done quite easily with a formula. For instance,
assume that you are entering the local time in cell B7, and that you are in the Pacific time zone.
In this time zone, you are either seven or eight hours behind GMT, depending on if daylight
savings time is in effect. The following formula will adjust the time entered in B7 by either seven
or eight hours, depending on whether the date associated with the time is within the period of
daylight savings time.
=IF(AND(B7>=DATEVALUE("3/8/2009 02:00"),B19<=
DATEVALUE("11/01/2009 02:00")),B7+7/24,B7+8/24)
Remember that whenever you enter a time into a cell, Excel automatically attaches a date to it.
Thus, if you enter a time of 10:15 into a cell, and the day you make the entry is January 17, then
Excel automatically converts the entry in the cell to 01/17/2009 10:15:00. This is done even
though you may only be displaying the time in the cell—in Excel, every date has a time
associated with it, and every time has a date associated with it.
Because of this entry behavior, Excel would use the formula just shown to do the proper
adjustment based on the default date when you enter a time (today’s date) or a date you may
explicitly enter.
The only drawback to this formulaic approach is that you must remember to change the daylight
savings time boundary dates from year to year. (The ones in the formula are for 2009.) You
could change the formula so that you actually stored the boundary dates in cells, such as E1 and
E2, as follows:
=IF(AND(B7>=$E$1,B19<=$E$2),B7+7/24,B7+8/24)
While the formula is shorter, it still has a problem with the rather static determination of when
daylight savings time begins and ends—you must remember to update that information
manually. In addition, if you move to a different time zone, you must remember to modify the
values by which the date and time are adjusted.
A really handy way around these drawbacks is to create a user-defined function that accesses the
Windows interface and determines what the system settings are in your computer. Your system
keeps track of daylight savings time automatically, as well as which time zone you are in.
Accessing this information through a user-defined function means you will never need to worry
about those items in your worksheet. You can use the following macro to do just that:
Option Explicit
dteLocalSystemTime.wYear = CInt(Year(dteTime))
dteLocalSystemTime.wMonth = CInt(Month(dteTime))
dteLocalSystemTime.wDay = CInt(Day(dteTime))
dteLocalSystemTime.wHour = CInt(Hour(dteTime))
dteLocalSystemTime.wMinute = CInt(Minute(dteTime))
dteLocalSystemTime.wSecond = CInt(Second(dteTime))
Call SystemTimeToFileTime(dteLocalSystemTime, _
dteLocalFileTime)
Call LocalFileTimeToFileTime(dteLocalFileTime, _
dteFileTime)
Call FileTimeToSystemTime(dteFileTime, dteSystemTime)
End Function
This may look imposing, as is often the case when working with system calls, but it works
wonderfully. There are three system routines referenced (SystemTimeToFileTime,
LocalFileTimeToFileTime, and FileTimeToSystemTime). By setting up the calls and using them
in order, the date and time are automatically adjusted to GMT. To use the function, in your
worksheet you would enter this to convert the time in cell B7:
=localtimetoutc(B7)
Format the cell as date/time, and the output is exactly what you wanted.
If you don’t want to use a macro, you can easily set up three columns for your timing. The first
column can be used to record the start time, the second column the end time, and then the third
column the elapsed time (calculated by using a formula that subtracts the start time from the end
time). In order to record times, you select a cell in either the start time or end time columns and
press CTRL+: (the colon). Excel enters the current time in that cell.
If you want to use a macro that simply returns the elapsed time, then you can use the following:
vStartTime = Time
MsgBox Prompt:="Press the button to end the timing" & vbCrLf _
& "Timing started at " & Format(vStartTime, "hh:mm:ss"), _
Buttons:=vbOKOnly, _
Title:="Time Recording Macro"
ActiveCell.Value = Time - vStartTime
End Sub
This macro records a start time (in vStartTime), and then displays a message box. When you
click on the message box button, the difference between the current time and the start time is
stored in the current cell. (You need to make sure the current cell is formatted with one of the
time formats.)
The above macro works very well for recording short events during which you don’t need to use
Excel for other tasks. If you need to record longer events, then a different approach is in order.
The following macros work in tandem. The first one records a start time; that is all it does. The
second one uses that recorded time to calculate an elapsed time which is placed in the currently
selected cell.
Global vStTime
Sub StartTiming()
vStTime = Time
End Sub
Sub EndTiming()
ActiveCell.Value = Time - vStTime
End Sub
You could easily assign these two macros to the Quick Access Toolbar or to different toolbar
buttons that would, respectively, start and stop the timing process.
Calculating TV Time
John works in the TV industry, where timing is done to a resolution finer than a second.
Television video must take into account hours, minutes, seconds, and frames. (There are thirty
frames per second.) John was wondering if there was a way to handle frames in Excel.
There is no way to handle frames as part of the native time values in Excel. There are, however,
a couple of things you can do to work with frames. Perhaps the most obvious suggestion is to
keep hours, minutes and seconds as a regular time value, and then put frames in a separate cell.
The immediate drawback to this approach is that calculations for the “TV times” are not as easy
as they would be if they were represented in a single value.
A way around this is to try to do your own calculations in a macro. Excel goes through an
internal process of converting times to decimal values that can be worked with very easily. You
could simulate this same conversion process, converting a time value (including frames) to a
decimal value. The TV time, in the format 00:29:10:10, could be stored in a cell (where Excel
will treat it as a string) and then converted to a value by the macro.
There is a problem here, of course: You cannot convert the time to a true decimal value between
0 and 1 like Excel does for times. The reason has to do with the limits on Excel’s significant
digits. To arrive at a value, you would divide the hours by 24, the minutes by 1440 (24 * 60), the
seconds by 86400 (24 * 60 * 60) and the frames by 2592000 (24 * 60 * 60 * 30). When you start
getting into values that small, it exceeds Excel’s limits of maintaining everything to fifteen
significant digits. Thus, you end up with unavoidable rounding errors on the frames value.
One solution to this problem is to not try to work with decimal values between 0 and 1, but
instead work with integers. If you convert the string time into an integer value that represents the
number of total frames in the time, then you can easily do math on the resulting value. The
following macro will do the conversion of a string in the format already mentioned:
Application.Volatile
T2D = CLng(NumHours)
T2D = T2D * 60 + NumMinutes
T2D = T2D * 60 + NumSeconds
T2D = T2D * 30 + NumFrames
Time2Num = T2D
End Function
To see how this works, if you have a string such as 37:15:42:06 in cell A4, and you use the
formula =Time2Num(A4), the result is the value 4024266, which is the number of frames in 37
hours, 15 minutes, 42 second, and 6 frames. To convert such values back to an understandable
time, you can use the following function:
Application.Volatile
NumSeconds = RemainingTime \ 30
RemainingTime = RemainingTime Mod 30
NumFrames = RemainingTime
By combining the two functions, you can do some math with the times. For instance, suppose
you had the time 00:29:10:10 in cell A4 and the time 00:16:12:23 in cell A5. If you put the
following formula in a cell, you can find out the difference between the two times:
=Num2Time(Time2Num(A4)-Time2Num(A5))
The examples presented here are rudimentary; they don’t take into account any error handling or
limit checking on the times used. You can either expand on the examples to fit your needs, or
you can look to a third-party source. For instance, you can find an explanation (with a sample
workbook) for NTSC and PAL times at the following URL:
http://www.kenstone.net/fcp_homepage/timecode_spreadsheet.html
There are several different ways you can accomplish this task. The first is to manually enter a
time by selecting the adjacent cell in column B and pressing CTRL+SHIFT+; (that’s the
semicolon). This shortcut enters the current time in the cell. The problem with this approach, of
course, is that it isn’t automatic and it takes some extra movement and keystrokes to implement.
A better approach would be to use a formula to enter the time. The NOW function returns the
current date and time, and you can use it in a cell in this manner:
=NOW()
Of course, this simple formula is updated every time the worksheet recalculates. That means that
the function returns the current time every time you enter a value in column A. This is
undesirable because you don’t want previous times to update. You could try to use a formula to
check to see if something is in column A, as in this manner:
=IF(A3="","",IF(B3="",NOW(),B3))
The problem is that a formula like this introduces a circular reference into the worksheet, which
presents a whole host of challenges to work with. A better approach is to create a macro that
automatically runs every time something is entered in column A. Right-click on the tab of the
worksheet used for data entry and choose View Code from the Context menu. You’ll see the
Code window for the worksheet in the Visual Basic Editor, and then enter this into the window:
ExitHandler:
Set rCell = Nothing
Set rChange = Nothing
Application.EnableEvents = True
Exit Sub
ErrHandler:
MsgBox Err.Description
Resume ExitHandler
End Sub
With the macro in place, anytime you enter something into a cell in column A, the adjacent cell
in column B will contain the date and time (formatted to show only the time). If you delete
something in column A, then the adjacent cell in column B is cleared, as well.
There are several ways you can approach this task. One way is to use the TIME function to
convert the value to a time, as shown here:
=TIME(LEFT(A1,2),RIGHT(A1,2),)
This formula assumes that the time in cell A1 will always contain four digits. If it does not (for
instance, it might be 427 instead of 0427), then the formula needs to be modified slightly:
=TIME(LEFT(A1,LEN(A1)-2),RIGHT(A1,2),)
The formula basically pulls the leftmost digit (or digits) and uses them for the hours argument of
the TIME function, and then uses the two rightmost digits for the minutes argument. TIME
returns an actual time value, formatted as such in the cell.
=TIMEVALUE(REPLACE(A1,LEN(A1)-1,0,":"))
This formula uses REPLACE to insert a colon in the proper place, and then TIMEVALUE
converts the result into a time value. You will need to format the resulting cell so that it displays
the time as you want.
Another variation on the formulaic approach is to use the TEXT function, in this manner:
=--TEXT(A1,"00\:00")
This returns an actual time value, which you will then need to format properly to be displayed as
a time.
Another approach is to simply do the math on the original time to convert it to a time value used
by Excel. This is easy once you realize that time values are nothing more than a factional part of
a day. Thus, a time value is a number between 0 and 1, derived by dividing the hours by 24 (the
hours in a day) and the minutes by 1440 (the minutes in a day). Here is a formula that does that:
=INT(A1/100)/24+MOD(A1,100)/1440
This determines the hour portion of the original value, which is then divided by 24. The minute
portion (the part left over from the original value) is then divided by 1440 and added to the first
part. You can then format the result as a time, and it works perfectly.
All of the formulas described so far utilize a new column in order to do the conversions. This is
handy, but you may want to actually convert the value in-place, without the need for a formula.
This is where a macro can come in handy. The follow macro will convert whatever cells you
have selected into time values and format the cells appropriately:
Sub NumberToTime()
Dim rCell As Range
Dim iHours As Integer
Dim iMins As Integer
The macro uses an integer division to determine the number of hours (iHours) and stuffs the
remainder into iMins. This is then adjusted into a time value and placed back into the cell, which
is then formatted as a time. You can change the cell format, if desired, to any of the other time
formats supported by Excel.
If you are interested in an approach that is based on your local machine, then you need to make
calls to the Windows API. Rather than re-invent the wheel, information on how to do this can be
found in the detailed explanation by Chip Pearson at this site:
http://www.cpearson.com/excel/TimeZoneAndDaylightTime.aspx
If you are sure that your machine will have access to the Internet at the time that you need to
know about Daylight Savings Time, you could also do some comparisons with information you
get from the National Institutes of Standards and Technology (NIST) concerning the current
time.
You can, over the Web, go to an NIST site that will return information about the current time.
The URL to use is similar to this one:
http://nist.time.gov/timezone.cgi?Eastern/d/-5
In this case, the time returned will be in the Eastern time zone, which is five hours before the
standard universal time. Using Excel’s Web Query capabilities, you can access the information
returned by the URL and then compare it to the time on the local machine.
Then
answer = sST
Else
answer = sDST
End If
myWksht.UsedRange.Select
Selection.Delete
Application.ScreenUpdating = True
MsgBox answer, , "System Time On Your Machine"
End Sub
The macro compares the official time gathered by the query to the system time on the local
machine. A tolerance of +/-5 minutes is assumed, as specified in the myT variable.
If the absolute difference between the two values is <= 5 min and NIST time is not DST or, if the
difference is > 5 min and NIST time is DST, then the system time = Standard Time. Otherwise
the system time = Daylight Savings Time.
Sub AddNameNewSheet1()
Dim Newname As String
Newname = InputBox("Name for new worksheet?")
If Newname <> "" Then
Sheets.Add Type:=xlWorksheet
ActiveSheet.Name = Newname
End If
End Sub
This macro works fine, as long as the user enters a worksheet name that is “legal” by Excel
standards. If the new name is not acceptable to Excel, the worksheet is still added, but it is not
renamed as expected.
A more robust macro would anticipate possible errors in naming a worksheet. The following
example code will add the worksheet, but keep asking for a worksheet name if an incorrect one is
supplied.
Sub AddNameNewSheet2()
Dim CurrentSheetName As String
CurrentSheetName = ActiveSheet.Name
The fact that the recorded macro didn’t work isn’t terribly surprising. When you record a macro,
you tell Excel to record the steps you take. Those steps (in this instance) included the naming of
the worksheet, so that name was recorded in the macro. Try to run the macro a second time, and
you will get an error because the worksheet you are trying to create on the second pass was
already created on the first.
In this case you have to write a macro manually. You can start with recording the process, and
you will get a code like the following:
Sub Macro1()
Sheets("Master").Select
Sheets("Master").Copy After:=Sheets(3)
Sheets("Master (2)").Select
Sheets("Master (2)").Name = "NewMaster"
End Sub
Note that the code places the worksheet (after the third sheet) and then always names it the same
thing. There’s a lot to change here. What you want to do is change it to something like the
following:
Sub CopyRename()
Dim sName As String
Dim wks As Worksheet
Worksheets("Master").Copy after:=Sheets(Worksheets.Count)
Set wks = ActiveSheet
Do While sName <> wks.Name
sName = Application.InputBox _
(Prompt:="Enter new worksheet name")
On Error Resume Next
wks.Name = sName
On Error GoTo 0
Loop
Set wks = Nothing
End Sub
This macro will copy the worksheet named "Master" to the end of sheet list (no matter how
many sheets you have in the workbook) and continue to prompt for a new worksheet name until
a valid name is entered.
One option that works well if you have a limited number of worksheets (say, 30-40 sheets or
less) is to right-click the sheet navigation buttons at the left of the sheet tabs. Doing so will pull
up a list of worksheet names, and you can select which one you want to jump to. If there are
more worksheets than can comfortably fit in the list, then one of the options is “More Sheets.”
Select that option, and you end up with a dialog box that lists all the worksheets and you can
make your selection.
Another option that many people employ is to create a “table of contents” for your workbook. In
the first worksheet, enter a bunch of hyperlinks that jump to the various worksheets in your
workbook. That way you can display the TOC, click a link, and you are on your way.
If you know the name of the worksheet you want to jump to, you can also use the Go To
capabilities of Excel. Follow these steps:
Another option is to create a macro to prompt for either the name or number of the worksheet
you want to display. The following macro could be assigned to a shortcut key, and then you can
use it to jump to whatever sheet is desired.
Sub GotoSheet()
Dim sSheet As String
sSheet = InputBox( _
Prompt:="Sheet name or number?", _
Title:="Input Sheet")
On Error Resume Next
If Val(sSheet) > 0 Then
Worksheets(Val(sSheet)).Activate
Else
Worksheets(sSheet).Activate
End If
End Sub
If desired, you could define two macros that would do the jumping. One macro would jump to
Sheet1 and the other to Sheet4. These would be easy enough to create using the macro recorder,
and you could assign a shortcut key to each of the macros.
If you are looking for a single shortcut that will toggle between the two worksheets, then you can
use a macro such as this:
Sub JumpBetween1()
If ActiveSheet.Name = "Sheet1" Then
Worksheets("Sheet4").Activate
Else
Worksheets("Sheet1").Activate
End If
End Sub
The macro simply checks to see which worksheet is currently displayed. If it is Sheet1, then
Sheet4 is displayed. In all other instances, Sheet1 is displayed. This is handy, but it means that if
you currently have Sheet2 displayed, the shortcut will always display Sheet1. You might not
want the macro to do anything unless either Sheet1 or Sheet4 is displayed. In that case, you
should use this variation of the macro:
Sub JumpBetween2()
If ActiveSheet.Name = "Sheet1" Then
Sheets("Sheet4").Activate
ElseIf ActiveSheet.Name = "Sheet4" Then
Sheets("Sheet1").Activate
End If
End Sub
Note that the only difference between the two macros is that the latter variation uses ElseIf to
check if Sheet4 is displayed. This means that if any worksheets other than Sheet1 or Sheet4 is
displayed, the macro will do nothing.
Normally, as Terri alludes to, you would display a given worksheet by using its name in the
statement, in this manner:
Worksheets("Consolidated").Activate
This works great, as long as there is a worksheet by this name (Consolidated) in the workbook.
Displaying a particular worksheet (like the first one in the workbook) when you don't know what
the name of that worksheet might be takes a different approach.
The simple answer is to start referring to the worksheet using its position within the Worksheets
collection. All the worksheets in a workbook belong to a collection of worksheet objects. This
collection is (oddly enough) referred to as the Worksheets collection. You can refer to an
individual worksheet in the collection by name (as was done in the previous example) or you can
refer to them by using an index number within the collection. For instance, you can activate the
first worksheet in the collection in this manner:
Worksheets(1).Activate
Using this method, it really doesn't matter what the name of the first worksheet is; it could easily
be "Consolidated" or some other name. Excel dutifully activates the first worksheet in the
workbook.
The only time this wouldn't work is if the first worksheet in your workbook is hidden. If the
worksheet is not visible, then Excel automatically (after execution of this statement) displays the
first visible worksheet.
Note that this displays the first (leftmost) worksheet tab in the workbook. If you instead want to
display the first created worksheet in a workbook, regardless of its position, you can try a
different approach. Each worksheet has (for lack of a better term) a behind-the-scenes "code
name." These code names should sound familiar; they are Sheet1, Sheet2, Sheet3, etc. These
names are retained even though you may change the name of the worksheet itself or change the
position of the tabs. If you want to display the first worksheet created (again, regardless of
position), you could try the following:
Sheet1.Activate
There is one caveat to this: It is possible that the code name for your worksheets has been
changed, if you write the programming code to do so. If that is the case, then the above statement
may not provide the results desired. (Testing is always a good idea.)
There are two ways you can set up your macro. First, you can use a traditional Auto_Open macro
that is automatically run whenever a workbook is opened:
Sub Auto_Open()
Sheets(“OpenToThisSheet”).Select
End Sub
All you need to do is replace OpenToThisSheet with the name of the worksheet you want
displayed when the workbook opens. A similar approach is to create a Workbook_Open event
handler:
Sub Workbook_Open()
ActiveWorkbook.Sheets("OpenToThisSheet").Activate
End Sub
Again, change the sheet name to reflect the name of the actual sheet you want displayed. This
event handler should be added as part of the ThisWorkbook module.
Sub ShowSheets()
Dim aSheet As Variant
Once you understand how to get the worksheet names, they can be put into an array or used in
any other way deemed necessary.
The solution to this problem is best done with a user-defined function (a macro). There are, in
reality, two numbers that the macro could return for each worksheet. The first is the index
number for the worksheet. This number represents the index of the worksheet's Worksheet object
within the Worksheets collection. This value can be returned by a macro similar to the following:
Application.Volatile
For Each sht In ThisWorkbook.Worksheets
If LCase(sht.Name) = LCase(shtname) Then
SheetNumber1 = sht.Index
Exit Function
End If
Next
SheetNumber1 = -1
End Function
This function, when used in a worksheet, will return the index number of any worksheet whose
name is passed to the function. If the name that is passed to the function doesn’t exist in the
worksheets collection, then a value of -1 is returned by the function. For instance, the following
used in a cell would return the index value for the worksheet named "January" within the
collection:
=SheetNumber("January")
The problem with this approach is that the order of Worksheet objects in the Worksheets
collection can change over time. Thus, you can’t always assume that the eleventh sheet in the
collection is the sheet that was originally Sheet11.
A more consistent way of figuring out the original name for a worksheet (regardless of how it is
renamed) is to use what Visual Basic refers to as the sheet’s “CodeName.” This is a property of
the worksheet and can be determined in the following manner:
Application.Volatile
For Each sht In ThisWorkbook.Worksheets
If LCase(sht.Name) = LCase(shtname) Then
sTemp = sht.CodeName
SheetNumber2 = Val(Mid(sTemp, 6, 4))
Exit Function
End If
Next
SheetNumber2 = -1
End Function
The CodeName property is read-only in a macro. It is assigned at the time that the worksheet is
created, but it is possible for it to be manually changed within the Visual Basic editor. The
CodeName is always a string, representing the very first name that was applied to the worksheet,
so it will be something like “Sheet11”. Once the CodeName is set, even if the worksheet is
renamed (such as to “January”), it will remain stable (“Sheet11”).
In the macro example (SheetNumber2) the CodeName property is assigned to the sTemp
variable. This will, most of the time, be something like “Sheet3” or “Sheet11”. So, the macro
then grabs the numeric value of whatever begins with the sixth character (right after “Sheet”).
This is the value that is returned by the function.
If you include the following in a cell, Excel returns the full path of the workbook, along with the
worksheet name:
=CELL("filename")
For instance, if you entered this into a cell in the Sheet3 worksheet of the MyBook workbook,
the information returned by Excel might be something like C:\My
Documents\[MyBook.xls]Sheet3 (depending, of course, on the drive and directory in which the
workbook is saved).
To return just the worksheet name from this value, you could use the following in your cell:
=MID(CELL("filename"),(FIND("]",CELL("filename"))+1),50)
This will work for any worksheet name up to 50 characters in length. (If you routinely use
different lengths, simply change the value in the expression.)
If you would prefer to use a macro-oriented approach, you can create a full-featured macro that
will do the job. The following macro, SheetStuff, will return any of three separate items.
SheetStuff = ThisWorkbook.Name
Case 3
SheetStuff = ThisWorkbook.FullName
Case Else
SheetStuff = ActiveSheet.Name
End Select
End Function
To use this macro function, simply put =SheetStuff(X) in a cell in your worksheet. You should
replace X with either 1, 2, or 3, depending on the information you want. If you use 1, the name
of the current worksheet is returned. If you use 2, then the name of the workbook is returned.
Finally, 3 returns the name and full path of the workbook.
The short answer is yes, there is a way. In fact there are a couple of ways. And, interestingly
enough, you don’t have to use a macro or function if you don’t want to. For instance, here is a
regular worksheet formula that will work in any cell on the worksheet:
=MID(CELL("filename",A1),FIND("]",CELL("filename",A1))+1,255)
The instance of the CELL function in this formula returns the full name of the worksheet,
including the filename and file path. The use of the FIND function results in the stripping out of
everything except the worksheet name.
Note the use of a cell reference (A1) in each instance of the CELL function. This forces the
CELL function to return the name of the worksheet that contains the cell reference; without it,
you will get the same result (the first worksheet) for each instance of the formula.
You should also know that the formula will not return valid results if you use it in a new
workbook—one that hasn’t been saved. You need to save the workbook so it actually has a name
that can be returned by the CELL function successfully. It also will not work properly if the
workbook or worksheet name contains a right bracket character (“]”). In that case, you’ll want to
use one of the other solutions discussed in this tip.
If you prefer to use a user-defined function, you can try something simple, like this function:
This function won’t provide the desired outcome, however, because it always returns the name of
the active worksheet. That means that if you have the function called on each of the sheets in
your workbook, it will always return the name of the active sheet on each of those worksheets,
instead of the name of the sheet on which the function is used. The following function provides
better results:
If you think you’ll want to use the function to refer to a worksheet name elsewhere in the
workbook, then this function will work better for you:
This version of the function requires that you provide a cell reference—any cell reference—to a
cell on the worksheet whose name you want to use.
Of course, if you would rather not use a user-defined function, you could simply create a macro
that would stuff the name of each worksheet tab into the same cell in each worksheet. For
instance, the following macro steps through each of the worksheets in the workbook and places
the name of each worksheet into cell A1.
Sub TabName4()
For J = 1 To ActiveWorkbook.Sheets.Count
Sheets(J).Cells(1, 1).Value = Sheets(J).Name
Next
End Sub
You should note that this approach is not dynamic (it needs to be rerun each time you change
worksheet names or add new worksheets). It also overwrites anything that is in cell A1. (If you
want the worksheet names placed in a different cell on each worksheet, change the values used in
the Cells collection.)
following macro, GetSheets, will quickly retrieve the names of the worksheets in the current
workbook and put them in the first column of the current workbook, beginning at cell A1.
Sub GetSheets()
Dim J As Integer
Dim NumSheets As Integer
NumSheets = Sheets.Count
For J = 1 To NumSheets
Cells(J, 1) = Sheets(J).Name
Next J
End Sub
Unfortunately, Excel doesn't provide an intrinsic function to handle this sort of task. It is a
relatively simply task to develop such a function using a macro that will do the job for you. For
instance, the following macro will change the tab name to the contents of A1:
Sub myTabName()
ActiveSheet.Name = ActiveSheet.Range("A1")
End Sub
There are several important items to note about this macro. First of all, there is no error checking.
This means that if A1 contains a value that would be illegal for a tab name then the macro
generates an error. Second, the macro must be manually run.
What if you want a more robust macro that does check for errors and runs automatically? The
result is a bit longer, but still not overly complex:
2. Right-click the worksheet tab and select View Code from the resulting Context menu.
Excel displays the VBA Editor.
3. Paste (or type) the above macro into the code window.
4. Close the VBA Editor.
5. Locate the XLStart folder on your system. (Use the Windows search capabilities to
locate the folder.)
6. Save the workbook as an Excel macro-enabled template using the name Book.xltm in
the XLStart directory. This causes the template to become your pattern for any new
workbook you create.
7. Again save the workbook as a macro-enabled template in the same directory, this time
using the name Sheet.xltm. This causes the template to become the pattern for any new
worksheets you insert in a workbook.
8. Close and restart Excel.
Now, anytime you change the value in cell A1, the worksheet tab also updates.
There is one caveat to using this tip: If the value in cell A1 is a date and you want the worksheet
tab to contain that date, then you may not get what you expect. The reason is simple: Excel stores
dates internally as serial numbers, and that is what gets assigned to the worksheet tab, not a
formatted date. If you are working with dates, then you'll need to change what actually is
assigned to the tab name:
Note that the only change here is what is assigned to the worksheet's Name property—it is a
formatted date. You can, if you prefer, modify the date format used in the macro. You should
not, however, choose a format that uses slashes because those are illegal in worksheet names.
The only way to handle this is with a macro. The macro needs to step through each worksheet in
the workbook, and then check the key cell in each subsequent worksheet to see how it compares
to the same cell in other worksheets. If the cell value is less than the current worksheet, then the
worksheet that contains the lesser value can be moved.
Sub SortWksByCell()
Dim i As Integer
Dim j As Integer
For i = 1 To Worksheets.Count
For j = i To Worksheets.Count
If UCase(Worksheets(j).Range("H7")) < _
UCase(Worksheets(i).Range("H7")) Then
Worksheets(j).Move Before:=Worksheets(i)
End If
Next
Next
End Sub
Note the use of the Move method, which does the actual movement of the worksheets. The
names of the worksheets don’t matter, only their positioning based on the value in cell H7 of
each worksheet.
How, then, is one to copy worksheets within a macro? The answer is to use the Copy method
with an individual worksheet or group of worksheets. For instance, the following macro code
will copy the currently selected worksheet to a new workbook:
ActiveSheet.Copy
That’s it; a single line is all that is necessary to copy the worksheet to a new, unnamed
workbook. After executing the line, the new workbook is selected and you can save it using code
similar to the following. The first line in the code saves the workbook, and the second closes it.
ActiveWorkbook.SaveAs Filename:="MyNewFile.xlsm", _
FileFormat:=xlOpenXMLWorkbookMacroEnabled
ActiveWindow.Close
If you want to copy a specific sheet to another workbook, you do it by specifying the name of the
sheet you want to copy, instead of using the ActiveSheet object:
Sheets("Sheet1").Copy
This example copies the worksheet named Sheet1, from the Sheets collection, to a new
workbook. You can then save the new workbook, as already discussed.
The Copy method, when used with worksheets, is not limited to copying a single sheet at a time.
If you have a group of sheets selected, you can still use a single command line to copy all of
them to a new workbook. That is what is done in this macro:
Sub CopyWorkbook()
Dim sCopyName As String
SelectedSheets.Copy
ActiveWorkbook.SaveAs Filename:=sCopyName, _
FileFormat:=xlOpenXMLWorkbookMacroEnabled
End Sub
Note the use of the Copy command. The macro will work whether you have one worksheet
selected or fifty; it doesn’t matter. If you wanted to, instead, copy all of the worksheets from one
workbook to another, all you need to do is make a single change in the macro, to the line where
the Copy method is invoked:
Sheets.Copy
This copies the entire Sheets collection, which consists of all the worksheets in the workbook.
It should be noted that the Copy method isn’t just for copying worksheets to a new workbook; it
can also be used to copy worksheets within the same workbook. The only thing you need to do is
specify where in the current workbook you want to make the copy:
ActiveSheet.Copy After:=Sheets("Sheet7")
This code line copies the active worksheet into the same workbook so that it appears after the
worksheet named Sheet7. If it is more appropriate for your needs, you could instead specify the
worksheet before which the copy should be placed:
ActiveSheet.Copy Before:=Sheets("Sheet7")
This results in the worksheet being placed before Sheet7 instead of after it.
This task is rather easy to accomplish, with or without a macro. If you want to do it without a
macro, follow these steps if you are using Excel 2007 or later:
1. Right-click on the worksheet tab of the worksheet you want to copy. Excel displays a
Context menu.
2. Choose Move or Copy Sheet from the Context menu. Word displays the Move or Copy
dialog box.
1. Choose Move or Copy Sheet from the Edit menu. Word displays the Move or Copy
dialog box.
2. Check the Create a Copy check box.
3. Using the To Book pull-down list, choose New Book.
That's it. Your newly created worksheet doesn't contain any formulas, only the results of the
formulas in the original worksheet. If you prefer to use a macro-based approach, it only takes a
few lines of code:
Sub CopyWorksheetValues()
ActiveSheet.Copy
Cells.Copy
Range("A1").PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
End Sub
Of course, if you want to distribute only the results of your worksheet, you might consider
simply printing a PDF file and then distributing it. The added benefit is that your recipients don't
need to have Excel to view it. The downside is that if your worksheet is very large, a PDF file
can be rather unwieldy.
Like named ranges, Excel treats worksheet names as absolute. Each worksheet object is
independent of all other worksheets in the workbook. When you paste a formula that includes a
sheet reference, that sheet reference is left unchanged in what is pasted.
There are a couple of things you can do. One is to simply modify the formula reference after it is
pasted so that it references the correct sheet. If you have many of them to change, then you can
select all the formulas in the target worksheet (F5 | Special | Formulas) and then use Find and
Replace to replace the original worksheet name (Sheet1) with the correct worksheet name
(Sheet2).
If your referencing needs are not complex, then you can use a macro approach. For instance, if
you want a formula in a particular cell to refer to a cell on the sheet previous to the current sheet,
then you can do that by macro rather easily. Consider the following macro:
The macro looks at the current worksheet and then figures out which worksheet is before it. The
reference is then made for that worksheet. Once you've created the PrevSheet macro, here's one
way the function can be used in a cell:
=PrevSheet(A1)
This returns the value of cell A1 from the previous worksheet. If you have Sheet1, Sheet2, and
Sheet3, and you use this formula on Sheet3, then it returns the value of Sheet2!A1. If the
previous sheet is the first sheet of the workbook or it is not a worksheet, then the function returns
a #Value error.
If you later copy this formula to a different sheet (say to Sheet 5), then it pulls up the value
relative to its new location, which means it pulls up the value from Sheet4!A1.
You can also include a sheet name and the function will work just fine:
=PrevSheet(Sheet3!A5)
This version will always return Sheet2!A5 since sheet2 is the previous sheet of Sheet3.
The following macro was developed to help in these situations. It checks the names of the
worksheets in your workbook, renaming them to the days of the month if they begin with the
letters “Sheet”. If there are not enough sheets in the workbook, it adds sheets, as necessary, for
each day of the month.
Sub DoDays()
Dim J As Integer
Dim K As Integer
Dim sDay As String
Dim sTemp As String
Dim iTarget As Integer
Dim dBasis As Date
iTarget = 13
While (iTarget < 1) Or (iTarget > 12)
iTarget = Val(InputBox("Numeric month?"))
If iTarget = 0 Then Exit Sub
Wend
Application.ScreenUpdating = False
sTemp = Str(iTarget) & "/1/" & Year(Now())
dBasis = CDate(sTemp)
For J = 1 To 31
sDay = Format((dBasis + J - 1), "dddd mm-dd-yyyy")
If Month(dBasis + J - 1) = iTarget Then
For J = 1 To (Sheets.Count - 1)
For K = J + 1 To Sheets.Count
If Right(Sheets(J).Name, 10) > _
Right(Sheets(K).Name, 10) Then
Sheets(K).Move Before:=Sheets(J)
End If
Next K
Next J
Sheets(1).Activate
Application.ScreenUpdating = True
End Sub
The macro sets each tab name equal to the day of the week followed by the actual date, as in
“Wednesday 03-28-2012.” If you want to change the way that the tabs are named for each day,
just change how the sDay variable is constructed in the macro.
The last step in the macro is that it places the worksheets in proper order, based on the days of
the month. The result is that if you have any other worksheets left in the workbook (in other
words, you had some that did not begin with the letters “Sheet,” then those worksheets end up at
the end of the workbook, after the sheets for each day.
There are two ways to go about this. If the names of your worksheet tabs consist only of dates
(no other text in them), then you can use the following Excel formula to extract the date:
=MID(CELL("filename"),FIND("]",CELL("filename"),1)+1,10)
This works because =CELL("filename") function returns the complete path and name of the
current file along with the text on the worksheet tab. The filename itself appears in square
brackets. The formula finds the position of the closing bracket and extracts the first eight
characters from that position to the end. (Dates can be expressed in a maximum of 10 characters,
as in 12-31-2011.)
One caveat with using this formula is that it only returns anything of value if you first save the
workbook. If you use it in a brand new, unsaved workbook, it will return a #VALUE error.
Another approach that is very appealing, particularly if you have additional text in the worksheet
tab, is to create a user-defined function. For instance, let’s assume that your worksheet tabs have
the name “Month Ending 10-31-11”. In this case, you could use a function such as the following:
To use this function in your worksheet, you simply enter the following in a cell:
=SheetName()
The function returns a date serial number, so you will need to format the cell using one of the
available date formats. The function works because it assumes that the date is the last 8
characters of the text in the worksheet tab. If your worksheet tabs use a different naming
convention (such as placing the date at the beginning of the tab or using 10 digits for the date),
then all you need to do is pull the name apart differently in the macro.
There are a couple of ways to approach this problem, depending on what you need to do. If you
are working with a worksheet that has already been saved, then the following formula will
provide you with the worksheet name for Sheet4:
=MID(CELL("filename",Sheet4!A1),FIND("]",CELL(
"filename",Sheet4!A1))+1,LEN(CELL("filename",
Sheet4!A1)))
You should note that there are a couple of assumptions in this formula. First (and most
importantly) it assumes that you know the initial name of the worksheet. In this case, the initial
name is Sheet4. After the formula is in place, subsequent changes to the worksheet name will be
reflected automatically in the formula. The second assumption is that the workbook you are
working in has been saved. If it hasn’t, then the formula returns an error until the workbook is
saved and recalculated.
A different approach is to use a user-defined function. In VBA’s object model, all the worksheets
in a workbook are contained within the Sheets collection. These are, in turn, indexed. Thus, you
can pass an index value to the function and get back the name of the worksheet at the collection’s
index number.
For instance, if you wanted to know the name of the fourth worksheet in the collection, you
could use the following in your worksheet:
=TabName(4)
The function will work just fine, even in a workbook that has not been saved. It also returns the
proper worksheet name even if the worksheets are renamed or moved around.
If you are using a version of Excel prior to Excel 2007, follow these steps:
The user can no longer make changes to the names of the worksheet tabs or to anything else
affecting the structure of the workbook. (For instance, they cannot enter new worksheets or
delete existing ones.)
If you want to protect the workbook under the control of a macro, then you can use this code:
All you need to do is provide password you want to use in place of the “MyPassword” example.
The short answer is no, there is not a way in Excel to freeze the worksheet tabs. That being said,
there are several things you can do to get the results you want.
One possible solution is to use hyperlinks in your worksheets. Many people set up a system
where their main worksheet functions as a table of contents to the other worksheets in the
workbook. Each worksheet is hyperlinked from the main worksheet, and each non-main
worksheet has a hyperlink back to the main worksheet. Thus they can navigate very quickly
between the main and secondary worksheets just by clicking the hyperlinks.
Another option is to remember that you can right-click on the worksheet tab controls at the left
of the tabs at the bottom of the Excel window. When you do, you get a list of the first fifteen
worksheet names, and you can easily select the “Main” worksheet.
Still another option is to set up a very simple macro that always displays the “Main” worksheet:
Sub GoToMain()
Sheets("Main").Select
End Sub
You can assign this macro to either a shortcut key or a toolbar button so that you could use it
very quickly. When run, the worksheet named “Main” is always displayed.
If you absolutely want to always have the “Main” sheet visible in the tabs area, then you must
resort to a macro that will continuously reorder the tabs so that “Main” is always visible.
Application.EnableEvents = False
Application.ScreenUpdating = False
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
This macro needs to be part of the ThisWorkbook object, so make sure you add it into the proper
place in the VBA Editor. It always moves the worksheets in positions 2 through however many
sheets you have so that the desired worksheet is in the second position. This means that the
worksheet in the first position (Main) never moves.
The upshot of this is that if you want to run a macro and have it access information on a hidden
worksheet, you must first “unhide” the worksheet. To do this, you use the following line of code
in your macro:
When this line is executed, then the worksheet named My Hidden Sheet will no longer be
hidden. It is then easily accessible by regular macro commands. When you are later ready to hide
the worksheet again (when you are done processing), use this line of code:
Of course, unhiding and later hiding worksheets can cause a lot of flashing on the screen as
Excel tries to update its screen display based on the commands executed in your macro. If you
want to avoid this, then use the following line of code at the beginning of your macro:
Application.ScreenUpdating = False
With screen updating turned off in this way, nobody will ever know that you unhid a worksheet
and later rehid it. Make sure that before ending the macro, however, you set the ScreenUpdating
property back to True.
In order to have the macro complete these steps, you must know the password used to protect the
worksheet. The following simple example assumes that the password is “mypass.”
Sub SpellCheckCell1()
With ActiveSheet
.Unprotect ("mypass")
.Range("A15").CheckSpelling
.Protect ("mypass")
End With
End Sub
You’ll obviously need to change the password used in the macro to the one appropriate for your
worksheet. You’ll also need to change the cell being checked; this macro checks cell A15. If you
would rather have the macro check whatever cell is selected when the macro is run, then you can
change it in this manner:
Sub SpellCheckCell2()
With ActiveSheet
.Unprotect ("mypass")
Selection.CheckSpelling
.Protect ("mypass")
End With
End Sub
Regardless of which macro you use, you can assign it to a shortcut key or a toolbar button in
order to make it easy to run. (How you do these assignments has been discussed in other
ExcelTips issues.)
Disabling copying and pasting is theoretically easy enough to do. All you need to do is use a
short macro, like the following, in the ThisWorkbook object:
Using this macro essentially clears the Clipboard every time someone deactivates the worksheet
by selecting another worksheet or another application.
Of course, this offers only the most rudimentary of protection. A determined user can still copy
the worksheet by using Edit | Move or Copy Sheet, or they could disable macros when starting
the workbook, and thereby disable your Clipboard-clearing routine.
Perhaps a better way is to look at how business is done in the organization. If you don’t want
people to copy the worksheet, tell them up front, and make sure they know that you won’t accept
any duplicates. There are very easy ways to check to see if what you get back is a duplicate. Here
are a few of them:
• Put a formula in a cell, then hide the cell contents during your protection process. If you
get the worksheet back and unprotect the worksheet, and the formula is not there, the
worksheet is a copy.
• Protect the worksheet by using a password. If you cannot later unprotect the worksheet
with the same password, you know that someone else copied the worksheet and used
their own password.
• Have your worksheet use hidden formulas to access data on a hidden worksheet. If the
user copies the worksheet, the hidden worksheet isn’t copied to the new workbook, so
the formulas won’t give the correct answers.
• Insert a macro module in the workbook, and then protect the module. The module
doesn’t need to do anything, but if the workbook you get back doesn’t have the
protected module or is a simple XLSX file, it is a copy.
• Add something into the custom properties area of the workbook. If the custom property
is not in the workbook you get back, chances are good that the workbook is not the
original.
Another thing to try is to set the cell protection property to Hidden before password protecting
your worksheet. Users can see the results of what is in the cells, but they cannot see the formulas.
If they copy and paste the contents elsewhere, the formulas won’t be transferred, only the results.
This is very easy to spot in the returned workbook.
The problem does not occur with all items from the Forms toolbar, but only occurs under certain
circumstances. It primarily occurs because a macro button is associated with a cell (such as cell
B2), and then the cell is deleted. This means the button is essentially “unattached,” so Excel is
confused as to where the button belongs. When the worksheet is protected, Excel acts oddly
because it believes that the button is “everywhere” since it doesn’t really know where the button
belongs.
The obvious solution is to make sure that the macro button is always attached to a cell that
doesn’t get deleted. Unprotect the workbook, select the sliver of the button near the column
headers, and move it to a cell you want to associate it with. Reprotect the worksheet and the odd
behavior should disappear.
If you cannot see the button that is causing the problem, it could be because it is too small. The
solution to that situation is to run a macro that searches for all the buttons in the worksheet and
makes them visible. On the unprotected worksheet, run the following:
Sub CheckShapes()
Dim myShape As Shape
For Each myShape In ActiveSheet.Shapes
With myShape
If .Height < 2 Then .Height = 20
If .Width < 2 Then .Width = 20
End With
Next myShape
End Sub
The macro steps through all the shapes in the worksheet and, if they have a height or width less
than 2 pixels, increases their height and width so they are visible. Now you should be able to see
the macro button and can drag it to a location on the worksheet or delete it.
Of course, the easiest way to check to see if something is unprotected is to just start looking at
the tools on the various ribbon tabs. If the full range of tools is there, then the worksheet and
workbook are unprotected. If there are significant numbers of tools that are unavailable (“grayed
out”), then protection is turned on.
Another easy solution is to create a user-defined function that returns a value indicating whether
the workbook or worksheet are protected. The following will do the trick:
Else
WkbProtected = "Not Protected”
End If
End Function
To use the macros, just include formulas like the following anywhere in the worksheet:
=WksProtected(A1)
=WkbProtected(A1)
The result of the formulas is either “Protected” or “Not Protected,” depending on the state of the
worksheets and workbook. You could use conditional formatting to highlight the cells based on
what is returned by the functions.
There are several ways you can go about solving this problem. If you've assigned a password to a
worksheet, then you simply need to make sure that the same password is used to reprotect the
worksheet when the workbook is saved. This is easily done by using a macro that can be tied to
the BeforeSave event. This macro should be added to the ThisWorkbook object:
This example assumes that the worksheet you want to protect is named ABC and that the
password used to protect the worksheet is XYZ. You'll want to change these values to reflect
your actual worksheet and password.
Note that this macro automatically reprotects the worksheet whenever the workbook is saved.
Thus, if a user has a long working session with the worksheet and saves the workbook many
times during that session, then they will need to unprotect the worksheet quite often. If you
prefer, you can create a macro that will ask if the worksheet should be reprotected:
Of course, this approach means that it is possible that a worksheet would not be protected again,
if the user chose to not reprotect it.
Another approach doesn't involve using macros at all, but uses a different way to do your
protection. In traditional worksheet protection, you format individual cells as unlocked, then you
apply protection to the worksheet so that any locked cells cannot be changed. If you don't mark
any cells as unlocked (which seems to be what Barry is doing), then nothing in the worksheet can
be changed without the password.
Starting with Excel 2002 you can actually protect individual ranges of cells within a worksheet.
Follow these steps:
1. Display the Allow Users to Edit Ranges dialog box. (In Excel 2007 or later display the
Review tab of the ribbon and click Allow Users to Edit Ranges. In Excel 2002 or Excel
2003 choose Protection from the Tools menu and then choose Allow Users to Edit
Ranges.)
2. Click the New button. Excel displays the New Range dialog box.
3. In the Title box, enter the name you want to use for this range.
4. In the Refers to Cells box, enter the range you want users to be able to edit. (If there are
multiple ranges you want to use this same password, you can separate those ranges with
a comma.)
5. In the Range Password box, enter the password you want to give to your users.
6. Click on OK. You are again asked to enter the password.
7. Enter the password you used in step 5 a second time. The range now appears in the
Allow Users to Edit Ranges dialog box.
8. Click OK to close the Allow Users to Edit Ranges dialog box.
9. Protect your worksheet as you normally would.
There is only one thing you need to remember when you protect your worksheet (step 9). Since
you've not unlocked any cells, then all cells in the worksheet will be protected. You need to
make sure that the protection you apply allows locked cells to be selected. If, after the worksheet
is protected, a user tries to edit a cell that is in the range you specified in step 4, they are asked
for the password you specified in step 5. When they provide it, they can make edits to any cells
in the range.
The cool thing about this approach is that worksheet protection is not removed—the worksheet is
still protected because the user never removed that protection. Thus, the user never needs to
know the password for the entire worksheet. When the user closes and reopens the workbook,
the worksheet is still protected, just as you need. Plus, you don't have the unavoidable downside
of macros—that they can be disabled by a user when they open the workbook.
The only way around this is to use a macro to unhide the worksheets. The following VBA macro
will unhide all the worksheets in the current workbook:
Sub UnhideAllSheets()
Dim wsSheet As Worksheet
If you would rather not unhide all the worksheets at once, you can cause the macro to ask about
each hidden worksheet and then unhide each that you agree to unhide. The following macro will
handle this task:
Sub UnhideSomeSheets()
Dim sSheetName As String
Dim sMessage As String
Dim Msgres As VbMsgBoxResult
Before you can figure out what types of worksheets are in a workbook, it is helpful to know how
Excel internally stores some of the objects that make up the workbook. Excel maintains both a
Worksheets collection and a Charts collection. The Worksheets collection is made up of
worksheet objects, and the Charts collection is made up of chart sheet objects. Chart sheet
objects are those charts that take up an entire worksheet; it does not include those that are objects
embeded within a worksheet.
Interestingly enough, worksheet and chart sheet objects are also members of the Sheets
collection. So, if you want to process a workbook in the order that the sheets occur, it is easiest to
do so by stepping through the Sheets collection. When you do so, you can examine the Type
property of individual objects within the collection to determine what type of object it is. Excel
defines four types of objects that can belong to the Sheets collection:
You might be tempted to think that looking at the list of sheet types is enough. Interestingly,
however, Excel doesn't always return what you would expect for the Type property. Instead, if
you examine the Type property for a chart, it returns a value equal to xlExcel4MacroSheet. This
can cause problems for any macro.
The way around this, then, is to compare the name of each item in the Sheets collection against
those in the Charts collection. If the name is in both collections, than it is safe to assume that the
sheet is a chart. If it is not in both, then you can analyze further to see if the worksheet is one of
the other types. The following macro, SheetType, follows exactly this process:
Sub SheetType()
Dim iCount As Integer
Dim iType As Integer
Dim sTemp As String
Dim oChart As Chart
Dim bFound As Boolean
sTemp = ""
For iCount = 1 To Sheets.Count
iType = Sheets(iCount).Type
sTemp = sTemp & Sheets(iCount).Name & " is a"
bFound = False
For Each oChart In Charts
If oChart.Name = Sheets(iCount).Name Then
bFound = True
End If
Next oChart
If bFound Then
sTemp = sTemp & " chart sheet."
Else
Select Case iType
Case xlWorksheet
sTemp = sTemp & " worksheet."
Case xlChart
sTemp = sTemp & " chart sheet."
Case xlExcel4MacroSheet
sTemp = sTemp & "n Excel 4 macro sheet."
Case xlExcel4IntlMacroSheet
sTemp = sTemp & "n Excel 4 international macro sheet"
Case Else
sTemp = sTemp & "n unknown type of sheet."
End Select
End If
sTemp = sTemp & vbCrLf
Next iCount
MsgBox sTemp
End Sub
When you run the macro, you see a single message box that shows the name of each sheet in
your workbook, along with what type of sheet it is.
Excel's online help suggests using the Array function with the Sheets collection to select sheets
by name. This works great when you know the names of each sheet in the workbook. This poses
a problem when you want to create generic code to select all sheets for any workbook. The good
news is that you can use a variant of Microsoft's technique to reference sheets by index number.
Below is the code:
Sub SelectSheets()
Dim myArray() As Variant
Dim i As Integer
For i = 1 To Sheets.Count
ReDim Preserve myArray(i - 1)
myArray(i - 1) = i
Next i
Sheets(myArray).Select
End Sub
This works great, unless the workbook contains hidden sheets, where Sheets(i).Visible = False.
Of course, the above code can be adapted to ignore hidden worksheets:
Sub SelectSheets()
Dim myArray() As Variant
Dim i As Integer
Dim j As Integer
j = 0
For i = 1 To Sheets.Count
If Sheets(i).Visible = True Then
ReDim Preserve myArray(j)
myArray(j) = i
j = j + 1
End If
Next i
Sheets(myArray).Select
End Sub
However, there is a little known parameter of the Select method: the Replace parameter. By
using the Replace parameter, selecting all visible sheets becomes much easier:
Sub SelectSheets1()
Dim mySheet As Object
For Each mySheet In Sheets
With mySheet
If .Visible = True Then .Select Replace:=False
End With
Next mySheet
End Sub
Note that mySheet is defined as an Object data type, instead of a Worksheet data type. This is
done because in testing I encountered a problem with Chart sheets—they wouldn’t be selected
because they weren’t of a Worksheet type.
ActiveSheet.Delete
If you issue the command in your macro, you will find that Excel pauses the macro and asks you
if you are sure you want to delete the worksheet. When you click on Yes, the worksheet is
deleted and the macro resumes.
The whole idea behind macros, of course, is to automate many of the tasks you do on a regular
basis. Stopping and asking for confirmation may be the safe way to go, but it doesn’t do much to
help the cause of automation. If you want the worksheet to be deleted without a pause, there are a
couple of things you can do. First, you can use the SendKeys method to simulate pressing the
ENTER key, which is the same as clicking on Yes in the confirmation dialog box. All you need to
do is add a single line before the line that deletes the worksheet:
Application.SendKeys ("{ENTER}")
ActiveSheet.Delete
SendKeys does nothing but stuff keypresses into the keyboard buffer, the same as if you typed
them from the keyboard. Thus, the SendKeys line must precede the Delete line so that the ENTER
keypress is in the buffer before it is needed.
Any longtime macro developer can point to several potential problems with using SendKeys, the
primary problem being that you cannot use it to specify that you are accepting the Yes option in
the confirmation dialog box, and only in that dialog box. However unlikely, if some other dialog
box pops up (perhaps one generated by a different program) at just the right time, the ENTER
keypress will be applied to that dialog box, not to the one you expected.
A better solution is to turn off the alerting capabilities of Excel for a short time. Consider the
following macro code:
Application.DisplayAlerts = False
ActiveSheet.Delete
Application.DisplayAlerts = True
This code turns off the alerts, deletes the worksheet, and then turns the alerts back on. While they
are turned off, Excel will not display the confirmation dialog box, but will act as if it had been
displayed and the default option (Yes) selected.
It is important to remember the last line of code shown here. If you do not set the DisplayAlerts
property back to True, then Excel will not show any more alert messages, even after the macro
has ended. This could cause problems, as you might imagine. It is best to only set it to False for
the short time you need the alerts turned off.
Even with DisplayAlerts set to False, you will still see error messages, if one is generated. For
instance, if you execute the above code and there is only a single worksheet in the workbook,
you will still see an error message. (This happens because you cannot delete the last worksheet in
a workbook.)
The concept behind doing the condensation is rather easy: You simply need to copy the data
from the second and subsequent worksheets to the first empty row on the first worksheet.
Fortunately, Excel includes a feature that allows you to do this very process—the Consolidate
tool.
The Consolidate tool allows you to combine worksheets where data is defined by position or by
category. By position means that the data is in the same position on every worksheet. For
instance, if the data tables on each worksheet have the exact same columns, then you would
consolidate by position. By category means that you want to combine data from tables in which
the data may not use a consistent structure. You use this type of consolidation if the columns in
the data tables are in different orders.
In the workbook whose worksheets you want to consolidate, choose Data | Consolidate. (If you
are using Excel 2007 or a later version, display the Data tab on the ribbon, then click Consolidate
in the Data Tools group.) Excel displays the Consolidate dialog box. There are many controls in
the dialog box, but the primary thing you need to worry about is specifying the ranges to
consolidate.
You specify ranges by using the Reference box. Specify in the box the first range you want to
consolidate. If you are consolidating by position, then the reference should not contain any
column labels; if by category, then you should. When you specify the range reference, you click
Add, and the reference appears in the All References list. You continue to define reference
ranges until they are all complete.
If you want the consolidated data to contain links to the original data, then make sure the Create
Links to Source Data check box is selected, otherwise clear it. You can then click OK to do the
consolidation.
Note that there are other controls in the Consolidate dialog box; the controls mentioned above
are the ones you should pay attention to at a minimum. The best way to find out what the others
do is to play around with them, doing a few consolidations.
If you prefer to not use the Consolidate tool, you can easily create a macro that will do the
consolidation for you—provided the structure of each worksheet is identical. The following
macro steps through all the worksheets and combines the data to a new worksheet it adds at the
beginning of the workbook.
Sub Combine()
Dim J As Integer
When the macro is done, the first sheet in the workbook, named Combined, has all the data from
the other worksheets. The other worksheets remain unchanged.
There are a number of different ways you can approach this problem, and all of them involve the
use of macros. (This should be no surprise—macros are designed to make quick work of tedious
manual tasks.)
The following macro is simple in design; it loops through all the currently open workbooks and
for each workbook (except the workbook that contains the macro) copy the sheet named
"Sheet1" from that workbook to the workbook containing the code.
Sub CopySheets1()
Dim wkb As Workbook
Dim sWksName As String
sWksName = "Sheet1"
For Each wkb In Workbooks
If wkb.Name <> ThisWorkbook.Name Then
wkb.Worksheets(sWksName).Copy _
Before:=ThisWorkbook.Sheets(1)
End If
Next
Set wkb = Nothing
End Sub
If you want the macro to grab a different worksheet than Sheet1, simply change the value of the
sWksName variable to reflect the worksheet name desired. If you don't know what the name of
the worksheet will be, but you know the worksheet to copy will always be the second worksheet
in each workbook, then you can use this variation on the macro:
Sub CopySheets2()
Dim wkb As Workbook
Dim sWksName As String
Perhaps the biggest drawback to the approaches thus far is that all the workbooks need to be
open. This might not always be feasible. For instance, you could have a hundred different
workbooks in a folder and you need to combine a worksheet out of each of them. Opening a
hundred workbooks, while technically possible, probably isn't practical for most people. In that
case you need to take a different approach.
The following macro, CombineSheets, is interactive in nature. It asks you for several pieces of
information, and then adds worksheets to the workbook based upon your responses. It first asks
for a path to the worksheets (don't include the trailing slash) and then for a pattern to use for the
workbooks. You can specify a workbook pattern using the regular asterisk (*) and question mark
(?) wildcards. For instance, a pattern of * would match all workbooks, while a pattern of
Budget20?? would return only workbooks that have "Budget20" at the beginning and any two
characters after that.
Sub CombineSheets()
Dim sPath As String
Dim sFname As String
Dim wBk As Workbook
Dim wSht As Variant
Application.EnableEvents = False
Application.ScreenUpdating = False
sPath = InputBox("Enter a full path to workbooks")
ChDir sPath
sFname = InputBox("Enter a filename pattern")
sFname = Dir(sPath & "\" & sFname & ".xl*", vbNormal)
wSht = InputBox("Enter a worksheet name to copy")
Do Until sFname = ""
Set wBk = Workbooks.Open(sFname)
Windows(sFname).Activate
Sheets(wSht).Copy Before:=ThisWorkbook.Sheets(1)
wBk.Close False
sFname = Dir()
Loop
ActiveWorkbook.Save
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
When you run the macro you are also asked for the name of a worksheet to copy from each
matching workbook. Provide a name, and if such a worksheet exists in the workbook it is copied
to the beginning of the current workbook.
If you prefer not to create your own macro for combining worksheets, you might consider the
RDBMerge add-in created by Excel MVP Ron de Bruin. You can find it for free, here:
http://www.rondebruin.nl/merge.htm
This code increments the value in cell A1 every time the worksheet is activated. You can modify
the cell locations where the macro writes its information, according to your needs.
A more thorough approach is to create a macro that increments named references within the
workbook. Consider the following macro:
This function is designed to be called from a different macro—one triggered by the event that
should cause the usage counter to increment. For instance, if you want to keep track of every
time the worksheet is activated, then you would use the following macro as part of the
ThisWorkbook object:
The macro increments a counter named “Activated” for the worksheet. It does this by calling the
IncrementEventCounter macro, with the name of the counter and the name of the worksheet. If,
instead, you wanted to count the number of times that a worksheet was changed, you could use
the following macro as part of the ThisWorkbook object:
The only difference between this macro and the previous one is that it increments a counter
named “Changed.” To see the values of the counters, just enter a formula in a cell that references
the counter. For instance, you could enter =Changed to see the value of the Changed counter, or
=Activated to see the value of the Activated counter. The value of each counter will differ from
sheet to sheet, since the counters are maintained on a sheet-by-sheet basis.
There are a couple of ways you can approach this problem. If the numbers don’t need to be
sequential, you could create a ticket number based on the current time of day, in seconds. The
following macro can be added to the ThisWorksheet object:
The macro is triggered every time a new worksheet is added to the workbook. It takes the current
time, converts it to an integer number of seconds, and then places that value into cell A1. The
likelihood of duplicating ticket numbers within any given day is remote, but it could happen over
time. (For instance, if you create a ticket at the exact same time today that you did yesterday or
last week.)
To get around this problem, you could create a ticket number in the following manner:
This version of the event handler constructs a ticket number based both the date and time. Unless
you are creating tickets very quickly, this approach should reduce the possibility of duplicate
numbers generated by the macro.
If the numbers must be sequential within the current workbook, then you can define a name that
contains the current high value of your ticket number, and then a macro that places that number
in a cell on a new worksheet and increments the value of the stored number. Follow these steps if
you are using Excel 2007 or later:
1. Choose Name from the Insert menu, then choose Define. Excel displays the Define
Name dialog box.
Now, add the following macro to the ThisWorksheet object in the VBA Editor:
This macro is executed every time you insert a new worksheet in the workbook. It retrieves the
value you stored in the MaxNum, places that value into cell A1 of the new worksheet, and then
increments what is stored in MaxNum.
For instance, one way to move between worksheets is to press CTRL+PAGE UP or CTRL+PAGE
DOWN. To disable these keys for a particular workbook, you need to use the OnKey method, in the
following manner:
These two macros should be placed in the ThisWorkbook object. The first is run whenever the
workbook is activated and it disables CTRL+PAGE UP and CTRL+PAGE DOWN by having nothing
run when they are pressed. The second macro is run when the workbook is deactivated, and re-
enables the keys.
There are still a number of other ways to switch between worksheets, such as manually selecting
the sheet, using Go To, using hyperlinks, etc. The easiest way to prevent moving between
worksheets is to hide the worksheets you don’t want accessed. Protecting the workbook and
protecting the VB project will also aid in "thwarting" the user from moving between sheets.
If the sheets are hidden, they cannot be selected and thus you cannot move to them. Go To will
not go to them, hyperlinks will not go to them. If you want users to be able to view the hidden
worksheets later, you must create a macro routine with your own controls/buttons to go to those
sheets. This routine would "unhide" the sheet you are going to, and hide the one you just left.
Depending on your needs, there is one other approach you can try. You could add the following
macro to the ThisWorkbook object:
This macro is executed every time the current worksheet is deactivated. It essentially
“reactivates” the worksheet that is being left, which means that no other worksheet can ever be
selected.
It is unclear what might be causing this problem, but there are a few things you can check. If the
workbook is stored on a network, where it can be accessed by other people, it could be that the
change is occurring while someone else has the workbook open. In addition, if the workbook is
opened on different machines, it could be that the other machines on which it is opened may be
affecting the workbook, provided they have different screen resolutions or different printer fonts
installed.
Being unsure as to the cause, it may be that the best solution is to create a macro that runs
automatically when the workbook opens. This macro could go through the worksheets and set
the column widths and row heights to what you need. The following macro will perform these
steps:
The answer is yes—sort of. You can use the Worksheet_Change event to write a handler that
will record when any particular cell in a worksheet is changed. A macro that does this could be
rather simple, such as this one:
The macro simply puts the address of the last change into the status bar. You could modify the
macro so that it maintained the address in a global variable (declared outside of the event
handler) in this manner:
End Sub
You then could use a regular macro to retrieve the address stored in the sAddr variable and do
whatever you want with it.
As for making sure that the event handler doesn’t record any changes done by macros, the only
way to do this is to turn off event handling before executing any macro command that will
modify the worksheet. For instance, the following EnableEvents property change could be used
before and after a command that changes the contents of cell A1:
Application.EnableEvents = False
Range("A1") = "Hello"
Application.EnableEvents = True
With event handling turned off, the Worksheet_Change event handler won’t be triggered and the
“last changed” address won’t be updated. The result is that you end up tracking only those
changes done by users, not changes done by macros.
There are actually several ways you can do it, but all of the methods have two prerequisites: The
identification of the source range and the identification of the target range. The source range is
easy because it is named. You can specify the source range in your macro in this manner:
Figuring out the first empty row in the target worksheet is a bit trickier. Here's a relatively easy
way to do it:
iRow = Worksheets("Sheet2").Cells(Rows.Count,1).End(xlUp).Row + 1
Set rngTarget = Worksheets("Sheet2").Range("A" & iRow)
When completed, the rngTarget variable points toward the range of cell A in whatever the first
empty row is. (In this case, an empty row is defined as any row that doesn't have something in
column A.)
Now all you need to do is put these source and target ranges to use with the Copy method:
Sub CopySource()
Dim rngSource As Range
Dim rngTarget As Range
Dim iRow As Integer
Note that with the ranges defined, all you need to do is use the Copy method on the source range
and specify the target range as the destination for the operation. When completed, the original
data is still in the source range, but has been copied to the target.
The only way to accomplish this task is through the use of macros. What has to happen is that the
macro needs to determine which rows and columns are visible when a sheet is deactivated (being
left) and then set the display of the activated sheet (the one you are going to) to the same rows
and columns. The following macros, added to the ThisWorkbook module, perform exactly this
task.
End With
oSheet.Activate
Application.EnableEvents = True
End If
End Sub
Note the use of the variables outside of the event handlers. These variables are used to pass the
values of the column, row, and selected area from the SheetDeactivate handler to the
SheetActivate handler.
Of course, you may not want an automatic solution. Instead, you may want the user to take a
specific step to trigger whether the worksheets are synchronized. This can be done by adding the
following macro to a regular module in your workbook:
Global WindowScrollRow
Global WindowScrollCol
Global WindowSyncOn As Boolean
All that this macro does is to check the status of the global variable WindowSyncOn. If the value
is False, then the current settings for the top visible row and leftmost visible column are stored
into global variables. The setting of these variables are then used by the following event handler,
added to the ThisWorkbook module:
The macro simply checks the setting of the WindowSyncOn variable, and if it is True (it has
been set), then the macro sets which row and column are at the top and left of the active window.
There are several ways you can go about testing for an empty worksheet. Of course, it depends
on what you really mean by “empty,” at least to a degree. For instance, if a worksheet has
absolutely nothing in it—nothing in any cell of the worksheet—we could consider it empty.
However, you might have a worksheet that contains some column headings that you added, but
nothing except those headings. While Excel would consider the worksheet not empty, you might
consider it empty for printing purposes.
Perhaps the easiest way to check if a worksheet is empty is to use the UsedRange object to
deterrnine what is in the worksheet:
IsSheetEmpty = ActiveSheet.UsedRange.Rows.Count=1 _
AND ActiveSheet.UsedRange.Columns.Count=1 _
AND Cells(1,1).Value=""
Note that the UsedRange object consists of, well, the range of used cells within a worksheet.
Thus, if the count of rows in this range is 1 and the count of columns in this range is 1, and there
is nothing in cell A1, then the worksheet is probably empty.
If you have a header row (or two) in your worksheet, then you can adjust this technique to
however may rows and columns you have in those headers. For instance, if you have headers in
the range A1:F4, then you might adjust the technique in this manner:
IsSheetEmpty = ActiveSheet.UsedRange.Rows.Count=4 _
AND ActiveSheet.UsedRange.Columns.Count=6
You don’t need to check the contents of A1 in this instance because you already know that it
(and several other cells) contain information—your headers. You just want to ignore everything
in those headers to determine if there is additional information in the worksheet.
If the worksheet is completely empty (no header information that you’ve added), you can use the
CountA worksheet function to analyze the cells in the worksheet. If the result of the function is
greater than zero, then the worksheet is not empty. For example, let’s say that the worksheet you
want to analyze is specified by the object sht. You can use this technique in this manner:
IsSheetEmpty = Application.WorksheetFunction.CountA(sht.Cells) = 0
Of course, it is possible for a worksheet to contain items other than information in cells. If you
suspect you will have these types of objects in a worksheet (things like AutoShapes, graphics, or
embedded charts), then your testing for “emptiness” will need to be more complete. Each of
these items are contained within collections that are accessible in VBA, and you can check the
Count property for each collection to see if it is zero or not.
One way to get this to happen is to set up your own default workbook. Follow these steps:
If you are unsure of where the XLStart folder is located (step 6), use the Find feature of
Windows to locate the folder. With the template in that folder, any time you create a new
workbook, the settings within the workbook (including whether zero values are displayed or not)
should be set according to however they were in the template.
Of course, this approach doesn't help with existing workbooks or with workbooks that you may
receive from others. In that case, you may want to adopt the use of a couple of small macros that
control the display of zero values.
Sub Display0()
ActiveWindow.DisplayZeros = True
End Sub
Sub Hide0()
ActiveWindow.DisplayZeros = False
End Sub
The first macro (Display0) turns on the display of zero values, while the second (Hide0) turns off
the display. These could easily be assigned to toolbar buttons or shortcut keys so you don't have
to wade through the Options dialog box to turn the display on and off.
columns, and rows. This code needs to be available on every worksheet in a workbook, even if
the user adds new worksheets. Tim wonders if there is a way, using VBA, to have the code of
one worksheet automatically copied to a new worksheet in the workbook.
There are a few ways you can approach this problem. One way—and perhaps the simplest way—
is to remove the macros from the worksheet's code sheet and move them to the ThisWorkbook
module. The worksheet's code sheet is what you see when you right-click a worksheet tab. Code
in that sheet intended to handle events that occur in the worksheet and only in that worksheet. If
you move the code to the ThisWorkbook module, then events can still be handled, but those
events apply to all worksheets in the workbook.
For instance, when you right-click on a worksheet tab and look at the code window, you are
initially working in the Worksheet_SelectionChange event. If you wanted to move this code to
the ThisWorkbook module, you could place it within the Workbook_SheetChange event.
If such a "level change" of your code won't work for some reason, then another approach is to
create a template worksheet within the workbook. Give it a name such as "MyMaster," and make
sure it includes all the code that you want to add to your newly created worksheets. You can
even hide this worksheet, if desired, so it doesn't distract the users. Then, place the following
macro into the ThisWorkbook module:
tmpName = Sh.Name
Sheets("MyMaster").Copy Before:=Sheets(Sh.Name)
Application.DisplayAlerts = False
Sheets(Sh.Name).Delete
Application.DisplayAlerts = True
Sheets("MyMaster (2)").Name = tmpName
End Sub
This code is triggered every time a new worksheet is added to the workbook. It looks at the name
of the newly added worksheet (which will be something like "Sheet4") and saves that name in a
temporary variable. The code then copies the MyMaster worksheet to the workbook (which also
copies the macros in the worksheet), deletes the worksheet that was originally created, and then
renames the new MyMaster copy to have the same name as the original worksheet.
Figuring out the “size” of individual worksheets depends, in large part, on what is meant by
“size.” Does it mean the number of cells used? The columns and rows used? How much text is
stored in the worksheet? The list of metrics could go on and on.
The problem is that questions such as these miss the mark; a worksheet can have many, many
items stored on it. For instance, it could contain comments, formulas, text, charts, sound files,
and any number of other items. One chart may be larger than another in terms of numbers of
cells, but the other could be larger in terms of objects (such as charts or PivotTables).
The only real way to compare relative sizes of worksheets is to save each worksheet out into its
own workbook and then examine the size of each resulting workbook. This obviously doesn’t
answer precisely how large each individual worksheet is because the act of saving a workbook
introduces additional overhead into the saved file. However, if each worksheet is saved in the
same way, each one will have comparable overhead and thus can be compared to each other to
see which is larger.
The following macro adds a worksheet to the current workbook in order to record the sizes of
each workbook created. It then steps through each worksheet and saves it into an individual
workbook. The size of the workbook is then determined, recorded, and the new workbook
deleted.
Sub WorksheetSizes()
Dim wks As Worksheet
Dim c As Range
Dim sFullFile As String
Dim sReport As String
Dim sWBName As String
Application.ScreenUpdating = False
' Loop through worksheets
For Each wks In ActiveWorkbook.Worksheets
If wks.Name <> sReport Then
wks.Copy
Application.DisplayAlerts = False
ActiveWorkbook.SaveAs sFullFile
ActiveWorkbook.Close SaveChanges:=False
Application.DisplayAlerts = True
c.Offset(0, 0).Value = wks.Name
c.Offset(0, 1).Value = FileLen(sFullFile)
Set c = c.Offset(1, 0)
Kill sFullFile
End If
Next wks
Application.ScreenUpdating = True
End Sub
• In Excel 2007 or later, when someone displays the View tab of the ribbon and clicks the
Switch Windows tool, it shows the available workbooks to which the user can switch
• In Excel 2003 or earlier, when someone clicks the Window menu (in Excel), the
available workbooks are listed at the bottom of the menu.
Andrew wonders if there is a built-in dialog box to do this, or if he needs to create his own.
The short answer is that there is no built-in dialog box to accomplish this task. You can,
however, easily create your own. Here is a simple example:
Sub SwitchWindows()
Dim i As Integer
Dim n As Integer
Dim s As String
Dim v As Variant
n = Windows.Count
s = "Choose Window from:"
For i = 1 To n
s = s & Chr(10) & i & ") " & Windows(i).Caption
Next
s = s & Chr(10) & "Enter a number from 1 to " & n
v = Application.InputBox(prompt:=s, Type:=2)
i = Val(v)
If i >= 1 And i <= n Then
Windows(i).Activate
End If
End Sub
All this does is create a list of the names for each window in your system. It presents them in an
InputBox, and then switches to whatever window the user selected.
This can be done rather easily if one knows which objects and properties to use in your macro.
The object you want to use is the Application object, which refers to the Excel application. Here
are the pertinent properties:
• Top. The screen pixel at which the top edge of the application window should be
placed.
• Left. The screen pixel at which the left edge of the application window should be
placed.
• Width. The width of the application window, in pixels.
• Height. The height of the application window, in pixels.
With these in mind, you could set the position and size of the program window in this manner:
Sub SetWindowSize1()
Application.WindowState = xlNormal
Application.Top = 25
Application.Left = 25
Application.Width = 300
Application.Height = 200
End Sub
This macro specifies the upper-left corner of the program window to be 25 pixels from the top of
the screen and 25 pixels from the left of the screen. Then, the program window is set to be 300
pixels wide and 200 pixels tall. Note, as well, the setting of the WindowState property at the first
of the macro. This sets the window to be in a “normal” state, meaning one that can be resized to
something larger than minimized and smaller than maximized. (If you want the Excel program
window to take their entire screen, simply set the WindowState property to xlMaximized and
forget the rest of the settings in the macro.)
Of course, this macro sets the Excel program window to be rather small. In all likelihood you’ll
want it to be larger, but you don’t want it to be larger than the size of the user’s screen. The
easiest way to figure out the size of the user’s screen is to simply maximize the Excel application
window and then look at the Width and Height properties. You can then adjust those figures
based on where you want the upper-left corner of the screen to be and then adjust accordingly.
As an example, let’s say that you want the program window to start at 25, 50 and you want it to
be 1000 x 500. You could use code similar to the following:
Sub SetWindowSize2()
Dim iMaxWidth As Integer
Dim iMaxHeight As Integer
With Application
.WindowState = xlMaximized
iMaxWidth = Application.Width
iMaxHeight = Application.Height
.WindowState = xlNormal
.Top = iStartY
.Left = iStartX
.Width = iDesiredWidth
.Height = iDesiredHeight
End With
End Sub
One thing you may want to do with your macro to make it run faster and to prevent distracting
flashes on the screen is to turn off screen updating while the macro is running. The following
macro lines will, respectively, turn off screen updating and then turn it back on in a VBA macro.
Application.ScreenUpdating = False
Application.ScreenUpdating = True
The idea is to use the first line near the beginning of your macro, and then use the second line
near the end. Thus, the main body of your macro can do its work behind the scenes without the
necessity of stopping to update the screen.
If you’ve written such a system, you no-doubt rely on the automatic macros that run when you
first start Excel or open a workbook. It is common to use these macros to configure the Excel
environment and start the application running. It is frustrating to think that someone could
disable your entire system simply by holding down the SHIFT key when opening the workbook.
(Holding the SHIFT key disables any of the automatic macros associated with a workbook.)
There is no way in Excel to disable the shift-key bypass of startup macros. The reason is quite
simple—security. If this feature could be blocked or disabled it would be possible for macro
viruses to start running, without the user being able to do anything about it. This would be very
bad.
One possible workaround is to not have the workbook do anything useful if the startup macros
are not allowed to run. The default worksheet that displays when the workbook is opened should
say something to the effect that the workbook must be opened with the macros enabled in order
to function properly. The user could then be directed to close the workbook and try again.
In this default condition, the other worksheets in the workbook could be set to a “very hidden”
state. This is done by setting the Visible property of each sheet to xlSheetVeryHidden. With this
property set, the worksheets cannot be manually made visible; this can only be done via VBA.
If the user opens the workbook and the macros successfully run, they could hide the default
worksheet or simply delete it. The macro could then unhide the “very hidden” worksheets, as
necessary, to implement the application in the way desired.
There is no way to disable the macros in the second workbook when opening it under macro
control. (If you are opening it manually, you can obviously hold down the SHIFT key as the
workbook opens, but that doesn’t help your macro—it has no fingers to hold sown that key!)
There are a couple of workarounds, however. The first involves modifying your code that closes
the second workbook, in this manner:
Application.EnableEvents = False
Workbooks("SecondBook.xls").Close
Application.EnableEvents = True
By setting the EnableEvents property to False, the event that is going to happen (closing the
workbook) will not trigger the AutoClose macro. You can (and should) then set the
EnableEvents property to True so that events can later continue.
Another workaround is to set some sort of “flag” in the AutoClose macro of the second
workbook. This flag could test to see if the first workbook is open, and if it is, not run the main
code in the AutoClose macro.
To do this, in the second workbook at the top of the module pages add the following code:
Note that the declaration statement for the AutoCloseDisabled variable is outside of any
procedure, which means that it will be global in scope and accessible within all the procedures.
Next, modify the AutoClose macro so that its body is enclosed within an If statement, as shown
here:
Sub AutoClose()
'variable declarations here
End if
End Sub
The idea is that when the second workbook is opened normally, the AutoCloseDisabled variable
will be automatically set to False. (Boolean variables default to False when they are declared.)
Since the DisableAutoClose procedure is never run in the workbook, the If statement in the
AutoClose macro allows the actual body of the macro to be executed.
If you open the second workbook from your first workbook, then the code in your first workbook
can call the DisableAutoClose macro in the second workbook, thereby setting the
AutoCloseDisabled flag to True. This means that when the second workbook is closed, the If
statement will skip over the body of the AutoClose macro.
There is no way to do this in Excel. The closest you can come is to make sure that cell editing is
enabled (so that editing can be done in either the Formula bar or the cell) and then hiding the
Formula bar. You can hide the Formula bar by these steps if you are using a version of Excel
prior to Excel 2007:
1. Choose Options from the Tools menu. You will see the Options dialog box.
2. Make sure the View tab is selected.
If you are using Excel 2007 or a later version, then you should follow these steps:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
3. In the Display section of the options, clear the Show Formula Bar check box.
4. Click on OK.
If you prefer, you can also programmatically turn off the Formula bar for a specific worksheet.
You can do this by using the following two macros, which should be assigned to the code for the
specific worksheet you want to affect. (You can display the proper code window by right-
clicking the worksheet’s tab and selecting View Code from the resulting Context menu.)
The first macro turns off the Formula bar when the worksheet is activated, and the second turns it
back on when the worksheet is deactivated (when another worksheet is selected).
There is no way to do synchronous scrolling in Excel with more than two windows. Depending
on your needs (and the nature of your data) you may be able to get around this by creatively
splitting windows, such that you end up with two actual windows, but one of them is split to
show two different parts of the same worksheet.
If that doesn't fit your needs, the only thing you can do is to simulate the synchronicity between
windows. This must be done with a macro, similar to the following:
Sub SynchSheets()
' Duplicates the active sheet's cell position in each sheet
Application.ScreenUpdating = False
shUser.Activate
Application.ScreenUpdating = True
End Sub
This macro essentially steps through each worksheet in the workbook and makes the same cell
active and visible in each worksheet. If you start with your worksheets displayed on the screen,
then the macro will "synchronize" what you see in each worksheet so that it is the same.
2. In the Reference box, enter MySheet!A1. (Replace “MySheet” with the name of the
worksheet you want to jump to.)
3. Click OK.
This works great for regular worksheets, but it won’t work if you want to jump to a chart sheet.
Why? Because Go To is used to jump to specific cells (in this case, cell A1 on MySheet), and
chart sheets have no cells you can reference.
If you want a quick way to jump to a chart sheet, you will need to resort to a macro. You can
have the macro ask for a chart sheet name, and then use the Activate or Select methods with the
sheet name. The pertinent line of the macro—the one that does the actual “jumping”—can be
either of these:
Sheets("MyChart").Activate
Sheets("MyChart").Select
All you need to do is substitute the proper name of the chart sheet in place of “MyChart.”
An easier method, however, is to just adjust the zoom factor for a desired number of columns.
This can be done manually, but the procedure differs based on the version of Excel you are
using. If you are using Excel 2007 or later, select the columns, display the View tab of the
ribbon, and click the Zoom to Selection tool in the Zoom group. If you are using an older version
of Excel just select the columns and then choose View | Zoom | Fit Selection.
If you want to do it programmatically, it is even easier. Right-click a worksheet tab (the one you
want this macro to apply to) and then choose View Code from the resulting Context menu. Excel
displays the Visual Basic Editor, and you should enter the following into the code window:
This particular macro assumes that you want to view columns A through L in the window. It
selects the range A1:L1, and then sets the zooming factor to display just that selection (the
columns you want). Finally, it selects cell A1 and ends.
There are numerous ways that just the visible cells can be selected without a macro, but those
won't be gone into here. The assumption is that you want to select the visible cells as part of a
larger macro you may be creating. For instance, you might need to select the visible cells before
doing some sort of formatting or before you process the cells in some other way.
To select just the visible cells from a range of selected cells, you can use the following line of
code:
Selection.SpecialCells(xlCellTypeVisible).Select
If you need to work on some other initial range of cells before selecting the visible subset of
those cells, all you need to do is change the "Selection" portion of the line. For instance, you
could select the visible cells in the used range of the worksheet by using this line:
ActiveSheet.UsedRange.SpecialCells(xlCellTypeVisible).Select
Similarly, you could select all the visible cells on the entire worksheet by using this line:
Cells.SpecialCells(xlCellTypeVisible).Select
When it comes to VBA, there is very little difference between a contiguous selection and a non-
contiguous selection. Excel lets you access each of them the same. Consider the following code
snippet:
Dim c As Range
In this case the cells in the selected range are stepped through, one at a time, using the For ...
Next loop. Inside the loop the c variable represents an individual cell and can be used in
references, as shown.
If, for some reason, you want to access each contiguous area within the selection, you can do so
by specifically addressing the Areas group, as shown in this snippet:
Dim a As Range
Dim c As Range
You should also note that if the range you want to access (contiguous or non-contiguous) has
been named in Excel, you can also access just the cells in the named range. Simply replace the
word "Selection" in each of these examples with name of the range, in this manner:
Dim c As Range
If you are writing an application in VBA, you may need a way to completely “hide” Excel so
that the user never sees it. To do so, you can use this code in a macro:
Application.Visible = False
If your application ends without exiting Excel (such as if an error is encountered), it is important
that you set the Visible property to True. If you don’t, Excel will remain in memory, but the user
will never see it. The user cannot set this property; it must be done under macro control.
When you run a macro, however, the macro doesn’t “play nice” with the Undo list. In fact,
running a macro completely erases the Undo list, and therefore you cannot automatically undo
the effects of running the macro. There is no intrinsic command—in Excel or in VBA—to
preserve the Undo list. There are a couple of ways that you can approach the problem, however.
If you feel that you might want to undo the effects of a macro, the first thing you can do is to
save your workbook before running the macro. This, in effect, gives you a “pre-macro” version
of the workbook. If you want to later revert to this version, simply close the workbook without
saving and then reload it from disk.
Another option is to rethink the way you do your macros. If you have a macro that does a lot of
processing of information in your worksheet, code the macro so that it maintains, in memory, the
state of anything that it changes. You can then create a separate macro that reads this information
and effectively undoes the effects of the first macro.
To make this approach really handy, the last step in your primary macro can be to “stuff”
information on the Undo stack. This info can then be used, by the user, to “undo” the macro that
you created. For instance, the following macro command could be the last one in your primary
macro:
After this command, when the user looks at the Undo list, he or she will see the text “Primary
Macro.” If they choose this option from the Undo list, then your “undo” macro (UndoPrimary) is
executed.
You should note that this approach doesn’t save what was on the Undo list before you ran the
macro—there seems no way to do that. When your primary macro is through running, there will
only be a single option available on the Undo list: Primary Macro.
Excel VBA doesn’t provide a method like UndoClear. The reason is because the undo stack is
automatically cleared by Excel whenever your macro makes a change (any change) to the
workbook. If your macro doesn’t make any changes, and you still want it to clear the undo stack,
then all you need to do is make an innocuous change to the worksheet. For instance, the
following macro copies the contents of cell A1 back into A1, and in the process clears the undo
stack:
Sub ClearUndo()
Range("A1").Copy Range("A1")
End Sub
Unfortunately, there is no direct method to just calculate a particular workbook. You can,
however, calculate just the active worksheet, if desired. First, set the recalculation mode to
manual by following these steps if you are using Excel 2007 or later:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
2. Click the Formulas area at the left of the dialog box.
3. In the Calculation Options section of the dialog box, make sure the Manual radio button
is selected.
4. Click on OK.
1. Choose Options from the Tools menu. Excel displays the Options dialog box.
2. Make sure the Calculation tab is displayed.
Now the only time your workbook (actually, all your open workbooks) will be recalculated is
when you press F9. If you want to recalculate only the current worksheet, then press SHIFT+F9.
Excel also provides macro functions that allow you to do any of these three things: calculate all
open workbooks, calculate a specific worksheet in a workbook, or calculate a specified range of
cells on a worksheet. With this knowledge you could create a macro that would loop through all
the worksheets in a workbook and recalculate each of them.
The following macro sets the calculation mode to manual (so the other workbooks will not
calculate) and then loops through and calculates each sheet of the active workbook.
Sub CalcBook()
Dim wks As Worksheet
Application.Calculation = xlManual
For Each wks In ActiveWorkbook.Worksheets
wks.Calculate
Next
Set wks = Nothing
End Sub
If you believe that you may want to calculate different parts of your workbook at different times,
you can expand the macro so that it will perform any type of calculation you may want:
Sub CalcWhat()
Dim iAnsure As Integer
Application.Calculation = xlManual
iAnsure = InputBox("1 = Calculate A Used Range" _
& vbCrLf & _
"2 = Calculate This Worksheet" _
& vbCrLf & _
"3 = Calculate This Workbook" _
& vbCrLf & _
"4 = Calculate All Workbooks in Memory" _
& vbCrLf & vbCrLf & _
"Input Your Selection Number From Above" _
& vbCrLf & "Then Click OK", _
"Calculate What?", "Input Number Please", _
5000, 5000)
This macro presents an input box that prompts the user as to which type of recalculation is
desired. When the user enters a number from 1 to 4, the desired type of recalculation is
performed.
Testing has shown that the calculation setting is set to Automatic by default. It will only be set to
Manual if (1) you have changed the default workbook to one that has the calculation mode set to
Manual; (2) if there is some sort of AutoOpen macro that sets the calculation mode; (3) if you
have some automatically loading workbooks (including templates and the Personal workbook)
that have calculation set to Manual; or (4) if you start Excel by double-clicking, in Windows, on
a workbook that has calculation set to Manual.
Note, especially, conditions 3 and 4. Excel may very well be starting with the calculation mode
set to Automatic, but it is overridden by the setting within the file that is first opened. If that
workbook has calculation mode set to Manual, then Excel presumes you want Manual as your
default calculation mode for that session. The only solution to this problem is to open those
workbooks, change the calculation mode in them, save them, and restart Excel.
The other option is to add an AutoOpen macro to any of your workbooks that absolutely must be
opened with calculation mode set to Automatic, no matter what. This can be a simple macro,
such as the following:
Sub CountCharacters()
Dim wks As Worksheet
Dim rng As Range
Dim rCell As Range
Dim shp As Shape
lTotal = 0
lTotal2 = 0
lConstants = 0
lFormulas = 0
lFormulaValues = 0
lTxtBox = 0
bPossibleError = False
bSkipMe = False
sMsg = ""
ExitHandler:
Application.ScreenUpdating = True
Exit Sub
ErrHandler:
If bPossibleError And Err.Number = 1004 Then
bPossibleError = False
bSkipMe = True
Resume Next
Else
MsgBox Err.Number & ": " & Err.Description
Resume ExitHandler
End If
End Sub
The macro may seem quite long, but it is very well structured in exactly what it does. First, it
looks through all the text boxes in a worksheet. If they are not grouped (you cannot count
characters in grouped text boxes), then the characters in them are tallied up. Then the macro
tallies up the characters in cells containing constants. Finally, it counts all the characters used in
cells containing formulas. The balance of the macro is used to present the information in a
message box.
If you want to simply find out the names of the objects in a worksheet, the following macro will
do so very nicely. It shows not only the name, but also the type of object.
Sub ListObjects()
Dim objCount As Integer
Dim x As Integer
Dim objList As String
Dim objPlural As String
Dim objType(17) As String
objList = ""
If objCount = 0 Then
objList = "There are no shapes on " & _
ActiveSheet.Name
Else
objPlural = IIf(objCount = 1, "", "s")
objList = "There are " & Format(objCount, "0") _
& " Shape" & objPlural & " on " & _
ActiveSheet.Name & vbCrLf & vbCrLf
For x = 1 To objCount
objList = objList & ActiveSheet.Shapes(x).Name & _
" is a " & objType(ActiveSheet.Shapes(x).Type) _
& vbCrLf
Next x
End If
MsgBox (objList)
End Sub
This macro returns the names and types of all objects in the worksheet. Another approach,
however, is to display all the object names and then, if the object is hidden, ask if you want it
unhidden. The following macro does just that:
Sub ShowEachShape1()
Dim sObject As Shape
Dim sMsg As String
For Each sObject In ActiveSheet.Shapes
sMsg = "Found " & IIf(sObject.Visible, _
"visible", "hidden") & " object " & _
vbNewLine & sObject.Name
If sObject.Visible = False Then
If MsgBox(sMsg & vbNewLine & "Unhide ?", _
vbYesNo) = vbYes Then
sObject.Visible = True
End If
Else
MsgBox sMsg
End If
Next
End Sub
If you want the macro to only work on hidden objects and ignore those that are visible, then you
can modify the macro to the following:
Sub ShowEachShape2()
Dim sObject As Shape
Dim sMsg As String
For Each sObject In ActiveSheet.Shapes
If sObject.Visible = False Then
sMsg = "Object & sObject.Name & _
" is hidden. Unhide it?"
If MsgBox(sMsg, vbYesNo) = vbYes Then
sObject.Visible = True
End If
End If
Next
End Sub
To simply make all the objects visible in one step, you can shorten the macro even more:
Sub ShowEachShape3()
Dim sObject As Shape
For Each sObject In ActiveSheet.Shapes
sObject.Visible = True
Next
End Sub
To access the named range using the Range object, all you need to do is provide the name of the
range as a parameter to the object. This name is the same one that you defined within Excel. For
instance, the following line could be used to change the interior color of the entire range:
Worksheets("Sheet1").Range("Account").Interior.Color = vbYellow
Note that the Range object is used relative to a particular worksheet, in this case Sheet1. You
could also define a range object within VBA and then assign it to be equal to the named range, in
this manner:
The other method of using the named range is to use the Names collection. The following line
will again set the interior color of the range to yellow:
Workbooks("Book1.xls").Names("Account").RefersToRange.Interior.Color = vbYellow
Note that the Names collection is relative to the entire workbook, so it is not necessary to know
which worksheet the named range is associated with when you use this method of access. You
can also define a range object in VBA and assign it to be the same as the named range:
You should know that the Names collection method of accessing a named range will only be
viable if you don't have the same named range defined on different worksheets in the workbook.
If you do, then you will need to use the Range object method, which requires the use of a
specific worksheet name in the reference.
One possible solution is to simply create a new workbook and copy the cells from the old
workbook to the new one. Highlight the cells in the old workbook, use CTRL+C to copy them,
then paste them into worksheets in the new workbook. This will copy almost everything from the
old workbook—formulas, formatting, etc. It does not bring copy over print settings or range
names. The only task then remaining is to redefine the few names you want in the new
workbook.
If you prefer to work with the old workbook (the one with all the names), it is best to create a
macro that will do the name deletion for you. You need a macro that will allow you to delete all
the names except those you want to keep. The following is a simple approach that accomplishes
this task:
Sub DeleteSomeNames()
Dim vKeep
Dim nm As Name
Dim x As Integer
Dim AWF As WorksheetFunction
Before using the macro, modify the line that creates the vKeep array. Simply enter the names
you want to keep within the array, each name surrounded by quotes and separated by commas.
(In the example shown here, the names “Name1” and “Name2” will be kept.) The macro loops
through all the names in the workbook and uses the Match function to see if the name is one in
the array. If it is not, then it is deleted.
If you prefer to use a third-party solution to managing the names in your workbook, a great
choice is the Name Manager add-in, written by Jan Karel Pieterse. You can find more
information on the add-in here:
http://www.jkp-ads.com/officemarketplacenm-en.asp
You may not want to move the remaining cells according to Excel's assumptions; you may want
to always move the remaining cells in one particular direction. There are two ways you can go
about making this happen. The first is to simply memorize the keystrokes required to always
move remaining cells in the desired direction. If you want to always move cells left, you would
use the keystrokes ALT, H, D, D, L, ENTER (Excel 2007 later) or ALT, E, D, L, ENTER
(older versions of Excel). Similarly, if you want to move cells up, just press ALT, H, D, D,
U, ENTER (Excel 2007 and later) or ALT, E, D, U, ENTER (older versions of Excel). If you
memorize the keystrokes, you can enter them very quickly and achieve the desired results.
If you are a "mouse person," you may want to create a couple of macros that achieve the desired
effect, and then assign those macros to shortcut keys that can pull them up quickly. The
following macro will delete the selected cells and shift the remaining cells to the left:
Sub DeleteShiftLeft()
Selection.Delete xlShiftToLeft
End Sub
With one small change, the macro can shift the remaining cells up:
Sub DeleteShiftUp()
Selection.Delete xlShiftUp
End Sub
The only drawback to remember about using a macro is that when you invoke any macro, Excel
clears the Undo stack. Whereas you could undo a deletion if you used the menus or keyboard, if
you use a macro, you cannot undo it or any edits you did before the deletion.
One solution, of course, is to turn off automatic recalculation before you open the workbook. If
you are like me, this solution isn't that great because my memory isn't always that great.
A better solution is to turn off automatic recalculation for certain workbooks. Since Excel doesn't
allow you to specify manual or automatic recalculation on a workbook-by-workbook basis, you
will need to add this feature through the use of a macro that automatically runs when the
workbook is opened. This macro can turn off automatic recalculation, as shown here:
This macro must be placed in the ThisWorkbook project window. This means that you should
open the workbook, press ALT+F11 to display the VBA Editor, and then double-click on the
ThisWorkbook object in the Object Browser (upper-left corner of the VBA Editor window).
If you want, you can also place another macro right after the previous one. This macro is run
automatically when the workbook is closed and, in this case, turns automatic recalculation back
on:
There is an important caveat to remember in relation to using this macro. You can only set the
calculation mode for the application as a whole. Thus, with automatic recalculation turned off,
no other worksheets will be automatically recalculated, either.
However, this approach only works well if the formulas used in the worksheet are rather short. If
the formulas are longer, then understanding a worksheet with formulas displayed can quickly
become a bothersome chore.
One solution is to pull the formulas from Excel and place them in a program such as Word. Why
Word? Because you can easily format text attributes (such as typeface and point size) to best
display your formulas. You can also add additional text to explain the formulas, if desired.
The simplest way to get formulas from Excel into Word is to follow these steps if you are using
Excel 2007 or later:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
2. At the left side of the dialog box click Advanced.
3. Scroll through the list of options until you see the section entitled Display Options for
this Worksheet.
4. Ensure the Show Formulas In Cells Instead of Their Calculated Result check box is
selected.
5. Click on OK. Excel should now be displaying formulas.
6. Select the cells whose formulas you want to copy to Word.
7. Press CTRL+C to copy the cells to the Clipboard.
8. Switch to Word.
9. Position the insertion point where you want the information added.
10. Display the Home tab of the ribbon.
11. Click the down-arrow under the Paste tool, and then choose Paste Special. Word
displays the Paste Special dialog box.
If you are using an older version of Excel, follow these steps instead:
1. In Excel, choose Options from the Tools menu. Excel displays the Options dialog box.
2. Make sure the View tab is selected.
At this point your formulas are in Word, and you can do with them as you see fit. While this
approach works well, it can become bothersome to do this over and over again if you have a
large number of formulas to copy. If you are in such a situation, you would benefit from having a
macro that actually pulled the formulas and placed them in a Word document for you. The
following Excel macro will do just that:
Sub WriteFormulasToWord()
Dim Wrd As New Word.Application
Dim CellTxt As String
Dim CellAddr As String
Dim SRow As Long
Dim SCol As Long
Wrd.Visible = True
Wrd.Documents.Add
Next SCol
End Sub
There are a couple of things to note in this macro. First of all, you can change the range of rows
and columns over which the macro works by changing the noted For statements that use both
SCol and SRow. In the example shown above, the macro pulls formulas from columns 1 through
5 (A through E) and rows 1 through 10.
In addition, this macro will not work properly unless you set up Excel macros to handle
references to Word objects. You do that by following these steps within the VBA Editor:
1. Choose References from the Tools menu. VBA displays the References dialog box.
2. Scroll through the list of references until you see one called Microsoft Word Object
Library. (There may be a version number included in the reference name, such as
Microsoft Word 11.0 Object Library.)
3. Make sure the check box to the left of the object library is selected.
4. Click on OK.
An advantage to using a macro to actually pull your formulas is that you can customize exactly
what is placed in the Word document. In the case of this macro, the address of the cell is
inserted, followed by a tab character, and then the actual formula. You could just as easily
change the information inserted to be anything you need in your particular instance.
Internally, Excel maintains what is commonly called a "dirty flag." This flag gets set whenever
you do some sort of change to a workbook. Whenever you save the workbook, the flag is
cleared. If the flag is set when you close the workbook, Excel asks if you want to save the
workbook.
The dirty flag can obviously get set if you make some explicit change to a workbook, such as
editing a cell or modifying the structure of the workbook in some way. However, it can also get
set even if you don't do anything explicit. Sometimes, Excel does something that affects the
contents of the workbook just by virtue of the fact you opened it. This sets the dirty flag and thus
triggers the request about saving.
Two big culprits in making such automatic changes are the TODAY and NOW worksheet
functions. These return the system date and the system time, respectively. When you first open a
workbook, they are updated in the normal course of recalculating. Since they represent a change,
Excel sets the dirty flag.
The dirty flag can also be set automatically if your workbook includes links to data on other
worksheets. Excel retrieves the data, which represents a change to the workbook you just
opened. Excel doesn’t set the dirty flag if you simply navigate around the workbook, doing
things like selecting cells or changing to a different worksheet.
One way you can get around the problem is to, of course, remove whatever is causing changes in
your workbook. For most people, this just isn't practical. You can also add an automatic macro
that will run just before the workbook closes, such as the following, which should be part of the
ThisWorkbook object:
This macro does nothing more than clear the dirty flag (the Saved property). While this approach
will work, there is a huge risk inherent in using it. With the macro in place, Excel will never ask
you if you want to save changes upon exiting, even if legitimate changes were done to the
workbook. Thus, you would need to remember to explicitly save anything in the workbook
whenever you make changes. If you don't, you may loose some of your work.
Now, Excel opens the workbook, recalculates (including making changes based on functions
such as TODAY and NOW), and then clears the dirty flag. If you close right away, you aren't
asked if you want to save your changes. You will be asked if you want to save changes, however,
if you make changes after this macro has run—in other words, after the worksheet was fully
opened.
Besides automatically recalculating functions that set the dirty flag, it is also possible that your
workbook contains a macro or two that automatically run when you open it. If the macro is
making some sort of change in the workbook, then it will naturally set the dirty flag. You can
check out the VBA Editor to see if this is the case.
Precedents are those cells on which a formula is based. Thus, if cell A5 contains the formula
=A3 + A4, then both A3 and A4 are precedents for cell A5. Dependents are the reverse of
precedents. Thus, in this example, cell A5 is a dependent of cells A3 and A4. You can use the
auditing tools in Excel to graphically depict these relationships between cells, as described in
other issues of ExcelTips.
What if you want to know how many dependents and precedents there are in a worksheet,
however? There is no Excel command that displays this information. You can use a macro to
calculate and display this information, however. The following macro will do just that:
Sub CountDependentsPrecedents()
Dim ws As Worksheet
Dim lDep As Long
Dim lPre As Long
When you run this macro, it steps through each worksheet in your workbook and displays the
number of dependents and precedents in each.
There is obviously no way to do this with native Excel commands, but you can create a macro
that will extract the information you desire. The following macro will list the dependent cells for
whatever cell is selected when you run the macro:
Sub ListDependents()
Dim rArea As Range
Dim rCell As Range
Dim sActiveCell As String
Dim rDep As Range
Dim lRow As Long
On Error GoTo 0
sActiveCell = ActiveCell.Address(False, False)
Worksheets.Add
lRow = 1
Cells(lRow, 1).Value = "Dependents for " & sActiveCell
For Each rArea In rDep
For Each rCell In rArea
lRow = lRow + 1
Cells(lRow, 1) = rCell.Address(False, False)
Next
Next
Set rArea = Nothing
Set rCell = Nothing
Set rDep = Nothing
End Sub
When the macro is first run, it checks to see if there are any dependents for the cell. If there
aren't, then you are notified and the macro is exited. If there are dependents, then a new
worksheet is added to the workbook and the dependents of the cell are added to the worksheet.
If you want the macro to instead list precedents, all you need to do is change the all instances of
"Dependents" in the macro to "Precedents."
If you have workbook A and workbook B, and workbook B includes a link to workbook A, then
workbook B is dependent on workbook A and workbook A is a precedent to workbook B.
In workbook B you can easily find out the links used in the workbook; you would know that
workbook A is a precedent to workbook B. As Beth said, she knows how to find out this
information.
In workbook A there is no way to determine that workbook B has a link to workbook A and is
therefore dependent on workbook A. Thus, it is possible to make changes to workbook A that
can, inadvertently, affect workbook B. For instance, you could change a named range or rename
a worksheet or delete information you think is no longer needed. When you next open up
workbook B, you would be in for a rude surprise because the information that it depended on in
workbook A was no longer available.
Some changes you make in workbook A may not affect workbook B. For instance, you should be
able to add worksheets, add named ranges, and possibly insert columns or rows. In all these
cases Excel may adjust naturally to the changes without affecting workbook B. Problem is, you
won't know if there's been a negative effect until you later open workbook B. And you wouldn't
even know to open workbook B unless you knew beforehand that there was a relationship
between the two workbooks.
One way around the problem is to open all the workbooks you can think of, at the same time, and
then use the auditing tools in Excel to check for dependencies. This can work nicely if you have
a very limited number of workbooks on your system. It doesn't work that great if you have a lot
of workbooks or if the workbooks are on a network.
If you have your workbooks in a set location on your local system (all in a single folder), then
you might try using a macro to determine the dependencies. The following steps through all the
Excel workbooks in a given directory and identifies workbooks linked to your currently open
workbook by formulas.
Sub DiscoverDependentFiles()
Dim i As Integer
Dim iFile As String
Dim fLink As Variant
Dim sLink As String
Dim myFldr As String
Dim curFile As String
This approach should work fine in simple situations. In some cases, however, such a macro could
provide only a partial solution, because links can be hidden in numerous places—in Excel
names, text boxes, charts, and other objects. There is always a chance that something can be left
unchecked. The upshot of this is that because your changes could affect other workbooks that are
dependent on the one you are changing, you may want to make a backup of the workbook file
before making changes.
To turn off the AutoFill feature for a particular workbook, follow these steps:
1. Open the workbook for which you want to disable the AutoFill feature. (It should be the
only workbook you have open.)
2. Press ALT+F11 to open the VBA editor.
3. Using the Project window, select the ThisWorkbook object within the workbook you
opened in step 1.
4. Press F7. The Code window appears for the ThisWorkbook object.
5. Within the Code window, enter the following two macros:
Private Sub Workbook_Activate()
Application.CellDragAndDrop = False
End Sub
The first macro is only run when the particular workbook is activated. In other words, when the
workbook attains focus or is selected. When a different workbook attains focus, then the second
macro is executed.
This means that the answer lies in using a macro. For instance, you might create a macro that
would determine the current date and input it (as text) into a particular cell. This macro could
then be run whenever you created a new workbook by naming the macro Auto_Open. The
following is an example of such a macro:
Sub Auto_Open()
If Worksheets.Application.Range("A1") = "" Then
Worksheets.Application.Range("A1") = Format(Date, "long Date")
End If
End Sub
The macro checks to see what is in cell A1. If there is nothing there, then it puts the text version
of today's date in there. If there is something already there (which there would be every time you
subsequently open the workbook), then the information is left intact and unscathed.
Perhaps the most satisfactory approach, however, is to actually access the operating system and
pull the file creation date for the current workbook. This can be done with the following macro
function:
GetFile(ActiveWorkbook.Name).dateCreated
Notice that this approach isn't tied to a particular cell in your worksheet. To use the macro,
simply put the following in any cell of your worksheet:
=CreateDate()
The function returns either "Not Saved" (if the workbook is brand new and hasn't been saved
before) or it returns a text value that represents the date on which the workbook was created.
If you want to use this date in your workbook (perhaps in a header or footer), you can do so by
using the BuiltinDocumentProperties property (that almost sounds redundant). The following
macro will add the proper date to the header of your document:
Sub MyHeader1()
Dim sLMD As String
There are a number of items to note in this macro. First of all, it attempts to determine the last
date the workbook was saved. If that information cannot be determined, then it sets the header to
"Not Set."
Notice that there is some error handling done in this macro. The reason is that Excel will return
an error if a particular document property (BuiltinDocumentProperties in this case) is not set.
The error needs to be intercepted and handled, which is done here.
There is another item to note here. In some versions of Excel, the Err value returned if the
property is not set is not really 440 (as shown here), but some other odd number, such as -
2147467259. This is very bizarre, indeed. Why the 440 value (which is the proper error code)
would be returned in one circumstance and not in another, I don't know. (Perhaps some other
Excel guru will know the answer.) If you have this problem, there are two approaches you can
take. First, you can replace the 440 value with the other value (-2147467259). The second
option, assuming you have already saved the workbook at least once, is to use a different macro.
The following reads the "last modified" attribute from the file itself and stores that info in the
header:
Sub MyHeader2()
Dim fs As Variant
Dim f As Variant
Dim sLMD As String
Set fs = CreateObject("Scripting.FileSystemObject")
Set f = fs.GetFile(ActiveWorkbook.Path & "\" & _
ActiveWorkbook.Name)
sLMD = Left(f.DateLastModified, 8)
ActiveSheet.PageSetup.LeftHeader = "Last Modified: " & sLMD
End Sub
Regardless of which macro you use, remember that the macro, once run, will set the left header
to the desired information. That information will not change again until you run the macro again.
Thus, if you always want an up-to-date date in the header, then you should either run the macro
periodically (perhaps right before printing) or set it up to run whenever you open your document.
Sub DateLastModified()
Dim fs, f
Set fs = CreateObject("Scripting.FileSystemObject")
Set f = fs.GetFile("D:\MyFolder\MyFile.xls")
Cells(1, 1) = f.DateLastModified
End Sub
To use the macro, just replace the D:\MyFolder\MyFile.xls file specification with whatever is
appropriate for you.
If you want a history sheet of who did what with your workbook, then a different approach is
necessary. Perhaps the best solution is to try Excel’s sharing feature, which can be configured to
keep a history log for a workbook. Follow these steps if you are using Excel 2007 or a later
version:
10. Make sure the List Changes on a New Sheet check box is selected.
11. Click OK.
If you are using an older version of Excel, follow these steps instead:
1. Choose Share Workbook from the Tools menu. Excel displays the Share Workbook
dialog box.
2. Make sure the Editing tab is displayed.
3. Select the Allow Changes check box.
4. Display the Advanced tab.
5. Make sure the Keep Change History radio button is selected.
6. Using the other controls in the dialog box, select the tracking options you want used
with the workbook.
7. Click on OK.
8. Choose Track Changes from the Tools menu, then choose Highlight Changes from the
submenu. Excel displays the Highlight Changes dialog box.
9. Make sure the List Changes on a New Sheet check box is selected.
10. Click OK.
As changes are made to the workbook, Excel tracks those changes (along with who made them)
and puts them in a separate worksheet so you can review them later.
perform macros whenever certain events occur, such as changes to a workbook. All you need to
do is add a macro such as the following to the ThisWorkbook object in the VBA Editor:
The macro results in each footer on each worksheet in the workbook having separate dates and
times on them, since each worksheet would be updated at different times. You can change the
destination property (.CenterFooter) to one of the other header or footer properties (.LeftHeader,
.CenterHeader, .RightHeader, .LeftFooter, .RightFooter) as desired.
You may want the header or footer to instead include the date that the workbook was last saved.
(This is what many people really view as the “last edit date.”) The information is visible in the
Properties dialog box for a worksheet, but Excel has no menu selection or other command that
allows you to insert this date into a header or footer. Instead, you must use a macro to add the
desired information.
The best way to accomplish the task is to add a macro to the ThisWorkbook object that is
triggered just before a workbook is saved:
This macro steps through each worksheet in the workbook and changes every center footer to
have the date that the workbook was saved.
There are several ways to find the path to the desktop in VBA. One way is to call the Windows
scripting host, in this manner:
Note that this is a user-defined macro that you can use either from the worksheet or from another
macro. The use from the worksheet would be as follows:
=GetDesktop()
Another way to determine the path to the desktop is to use the following line in your code:
The username that a person sets in Excel when first installing the software or when changing the
general options for the program cannot be accessed via formula. Instead, you need to use a macro
to access the information and then make it available to your worksheet. This is possible through
the use of a user-defined function. Consider the following simple example:
Function GetUserName()
GetUserName = Application.UserName
End Function
Note that the macro does nothing more than to access the UserName property of the Application
object. You use this function in your worksheet in the following manner:
=GetUserName()
With this simple formula in a cell, the username is displayed in the cell.
The way to do this is to implement a short, one-line macro that accesses the UserName property
of the Application object. This technique is illustrated in this macro:
Function GetUserName()
GetUserName = Application.UserName
End Function
This approach is great at determining the user name associated with the current installation of
Excel. However, that may not be the same thing as who is using the current workbook. For
instance, if the workbook is shared, it is possible that multiple people could be using it at the
same time. In that case, you need a way to determine those names, as shown here:
Users = ActiveWorkbook.UserStatus
UserNames = sMsg
End Function
To use the function, just enter the following formula in the cell where you want the names to
appear:
=UserNames
If you instead want to know who is using the computer currently, it is best to look beyond Office
and instead grab the name from Windows itself. In that way you can determine who is logged in
to Windows and use that as the user name. This takes an API function call declaration, but is
otherwise relatively easy:
lngBuffLen = 100
GetUserName strBuff, lngBuffLen
UserName2 = Left(strBuff, lngBuffLen - 1)
End Function
If you want to grab the names of the MRU files and insert those names in a worksheet, you can
use the following macro:
Sub MostRecent()
Dim J As Integer
For J = 1 To Application.RecentFiles.Count
Cells(J, 1) = Application.RecentFiles(J).Name
Next J
End Sub
Traditionally, the normal method used to switch from one workbook to another is to display the
View tab of the ribbon and use the Switch Windows tool to select the desired workbook. If there
are more than nine workbooks open and you click the Switch Windows tool, Excel displays an
option that says “More Windows.” Click the option and you can see a display of all open
workbooks. Selecting a workbook from this list ends up in that workbook being displayed.
If you routinely work with many open workbooks, it can be a pain to repeatedly click the Switch
Windows tool, click More Windows, and then select which workbook you want to view. It
would be much easier if the workbooks were all listed and if you could then select from the list.
Unfortunately, Excel doesn’t allow you to display more than nine workbooks using the Switch
Windows tool. (Nine seems to be a rather arbitrary number, but Microsoft had to set a limit
somewhere. Nine is just as good as any other arbitrary limit.) There are, however, some
workarounds you can use.
The first workaround is to use the task-switching capabilities of Excel. Just hold down the ALT
key as you repeatedly press the TAB key to switch from one window to another. When the desired
workbook window is selected, release the ALT key and the actual workbook is displayed.
You can also develop macros to display a list of workbooks, allow you to select one, and then
switch among them. The most efficient way to do this is to create a UserForm and attach several
macros to it. Follow these steps:
1. Make sure you have created a Personal workbook to contain your common macros.
(How this is done has been discussed in other issues of ExcelTips.)
2. Press ALT+F11 to display the VBA Editor.
3. Make sure the Personal workbook is selected in the Object Browser window. (Upper-
left corner of the VBA Editor.)
4. Choose UserForm from the Insert menu. A new UserForm appears in the center of your
screen.
5. Using the toolbox at the left side of the VBA Editor, place a ListBox control on the
UserForm. This control will hold the list of open workbooks. (Feel free to make the
ListBox as large or small as desired.)
6. Add any other items desired to the UserForm, such as explanation text, etc.
7. Resize the UserForm to the size you want displayed.
8. Right-click on the UserForm (not on the ListBox) and select View Code from the
Context menu. You then see the Code window for the UserForm.
9. Replace whatever is in the Code window with the following code:
Private Sub ListBox1_Click()
Windows(ListBox1.Value).Activate
Unload Me
End Sub
10. Choose Module from the Insert menu. A Code window appears for the new module.
11. Add the following code to the module’s Code window:
Sub AllWindows()
UserForm1.Show
End Sub
12. Close the VBA Editor and return to Excel by pressing ALT+Q.
Once in Excel, you can assign the AllWindows macro to the Quick Access Toolbar or to a
shortcut key. When you then click on the toolbar button or the shortcut key, the UserForm is
displayed, showing all the open workbooks. You can then select one, and the UserForm
disappears and the selected workbook is displayed.
It is much easier to have your code, after opening, check to see if the workbook opened in read-
only mode. If it did, then you can take an action appropriate to your situation. (For instance, you
could close the workbook, wait a short period, and retry the operation and test.) Here's how you
can check to see the read-only status:
If wkBook1.ReadOnly Then
wkBook1.Close False
End If
Note that it is the ReadOnly property that yields the desired info. If you need to check the file
ahead of time, you might try using some of the file-access statements available in VBA. That's
what the following function does.
To use the function, pass it the name (including full path) of the workbook you want to check.
The function returns True if the file is locked and False if it isn't. Remember, though, that from
the time this function checks the file to the time that you actually try to open the file, it could
have been opened by someone else. Thus, the first approach (checking after trying to open) may
be the best approach to use.)
It should be noted, as well, that you could also save the other workbooks as shared workbooks.
This would allow them to be opened by multiple users with no problems. Of course, you'll want
to check how this approach affects the data you may be wanting to save in the workbooks.
The “Copy of” verbiage is added by Excel automatically. If you are using Save As, there is no
way to change this without using a macro to control the saving process. The following macro,
saved as part of the ThisWorkbook object, shows how this can be done.
If SaveAsUI Then
sTemp = ThisWorkbook.Name
If Right(sTemp, Len(sCheck)) = sCheck Then
sTemp = Left(sTemp, Len(sTemp) - Len(sCheck))
sTemp = sTemp & Format(Now, "dd") & ".xls"
sTemp = ThisWorkbook.Path & "/" & sTemp
ThisWorkbook.SaveAs Filename:=sTemp, _
FileFormat:=xlNormal
Cancel = True
End If
End If
End Sub
The macro first checks to see if the Save As dialog box is about to be displayed. If it is, then the
workbook’s name is assigned to the sTemp variable. This name is checked to see if the last six
characters are “xx.xls” (from the sCheck variable). If they are, then the workbook is assumed to
be the one where the name needs to be changed.
If you are using Excel 2007 or a later version, you’ll want to change the line that assigns the
value to sCheck so that it checks for either “xx.xlsx” or “xx.xlsm”, depending on your needs.
First the “xx.xls” characters (or whatever you’ve assigned to sCheck) are stripped from the end
of the workbook name. Then today’s date (two digits, for the day of the month) is appended to
the file name, followed by the “.xls” suffix; this suffix should be changed if you are using Excel
2007 or a later version. Finally, the workbook is saved using this newly constructed filename.
The Cancel flag is set to True so that the Save As dialog box never displays.
Note that the name is never checked for the verbiage “Copy of”. The reason for this is simple:
The wording is not added to the start of the file name until the actual Save As dialog box is
displayed. Before that point (when this event handler is being executed) the workbook name
remains unchanged.
As you are developing macros, you may wonder if there is a way to retrieve a list of defined
names within a worksheet. This is actually quite easy, if you remember that the defined names
are maintained in the Names collection, which belongs to the Workbook object. With this in
mind, you can use the following code to put together a variable array that consists of all the
names in a workbook:
Dim NamesList()
Dim NumNames As Integer
Dim x As Integer
NumNames = ActiveWorkbook.Names.Count
For x = 1 To NumNames
NamesList(x) = ActiveWorkbook.Names(x).Name
Next x
Simultaneous Scrolling
If you have worksheets that are very similar in nature, you may like to work with them side-by-
side on the screen at the same time. This makes it easy to examine both worksheets for
differences or for other reasons. It can be a bother, however, to scroll down at the same rate in
both worksheets. First you have to scroll in one window, then in the other.
As with most tedious tasks, you can automate the process a bit. Consider the following macros:
Sub myScrollDown()
ActiveWindow.SmallScroll Down:=1
ActiveWindow.ActivateNext
ActiveWindow.SmallScroll Down:=1
ActiveWindow.ActivatePrevious
End Sub
Sub myScrollUp()
ActiveWindow.SmallScroll Up:=1
ActiveWindow.ActivateNext
ActiveWindow.SmallScroll Up:=1
ActiveWindow.ActivatePrevious
End Sub
If you add these to a workbook, and then assign them to a toolbar button, a shortcut key, or the
Quick Access Toolbar you can scroll through both workbooks at the same rate. The SmallScroll
method is used to move down one row at a time through a window. If you want to scroll a page
at a time, simply replace all instances of SmallScroll with the LargeScroll method.
To return all the proper names, there are a couple things you could do. One method would be to
bypass using a formula altogether. Instead, you could use the AutoFilter feature in Excel:
When you follow these steps, you may actually end up with more than five records visible,
particularly if there are ties in the employee salaries. The filter identifies the top five salaries and
then displays all the records with salaries matching those.
If you don’t want to use filtering, another option is to simply make sure that there is something
unique about each of the records in your employee list. For instance, if the employee names are
in column B and the salaries are in column C, then you could use the following formula in
column A to make each record unique:
=C2+ROW()/100000000
This will add the row number divided by 100,000,000 and will make a unique value. If you have
(for example) identical salaries of 98,765.43 in rows 2 and 49 in column A they will be:
98765.43000002
98765.43000049
The large number (100,000,000) is so that if you had an identical number in row 65536, you
would get:
98765.43065536
And even in this case the rounded value to 2 decimal places would still be the real number. If the
LARGE and VLOOKUP are done with the "non-unique" values in column A, then you will
return the largest salaries (and their associated people), based on the person’s position within the
list.
A third approach is to use the RANK and COUNTIF functions to return a unique “ranking” for
each value in the list of salaries. If the salaries are in the range B1:B50, enter the following in
cell C1 and copy it down the range:
=RANK(B1,$B$1:$B$50)+COUNTIF($B$1:B1,B1)-1
You can now use INDEX on the ranking values to return the name associated with each salary.
Finally, a fourth approach is to create a macro that can return the desired information. There are
many ways that a macro could be implemented; the following is just one of them:
VLIndex = vArray(lIndex)
End If
errhandler:
If Err.Number <> 0 Then VLIndex = CVErr(xlErrValue)
End Function
The parameters passed to this user-defined function are the value, the range of cells to lookup in,
the "offset" from this range for the lookup (the number of columns to the right is positive, to the
left is negative) and the number of the duplicate (1 is first value, 2 the second, and so on).
To use it, for example's sake, assume A1:B1 contain column headers, A2:A100 contains the
salaries, and B2:B100 contains the employee names. In cell E2 you can enter the following to
determine the largest salary in the table:
=LARGE($A$2:$A$100,ROW()-1)
In cell F2 you can enter the following formula to determine if the row has any duplicates and
keep track of the current “value” of that duplicate:
=IF(E2=E1,1+F1,1)
In cell G2 you can use the following formula, which invokes the user-defined function:
=VLIndex(E2,$A$2:$A$100,1,F2)
Copy cells E2:G2 to E3:G6, and you will have (in column G) the names of the employees with
the five largest salaries.
If Selection.Information(wdCapsLock) Then
Print "The Caps Lock key is on"
Else
Print "The Caps Lock key is off"
End If
Excel makes these two views (Normal and Page Layout) available from the View tab of the
ribbon. One might think that the solution is to simply modify the user interface so that the Page
Layout tool is no longer available on the ribbon. This, unfortunately, is easier said than done.
If you are using Excel 2007, the user interface is notoriously hard to change. It requires writing
XML code and making sure that the code is executed every time the workbook is opened. If you
like notoriously hard things, you can find a bit about how to start at this page:
http://msdn.microsoft.com/en-us/library/aa338202.aspx
If you are using Excel 2010 modifying the user interface is a bit easier. You can do it by
following these steps:
1. Click the File tab and then click Options. Excel displays the Excel Options dialog box.
2. At the left side of the dialog box click Customize Ribbon.
3. In the right column of the dialog box, click the small plus sign at the left of the View tab
entry. Excel shows you the options that are under the View tab.
That's it. Now, if you go look at the View tab, you'll notice that the user can no longer switch to
Page Layout view. In fact, the user cannot pick any view other than whatever view you happen
to be in at the current time. This change affects only the current machine, for all workbooks, and
cannot be tied to any particular workbook. (The reason is that while you can modify the ribbons
a bit in Excel 2010, you cannot modify them in macros. Big pain, and you need to go back to
writing XML code like in Excel 2007.)
Perhaps a better solution is to create a small macro that will always make sure that the worksheet
is always being displayed in Normal view. This is easy to do; just right-click on a worksheet tab
and choose View Code from the resulting Context menu. In the code window, enter the
following:
This code causes Excel to switch to Normal view every time someone changes what is selected
on the screen. Someone could use the tools on the View tab of the ribbon to switch to Page
Layout view, but as soon as they select a different cell the macro kicks in and switches back to
Normal view.
The only way to disable a key such as this is to create a macro. The following macro will do the
trick quite nicely:
Actually, there are two macros here. The first one executes whenever the workbook is opened,
and the second is executed when the workbook is closed. In the case of the first macro, the
OnKey method traps every keypress of F1 and cancels it. The macro that runs when the
workbook closes restores the normal operation of the F1 key.
These macros can be placed in a given workbook, in which case they will only apply while that
workbook is open. If you want them to apply at all times when using Excel, store the macros in
the Personal workbook. (The use of this workbook has been covered in other issues of ExcelTips.
You can also find information on it in the Excel Help system which, ironically, is invoked by
pressing the F1 key.)
Depending on how you type, you may find the F1 key bothersome. For instance, if you meant to
press F2 to edit the contents of a cell, but you instead press F1, this can throw a real crimp in
your editing stride. For this reason, you may look for an easy way to disable the F1 key.
One definitely low-tech solution is to simply remove the key. They F1 keycap, on most
keyboards used with desktop systems, is relatively easy to remove. If it is a bit stubborn, you
may need to slip the edge of a small screwdriver under the cap to help pry it loose.
If you don't like doing this type of keyboard surgery, you can disable the key through the use of a
macro. This macro could be included in your Personal.xls file, as a part of the Open event, so
that it runs every time that Excel is started. The macro should contain a single command:
The OnKey method is only triggered, in this case, when the F1 key is pressed. This usage results
in the F1 key being ignored. If you wanted the F1 key to run some different procedure, you
could use it as follows:
The answer is yes, there is a relatively easy way. You could set up a couple of macros that
disable and restore the most common ways of opening the Help system. The following macros,
DisableHelp and EnableHelp, do that.
Sub DisableHelp()
Sub EnableHelp()
EnableControl 984, True ' help
EnableControl 1004, True ' Office Assistant
Application.OnKey "{F1}"
End Sub
Both of the main macros call the EnableControl macro. This macro does the actual work of
removing the Help options from the menus and toolbars. Note that the main macros also use the
OnKey method to disable (or restore) the functioning of the F1 function key. To use these
macros, you can call them from a suitable event procedure, such as those that automatically run
when a workbook is opened and closed.
The macros will run just fine in all the modern versions of Excel, but they are particularly useful
in versions prior to Excel 2007. In those versions the menus and toolbars are modified by the
macros, but not so starting with Excel 2007. The Help button (small question mark within a
circle) remains at the upper-right corner of the worksheet window and can still be clicked.
Regardless of your version, the F1 key is disabled and enabled by the macros.
Even with a macro such as this at work, you need to realize that the Help system is not totally
disabled. The Help files still reside on disk, and could be located via Windows and opened. (You
don’t even need Excel to open and view them.)
Typically the Excel Help files are stored in files that use the CHM file extension. Disabling the
file can be as simple as locating the proper CHM help file on the disk and renaming it to
something different.
For example, Excel may be installed on a certain machine in the directory “c:\program
files\microsoft office\office11”. The Help file for this installation of Excel can be found in
“c:\program files\microsoft office\office11\1033”. The main Excel file is XLMAIN11.CHM, but
there may be other Help files (CHM extension) in the directory as well. All you need to do is to
rename these files something such as XLMAIN11.XXX. Since the Help program cannot locate
the file, it cannot display any help in Excel.
The following macro will retrieve the requested information. All you need to do is provide the
column headings. The macro assumes that you’ll have three columns: In cell A1 you should
place the heading "Drive," in cell B1 you place the heading "Free%," and in cell C1 you place
the heading "Used%." In addition, you should format columns B and C as percentages.
Sub DriveSizes()
Dim Drv As Drive
Dim fs As New FileSystemObject
Dim Letter As String
Dim Total As Variant
Dim Free As Variant
Dim FreePercent As Variant
Dim TotalPercent As Variant
Dim i As Integer
When you first run this macro, you may get an error. If you do, it means that you need to
configure your macro to reference the Microsoft Scripting Runtime. Follow these steps from
within the VBA Editor:
1. Choose the References option from the Tools menu. VBA displays the References
dialog box.
2. In the list of available references, make sure Microsoft Scripting Runtime is selected.
3. Click on OK.
Now the macro should run just fine, and you will have a fully populated table representing all the
drives available on your system. (If your system has drives that use removable media—such as
floppy drives—they may not show up unless you have media in them.)
If you only need to know the full path name once in a while, then you can create a very simple
macro and assign it to the QAT or a shortcut key. When you run the macro, the information in
the title bar for the active window is changed to reflect the full path name. This macro, called
ChangeCaption, is as follows:
Sub ChangeCaption()
ActiveWindow.Caption = ActiveWorkbook.FullName
End Sub
The only drawback to this approach is that whenever you rename your workbook by saving it
under a different name, the new file name (and path) are not updated in the title bar unless you
rerun the macro.
The following series of three macros will implement just such a system. The first macro
(Workbook_Open) is executed when a workbook is first opened. It uses the run_time subroutine,
which uses the OnTime method to specify that ten minutes in the future the close_wb macro will
be run.
Procedure:="close_wb", _
Schedule:=False
close_time = Empty
End If
close_time = Now + TimeValue("00:10:00")
run_time
End Sub
Public close_time
Sub run_time()
Application.OnTime _
EarliestTime:=close_time, _
Procedure:="close_wb", _
Schedule:=True
End Sub
Sub close_wb()
Application.DisplayAlerts = False
With ThisWorkbook
.Saved = True
.Close
End With
End Sub
If the close_wb macro is ever executed, any changes to the current workbook are discarded, and
the workbook is closed.
First, choose Links from the Edit menu, if the option is available. (It will only be available if
Excel recognizes explicit links in the workbook.) If you are using Excel 2007 or a later version,
you should display the Data tab of the ribbon and then click the Edit Links tool. From the
resulting Links dialog box you cannot delete links, but you can change the links so that they
point to the current workbook. When you later save and again open your workbook, Excel will
recognize the self-referent links and delete them.
Another way you can find links is to search for either the left bracket ([) or right bracket (]) in
your workbook. The brackets are used by Excel when putting together the links to other files. For
instance, this is a link to an external file, as it would appear in a cell:
=[Book1.xls]Sheet1!$D$7
When you find links similar to the above, all you need to do is delete them. Make sure that you
search each worksheet in your workbook.
You can, if desired, try to use the Auditing tools to locate links in your cells. This can be done
manually using the Auditing toolbar, and it works great—for a few cells at a time. If you have
many cells and many sheets, a tiny macro may be more efficient.
The following macro will loop through all sheets in the workbook, selecting only those cells
which contain a formula. Once selected, the Auditing feature is run against the cells, then
processing continues to the next sheet.
Sub DisplayPrecedents()
' Loops through all sheets and selects any Formula
' cells then displays the Precedents of those cells
' before moving on to the next sheet.
Dim c As Range
Dim sht As Worksheet
ActiveWorkbook.Worksheets(1).Activate
End Sub
When the macro is completed, you can examine the different Auditing symbols placed in your
workbook. The cells that have an external link will have an icon which looks like a spreadsheet
with an arrow head pointing to the cell containing the formula creating the link. You can then
examine the cell and delete the link.
Another place to look for links (and which this macro will not look) is in the defined range
names maintained by Excel. This is a particularly common place for links if you are working
with a workbook that contains worksheets that were copied or moved from other locations. The
defined names, rather than pointing to a cell range in the current workbook, could be pointing to
a range in a different workbook. Choose Insert | Name | Define to display the proper dialog box.
(In Excel 2007 and later versions, display the Formulas tab of the ribbon and click Define name
in the Defined Names group.) Then step through each defined name, examining the address to
which it refers. Delete or change any that refer to other workbooks.
Another place to check is your macros. It is possible to assign macros to toolbar buttons or to
graphics in a worksheet. Click on any custom toolbar buttons or graphics and see if you get an
error. If you do, this is a good indication that the button or graphic is linked to a macro contained
in a different file. If you delete the button or graphic, or change the macro assignment, the link
problem should go away.
Still another possible location for wayward links is in PivotTables. When you create a
PivotTable, it can refer to data on a different worksheet in your workbook. If you later move that
source worksheet to a different workbook, your PivotTable will be linked to the external data
source. The only solution here is to delete the PivotTable, copy the source data back to the
current workbook, or move the PivotTable to the external workbook.
Finally, you should check graphs and charts. If you recently moved worksheets out of your
current workbook into another workbook, it is possible that charts and graphs remaining in your
current workbook now refer to data on a worksheet you moved to another workbook. If this is
the case, you will need to either remove the graph or chart, move it to the other workbook, or
copy the source data back into the current workbook.
Since links can hide in so many places, there are special tools you can use to help track down
links in a workbook. One such tool is described in the Microsoft Knowledge Base:
http://support.microsoft.com/?kbid=188449
In addition, several subscribers suggested the FindLink utility, created by Bill Manville. You can
find this free utility here:
http://www.cpearson.com/excel/xltools.htm
There are several ways you can approach such a task. One is to manually break the links by
choosing Links from the Edit menu (in Excel 2007 and later versions you display the Data tab of
the ribbon and click the Edit Links tool) and then selecting all the links and clicking Break Link.
You can even select all the links at once by creating a selection set (using SHIFT and CTRL to
compose the set) prior to clicking on Break Link.
If you prefer not to use the manual method, you can use a short macro to get rid of the links. The
following is one example that will do the task:
Sub BreakLinks()
Dim strLink
For Each strLink In ActiveWorkbook.LinkSources
ActiveWorkbook.BreakLink Name:=CStr(strLink), _
Type:=xlExcelLinks
Next strLink
End Sub
A third way to manage your links is to look to a third-party solution, such as FindLink or Name
Manager. You can find them at the following page:
http://www.oaltd.co.uk/mvp/MVPPage.asp
FindLink was written by Bill Manville and Name Manager by Jan Karel Pieterse, both Excel
MVPs.
One thing to try is to open the workbooks that contain the links and then use Excel’s tools to
break the links. Make sure you keep a backup of your workbook (in case you mess things up)
and follow these steps:
The result is that all the links are done away with, but the values last retrieved through the links
remain in the workbook.
Another approach is to use Paste Special to “overwrite” your links. (This works well if you have
a limited number of links in a worksheet.) Follow these steps:
If you have quite a few links in your workbook, then you will want to use a macro to do the link
breaking. The following is an example of a simple macro to do the breaking:
Sub BreakLinks()
Dim aLinksArray As Variant
aLinksArray = ActiveWorkbook.LinkSources(Type:=xlLinkTypeExcelLinks)
Do Until IsEmpty(aLinksArray)
ActiveWorkbook.BreakLink Name:=aLinksArray(1), _
Type:=xlLinkTypeExcelLinks
aLinksArray = _
ActiveWorkbook.LinkSources(Type:=xlLinkTypeExcelLinks)
Loop
End Sub
It is important to remember, though, that links can be tricky. Links to other workbooks can be in
formulas, names, charts, text boxes, and other objects, both visible and hidden, and in different
combinations within formulas and those objects. Getting all the links and breaking them depends
on the complexity of your workbook. If you have a complex workbook, then you may benefit by
using the FindLink add-in created by Excel MVP Bill Manville. You can find it here:
http://www.oaltd.co.uk/mvp/mvppage.asp
Even though Brian is using Excel 2002, the problem he experienced could also occur for those
using Excel 2003. Excel automatically hides the Task pane upon startup if your copy of Excel
loads any files automatically. For instance, if you have any worksheets in your Startup folder, or
if you have a Personal.xls file that loads, then the Task pane will automatically be obscured. It
appears that this behavior—despite what you explicitly specify for the Task pane to do—is built
into Excel.
There are three ways around this problem. The first is to make sure that nothing automatically
loads when Excel starts. For many people, this may not be a practical solution—after all,
Personal.xls is often used for “global” user macros and customizations. The second solution,
then, is to add more programming code to the file being opened automatically (for instance,
Personal.xls). This code is designed to run automatically when the file is opened, and it displays
the Task pane:
If it is not possible for you to add such coding to the file or files being opened, then you can
modify a Registry entry to tell Excel to leave the Task pane open. As always, be careful when
you edit the Registry, as a mistake can make your system unstable or totally unusable.
1. Choose Run from the Start menu. Windows displays the Run dialog box.
2. In the Open box enter the name regedit.
3. Click on OK. This starts the Registry Editor program.
4. Locate and select the following key. (For Excel 2002 the path will differ slightly,
referencing 10.0 instead of 11.0.)
HKEY_CURRENT_USER\Software\Microsoft\Office\11.0\Common\General
5. Choose the New option from the Edit menu, then choose DWORD Value. A new value
appears in the right side of the Registry Editor, awaiting a name.
6. Name the value DoNotDismissFileNewTaskPane. Note that there should be no spaces,
and capitalization should be just as shown.
7. With the newly named value still selected, choose the Modify option from the Edit
menu. You will see the Edit DWORD Value dialog box.
8. Change the Value Data field from 0 to 1.
9. Click on OK.
10. Close the Registry Editor.
When you restart Excel, the Task pane should remain visible.
To do this same task from a macro, you use a very simple command, as shown here:
Sub FindLast1()
ActiveCell.SpecialCells(xlLastCell).Select
End Sub
This is functionally the same as pressing CTRL+END. However (and this is a big issue), Excel
doesn’t dynamically keep track of which rows and columns are the last used in a worksheet. For
instance, let’s suppose that you open a workbook, press CTRL+END, and you are taken to cell
F27. If you then delete 3 rows and one column, you would expect that CTRL+END would take you
to cell E24. It doesn’t; it still takes you to cell F27, until you save the workbook and reopen it.
This same problem affects the macro code shown in the FindLast1 macro; it will take you to the
“highest” cell, regardless of which columns or rows you have deleted during the current session.
What’s needed is a way to reset the “last cell” indicator, just as if you had saved and reopened
the workbook. There is no intrinsic macro command that does that, but there is a way to force
Excel to do the reset. All you need to do is adjust the macro as follows:
Sub FindLast2()
x = ActiveSheet.UsedRange.Rows.Count
ActiveCell.SpecialCells(xlLastCell).Select
End Sub
This macro always takes you to the proper cell—it works as you would expect CTRL+END to
always work. It works because apparently Excel, when it calculates the Count property for the
number of rows in the worksheet, always resets the “last cell” indicator.
The first thing to try is to simply save your workbook, get out of Excel, and then reopen the
workbook. Doing so “resets” the end-of-data pointer in the workbook, and you should be fine.
If that doesn’t solve the problem, then it is very likely that the data you imported into Excel
included non-printing characters, such as spaces. If these are loaded into cells, Excel sees them
as data, even though you don’t. To fix the workbook by deleting the data, select row 88 (the one
right after your data) and then hold down the SHIFT and CTRL keys as you press the Down
Arrow. All the rows from 88 through the last row in the worksheet should be selected. Press the
DELETE key, save the workbook, and reopen it. CTRL+END should work fine.
If you have quite a few of these files you need to “clean up,” or if you need to do it on a regular
basis, then you need a macro to help you. Consider the following macro:
Sub ClearEmpties()
Dim c As Range
Dim J As Long
J = 0
Selection.SpecialCells(xlCellTypeConstants, 23).Select
For Each c In Selection.Cells
J = J + 1
StatusBar = J & " of " & Selection.Cells.Count
c.Value = Trim(c.Value)
If Len(c.Value) = 0 Then
c.ClearFormats
End If
Next
StatusBar = ""
End Sub
This macro selects all the cells in the worksheet that contain constants (in other words, they don’t
contain formulas). It then steps through each of those cells and uses the Trim function to remove
any leading or trailing spaces from the contents. If the cell is then empty, any formatting is
cleared from the cell.
When the macro is done, you can save and close the workbook, reopen it, and you should be able
to use CTRL+END to go to the real end of your data. If this still doesn’t work, it means that the
cells being imported into the workbook have some other invisible, non-printing character in
them. For instance, there could be some bizarre control characters in the cells. In this case, you
need to talk with whoever is creating your import file. The best solution, at this point, would be
for the person to modify their program so it doesn’t include the “trash” that Excel is mistaking
for valid cell content.
Your macros can also force Excel to recalculate your worksheet. If you have automatic
recalculation turned on, then any change your macro makes in a worksheet will force Excel to
recalculate. If you have automatic recalculation turned off, then you can use the Calculate
method to recalculate a worksheet:
ActiveSheet.Calculate
Of course, if recalculation takes quite a while to perform, you might want to check to see if a
recalculation is necessary before actually forcing one. It appears that there is no flag you can
directly check to see if a recalculation is necessary. The closest thing is to check the Workbook
object's Saved property. This property essentially acts as a "dirty flag" for the entire workbook. If
there are unsaved changes in a workbook, then the Saved property is False; if everything is
saved, then it is True.
How does this help you figure out if a recalculation is necessary? Remember that calculation is
only necessary when there are changes in a worksheet. Changing anything in a worksheet will
also set the workbook's Saved property to False. Thus, you could check the Saved property
before doing the recalculation, as shown here:
There is only one problem with this approach, of course—the Saved property is only set to True
if the workbook is actually saved. This means that you could recalculate multiple times without
really needing to do so, unless you tie saving and recalculation together, as shown here:
End If
The wisdom of approaching this problem in this manner depends on the nature of your particular
situation. If it takes longer to save the workbook than it does to simply recalculate, then this
approach won't work. If, however, recalculation takes longer (which is very possible with some
types of operations), then this approach may work well.
Normally, circular references represent a mistake in a formula. There are some situations in
which circular references are desirable, however. Excel allows you to include circular references
in a worksheet, but it can get a bit picky about them.
For the most part, Excel is very lenient about circular references if you have the Iteration control
turned on. (Choose Options from the Tools menu and display the Calculation tab or, if you are
using Excel 2007 or a later version, display the Excel Options dialog box and click Formulas.) If
you select the Iteration check box (or the Enable Iterative Calculation check box) and then enter
a circular reference, Excel doesn’t protest. Instead, it uses the settings on the Calculation tab (or
in the Formulas area of the Excel Options dialog box) to control how many times the circular
reference is repeated before it is considered done.
It appears that the setting of the Iteration check box is stored as part of a workbook, but it is not
always paid attention to when the workbook is later loaded into Excel. In fact, the setting is
ignored completely if any of the following occur before you open the workbook:
• You open any other workbook beside the default workbook created when you first start
Excel.
• You change the Iteration check box while the default workbook is displayed.
What Excel does is to examine the Iteration check box setting for whatever workbook you first
open. That setting becomes the “default” for the current session with Excel. For any other
workbook loaded during the same session, the saved setting of the Iteration check box is ignored.
In addition, if you have a Personal.xls workbook defined on your system, then the setting of the
Iteration check box within that file is always used as the default. Why? Because Personal.xls will
always be the first workbook opened, and the first workbook opened always defines the default
for the setting.
If you have a saved workbook that uses circular references, and the Iteration check box is cleared
(either by default or explicitly), then when you open the workbook containing the circular
references, Excel displays a warning. If you don’t want to see the warning, then the obvious
solution is to either make sure that you open the workbook before any other workbook (so that
its Iteration setting is used) or explicitly set the Iteration check box before opening the
workbook.
If you don’t want to bother worrying about which order you open workbooks, and you don’t
want to always go change the setting of the Iteration check box, you can create a macro that
ensures the Iteration check box is selected for the workbook. If you assign the macro to the Open
event for the workbook, then it will run every time the workbook is opened, ensuring that you
won’t see the warning you don’t want to see. The macro appears as follows:
If you have a Personal workbook defined for your system, you can add this macro to it instead of
to individual workbooks. In that way you can ensure that the Iteration check box is always
selected for every Excel session.
Tracking down invalid references can be frustrating. There are several places you can start to
look. The first is in the formulas that are on the worksheets. (Yes, you need to do these steps for
each worksheet in the workbook.) Use the Go To Special dialog box (press F5 and choose
Special) to choose to go to only the cells that contain errors. You can then use the TAB key to
move amongst any cells that Excel selects.
You could also use the Find tool to look for possible errors. Just press CTRL+F to display the
Find tab of the Find and Replace dialog box, then search for the # character. Make sure you tell
Excel to do its searching within Formulas. Inspect anything that is found to see if it is an error or
not.
You should also take a look at any named ranges defined in your workbook. Look at each name
in the Define dialog box, making sure that whatever is in the Refers To box doesn't include any
error indications.
These aren't all the places that there could be errors; Excel is really good at letting errors exist in
lots of places. If you need to search for errors often, you might try a macro that looks through
your formulas for any potential errors.
Sub CheckReferences()
' Check for possible missing or erroneous links in
' formulas and list possible errors in a summary sheet
For i = 1 To Worksheets.Count
If Worksheets(i).Name = "Summary" Then
Worksheets(i).Delete
End If
Next i
iSh = Worksheets.Count
For i = 1 To iSh
sShName = Worksheets(i).Name
Application.Goto Sheets(sShName).Cells(1, 1)
Set rng = Cells.SpecialCells(xlCellTypeFormulas, 23)
For j = 1 To Len(c.Formula)
sChr = Mid(c.Formula, j, 1)
Exit For
End If
Next j
Next c
Next i
' housekeeping
With Application
.ScreenUpdating = True
.DisplayAlerts = True
.Calculation = xlCalculationAutomatic
End With
' tidy up
Sheets("Summary").Select
Columns("A:D").EntireColumn.AutoFit
Range("A1:D1").Font.Bold = True
Range("A2").Select
End Sub
This macro creates a worksheet called "Summary" that is used to list information about any
errors detected in the worksheet links.
You can also use Excel MVP Bill Manville's FindLink program, which does an amazing job of
locating information in links. You could use the add-in to search for the # character in all your
links, which should help you locate the errors. More information on FindLink can be found here:
http://www.oaltd.co.uk/MVP/
You determine the number of open windows by using the Count property of the Windows object.
This is done using the following syntax:
X = Windows.Count
worksheets you use. Wouldn’t it be great to have a way to paste commonly used formulas in a
workbook, the same way you can paste clip art or other common objects?
Unfortunately, such a capability is not resident within Excel. There are a couple of things you
can do to make your formulas more accessible, however. One thing you can do is keep a text
document (a Notepad document) on your desktop, and store your commonly used formulas in it.
With Excel open, you can open the text document, copy the desired formula to the Clipboard,
and quickly paste it in the desired cell of the workbook.
7. In the Names in Workbook box, enter the name you want assigned to this formula.
8. Select whatever is in the Refers To box, at the bottom of the dialog box, and press
CTRL+V. The cell reference is replaced with the formula that was on the Clipboard.
9. Make sure there are no dollar signs in the formula. If there are, select them and delete
them. (This method of using formulas does not work well with absolute references.)
10. Click OK.
The procedure to name formulas is a bit different in Excel 2007 and later versions. If you are
using one of those versions of the program, follow these steps, instead:
8. In the Name box, enter the name you want assigned to this formula.
9. Select whatever is in the Refers To box, at the bottom of the dialog box, and press
CTRL+V. The cell reference is replaced with the formula that was on the Clipboard.
10. Make sure there are no dollar signs in the formula. If there are, select them and delete
them. (This method of using formulas does not work well with absolute references.)
11. Click OK.
Now, whenever you want to use the formula, you simply enter an equal sign and the name you
gave to the formula in step 7 (step 8 in the steps for Excel 2007 and later versions). Even though
the name shows in the cell, the formula assigned to the name is actually used in doing the
calculation. Since the formula used relative references (you got rid of the dollar signs), it is
always relative to where you use the name in the worksheet.
Another approach works great if you are comfortable with macros and with the VB editor. This
approach involves making your common formulas part of your Personal.xls file. This workbook
is opened whenever you start Excel, and is designed primarily for macros and customizations
that you want available whenever you use Excel. But, there is no reason it cannot be used for
common formulas, as well.
Assuming you haven’t yet created a Personal.xls file, follow these steps if you are using a
version of Excel prior to Excel 2007:
3. Using the Store Macro In drop-down list, choose Personal Macro Workbook.
4. Click OK. The Macro Recorder is now running, and the Stop Recording toolbar should
be visible.
5. Select any cell in the worksheet. (It doesn’t matter which one you choose.)
6. Click Stop Recording on the Stop Recording toolbar.
You can record the same macro by following these steps in Excel 2007 or later versions:
The macro you recorded is now stored in your newly created Personal worksheet. To see the
code that you created, open the VB Editor (ALT+F11). In the upper-left corner of the editor is
the Project Explorer; it lists all the various pieces and parts accessible through the editor. One of
the items in the Project Explorer should be PERSONAL.XLS. (The filename extension will be
different if you are using Excel 2007 or later.) If you expand this object (click the small plus sign
to the left of the project name), you should see a Modules folder. Expand the Modules folder,
and it contains Module1. If you double-click on this module you see the macro you just
recorded; it looks something like this:
Sub Macro1()
'
Range("A4").Select
End Sub
You can now select this code and delete it, since you don’t need it any more. You can then place
other macros or user-defined function in the module, so they will be available.
What about formulas? Copy them to the Clipboard and paste them in the module, outside of any
procedures defined therein. All you need to do is make sure you preface the formula with an
apostrophe, so that the VB Editor thinks you are entering a comment. When you need the
formulas at a later time, just go to the VB Editor, open the module, copy the formula, and paste it
into the workbook you need.
At some time you may want to turn track changes off, so that they are no longer noted in the
workbook. If you turn it off, Excel assumes you also want to stop sharing the workbook, so it
automatically turns off sharing. If you want to still continue sharing—without tracking—then
you may wonder what your options are.
Unfortunately, Excel is rather confusing when it comes to sharing a workbook and tracking
changes. The two features are intimately related to each other.
• If you start with a brand new workbook, and then choose to share it (Review | Changes |
Share Workbook in Excel 2007 or later, or Tools | Share Workbook in older versions),
Excel allows others to access and modify the workbook. However, track changes is not
on at this point.
• If you start with a brand new workbook, and then choose to track changes (Review |
Changes | Track Changes | Highlight Changes in Excel 2007 or later, or Tools | Track
Is it any wonder that all this is confusing? The simplest way to turn off track changes and still
have a workbook shared is to turn off track changes, then save the workbook. This saves it in
single-user mode. You can then share the workbook and again save it. Four simple steps (turn off
tracking, save workbook, share workbook, and save workbook) and you are exactly where you
want to be. Remember, however, that if you choose Review | Changes | Track Changes |
Highlight Changes (Excel 2007 or later) or Tools | Track Changes | Highlight Changes (earlier
verions), it will appear that track changes is still turned on. Ignore the check box and click
Cancel; it is not turned on at this point.
The only way to achieve the desired outcome faster is to use a macro. The following macro
automates the steps just discussed:
Sub KeepShared()
Dim sFile As String
Dim sMsg As String
Dim iUsers As Integer
Dim iAnswer As Integer
With ActiveWorkbook
If .MultiUserEditing Then
sFile = .Name
iAnswer = vbYes
iUsers = UBound(.UserStatus)
If iUsers > 1 Then
sMsg = sFile & " is also open by " & _
iUsers - 1 & " other users:"
For x = 2 To iUsers
sMsg = sMsg & vbCrLf & .UserStatus(x, 1)
Next
sMsg = sMsg & vbCrLf & vbCrLf & "Proceed?"
iAnswer = MsgBox(sMsg, vbYesNo)
End If
The macro starts by checking the .MultiUserEditing property to make sure that the workbook is
shared. If it is, then the macro checks to see if the workbook is being used by multiple people at
the present time. If it is, then you are prompted whether you want to continue. If you do (or if
there are not multiple users with the workbook open at the current time), then the workbook is
set for exclusive access (single user) and then saved in shared mode. Setting the workbook for
exclusive access turns off the track changes feature, as well.
Opening a workbook can really slow down a macro; it takes time to access the disk and load the
file. Thus, if your macro can check to see if a workbook is open before going through the hassle
of actually trying to open it, you could speed up your macros greatly if the workbook is found to
already be open.
One very flexible way to approach the task of checking whether a workbook is open is to use a
function that does the checking, and then simply returns a TRUE or FALSE value based on
whether the workbook is open. The following short macro performs this succinct task:
To use the function, just pass it the name of the workbook you want to check, in the following
manner:
sFilename = "MyFileName.xls"
sPath = "C:\MyFolder\MySubFolder\"
If AlreadyOpen(sFilename) Then
'Do not have to open
Else
Workbooks.Open sPath & sFilename
End If
Unfortunately, there isn't such a command. The closest solution (at least if you are using Excel
97 through Excel 2003) is to hold down the SHIFT key as you click the File menu, then choose
Close All. In the process of closing, Excel will ask if you want each workbook saved. The big
drawback to this is that Excel closes and you need to again start Excel and open all your
workbooks.
If you want a true Save All command, you need to create it using a macro. The following is a
good example of one you could use:
Sub SaveAll()
Dim Wkb As Workbook
For Each Wkb In Workbooks
If Not Wkb.ReadOnly And Windows(Wkb.Name).Visible Then
Wkb.Save
End If
Next
End Sub
Save the macro in your Personal workbook, assign it to a toolbar button or a shortcut key, and
you can call it up as often as you like. It saves all the workbooks that are open, except those that
are read-only or hidden.
ActiveWorkbook.Save
If you want to save the workbook to a file with a new name, use the following basic syntax:
ActiveWorkbook.SaveAs FileName:=”filename”
where filename is the full name (including a path) that you want used for the file.
The size of a workbook in Excel can become very large, depending on the information it
contains. Keeping track of the size is important and can be accomplished a couple of different
ways.
If you don't want to use a macro, Excel keeps track of various pieces of information about a file
in the Properties dialog box. How you display the dialog box depends on the version of Excel
you are using. If you are using Excel 2010 or Excel 2013, follow these steps:
1. Click the Office button and then click Prepare | Properties. Excel displays the Document
Properties pane just below the ribbon and above the your worksheet.
2. Click Document Properties and the choose Advanced Properties. Excel displays the
Properties dialog box.
3. Make sure the General tab is displayed.
1. Select the Properties option in the File menu. Excel displays the Properties dialog box.
2. Click on the General tab.
In the General tab, Excel displays the size of the file. You will also see other information about
the file in this tab including the type of file and who created it. Manually obtaining the file size is
simple using this process, but it does not allow you to see the workbook size on the worksheet
itself. Unfortunately, there is no way around it; you will need to use a macro. The following is a
good example of one you could use:
Function wbksize()
myWbk = Application.ThisWorkbook.FullName
wbksize = FileLen(myWbk)
End Function
To use this macro within a worksheet, just type the following in any cell:
=wbksize()
One rather simplistic way to find all your workbooks containing macros is to just look for any
files that use the XLSM or XLSB extensions. (Obviously, this applies only for Excel 2007 or
later versions.) Workbooks that contain macros must be stored in files using these extensions.
While not 100% foolproof, it is a good place to start.
You could also use the search capabilities of Windows (outside of Excel) and search for any file
that contains the text "End Sub" or "End Function". That will quickly identify any potential
candidate workbooks, as any VBA procedure must use one of these two statements at its end.
If you are using legacy workbooks (those developed using Excel 2003's file format), then you
actually need to look inside each of the workbooks. This can be done programmatically, meaning
that you could have a macro that opens each workbook in a folder and examines it to see if there
are any macros within it.
As an example, you could create a macro that steps through each of the files in a directory and
determines if the file is an Excel workbook. It can then open the file and check to see if it has a
VBA project within it.
Sub FindMacros()
Dim sPath As String
Dim sFile As String
Dim sFoundFiles As String
sFile = Dir(sPath)
Do While sFile <> ""
If InStr(sFile, ".xls") > 0 Then
Workbooks.Open (sPath & sFile)
If Workbooks(sFile).HasVBProject Then
This example uses the HasVBProject property (introduced to the Excel object model in Excel
2007) to determine whether the file has any macros or not. When complete, the macro displays a
message box that lists those worksheets containing macros.
When running a macro, however, you may not want to be bothered with a dialog box asking if
you want to save your changes. If the macro modifies a workbook in some way, and you use the
Close method, you are asked if you want to save your changes, just as you are if you manually
close a workbook without first saving.
The way to get around this is to use one of the parameters available with the Close method.
Consider the following:
ActiveDocument.Close SaveChanges:=False
ActiveWorkbook.Close SaveChanges:=True
Both lines of code close the active worbook. The difference between the lines is in the setting of
the SaveChanges parameter. In the case of the first line, any changes will be discarded, while the
second line results in the workbook being saved when it is closed.
This is best accomplished by using a macro to modify the Saved flag in the workbook, just
before closing. This flag indicates, internally, whether a workbook needs saving or not. If the
flag is False, then Excel knows that the workbook has not been saved (changes have been made
without saving). If your macro sets the flag to True, then Excel will close directly because it
thinks that all the changes have been saved.
The macro should be added to the ThisWorkbook object in the VBA Editor. That way, it is
automatically executed just before the workbook is closed. The flag is set to True, and when the
macro ends, Excel continues with its normal closing procedures. Since Excel thinks that there are
no unsaved changes, the user sees no message and the workbook is closed.
It is possible to do this using macros, but you may not really want to do that from a business or
user-oriented perspective. For instance, let's say that a user has three workbooks open on his
system, so that comparisons can be made between them. It is possible to get "tied up" with two
of the workbooks for quite a while, with the third one being the one that triggers a shutdown.
Excel's VBA isn't terribly discriminating—when a workbook is closed, it is typically the one
which has focus at the current time.
Further, what do you do with unsaved changes when closing? If you save them, you run into the
issue that perhaps the user didn't intend to save them. If you don't save them, the converse
problem occurs—perhaps there was a lot of data that needed to be saved. You can't have the
closing procedure ask if information should be saved; that would keep the workbook tied up as
surely as keeping it open (and unused) would.
A possible solution is to simply share the workbook. If you enable sharing (as discussed in other
ExcelTips), then multiple people can have the same workbook open at the same time. If one of
those people leaves it open, then nobody else is inconvenienced because they can still open it
and, optionally, make changes in the workbook.
If you decide to go the macro-based route, then the solution is rather simple. You need some sort
of timer structure (easily implemented through use of the OnTime method) and some way to
check to see if someone is doing something in the workbook.
To start, add the following code to a standard macro module. Note that there are three routines to
be added:
Sub SetTimer()
DownTime = Now + TimeValue("01:00:00")
Application.OnTime EarliestTime:=DownTime, _
Procedure = "ShutDown", Schedule:=True
End Sub
Sub StopTimer()
On Error Resume Next
Application.OnTime EarliestTime:=DownTime, _
Procedure:="ShutDown", Schedule:=False
End Sub
Sub ShutDown()
Application.DisplayAlerts = False
With ThisWorkbook
.Saved = True
.Close
End With
End Sub
These three routines are fairly straightforward. The first two respectively turn on the timer and
turn it off. Note that these routines utilize the DownTime variable, which is declared outside of
any of the routines. In this way its contents can be utilized in multiple routines.
The third routine, ShutDown, is the one that actually closes the workbook. It is only invoked if
the OnTime method expires, at the end of an hour. It closes the workbook without saving any
changes that may have been made.
The next routines (there are four of them) need to be added to the ThisWorkbook object. Open
the VBA Editor and double-click on the ThisWorkbook object in the Project Explorer. In the
code window that Excel opens, place these routines:
The first two routines are triggered when the workbook is opened and when it is closed; they
start the timer and turn it off. The other two routines are executed automatically whenever a
worksheet is recalculated or whenever someone makes a selection in the workbook. Both are
good indicators that someone is using the workbook (it is not inactively open). They stop the
timer and then restart it, so that the one-hour countdown starts over.
There is a downside to using a set of macros such as these: you effectively eliminate Excel's
Undo capability. When a macro is executed, the Undo stack is automatically wiped out by Excel.
Since macros are running with every change made in the workbook, the person's changes cannot
be undone. (There is no way to get around this drawback.)
Another problem—at least for those using Excel 2007 or a later version—is that macros can only
be stored in macro-enabled workbooks. Thus, the macro-based solution will not work for regular
XLSX files, as they don't allow macros within them. In that case you are limited to non-macro
solutions, such as turning on workbook sharing.
Since Excel protects, via password, each workbook on a file-level basis, whenever Bill clicks a
hyperlink he needs to enter the password for the workbook he is trying to access. He wonders if
there is a way to simply enter the password once (it is the same password for all of the
workbooks in his suite) and have access to all the workbooks without the necessity of repeatedly
entering the password.
The short answer is that this cannot be done since Excel treats each file separately. Switch to a
separate file via your hyperlink, and Excel once again asks for the password. There are only two
possible ways to avoid the annoyance. The first is to combine all the separate workbooks into a
single workbook. This may not be an optimal solution, for any number of reasons. (For instance,
you may need to distribute individual workbooks to other users. If you combine all the
workbooks into one, you remove this capability.)
The other solution is to use a macro to handle switching between workbooks, rather than using
hyperlinks. There are many ways that such a macro system could be set up, but one simple way
that mimics the hyperlink method is to create a new worksheet that will act as your “gateway.” In
the cells where you would have added hyperlinks, instead place the full path and filename of
each workbook you want to link to. You should end up with a list of file specifications for your
workbooks.
Now, right-click the sheet tab of this new worksheet. Excel displays a Context menu from which
you should select View Code. This displays the VBA Editor, with the code pane displayed for
the worksheet. Enter the following macro into the code pane:
The only thing you should have to change in the code is the password you want used for the
workbooks you are accessing. (The code assumes that the same password is used for all of the
workbooks.)
Press ALT+Q to exit the VBA Editor, and you are back at your worksheet. Save the workbook,
and then double-click any of the cells containing the path and filenames. What Excel does is to
then pass control to the macro which grabs the path and filename and then opens that workbook.
There are a few ways you can go about doing this. If you simply want to know how many
instances of Excel are running, you can use a macro that makes use of the Windows API. The
following function implements this approach:
Do
'Get the next Excel window
hWndXL = FindWindowEx(GetDesktopWindow, hWndXL, _
"XLMAIN", vbNullString)
This code was developed by Excel MVP Stephen Bullen and can be found at this site:
http://www.officekb.com/Uwe/Forum.aspx/excel-prog/55941
This, obviously, won’t allow you access to the individual instances of Excel; it only returns a
count of the number of instances open. If you want to develop code to use the instances, then you
don’t need to rely upon the Windows API. You can, instead, use code such as the following to
determine if an instance of Excel is open:
If an instance is running you can access it using the xlApp object. If an instance is not running
you will get a run-time error. The GetObject function gets the first instance of Excel that had
been loaded. To get to others, you can close that one and then try GetObject again to get the next
one, etc.
If you want to set the xlApp to a particular instance of Excel, you can do so if you know the
name of an open workbook in that instance:
Unfortunately, there is no way to determine this information from VBA—it just isn’t accessible.
The WriteReservedBy property doesn’t show who has a file open; it shows who saved the
workbook using a password. In other words, when someone saves a workbook with the option to
have a password to modify it, the file is “WriteReserved.” The WriteReservedBy property
contains the name of the person that saved the file in the WriteReserved state.
If you only need to know the answer (about who has the file open) periodically, it is easiest to
gather a list of the open file names, and ask the network admin to tell you who has them open—
such information is maintained on the network and accessible to the admin.
Another potential solution is to add an AutoOpen macro to each workbook that writes a
temporary file to disk that contains the name of the person opening the file. The macro would
need to not only open the temporary file, but handle error conditions, such as a temporary file
that is already open. The temporary file could then be accessed by other macros to see the name
that it contains.
An additional place that may hold an answer is the VBNet site. The article at this page contains
code that may be adaptable for the desired information:
http://vbnet.mvps.org/index.html?code/network/netfileenum.htm
It's very hard to make anything but the most general of comments without actually seeing the
code, but that doesn't mean there isn't anything to say. <g> The first thing you'll want to do is
check to see how your macro is doing its work. For instance, does it step through all the rows of
the worksheet to do its processing? (Even though the macro is processing 500 rows of
information doesn't mean it isn't stepping through every row on the worksheet.) The reason this
might be critical is because earlier versions of Excel only used 65,000+ rows in a worksheet,
whereas the latest versions can handle 16 times as many rows (over 1,000,000). If your macro
was taking 12 seconds to run before, 16 times as long would be 192 seconds, which is between 3
and 4 minutes.
There are also reports that Excel 2010 communicates rather regularly with the printer driver, in a
way not done in previous versions of Excel. You can figure out if this is slowing down your
macro by turning off the communication at the start of your macro:
Application.PrintCommunication = False
At the end of your macro, remember to set the property back to True so that Excel can again
communicate with the printer.
It could also be that there is something different with the newer machine, even if the difference is
subtle. For instance, the newer machine could have different background programs running that
the older machine—anything from an anti-virus program to voice recognition software. In
addition it might have some different Excel add-ins loading that aren't on the earlier system. You
wouldn't think that such things would affect macro performance, but they really could. You
won't know, of course, until you track down what is loading, disable it, and then try running your
macro again.
Also, it could be that the newer machine has some external devices that it needs to poll
periodically which could be slowing things down. I have a system in my office that has an
external hard drive, connected through a USB port. At times, the system itself (Windows) needs
to go out and power-up the external drive, and things essentially stop on the computer while this
happens. Guilty devices could be USB attachments, scanners, network drives, etc. It can be very
frustrating, especially when it slows down something that shouldn't take long at all.
A good suggestion in this instance is to not rely solely on what you find with a single Excel 2010
system. If you have access to several machines—perhaps even some out of the office—try the
macro on them to see what happens. If it runs faster, then you know that there is something
unseen at play on the slow 2010 system.
If you find that everything really is "equal," then you may need to get into the VB Editor on the
system and start doing some timing of the various parts of your macro. This is tedious, but it can
help you narrow down where, exactly, the macro is bogging down.
If you decide to go this latter route, you'll find it worth your while to invest in a good Excel VBA
programmer's reference book. There are several on the market, so shop around a bit. (You can't
go wrong with anything authored by John Walkenbach, and I've heard good things about Excel
2007 VBA Programmer's Reference.)
One tab that is especially necessary for advanced users is the Developer tab. This tab contains a
variety of tools that allow you to "develop" and customize Excel. Of particular importance is the
fact that the Developer tab contains tools that allow you to create and access macros.
The Developer tab is not contextual in nature; it should always be visible on the ribbon. If it is
not visible, that means you have not configured Excel to display it. (The Developer tab is not
visible in a default installation of Excel.) To display the tab if you are using Excel 2007, follow
these steps:
1. Click the Office button and then click Excel Options. Excel displays the Excel Options
dialog box.
2. Make sure that Popular is selected at the left side of the dialog box.
If you are using Excel 2010 or Excel 2013 the steps are different:
5. At the right side of the dialog box make sure a check mark appears in the checkbox to
the left of the Developer option.
6. Click OK.
There are any number of reasons why the options would not be available, but they all boil down
to security settings for the workbook. For instance, when you first open the workbook, you may
be notified that it contains macros and asked if you want to enable them. If you don't enable
them, then the security setting on the workbook is set to a high enough level that the macros (and
doing anything with the macros) are disabled.
If you weren't asked about enabling macros when you opened the workbook, it could be because
of the security level you have set on Excel itself. Check the settings in the Trust Center (click the
Office Button, click Excel Options, click Trust Center, and then click Trust Center Settings) to
make sure that you allow macros.
If you prefer, you can display the Developer tab in Excel and then click the Macro Security tool.
Excel displays the same Trust Center Settings dialog box mentioned in the previous paragraph.
There are four settings for macros; if the “Disable All Macros without Notification” radio button
is selected, then the macro capabilities of Excel will be disabled. You’ll want to select one of the
less stringent settings, probably “Disable All Macros with Notification.” When you restart Excel,
you’ll be asked if you want to enable any macros that may be in the workbook.
1. Choose Customize from the Tools menu. Excel displays the Customize dialog box.
2. Make sure the Toolbars tab is selected.
3. In the list of toolbars, make sure there is a check mark beside the toolbar to which you
want your macro added. The check mark ensures that the toolbar is displayed on the
screen.
5. In the list of Categories, choose the Macros entry. Your macros should then appear in
the Commands list.
6. In the Commands list, select the macro you want assigned to a toolbar.
7. Using the mouse, drag the macro from the Commands list to the location on the toolbar
where you want it to appear.
8. When you drop the macro, it appears on the toolbar.
9. To add more macros, repeat steps 6 through 8.
10. Click on Close.
1. Click the Office button and then click Excel Options. Excel displays the Excel Options
dialog box.
2. At the left side of the dialog box click Customize.
3. Using the Choose Commands From drop-down list, choose Macros. You should see a
list of available macros.
4. In the list of macros, click on the one you want added to the Quick Access Toolbar.
5. Click the Add button. The macro moves to the right side of the dialog box.
6. Click OK.
The steps are slightly different if you are using Excel 2010 or Excel 2013:
The Quick Access Toolbar area of the Excel Options dialog box.
4. Using the Choose Commands From drop-down list, choose Macros. You should see a
list of available macros.
5. In the list of macros, click on the one you want added to the Quick Access Toolbar.
6. Click the Add button. The macro moves to the right side of the dialog box.
7. Click OK.
There are many ways that this can be approached, as one might assume with an environment as
diverse as Excel. One possible solution is to create a routine that simply checks if there are any
visible windows on the screen. If there are, then the toolbar buttons can be enabled; if there
aren’t, then they can be disabled. The following macro will do just that:
Sub CheckButtons()
Dim bOneOpen As Boolean
Dim I As Integer
Dim J As Integer
bOneOpen = False
For I = 1 To Workbooks.Count
For J = 1 To Workbooks(I).Windows.Count
If Workbooks(I).Windows(J).Visible Then bOneOpen = True
Next J
If bOneOpen Then Exit For
Next I
If bln Then
'enable buttons
Else
'disable buttons
End If
End Sub
Notice the two comments near the bottom of the macro. All you need to do is replace those
comments with the appropriate code to enable or disable your toolbar buttons. (The code will
vary, depending on the number and configuration of your buttons.)
This macro can be called either manually, or it can be called from any of the events that are
triggered by window changes, such as those that fire when windows are opened, resized,
minimized, maximized, or restored.
In Excel, you can only change the options on a shortcut menu by using VBA. The exact code you
use depends on whether you are removing items from a shortcut menu or adding them in. The
following code illustrates how to delete an item from a shortcut menu:
Sub Del_Item()
CommandBars("Cell").Controls("Cut").Delete
End Sub
Each shortcut menu is really a member of the CommandBars collection, and can thus be
manipulated in VBA. In this case, a CommandBar by the name of “Cell” is being manipulated.
This just happens to be the command bar for the shortcut menu that appears when you right-click
on a cell in a worksheet. Excel has 47 named shortcut menu CommandBars.
If you want to add a command to a shortcut menu, all you do is use code similar to the following:
Sub Add_Item()
CommandBars("Cell").Controls.Add _
Type:=msoControlButton, Id:=21, _
Before:=1
End Sub
In this instance you are again manipulating the shortcut menu that appears when you right-click
on a cell. You are adding a command that is defined by the Id value setting. In this case, the Id
value is 21, which represents the Cut command. There are scores, if not hundreds, of different
command IDs within Excel.
To aid you in determining both the Id to use for a command, as well as the names of the various
shortcut menu CommandBars, you should refer to the Knowledge Base article 213552. This
article is quite long, and can be found at the following address:
http://support.microsoft.com/?kbid=213552
The article is written for Excel 2000, but is also applicable to later versions of Excel, through
Excel 2003. In Excel 2007 and later versions there are still CommandBars, but it can be hit-and-
miss trying to see what will work with them. In my testing it appears that the CommandBars for
the Context menus (or, if you prefer, shortcut menus) are all available. What can be bothersome
is trying to figure out what the proper command IDs are for the various commands available in
these latest versions of Excel.
Unfortunately, you cannot edit Context menus in the same manner that you can edit other
menus—by using Customize from the Tools menu. Instead, you must manipulate Context menus
through VBA.
If you want to add an item to the Context menu that appears when you right-click on a cell, you
can use the following code:
Sub AddItemToContextMenu()
Dim cmdNew As CommandBarButton
Set cmdNew = CommandBars("cell").Controls.Add
With cmdNew
.Caption = "My Procedure"
.OnAction = "MyProcedure"
.BeginGroup = True
End With
End Sub
All you need to do is set the .Caption property to whatever menu text you want used, and then
change the .OnAction property so that it points to a macro or command you want used. When
you later want to remove the menu option, you can use the following macro:
Sub RemoveContextMenuItem()
To use this, change the “My Procedure” text to whatever text you used in the .Caption property
of the previous macro. The On Error statement is used in this macro just in case the specified
macro item had not been previously added.
By modifying your macro just a bit, you can specify that the addition to the Context menu should
occur only when right-clicking on specific cells. The following macro checks to see if you are
clicking on a cell in the range of C10:E25. If you are, then it adds a menu option for your
procedure at the end of the Context menu.
In the VBA editor, this macro needs to be added to the specific worksheet that you want it used
with. All you need to do is double-click on that worksheet, in the Project Explorer (upper-left
corner of the VBA editor), and then enter it into the code window for that worksheet.
As with the earlier macro, all you need to do is modify the settings specified for the .Caption and
.OnAction properties. In addition, you may want to change the cell range that is considered
“valid” when adding a menu choice—just change the “c:10:e25” range specification to the range
desired. You can even use a named range in place of the cell range, which is great if your valid
range is really a set of non-contiguous cells.
never uses the Create List option, Don would love to get rid of it completely, so that only the
Copy command is initiated by the C shortcut key.
The only way to change the Context menus is through a macro. The code to perform such a
change is identical to the code used for other menus or command bars. There is a huge discussion
(much too big for this tip) on how to make these types of changes in the Microsoft Knowledge
Base, at this address:
http://support.microsoft.com/?kbid=830502
Additional information, specific to Context menus (what Microsoft confusingly calls Shortcut
menus), can be found in this Knowledge Base article:
http://support.microsoft.com/?kbid=213209
While there is a wealth of information in these two articles, the actual code to modify the
Context menu that appears when you right-click a cell is relatively simple. In fact, it can be
boiled down to a single-line macro:
Sub RemoveCreateList()
Application.CommandBars("Cell").Controls("Create List...").Delete
End Sub
Run the macro, and the Create List item is gone. You could remove any other item from the
menu by simply replacing the "Create List..." text with the exact wording of the menu item you
want to remove. When you later want to restore the menu, you run another single-line macro to
reset it:
Sub ResetMenu()
Application.CommandBars("Cell").Reset
End Sub
It is possible that the Context menu that is displayed when you right-click on a cell has, over
time, become modified in some way. You can reset this particular Context menu by using a very
simple macro:
Sub ResetContextMenu()
CommandBars("Cell").Reset
End Sub
If this does not do the trick, or if the menu inexplicably changes at some future point, the culprit
could very well be some sort of add-in or a particular macro in a workbook. Determining which
of the macros is doing the modification can take some serious detective work. If you can access
the macros in the add-in or workbook, you might want to just search through the VBA code to
see if you can find the text “CommandBars” to see what is being changed.
If you cannot access the macro code (perhaps the add-in or workbook is protected), then you will
need to go through a trial-and-error process where you stop the loading of the add-ins, one by
one, until you determine which one causes a change in the Context menu.
You might think that you could use Paste Special to perform the task, but that doesn’t work. If
you copy the original cells and then use Edit | Paste Special | Comments, then only the comments
are pasted to the target cells. They are still comments, and not text in cells, which goes against
Hector’s goal.
The only way to handle this type of extraction is to use a macro. The following, when run on a
selection of cells, will extract the comments, move the comment text, and then delete the original
comment.
Sub CommentsToCells()
Dim rCell As Excel.Range
Dim rData As Excel.Range
Dim sComment As String
The macro uses the iColOffset constant to specify how many cells to the right a comment’s text
should be moved. In this case, the offset (78) is equal to three “alphabets” (26 * 3), so the text of
a comment originally in column C will end up in column CC.
If you need to simply copy the comments a single time, then doing so manually may be the best
bet. You can display the Comments field, select whatever contents you want to put into your
worksheet, and then press CTRL+C. Close the properties, select the desired cell, and then press
CTRL+V.
If you have more of a need for the inclusion to be dynamic, then the only way to add those
comments to a cell is to use a macro. If you want to have the contents appear in a specific cell
(such as A1), then you can simply use a single line of code:
Range("A1")=ActiveWorkbook.BuiltinDocumentProperties("comments")
That's it; a single line of code to stuff the comments into the cell. You can build upon this, if
desired, to create a user-defined function that is helpful for placing the comments anywhere you
desire.
In order to use this user-defined function, simply use the following in a cell:
=mySheetComment()
The only way to do this is with the aid of a macro. The reason is that you cannot manually select
and copy any graphic that has been stored in the background of a comment. You can, in a macro,
approximate “grabbing” the image:
Sub CommentPictures()
Dim cmt As Comment
Dim rCell As Range
Dim bVisible As Boolean
The macro steps through each comment in the active worksheet. The entire comment (including
the background) is copied as a graphic to the Clipboard, then it is pasted into the desired cell.
The background of the comment is then set to a different fill instead of the graphic.
You should note that this approach provides only an approximation of grabbing the background
picture. It also, in copying the entire comment as a graphic, copies any text that is contained in
the comment.
If you want a larger font used for your comment, you can change the character formatting of
anything within the comment itself. For versions of Excel prior to Excel 2007, select the text and
use the controls on the Formatting toolbar (or the Home tab in Excel 2007 and later versions), or
right-click the comment itself and choose Format Comment from the Context menu.
Excel does not provide a way for you to change the default font specifications for comment
balloons, across the board. You can change them individually (as already mentioned), but not
universally within Excel. You can make a change in Windows that will affect the comment font,
however. Follow these general instructions:
4. In some versions of Windows you should click the Advanced button. Windows displays
the Advanced Appearance dialog box.
In case you didn’t figure it out from these steps, the font used in the comment balloons of Excel
is determined by the ToolTip font used by Windows. Changing the ToolTip font changes the
comment balloons, but it also changes things in other applications besides Excel, as well. (Of
course, if you think the font is too small in the comment balloons, you also probably think the
font is too small everywhere else the ToolTip font is used.)
If you prefer to not change the ToolTip font, you can create an Excel macro that will step
through all the comments in a worksheet to make changes.
Sub FixComments()
Dim cmt As Comment
This macro changes the font to 12-point Arial for all comments. You could easily change the
font and font size by making a change to the appropriate two lines of the macro.
As far as we can tell, there is no way to copy comments using advanced filtering; only the cell
contents are copied. However, it is possible to easily copy the comments using a two-step
process.
First, filter your data, but make sure you do the filtering in-place; don’t specify that you want the
information copied to a different location. You end up with a filtered list, showing only the cells
that meet your criteria. Next, select the cells returned by the filtering. You should then make sure
that Excel knows you only want the visible cells selected:
4. Click on OK.
With the visible cells selected (those hidden by the filtering are not selected), you are ready for
the second step: Copy the cells to another location using normal editing techniques. The result is
that the comments are copied right along with the cell contents.
If you perform this task quite a bit and it even bugs you to do the two steps, you could automate
the task. The following macro will apply a filter in-place, copy the visible cells to the Clipboard,
and then paste them (and their comments) into a new workbook:
Sub AdvancedFilter_AndCopyComments()
With Range("Database")
' filter the data range
.AdvancedFilter Action:=xlFilterInPlace, _
CriteriaRange:=Range("Criteria"), Unique:=False
' copy visible cells only
.SpecialCells(xlCellTypeVisible).Copy
End With
Application.CutCopyMode = False
End Sub
The macro assumes you have two named ranges set up: one for the data to be filtered (Database)
and the other for the filtering criteria (Criteria). Run the macro, and the filtered, commented
information ends up on Sheet1.
The short answer is that there is no way to insert multiple comments at the same time. You can,
however, copy a comment to multiple cells. Follow these steps:
The result is that only the comment from the cell in step 2 is pasted to the cells you selected in
step 3. If any of those cells already had comments, those comments are replaced with the one
you are pasting.
If you really want to add the comment to all the cells at the same time, then the only way to do it
is through a macro. The following example will prompt you for a comment and then add the
comment to all the cells you’ve selected.
Sub InsertCommentsSelection()
Dim sCmt As String
Dim rCell As Range
sCmt = InputBox( _
Prompt:="Enter Comment to Add" & vbCrLf & _
"Comment will be added to all cells in Selection", _
Title:="Comment to Add")
If sCmt = "" Then
MsgBox "No comment added"
Else
For Each rCell In Selection
With rCell
.ClearComments
.AddComment
.Comment.Text Text:=sCmt
End With
Next
End If
Set rCell = Nothing
End Sub
You can, however, use a workaround—create your own comments. You can do this using a text
box to contain your comment, and then draw lines between the text box and whatever cells the
comment applies to. If you normally want your comments hidden, then you will need to use a
macro that takes care of making the text box and lines visible or invisible.
For instance, assume that you create a comment in a text box named Text Box 1. Further, assume
that you have two lines leading from the text box to the cells to which the comment applies. The
first line, named Line 1, leads to cell C15. The second line, named Line 2, leads to cell F7. You
could add the following macro to the worksheet’s object:
Anytime a selection is made on the worksheet, the three objects are hidden. If cell C15 is
selected, the textbox and the line appropriate line are made visible. Similarly, if cell F7 is
selected, the textbox and its line are made visible.
several screens away. Peggy is wondering if there is a way to make the comment appear next to
the cell it goes with.
This condition can be caused by several things. For instance, it is possible that while previously
editing the comment, you clicked the comment box’s border and dragged the comment to a
different place on the worksheet. If you did this, then Excel remembers where the comment was
moved to and always displays it in the remembered location.
Another common cause is that you do some filtering on your data, which results in some of the
rows or columns being hidden while the filter is in place. If you then edit comments in the
filtered cells, you have effectively “moved” the comment from the original location to a new
location that is associated with the row or column visible on the screen. When you later remove
the filter and try to edit the comment, it remembers where it was previously edited, and that is
where the new editing opportunity takes place.
In both of these instances, the normal solution is to just grin and bear it—manually move the
cells from where they are to where you want them. However, if you have this problem with a lot
of cells, all the manual moving can be a real bother. In that case, you may want to use a macro to
do the moving for you.
Sub MoveComments()
Dim cmt As Comment
For Each cmt In ActiveSheet.Comments
With cmt
.Shape.Top = .Parent.Top
.Shape.Left = .Parent.Offset(0, 1).Left
End With
Next
End Sub
This macro moves all the comments in a worksheet so that their upper-left corner is the same as
the upper-right corner of the cell to which they are attached. This puts the comments right next to
their cells, which is where you want them.
There is no way to do this without using a macro. The regular Find and Replace capabilities in
Excel don’t allow you to find text within comments, but you can use macro commands. The
following is a simple macro to do the replacing:
Sub ReplaceComments()
Dim cmt As Comment
The key lines here are those that set the sFind and sReplace variables. You should set those to
reflect what you are searching for and what you want it replaced with, respectively. The macro
steps through each comment in each worksheet of the current workbook and makes the changes
anywhere they are located.
Sub MakeComment()
With Worksheets(1).Range("C1").AddComment
.Visible = True
.Text "Total of cell A1 plus cell B1 is equal to " & _
([A1].Value) + ([B1].Value)
End With
End Sub
If you’d rather run the macro on a range of cells, then a different approach is necessary. The
following macro loops thru all the cells in a selection. If the cell contains a formula, the macro
puts the value (the formula’s result) in a comment attached to that cell.
Sub ValueToComment()
Dim rCell As Range
For Each rCell In Selection
With rCell
If .HasFormula Then
On Error Resume Next
.Comment.Delete
On Error GoTo 0
.AddComment
.Comment.Text Text:=CStr(rCell.Value)
End If
End With
Next
Set rCell = Nothing
End Sub
While looping through the cells in the selection, if one of the cells has a formula and an existing
comment, then the comment is deleted and replaced with the new comment that contains the
formula result. Afterwards the cell's value will display as well as a comment with the same
number. Instead of CStr you could also use Format function to display the value in any way you
might want.
You can also create a macro that will modify a comment whenever you update the contents of a
particular cell. For instance, let’s say that every time someone made a change in cell C11, you
wanted the result of whatever is in that cell to be placed into a comment attached to cell F15. The
following macro does just that:
With Range("F15")
.ClearComments
.AddComment
.Comment.Text Text:=sResult
End With
Application.EnableEvents = True
Application.ScreenUpdating = True
End If
End Sub
When someone enters a formula (or a value) into cell C11, the results of that formula (or the
value itself) is placed into a comment that is attached to cell F15. Since this is an event-triggered
macro, it needs to be entered in the code window for the worksheet on which it will function.
Finally, you may want to have your macro monitor an entire column. The following macro uses
the Change event of a worksheet, just like the previous macro. It, however, only kicks into action
if the change was made in column F, and only if a single cell in that column was changed.
Dim x As String
Application.EnableEvents = False
If Target.HasFormula Then
x = Evaluate(Target.Formula)
Else
x = Target.Text
End If
Target.ClearComments
If Target.Text = "" Then
Application.EnableEvents = True
Exit Sub
End If
Target.AddComment x
Target = ""
Application.EnableEvents = True
End Sub
If the user makes a change to a single cell in column F, the macro grabs the result of what was
entered and places it in a comment attached to that cell. The contents of the cell are then deleted.
If you want to get the number of comments and place it into a cell, then you need to use a macro
to create a user-defined function.
This function grabs the value of the Count property for the Comments collection. It is then
returned by the function to the worksheet. To use it in your worksheet, enter a formula such as
the following:
=CountComments(A1)
The cell address you use in the formula is unimportant; it should simply reference a cell on the
worksheet for which you want the count.
Notice that these steps require the use of named ranges. You could have a named range for each
portion of the worksheet that you want to print, and a named range (which would be a single
cell) that represents the header or footer information that you want for each print area. The
following macro will implement the above steps:
Sub PrintRegions()
Dim x As Integer
'Fill this array with the names of the ranges to be in the header
Head(1) = "NorthHead"
Head(2) = "SouthHead"
Head(3) = "EastHead"
Head(4) = "WestHead"
For x = 1 To UBound(Region)
ActiveSheet.PageSetup.PrintArea = Range(Region(x)).Address
ActiveSheet.PageSetup.LeftHeader = Range(Head(x)).Value
ActiveWindow.SelectedSheets.PrintOut Copies:=1
Next
End Sub
This example prints out only four areas of a worksheet. These areas are named ranges: North,
South, East, and West. Similarly, the named ranges—which are really single cells—used for the
left portion of the headers are NorthHead, SouthHead, EastHead, and WestHead.
Sub DoPath()
' Inserts the file name and path in the footer
' of each worksheet in the active workbook
For Each sheet In ActiveWorkbook.Sheets
sheet.PageSetup.CenterFooter = ActiveWorkbook.FullName
Next sheet
End Sub
To use this, simply run it and it adds the full path and file name for your spreadsheet file into the
center section of the footer. It does this for every worksheet in your workbook. If you want the
information added to a different place in the footer or header, you simply replace the
CenterFooter portion of the macro with one of the following: LeftFooter, RightFooter,
LeftHeader, CenterHeader, or RightHeader.
As noted, the above macro changes the header or footer for each worksheet in your workbook. If
you only want to change the current worksheet, you can use the following abbreviated version of
the macro:
Sub DoOnePath()
' Inserts the file name and path in the footer
' of the active worksheet
ActiveSheet.PageSetup.CenterFooter = ActiveWorkbook.FullName
End Sub
You should note that unlike other items you stick in the header or footer, the path and file name
inserted by these macros are not “dynamic.” Thus, if you move the spreadsheet file to a different
directory or save it under a different name, you need to run the macro again.
While the above solutions work just fine in all versions of Excel, if you are using Excel 2002 you
should know that there is an even easier way to add the path name to the header or footer.
Microsoft finally heard the requests of users, and added a button to the Header and Footer dialog
boxes that allows you to insert both the path and file name of a workbook. Follow these steps:
Layout tab of the ribbon and click the icon at the lower-right corner of the Page Setup
group.)
3. Click on either the Custom Header or Custom Footer buttons, as desired. Excel displays
either the Header or Footer dialog box.
4. Position the insertion point in the Left Section, Center Section, or Right Section boxes,
as desired.
5. Click on the File button. (It looks like a file folder with a piece of paper sticking out.)
Excel inserts the following code at the insertion point:
&[Path]&[File]
When you print the worksheet, Excel replaces the codes with the path name and the file
name of the workbook, respectively.
6. Click on OK two times to close both dialog boxes.
Unlike the macro solution provided earlier in this tip, the new header and footer codes provided
in Excel 2002 are dynamic. If you rename or relocate your workbook file, the information in the
header or footer will change the next time you print.
To insert the file path and filename into a cell, you use the CELL worksheet function in the
following manner:
=CELL("filename")
This formula returns the entire path, filename, and tab name of the current worksheet, like this:
E:\My Data\Excel\[Budget.xls]Sheet1
If you want to strip out the brackets and the sheet name, you can use the following variation on
the formula:
=SUBSTITUTE(LEFT(CELL("filename"),FIND("]",CELL("filename"))-1),"[","")
The LEFT function gets rid of everything from the right bracket to the end of the string, while
the SUBSTITUTE function gets rid of the left bracket.
4. Position the insertion point in the Left Section, Center Section, or Right Section boxes,
as desired.
5. Click on the File button. (It looks like a file folder with a piece of paper sticking out.)
Excel inserts the following code at the insertion point:
&[Path]&[File]
When you print the worksheet, Excel replaces the codes in step 5 with the path name and the file
name of the workbook, respectively.
If you are using Excel 97 or Excel 2000, then the above steps won’t work. Instead, you need to
use a macro to insert the path and filename:
Sub DoFullPath()
ActiveSheet.PageSetup.CenterFooter = _
ActiveWorkbook.FullName
End Sub
This macro will also work in later versions of Excel. To specify a different place for the path and
filename, simply change CenterFooter to another location (such as LeftFooter, RightFooter,
LeftHeader, CenterHeader, or RightHeader). If you decide to use the macro approach, you will
need to remember to run it every time that you change either the workbook’s filename (you use
Save As), or you change the place where the workbook is stored on your disk.
There are several different ways, depending on whether you want to use a macro or not.
If your filenames are all the same length, then you can simply use the RIGHT function to pull
out the last characters. (This formula assumes the full path and file name is in cell A1.)
=RIGHT(A1,11)
This assumes that the filename is always 11 characters long, such as "text001.txt". If the
filename is a different length in each instance, then this approach won't work. Instead, you can
try this formula:
=MID(A1,FIND(CHAR(1),SUBSTITUTE(A1,"\",CHAR(1),
LEN(A1)-LEN(SUBSTITUTE(A1,"\",""))))+1,LEN(A1))
Note that the formula uses the SUBSTITUTE function twice. In each case it replaces the
backslashes (\) with something else. The first time it replaces all of them with an ASCII value of
1 and the second it replaces them with nothing (an empty string) so that it can determine how
many backslashes were in the original path. The MID function is used to locate (with the help of
FIND and the SUBSTITUTE functions) the location of the last backslash in the path and return
everything after that point.
A shorter formula can be used if you are sure that the filename will never be more than 99
characters long:
=TRIM(RIGHT(SUBSTITUTE(A2,"\",REPT(" ",100)),99))
This formula replaces all the backslashes with 100 spaces, grabs the right-most 99 characters
from the resulting string (that would be the filename with a bunch of spaces in front of it) and
then trims off all the spaces.
If you want to use a macro you can create one that steps backward through the path until it
locates the last backslash. It then returns everything after the backslash. The following example
starts in cell B1, examining everything to the right of the cell (cell A1) and then starts pulling out
file names. It steps through all the cells in column A and puts the file name, if any, in column B.
Sub GetFileName1()
Dim Delimiter As String
Dim Target As String
Dim sFile As String
Dim J As Integer
Dim iDataLen As Integer
Delimiter = "\"
Range("B1").Select
Do While ActiveCell.Offset(0, -1).Value <> ""
Target = ActiveCell.Offset(0, -1).Value
iDataLen = Len(Target)
sFile = "Delimiter Not Found"
For J = iDataLen To 2 Step -1
If Mid(Target, J, 1) = Delimiter Then
sFile = Right(Target, iDataLen - J)
Exit For
End If
Next J
ActiveCell.Formula = sFile
ActiveCell.Offset(1, 0).Select
Loop
End Sub
You could also use a much shorter version of a macro, provided you can use the Split function.
This function was introduced in the version of VBA provided with Excel 2000, and it will pull a
string apart based upon a delimiter you specify and stuff the parts into an array. This example
shows the solution implemented as a user-defined function.
In this usage the Split function uses as a delimiter whatever path separator is appropriate for the
system on which Excel is running. The last element of the resulting array (determined with the
UBound function) contains the portion of the original path that is to the right of the last path
separator—the file name. To use the function, put a formula like this in a cell:
=GetFileName2(A1)
In Excel there is no native way to do this. It is a relative snap to do in Word, however, so one
solution is to paste the sorted names into a Word document and then add the desired header that
shows the names. While this can work, it becomes a pain to make sure that the Word version of
the list is always in sync with the Excel version of the list, and vice-versa.
If you decide you want to keep a single version of the voter list in Excel, the best way to
approach the problem is to use a macro to insert the first and last names in the header. The code
of such a macro, obviously, would need to be tailored to the layout of the data in your worksheet.
The following macro assumes that the names are in columns A through C, with the last names
(the ones you want to use for the headers) are in column C.
Sub PrintNamesInHeader()
Dim iPages As Integer
Dim iPage As Integer
Dim iHorPgs As Integer
Dim iHP As Integer
Dim iHPNext As Integer
Dim iCol As Integer
Dim iColLast As Integer
Dim lRow As Long
Dim lRowLast As Long
Dim sPrtArea As String
iCol = 1 'Col A
iColLast = 3 'Col C
With ActiveSheet
iPages = ExecuteExcel4Macro("Get.Document(50)")
iHorPgs = .HPageBreaks.Count + 1
sPrtArea = .PageSetup.PrintArea
When you run the macro, it steps through each page of the worksheet. The headers are set for the
page, then the single page is printed, and then the next page is examined and processed.
simply including the code Chr(13) into the string you use to set the header or footer. When used
with the previously discussed command codes, this is very powerful, indeed.
For instance, let’s say you wanted a centered header that included your company name on the
first line with today’s date on the second. You would use the following code:
Note the use of the &B command code to make the company name bold. The second use is to
turn the bold attribute off, so that the date does not end up being bold. Also, note the use of the
&C command code. Remember that the use of positioning command codes such as these
overrides placement. Thus, the following code (which would normally place the information at
the left side of the header) has the exact same results as the previous code:
That’s not nearly as easy. In fact, you can’t do it without using a macro. Perhaps the most
flexible approach is to write the macro so that it updates the date just before the worksheet is
printed, as shown in the following:
The macro places yesterday’s date into the center of the header; you can easily change the
CenterHeader property of one of the other available header locations (LeftHeader or
RightHeader). You can also change the macro to insert tomorrow’s date by changing the “- 1” to
“+ 1”.
Sub MyFooter()
Dim mh As String
On Error Resume Next
mh = ActiveWorkbook.BuiltinDocumentProperties("Last Save Time")
If Err = 440 Then
Err = 0
mh = ActiveWorkbook.BuiltinDocumentProperties("Creation Date")
If Err = 440 Then
Err = 0
mh = "Not Set"
End If
End If
mh = Left(mh, 8)
ActiveSheet.PageSetup.LeftFooter = "Saved: " & mh
End Sub
There are a number of items to note in this macro. First of all, it attempts to determine the last
date (and time) that the workbook was saved. If that information cannot be determined, then it
extracts the date it was created. Finally, if that cannot be found, then it sets the footer to “Not
Saved.”
Notice that there is some error handling done in this macro. The reason is that Excel will return
an error if a particular document property (BuiltinDocumentProperty in this case) is not set. The
error needs to be intercepted and handled, which is done here.
You should note that this macro, once run, will set the left footer to the desired information. That
information will not change again until you run the macro again. Thus, if you always want an up-
to-date date in the footer, then you should either run the macro periodically (perhaps right before
printing), or set it up to run whenever you open your document.
There is no actual formula that can put the last-saved date in a footer. Excel has no way (unlike
Word) to put this tidbit of information there. There is a way you can do it, but the solution
requires the use of a macro. The reason is because you are accessing system information—
information outside of Excel itself—and that information can only be retrieved using a
programming language such as VBA.
One approach is to add some code that runs whenever a workbook is saved. The code would
update the desired footer with the current date:
ActiveWorksheet.PageSetup.RightFooter = _
"Last Saved: " & Format(Date, "mmmm d, yyyy")
End Sub
This macro, which should be stored in the ThisWorkbook object for the workbook you want to
affect, updates the footer for the currently active worksheet. If you want to affect all the
worksheets in a workbook, then a small change to the macro is in order:
If today is December 12, 2011, then after running the macro (which is done automatically when
saving), the right footers will all be set to “Last Saved: December 12, 2011”.
You can also rely upon the file save date stored in Excel's built-in properties. The way you put
that date into the footer is as follows:
Sub RightFooterLastSaved()
ActiveSheet.PageSetup.RightFooter = _
ActiveWorkbook.BuiltinDocumentProperties(12)
End Sub
The drawback to this macro is that you need to remember to run it periodically, so it is not quite
as automatic as the previous approaches. You could, however, place the single line at the heart of
the macro into the Workbook_BeforePrint event handler.
There is another approach you can use. This one involves requesting from Windows the actual
date and time a file was saved.
sTemp = FileDateTime(ActiveWorkbook.FullName)
sTemp = "Last Saved: " & sTemp
For Each sht In Sheets
sht.PageSetup.RightFooter = sTemp
Next sht
End Sub
This macro is designed to run whenever a workbook is first opened—it is saved as the
Workbook_Open procedure of the ThisWorkbook object. The workhorse of the macro is the line
that calls the FileDateTime function. This function can be used to determine the date and time
any file was saved. It requires a full path name of a file, which is supplied by the FullName
property of the ActiveWorkbook object. This date and time is then placed in the right footer of
all the worksheets in the workbook.
Remember, as well, that the limit of what you can place into each section of the header or footer
is approximately 250 characters. So if you adjust the macros to add more information to the right
portion of the footer, make sure that it doesn't add up to that many characters, or you may have
problems with the macro.
This can be a drawback if you are required to maintain a certain type of system date format for
compatibility with other systems in your office, but you need to use a different date format in the
header or footer of a specific worksheet. The only way around this problem is to either change
the regional settings within Windows, or revert to using a macro to set the appropriate area of
your header or footer.
For instance, let’s say you wanted to set the right header equal to the current date in the format
m/d/yy. To do that, you can use a very simple macro, such as the following:
Sub HeaderDate()
ActiveSheet.PageSetup.RightHeader = Format(Date, "m/d/yy")
End Sub
To use this, simply run it and it adds the date, in the specified format, into the right section of the
header. If you want the information added to a different place in the footer or header, you simply
replace the RightHeader portion of the macro with one of the following: LeftFooter,
CenterFooter, RightFooter, LeftHeader, or CenterHeader.
To change the format in which the date is added, simply change the format used in the Format
function. There are all sorts of patterns you can use for the date; check the online Help system
for information about the Format function in VBA.
You should note that dates added to headers or footers in this manner are not dynamic, as is the
result of the [DATE] code. When you use the macro to insert the date, it is inserted as a text
string. If you later want to change the date to something else (like the then-current date), you will
need to rerun the macro.
There is no intrinsic method in Excel to handle this situation. There are some workarounds; for
instance, you could put your first page on one worksheet (without headers or footers) and the
subsequent pages on a different worksheet (which includes headers and footers). You could then
print the two worksheets in one pass, and effectively achieve your goal.
If you have the Report Manager installed, you could use it to put together different reports based
on the information in your worksheet. Using the Report Manager has been covered in other
issues of ExcelTips. The Report Manager add-in was last distributed with Excel 2002, but you
can still use it in Excel 2003. This Knowledge Base article explains how you can use it:
http://support.microsoft.com/?kbid=873209
The Report Manager isn't a viable option for later versions of Excel and neither of these
approaches work for all situations, however. For instance, you may not be able to split your
printout into multiple worksheets, or you may not have much experience with the Report
Manager (or you don't want to download and install it). If you prefer, you can create a macro
which will print your worksheet as you desire.
The following macro, GoodPrint, will print the first page of a worksheet without headers or
footers, and then all subsequent pages as normal.
Sub GoodPrint()
Dim hlft As String
Dim hctr As String
Dim hrgt As String
Dim flft As String
Dim fctr As String
Dim frgt As String
Yes, there is a way to do this, but it involves the use of macros. Before considering a macro-
based solution, you may want to consider restructuring your data so that each of your sections
are on different worksheets. (From a design perspective, this would be the easiest solution.) If
this is not possible, then you need to be looking at macros.
One easy approach is to simply change what is stored in the top row (row 1) of your worksheet,
depending on what row is selected. For instance, the following macro will make changes in the
top row based on where the active cell is located. If it is before row 40, then one set of headers
are stuffed into the first row; if after row 40 then another set of headers are stuffed.
iBottomData = 40
End If
End Sub
To use the macro, just make sure that you place it in the code window for the worksheet that
contains the two data sections. You should also change the value assigned to the iBottomData
variable to reflect the row number of where your bottom data section starts.
If you want to actually change the frozen row as you move down the worksheet, then the macro
needs to be a bit more robust. Actually, there are two macros that follow (both go, again, in the
code window for the worksheet), and they are kicked into action as you change the selected cell
and as you right-click on the worksheet.
The Worksheet_SelectionChange event handler automatically moves the frozen split to below
the second row of headings when your active cell cursor hits that line. This line is detected in the
If statement that checks if the first cell in the row contains the text “title2” or not. (Obviouly, this
should be changed to reflect what will really be in that first cell.)
The Worksheet_BeforeRightClick event handler moves the frozen split back to the first set of
headings but leaves the active cell at the row above the second set of headings.
You should understand that both of the macro solutions presented in this tip assume that you are
actually scrolling through the worksheet and changing the selected cell as you go. (In other
words, you are pressing the Down Arrow key to do your scrolling.) If you are simply changing
what is displayed in the worksheet by using the vertical scroll bar, then the frozen headings
won’t change because you are not changing the selected cell and the event handlers never trigger.
Creating a more extensive solution would be beyond the scope of this tip because it would
involve interfacing with the actual operating system. If you are interested in going this route,
however, a good starting place might be this page at Chip Pearson’s Web site:
http://www.cpearson.com/excel/DetectScroll.htm
The macro is run every time Excel does its normal recalculation—meaning every time the
contents of any cell changes or someone presses F9. If you want the contents to be in a different
part of the footer, you can change LeftFooter to CenterFooter, or RightFooter.
To apply any formatting to the footer other than the default you will need to add special
formatting codes, and you can also use special data codes that Excel recognizes for headers and
footers. Both the special formatting and special data codes are quite lengthy and have been
covered in other issues of ExcelTips.
If you are working with a very large worksheet, then changing the footer every time Excel
recalculates may unnecessarily slow down your computer. After all, the footer remains invisible
to the user until such time as the worksheet is actually printed. In this case, you simply need to
rename the above macro to some other name that you would then manually execute as the last
step before printing a worksheet.
If the header and footer is one you use quite a bit in new workbooks, and your main concern is to
have the header and footer available in those new workbooks (not in existing workbooks), then
the best approach would be to create a template workbook. Just set up a workbook as desired,
including the specification of headers and footers. Then, save the workbook as an Excel template
(XLT format). You can then create your workbooks based on this template and it will have the
headers and footers you desire.
One way to copy headers and footers from a worksheet in one workbook to a worksheet in
another is to use the traditional editing methods of copying and pasting. In other words, you can
select the header material you want to copy, press CTRL+C, display the header in the target
worksheet, and then press CTRL+V. The drawback to this approach is that it can involve quite a
few steps. After all, there are three sections (left, center, and right) for each header and three for
each footer. This means that you must do six copy and paste operations to copy the complete
header and footer.
Another way to copy headers and footers from one workbook to another involves the use of
native Excel commands to make copies of worksheets. Follow these steps:
1. Open the target workbook; the one to which you want the headers and footers copied.
2. Open the workbook that is the source of your header and footer, and make sure the
desired worksheet is displayed.
3. Right-click the worksheet tab and choose Move or Copy Sheet from the resulting
Context menu. Excel displays the Move or Copy dialog box.
4. Using the To Book drop-down list, select the target workbook you opened in step 1.
5. Using the Before Sheet area, indicate where you want the sheet copied.
6. Make sure the Create a Copy check box is selected.
7. Click on OK. The worksheet is copied to the target workbook.
8. Close the source workbook from step 2.
9. In the target workbook, display the worksheet you just copied.
10. In the tab area at the bottom of the window, right-click and choose Select All Sheets.
All the worksheets are now selected.
11. Display the Page Setup dialog box. (In Excel 2007 or later display the Page Layout tab
of the ribbon and click the small icon at the lower-right corner of the Page Setup group.
In older versions of Excel choose Page Setup from the File menu.)
12. Make sure the Header/Footer tab is selected.
13. Using the Header and Footer drop-down lists, select the header and footer used in the
worksheet you just copied.
14. Click on OK.
15. Delete the worksheet you copied in steps 1 through 7.
What you essentially did is to copy the worksheet containing the header and footer you desired,
then you copied that header and footer to other worksheets in the workbook, then you deleted the
original worksheet.
While these steps work fine, they can be tedious if you need to copy headers and footers to a
number of different workbooks. In this case, using a macro to do the copying is the saner
approach. The following two macros can be used to copy headers and footers in one simple step.
All you need to do is display the source worksheet and use the GetHeaders macro. This macro
copies the header and footer information to string variables. You can then display, in turn, each
worksheet that you want to have the same header and footer and run the DoHeaders macro.
Option Explicit
Sub GetHeaders()
With ActiveSheet.PageSetup
strHeadLeft = .LeftHeader
strHeadCenter = .CenterHeader
strHeadRight = .RightHeader
strFootLeft = .LeftFooter
strFootCenter = .CenterFooter
strFootRight = .RightFooter
bGotHeaders = True
End With
End Sub
Sub DoHeaders()
If bGotHeaders Then
With ActiveSheet.PageSetup
.LeftHeader = strHeadLeft
.CenterHeader = strHeadCenter
.RightHeader = strHeadRight
.LeftFooter = strFootLeft
.CenterFooter = strFootCenter
.RightFooter = strFootRight
End With
Else
MsgBox "Select the sheet with the " _
& "headers you want to copy," _
& vbCrLf & "then run 'GetHeaders'", _
vbExclamation, "No Headers In Memory"
End If
End Sub
You could even assign the macros to toolbar buttons, if desired, which can make them even
handier for copying headers and footers.
If you have quite a few worksheets and workbooks into which you want the headers and footers
copied, there is a different macro approach you can use. The following macro will copy the
headers and footers from the active worksheet to all other worksheets in all other open
workbooks.
Sub CopyHeaderFooter()
Dim PS As PageSetup
Dim WB As Workbook
Dim WS As Worksheet
Set PS = ActiveSheet.PageSetup
For Each WB In Workbooks
For Each WS In WB.Worksheets
With WS.PageSetup
.LeftHeader = PS.LeftHeader
.CenterHeader = PS.CenterHeader
.RightHeader = PS.RightHeader
.LeftFooter = PS.LeftFooter
.CenterFooter = PS.CenterFooter
.RightFooter = PS.RightFooter
End With
Next
Next
End Sub
In other words, if you want to copy headers and footers from the current worksheet to 150 other
worksheets spread across 15 different workbooks, all you need to do is open the 15 workbooks at
the same time, display the source worksheet, and run the macro.
Another option is to make “fake” headers and footers. If all you want to do is have a different
color header, then you can use the first couple of rows of the worksheet as your header. These
rows you could format as desired, including setting the color of the rows. You could then instruct
Excel to repeat those rows at the top of every page of the printout (us the Page Setup dialog box
for this).
Repeating rows for the footer area becomes more problematic, as Excel doesn’t include a feature
that allows you to repeat rows at the bottom of each page. Creating a macro to add rows for the
header and footer is possible, but it does result in a change to your worksheet—rows need to be
added for the fake headers and footers.
As an example, consider the following macro. It assumes that you want one-inch borders on the
left and right of the printout, and that you want to print only 46 rows per page. It sets the margins
and then steps through the worksheet, adding the fake header and footer rows, as necessary.
(Because the macro adjusts the design of the worksheet, make sure you save your worksheet
before running the macro.)
Sub FakeHeaderFooter()
Dim LHeader As String
Dim CHeader As String
Dim LFooter As String
Dim CFooter As String
Dim CBottom As Integer
Dim CRow As Integer
Dim PageSize As Integer
With ActiveSheet.PageSetup
.PrintTitleRows = ""
.PrintTitleColumns = ""
.PrintArea = ""
.LeftHeader = ""
.CenterHeader = ""
.RightHeader = ""
.LeftFooter = ""
.CenterFooter = ""
.RightFooter = ""
.LeftMargin = Application.InchesToPoints(1)
.RightMargin = Application.InchesToPoints(1)
.TopMargin = Application.InchesToPoints(0)
.BottomMargin = Application.InchesToPoints(0)
.HeaderMargin = Application.InchesToPoints(0)
.FooterMargin = Application.InchesToPoints(0)
.PrintHeadings = False
.Orientation = xlPortrait
End With
CBottom = Range("A16000").End(xlUp).Row
CRow = 1
Do Until CRow > CBottom
If CRow Mod PageSize = 1 Then
Rows(CRow).Select
Selection.Insert Shift:=xlDown
Selection.Insert Shift:=xlDown
CBottom = CBottom + 2
LastPageNumber = PageNumber + 1
LastRow = LastPageNumber * PageSize
If CBottom <> LastRow Then
Range(Cells(LastRow, 1), _
Cells(LastRow, 8)).Interior.ColorIndex = 34
Cells(LastRow, 1).Value = LFooter
Cells(LastRow, 4).Value = CFooter
End If
CBottom = Range("A16000").End(xlUp).Row
CRow = 2
Do Until CRow > CBottom
If CRow Mod PageSize = 1 Then
Cells(CRow, 1).PageBreak = xlManual
End If
CRow = CRow + 1
Loop
End Sub
To change the number of lines per page, just change the value assigned to the PageSize variable.
You can also change what appears in the “header” and “footer” area by changing what is
assigned to the LHeader, CHeader, LFooter, and CFooter variables.
First of all, try using the Report Manager add-in for Excel. This add-in allows you to define
different views and select the order in which those views are printed. Each view can also have its
unique headers and footers, which means you could set up a view of the first page—with the
footer desired—and a view of the subsequent pages with their own headers and footers. You
would then print the report using the Report Manager, resulting in the desired output.
You should note that the Report Manager add-in was last distributed with Excel 2002. If you are
using a later version of Excel, you can still use the Report Manager. This Knowledge Base article
explains how you can use it:
http://support.microsoft.com/?kbid=873209
It is unclear whether the add-in will work with Excel 2010 or Excel 2013, but chances are better
than average because it works with Excel 2007.
Another workaround is to use a macro to do your printing. The following macro will set the
footers for a worksheet depending on what is being printed. Actually, it sets the footers for the
first page, and then prints that page. Then it sets the footers for the other pages, and prints them.
Sub PrintSheet()
Dim sP1Left As String
Dim sP1Center As String
Dim sP1Right As String
Dim sP2Left As String
Dim sP2Center As String
Dim sP2Right As String
To use the macro, all you need to do is change the footer definitions. Change the variable values
in the “Define first-page footers” area and the “Define second-page footers” area in order to get
just the output you want.
Excel doesn’t provide very good formatting for its page numbers. One solution (perhaps the most
viable) is to print each page in your worksheet, one at a time, changing the page number as you
go. This macro takes that approach:
Sub FormattedPageNums()
Dim iPages As Integer
Dim J As Integer
Dim sFormat As String
sFormat = "00000"
' Get count of pages in active sheet
iPages = ExecuteExcel4Macro("Get.Document(50)")
With ActiveSheet
For J = 1 To iPages
.PageSetup.CenterFooter = Format(J, sFormat)
.PrintOut From:=J, To:=J
Next J
End With
End Sub
The macro discovers how many pages are in the entire printout, and then steps through each page
of that printout. Prior to printing each page, individually, the .CenterFooter property is set equal
to a formatted string that represents the page number with leading zeros.
You can modify the macro, as desired, to place different information in the footer. You could
also change the area of the footer that is changed by using .LeftFooter or .RightFooter instead of
.CenterFooter.
Some people may long for a way to print page letters (A, B, C) instead of page numbers (1, 2, 3).
There is no intrinsic way to do this in Excel. You can, however, develop a macro that will figure
out the letter that should be associated with a page, and then use that letter in the footer. The
following macro does just that:
Sub LetterPageNums()
Dim sArr(27 * 26) As String
Dim iPages As Integer
Dim J As Integer, K As Integer
First, the macro figures out the letter equivalent of pages numbers and puts them in an array. In
this case, up to 702 page letters are calculated, which should be more than enough for any print
job. The letters are A through Z, then AA through AZ, BA through BZ, and all the way up to ZA
through ZZ.
Then, iPages is set to the number of pages in the worksheet. Finally, each page is individually
printed, with the page letter being placed into the center footer of the worksheet. If you want the
page letter in some different place, use .LeftFooter or .RightFooter instead of the .CenterFooter
property. (You can also use .LeftHeader, .CenterHeader, and .RightHeader, if desired.)
There is no built-in feature that allows you to do that, but you can create a macro that will do the
trick. Consider the following macro:
Sub RomanPageNums()
Dim iPages As Integer
Dim J As Integer
This macro first figures out how many pages are in your printout and assigns the value to the
iPages variable. It then steps through each page, changing the page number in the center portion
of the footer prior to printing each page. The page number is set by converting the current page
number (J) to a Roman numeral using the ROMAN worksheet function.
If you want the Roman numerals to appear in other parts of the footer, you can replace the
.CenterFooter property with either .LeftFooter or .RightFooter. You can also use .LeftHeader,
.CenterHeader, or .RightHeader, if desired.
The code in the RomanPageNums macro works in all the recent versions of Excel. If you are
using Excel 2000 or greater, you could also replace the actual line that sets the footer with the
following code:
.PageSetup.CenterFooter = Application.Roman(J)
If you start each group or subtotal section on a new page, you may wonder if there is a way to
create custom headers that print differently for each section, similar to what you can do with
different sections in a Word document. Unfortunately, there is no way to do this in Excel. You
can, however, create a macro that iteratively changes the heading and prints each group of a
worksheet. Consider the following macro:
Sub ChangeSectionHeads()
Dim c As Range, rngSection As Range
Dim cFirst As Range, cLast As Range
Dim rowLast As Long, colLast As Integer
Dim r As Long, iSection As Integer
Dim iCopies As Variant
Dim strCH As String
Set c = Range("A1").SpecialCells(xlCellTypeLastCell)
rowLast = c.Row
colLast = c.Column
iCopies = InputBox( _
"Number of Copies", "Changing Section Headers", 1)
iSection = iSection + 1
Select Case iSection
' substitute your CenterSection Header data ...
Case 1: strCH = "Section 1"
Case 2: strCH = "Section 2"
' etc
' Case n: strCH = "Section n"
End Select
ActiveSheet.PageSetup.CenterHeader = strCH
rngSection.PrintOut _
Copies:=iCopies, Collate:=True
iSection = iSection + 1
' substitute your Center Header data ...
strCH = "Last Section ..."
ActiveSheet.PageSetup.CenterHeader = strCH
rngSection.PrintOut _
Copies:=iCopies, Collate:=True
End Sub
This macro is a good start toward accomplishing what you want to do. It starts by asking you
how many copies you want to print of each section, and then it starts to go through each row and
see if there is a page break before that row.
The actual row checking is done by looking at the PageBreak property of each row. This
property is normally set to xlPageBreakNone, but when you use the Subtotals feature of Excel,
any row that has a page break before it has this property set to xlPageBreakManual. This is the
same setting that would occur if you manually placed page breaks in your worksheet.
If the macro detects that a row has a page break before it, then the rngSection range is set equal
to the rows in the previous group. Also, the Select Case structure is used to set the different
headings used for the different sections of the worksheet. This heading is then placed in the
center position of the header, and the range specified by rngSection is printed.
After stepping through all the groups in the worksheet, the final group (which does not end with
a page break) is printed.
In order to use this macro, all you need to do is specify within the Select Case structure the
different headings you want for each section of the worksheet. You can also, if desired, change
where the heading is placed in the header. All you need to do is change the CenterHeader
property to LeftHeader or RightHeader. You can also use LeftFooter, CenterFooter, and
RightFooter, if desired.
The only way to handle the finding and replacing of information in a header or footer is to use a
macro. It is a rather trivial task to access what is stored in the various parts of the header and
footer, check them for what you want to find, and then replace it with some new text. The
following macro provides an example.
Sub FnR_HF()
Dim sWhat As String, sReplacment As String
Const csTITLE As String = "Find and Replace"
With ActiveSheet.PageSetup
' Substitute Header/Footer values
.LeftHeader = Application.WorksheetFunction.Substitute( _
.LeftHeader, sWhat, sReplacment)
.CenterHeader = Application.WorksheetFunction.Substitute( _
.CenterHeader, sWhat, sReplacment)
.RightHeader = Application.WorksheetFunction.Substitute( _
.RightHeader, sWhat, sReplacment)
.LeftFooter = Application.WorksheetFunction.Substitute( _
.LeftFooter, sWhat, sReplacment)
.CenterFooter = Application.WorksheetFunction.Substitute( _
.CenterFooter, sWhat, sReplacment)
.RightFooter = Application.WorksheetFunction.Substitute( _
.RightFooter, sWhat, sReplacment)
End With
End Sub
Note how the macro does the replacements in all three parts of the header and all three parts of
the footer.
If you prefer to not use your own macro, or if you want a more full-featured Find and Replace
for Excel, you might consider the free FlexFind add-in from Excel MVP Jan Karel Pieterse:
http://www.jkp-ads.com/officemarketplaceff-en.asp
This add in searches regularly, but also searched in lots of other areas including headers and
footers.
Save your workbook and test out your assignment by clicking on the graphic. The macro should
run, as desired.
Marty is right; trying to paste a graphic when you have multiple worksheets selected doesn’t
work. When you try, Excel tells you that it cannot make the paste, but if you then select just a
single worksheet you can paste quite nicely.
Instead, you need to use a macro to do the pasting. Assuming that the graphic has already been
copied to the Clipboard, you can run a macro such as the following:
Sub InsertLogo1()
Dim shtSheet As Worksheet
Application.ScreenUpdating = False
For Each shtSheet In Worksheets
With shtSheet
.Activate
.Range("A1").Select
.Paste
End With
Next
Set shtSheet = Nothing
Application.ScreenUpdating = True
End Sub
The macro steps through each worksheet in the workbook and pastes the graphic into cell A1. If
you want to use a different cell, then all you need to do is modify the line that selects the cell.
If you don’t want to copy the graphic to the Clipboard ahead of time, you can use a macro such
as the following to insert the graphic directly from an image file:
Sub InsertLogo2()
Dim strPath As String
Dim shtSheet As Worksheet
strPath = "C:\GraphicFolder\PictureName.bmp"
You can, of course, modify the path to the graphic file and the cell at which the file is pasted into
the worksheets.
This task is relatively easy to do if you realize that each cell in a worksheet has both a Top and
Left property that defines the location of both the top and left edges of the cell. You can adjust
those values, slightly, to get the offset that you want, in this manner:
Note that after this code is executed the graphic (defined by the name Picture 1) is placed just
below the top edge of cell A2 and just to the right of its left edge.
If you use the Copy method with the Selection object, you can copy everything—including
pictures—from your source to your target. Consider the following short macro:
Sub CopyPict()
Sheets("Sheet1").Select
Range("B3:F7").Select
Selection.Copy
Sheets("Sheet3").Select
Range("H8").Select
ActiveSheet.Paste
End Sub
Assuming that some of the cells within the source range (B3:B7 on Sheet1) contain pictures, then
the Paste method will paste those into the target (cell H8 on Sheet3). This technique is, in fact,
the same as using copy and paste manually with the information.
If you are identifying and moving information in a different manner (perhaps using an
intermediary variable instead of copying to the Clipboard), then it is very possible that the
pictures aren’t copying. If you need to do some processing of the data before pasting it into the
target, you could use the Paste method, as shown above, and then process the data and place it
back into the target cell. That would allow the pictures to remain undisturbed at the target.
There is no way to do this using the Comments feature of Excel, but there are some
workarounds. The first involves using hyperlinks. Just follow these steps:
5. If desired, in the Type the Cell Reference box, enter the address of a cell close to or
behind your graphic.
6. Click the ScreenTip button. Excel displays the Set Hyperlink ScreenTip dialog box.
The result is that when someone hovers the mouse pointer over the graphic, a small note
appears—usually below the graphic—that contains the ScreenTip text. It isn’t quite as noticeable
as a regular Excel Comment, but it does provide a little assistance.
If you want something a bit harder to miss, then a macro might be helpful. There are a number of
different ways you could approach a macro-based solution, but perhaps the easiest is to simply
create a macro such as the following:
Sub MyMacro()
MsgBox “This is my comment”
End Sub
Back in your worksheet, right-click on the graphic and choose Assign Macro from the resulting
Context menu. Excel shows you a list of all the macros available to you; you should pick the
short one you just created (in the example above it is “MyMacro”).
Now, when you click on the graphic, you see a message box that contains whatever text you
specified in your macro. It isn’t quite as automatic as only requiring the person to scroll over the
graphic, but it does provide a handy way to convey a lot of information to the user.
You might at first think that you could add a ScreenTip to the image, but that can only be done if
you assign a hyperlink to it. Adding the hyperlink (and ScreenTip) is easy enough, but you’ll
find that the hyperlink takes precedence over the macro, stopping it from being run.
This means that you need to look for other ways to tackle the problem. Unfortunately there is no
easy way to create this type of ScreenTip, but there are a couple of ways you can approach the
task. One thing you can do is to add a command button to the worksheet, and then assign the
image to the button. The whole image then serves as a button. When you click the button, it
executes the CommandButton1_Click event handler (assuming you use the default name for the
command button).
Next you need to create a text box that approximates what a ScreenTip looks like. Actually the
text box gives you more latitude than you have with a regular ScreenTip, because it can be
formatted in any manner you desire, and it can contain any explanatory text you desire. All you
need to do is make sure that the text box is given a unique name, such as “MyShape”. (You
assign a name to the text box by selecting it and then changing the name in Name box in the
upper-left corner of the worksheet area.)
With the command button and text box in place, right-click on the command button and choose
to display the code window for the command button. Then, add the following code to the code
window:
It is the Click event handler that you will need to modify to call your normal macro code. The
MouseMove code is executed when the mouse is moved over the command button. In this case,
the code displays the text box you created.
Next, insert the following macros into a standard macro module. These two macros show and
hide the text box shape that you created. Note that the first macro uses the OnTime method to
automatically hide the shape two seconds after it is first displayed.
Sub Display_and_Hide_Shape()
ActiveSheet.Shapes("MyShape").Visible = True
' adjust time
Application.OnTime Now + TimeValue("00:00:02"), "Hide_Shape"
End Sub
Sub Hide_Shape()
ActiveSheet.Shapes("MyShape").Visible = False
End Sub
With all the macros in place, just move the mouse pointer over the command button image. The
text box should disappear two seconds later, only to reappear when you again move the mouse
over the image.
Another approach is to embed the picture in a chart object, name the picture using whatever text
you want to appear in the ScreenTip, and then assign the macro to the chart object. This may
sound a bit confusing, but it is relatively easy to do by following these general steps:
1. Create a blank chart object. You can do this by simply selecting a blank cell, choosing
to insert a chart, and immediately clicking the Finish button. The chart won’t contain
anything, which is why it is a “blank chart object.”
2. Next add the picture to the chart object. Just copy the picture to the Clipboard and then
select the blank chart object (you created it in step 1) and paste the contents of the
Clipboard.
3. Adjust the size of both the chart object and the picture within the chart object so that
they represent your needs.
4. Select the picture within the chart object, and then give the picture a name by changing
whatever is in the Name box at the upper-left corner of the worksheet area. This name
should be the text you want to appear as your ScreenTip.
5. Now assign your macro to the chart object (not the picture within the chart object) by
right-clicking the chart object and choosing Assign Macro.
That’s it. Now, when you move the mouse pointer over the image, the name of the image appears
as a ScreenTip, and if you click then the macro assigned to the chart object is executed.
When you hold down the ALT key, it forces Excel to "snap" the sides of your text box to a
drawing grid which just happens to match the cell boundaries in your worksheet. The result is a
text box that is exactly the desired size.
If you need to create quite a few of these text boxes, all at one time, you can turn the snap-to-gird
feature on permanently. In Excel 2007 and later versions display the Page Layout tab of the
ribbon, click the Align tool in the Arrange group, then click Snap To Grid. In older versions of
Excel choose Draw (on the Drawing toolbar) | Snap | To Grid.
If you have many, many such text boxes to create, on lots of different workbooks, you can create
the desired text boxes using a macro. The following macro will create a text box directly over the
selected cell, and size it to be exactly the same size as the selected cell:
Sub TextBox2Cell()
With ActiveCell
ActiveSheet.Shapes.AddTextbox _
msoTextOrientationHorizontal, .Left, _
.Top, .Width, .Height
End With
End Sub
With a small change in the macro, you can modify it so that it will create text boxes that are just
as large as whatever range of cells you have selected:
Sub TextBox2Selection()
If TypeName(Selection) = "Range" Then
With Selection
ActiveSheet.Shapes.AddTextbox _
msoTextOrientationHorizontal, .Left, _
.Top, .Width, .Height
End With
End If
End Sub
Regardless of which approach you use to create the text box (manual or macro), it should be
noted that if you resize the cell by changing the column width or row height, the size of the text
box will also change to match the new cell size.
There are a couple of ways you can approach this task. One is to specify, in the macro, exactly
which cells you want to cover with the text box, and then adjust the properties of the text box to
match the characteristics of the cells you specify.
Sub ResizeBox1()
Dim sTL As String
With Selection
Set rng = ActiveSheet.Range(sTL)
.Top = rng.Top
.Left = rng.Left
Set rng = ActiveSheet.Range(sBR)
.Width = rng.Left + rng.Width
.Height = rng.Top + rng.Height
End With
Set rng = Nothing
End Sub
In order to use the macro, change the address of the cells you want to use for the top-left and
bottom-right of the text box. Then, select the text box and run the macro.
If you prefer, you could use a named range to specify the range to be covered by the text box.
The following macro expects that the range will be named RangeToCover. When you select the
text box and run the macro, the text box is resized to match the size of the range.
Sub ResizeBox2()
Dim l_rRangeToCover As Range
Dim l_rLowerRight As Range
Walter is right; you cannot find text located in text boxes in Excel. To test this, we opened a
brand new workbook, placed a single phrase in it (“my message”), and then placed some random
text and numbers in other cells in the worksheet. Then, with the text box not selected, CTRL+F
was pressed to search for “my message.” Excel dutifully reported that it couldn’t find the text,
even though it was still right there, in the text box.
Fortunately, you can search for text in a text box using a macro. Each text box in a worksheet
belongs to the Shapes collection, so all you need to do is step through each member of the
collection and see if it contains the desired text. Here’s a macro that prompts for a search string
and then looks for it in the text boxes.
Sub FindInShape1()
Dim rStart As Range
Dim shp As Shape
Dim sFind As String
Dim sTemp As String
Dim Response
The following is a version of the previous macro, but designed to work with pre-Excel 2007
systems. (The major change is that this version doesn't use the TextFrame2 object, which wasn't
available for those earlier verions.)
Sub FindInShape1()
Regardless of which macro you use, it looks through all the shapes in the worksheet, not just the
text boxes. If you prefer to limit your search to only text boxes, you can step through the
TextBoxes collection instead of the Shapes collection; either way will work fine.
Notice, as well, that this approach stops each time it finds matching text (the case of the text
doesn’t matter) and asks you if you want to continue. You may, instead, want a macro that
simply marks the matching text in text boxes. This can be done with a shorter macro, as shown in
this example that works with Excel 2007 or later:
Sub FindInShape2()
Dim shp As Shape
Dim sFind As String
Dim sTemp As String
Dim iPos As Integer
Dim Response
Length:=Len(sFind)).Font
.UnderlineStyle = msoUnderlineHeavyLine
.Bold = True
End With
End If
Next
MsgBox "Finished"
End Sub
This macro underlines the located text using a heavy line, and then makes it bold. When you are
done, you probably want to change the text back to regular text. You can do so by using the
following macro:
Sub ResetFont()
Dim shp As Shape
The following version of the FindInShape2 macro works with older versions of Excel:
Sub FindInShape2()
Dim shp As Shape
Dim sFind As String
Dim sTemp As String
Dim iPos As Integer
Dim Response
This macro highlights the located text using a bold, red font. When you are done, you probably
want to change the text back to regular text. You can do so by using the following macro:
Sub ResetFont()
There is no command to do this; you must instead use a macro. The following macro steps
through each textbox in a worksheet and makes the desired extraction:
Sub ExtractText()
Dim shp As Shape
Dim sLoc As String
Since Excel stores all graphic shapes in the Shapes collection, you can step through the
collection and make a determination as to which shapes you want to work with. In this case, the
first eight characters of the shape’s name is checked. Only if the name begins with “Text Box”
does the macro consider the shape to be a text box from which text can be extracted.
Rather than check for the “Text Box” wording in the name, the macro could also check to see
what type of shape is being considered. If you prefer to do this, then simply replace the test line
(If Left…) with the following test line:
The sLoc variable is used to store the location of the textbox, which is contained in the
.TopLeftCell property. A Do loop is then used to make sure that the cell pointed to by the
address is empty. (This prevents any existing contents of the cell from being overwritten.) If it is
not empty, then the address is “incremented” to the next cell in the column.
With the address of an empty cell determined, the text of the textbox is stored in the cell. The
.Delete method is then used to get rid of the actual text box.
Adding AutoShapes
The graphics features of Excel allow you to add a number of predefined shapes to a workbook.
These shapes, called AutoShapes, cover a wide range of needs. If you want to add shapes to the
AutoShapes feature, however, you are out of luck. The shapes are apparently hard-coded into
Excel, and cannot be modified.
You can, however, add shapes to the Clip Gallery. If you format the shapes as WMF files, they
are easy to add and easy to place within a worksheet. For instance, if you have a number of
different flowchart symbols that you want to make available in Excel, all you need to do is save
each symbol in the WMF format, and then import them into the Clip Gallery. (To save graphics
in the WMF format, you need to use a specialized graphics program, such as Paint Shop Pro or
Corel Draw.)
If you don’t want to use the Clip Gallery, you can simulate your own AutoShapes through a
combination of macros and graphics in a hidden worksheet. The following general steps detail
how to do this for a series of twenty flowchart symbols. The steps assume you are reasonably
comfortable writing macros and customizing toolbars.
1. Open a template workbook, and make sure it has only a single worksheet.
2. Place all the flowchart graphics on the worksheet.
3. Create a new toolbar, name it MyShapes, and make sure it is associated with the
template workbook.
4. Add twenty buttons to the toolbar, one for each flowchart graphic. The idea is that
clicking a button will add the associated flowchart shape to the active worksheet.
5. Edit each button face to show as closely as possible each flowchart graphic. (This is the
toughest part of these steps).
6. Change the ToolTip text for each button, as desired. This is helpful so the user can
understand the purpose of each flowchart graphic.
7. In turn, select and name each of the flowchart graphics. (You name the graphics by
selecting them and entering a name in the Name box at the left of the Formula bar.) For
the purposes of these steps, assume you use names such as FlowObj1, FlowObj2, etc.
8. Write twenty macros (one for each flowchart graphic) of the following kind:
Sub AddFlowObj1()
ThisWorkbook.Sheets(1).Shapes("FlowObj1").Copy
ActiveSheet.Paste
End Sub
What is happening is that an object—such as a graph, drawing object, text box, picture, or even
comment—cannot be correctly handled by Excel after the deletion or insertion. If the error
occurs when inserting rows or columns, it means that the insertion would push the object beyond
the right or bottom boundaries of the worksheet. The solution, of course, is to check whatever is
at the right or bottom of the worksheet and make changes to those objects (move or delete them)
as necessary.
If the error occurs while deleting rows or columns, it is normally because there are objects
attached to cells within those rows or columns, and deleting the rows or columns would leave the
objects “orphaned” in some way. For instance, let’s say you are deleting column D, and there is
an object associated with cell D4. The object doesn’t need to be situated over column D; it could
be several columns away, but still belong to cell D4. If you delete column D, then the object no
longer has an anchor point. Excel’s solution? Don’t let column D be deleted until you do
something with the object that would be orphaned by the edit.
The problem can also occur if the objects in a worksheet are formatted so that they cannot be
moved or sized automatically by Excel, and then you try to delete columns or rows associated
with the objects. In this case, you may want to try changing the formatting of the objects in the
worksheet. If you have a lot of such objects in the worksheet, the following macro can be helpful
in making the change:
Sub ResetShapes()
Dim s As Shape
On Error Resume Next
For Each s In ActiveSheet.Shapes
s.Placement = xlMoveAndSize
Next
End Sub
Microsoft provides a Knowledge Base article that can be helpful with this problem. The article
specifically addresses the issue of hiding rows and columns, but the solutions work when you are
trying to delete them, as well. You can check it out at this page:
http://support.microsoft.com/?kbid=211769
If you are using Excel 97, you should reference this page, instead:
http://support.microsoft.com/?kbid=170081
There are a couple of things to check out. First of all, you should ensure that you are using the
proper syntax to do the deletion. Check to make sure you are explicitly including the sheet object
in your code. For instance, the following line will not work:
Shapes(1).Delete
Instead, you must specify the sheet, using code similar to any of the following lines:
ActiveSheet.Shapes(1).Delete
Sheets("Sheet1").Shapes(1).Delete
Sheets(1).Shapes("Signature").Delete
If you determine that the expected image is not in the Shapes collection, then it is possible that
Excel (for strange reasons only known to Excel) moved the image to a different collection, such
as the Pictures collection. If you suspect this, then try using the following macro:
Sub WhatAmI()
Dim sTemp As String
Select the signature image, then run the macro. You should see a message box that indicates the
type of object you selected, along with its name. You can then use the information to modify
your macro so it deletes the image, as desired.
One solution, if there are not that many snapshots necessary, is to simply do the pasting
manually. You can display information in Excel, and then press the PrintScreen key to place a
picture of it in the Office Clipboard. Switch to PowerPoint and choose Office Clipboard from the
Edit menu. You can then see the contents of the Clipboard and choose what you want pasted into
the current slide.
A less repetitive approach would be to link data from the Excel workbook to the slides. You can
use Paste Special (in PowerPoint) to paste linked data. In this way, anytime the data in the
workbook is updated, the linked slides will also be updated. Done correctly, this solution carries
the possibility of only needing to do your pasting a single time.
If you prefer to take the route of developing macro to do the pasting, check out one developed by
Jon Peltier at his Web site:
http://peltiertech.com/Excel/XL_PPT.html#rangeppt
It will take a snapshot of whatever cells are selected, and then paste them into the active slide in
PowerPoint. (Obviously, you must have both Excel and PowerPoint open in order to use the
macro.)
Further, the macro could be relatively easily modified so that it stepped through a series of
named ranges in Excel and pasted the contents of those ranges into specified slides in
PowerPoint.
Another macro-based solution is to create a new PowerPoint presentation (from within Excel)
that will contain a snapshot of each of the worksheets in the current Excel workbook. The
following macro accomplishes this task:
Sub CopyWksToPPT()
Dim pptApp As Object
Dim sTemplatePPt As String
Dim wks As Worksheet
Dim sTargetTop As Single
Dim sTargetLeft As Single
Dim sTargetWidth As Single
iIndex = 1
Set pptApp = CreateObject("Powerpoint.Application")
With pptApp
.Visible = True
.Presentations.Open _
FileName:=sTemplatePPt, Untitled:=msoTrue
For Each wks In Worksheets
wks.Select
.ActiveWindow.View.GotoSlide _
Index:=.ActivePresentation.Slides.Add _
(Index:=iIndex, Layout:=12).SlideIndex
iIndex = iIndex + 1
wks.UsedRange.Copy
.ActiveWindow.View.Paste
With .ActiveWindow.Selection.ShapeRange
sScaleHeight = sTargetHeight / .Height
sScaleWidth = sTargetWidth / .Width
If sScaleHeight < sScaleWidth Then
sScaleWidth = sScaleHeight
Else
sScaleHeight = sScaleWidth
End If
.ScaleHeight sScaleHeight, 0, 2
.ScaleWidth sScaleWidth, 0, 2
.Top = sTargetTop + (sTargetHeight - .Height) / 2
.Left = sTargetLeft + (sTargetWidth - .Width) / 2
End With
Next
.Visible = True
End With
End Sub
Note the area that says “Change these as desired.” This contains the specifications of where the
pasted snapshot will be within each PowerPoint slide, as well as its height and width. Also
included, in the sTemplatePPt variable, is the full path to the template that should be used for the
new PowerPoint presentation.
Excel into thinking it is working with information from a worksheet, and then providing your
own. The following macro illustrates this concept:
Sub MakeChart()
'Add a new chart
Charts.Add
The comments in this example explain what is going on for each step. When setting the dummy
data range, the SetSourceData method assumes the range is on a worksheet named Sheet1. If you
don’t have such a sheet in your workbook, you need to alter the command accordingly.
Later, when manually setting the values for the data series, the SERIES command is used to
specify the label for the series (First Data, Second Data, and Third Data), the array of category
labels (a, b, c, and d in all series), the array of values for the series, and a number specifying
which series number this represents.
There are several ways that this can be done; I’ll examine three of them in this tip. For the sake
of example, let’s assume that the worksheet is named MyData, and that the first row contains
data headers. The company names are in the range A2:A151, and the sales data for those
companies is in B2:P151.
One approach is to use Excel’s AutoFilter capabilities. Create your chart as you normally would,
making sure that the chart is configured to draw its data series from the rows of the MyData
worksheet. You should also place the chart on its own sheet.
Now, select A1 on MyData and apply an AutoFilter. (Display the Data tab of the ribbon and
click the Filter tool or, in versions of Excel previous to Excel 2007, click Data | Filter |
AutoFilter.) A small drop-down arrow appears at the top of each column. Click the drop-down
arrow for column A and select the company you want to view in the chart. Excel redraws the
chart to include only the single company.
The only potential drawback to the AutoFilter approach is that each company is considered an
independent data series, even though only one of them is displayed in the chart. Because they are
independent, each company is charted in a different color. If you want the same charting colors
to always be used, then you will need to use one of the other approaches.
Another way to approach the problem is through the use of an “intermediate” data table—one
that is created dynamically, pulling only the information you want from the larger data table. The
chart is then based on the dynamic intermediate table. Follow these steps if you are using Excel
2007 or a later version:
7. Click OK to dismiss the dialog box. You now have a functioning Combo Box that, once
you use it to select a company name, will place a value in cell A1 of the ChartData
worksheet that indicates what you selected.
8. With the ChartData worksheet displayed, enter the following formula into cell A3:
=INDEX(MyData!A2:A151,$A$1)
9. Copy the contents of cell A3 to the range B3:P3. Row 3 now contains the data of
whatever company is selected in the Combo Box.
10. In cell B1 enter the following formula. (The result of this formula will act as the title for
your dynamic chart.)
="Data for " & A3
11. Select the column headers and data (B2:P3) and create a chart based on this data. Set
the chart’s title to some placeholder text; it doesn’t matter what it is right now.
12. In the finished chart, select the chart title.
13. In the Formula bar, enter the following formula:
=ChartData!$A$3
If you are using an older version of Excel, the steps are a bit different. The major difference has
to do in how you create the necessary form:
You now have a fully functioning dynamic chart. You can use the Combo Box to select a
company and the chart is redrawn using the data for the company you select. If you want, you
can move or copy the Combo Box to the sheet containing your chart so that you can view the
updated chart every time you make a selection. You can also, if desired, hide the ChartData
worksheet.
A third approach is to use a macro to modify the range on which a chart is based. To prepare for
this approach, create two named ranges in your workbook. The first name should be ChartTitle,
and it should refer to the formula =OFFSET(MyData!$A$1,22,0,1,1). The second name should
be ChartXRange, and it should refer to the formula =OFFSET(MyData!$A$1,22,0,1,15).
With the names defined, you can select the range MyData!B1:P2 and create your chart. You
should base the chart on this simple range, and make sure that you place some temporary text in
the chart title. Make sure the chart is created on its own sheet and that you name the sheet
ChartSheet.
With the chart created, right-click the chart and choose Select Data. Excel displays the Select
Data Source dialog box. Select the data series and click Edit. Excel displays the Edit Series
dialog box. Replace whatever is in the Series Values box with the following formula:
='Book1'!ChartXRange
Make sure you replace Book1 with the name of the workbook in which you are working. Click
OK, and the chart is now based on the named range you specified earlier. You can now select the
chart title and place the following in the Formula bar to make the title dynamic:
=MyData!ChartTitle
Now you are ready to add the macro that makes everything dynamic. Display the VBA Editor
and add the following macro to the code window for the MyData worksheet. (Double-click the
worksheet name in the Project Explorer area.)
Now you can display the MyData worksheet and double-click any row. (Well, double-click in
column A for a row.) The macro then updates the named ranges so that they point to the row on
which you double-clicked, and then displays the ChartSheet sheet. The chart (and title) are
redrawn to reflect the data in the row on which you double-clicked.
Unlocking Charts
A common task done in macros is to lock and unlock different cells and objects in a workbook.
This is often done for protection reasons, so that things cannot be modified inadvertently by
users. If you need to unlock the charts that are in your workbook, you can easily do so if you
remember that even though charts can be considered drawing objects, you don’t unlock them as
drawing objects—you specifically unlock the chart object.
In addition, how you unlock a chart depends on whether it is a Chart sheet or a Chart object on a
regular worksheet. The following code, named ChartUnProtect, provides an example of how to
successfully unprotect both types of charts.
Sub ChartUnProtect()
Dim wks As Worksheet
Dim cht As Chart
Dim chtObj As ChartObject
Dim PW As String
PW = "mypass"
Unfortunately there is no way to easily do this in Excel. There are, however, a couple of
workarounds you can try. The first is to use a macro to change the line colors of chart lines that
represent negative values. The following macro is an example of such an approach:
Sub PosNegLine()
Dim chtSeries As Series
Dim SeriesNum As Integer
Dim SeriesColor As Integer
Dim MyChart As Chart
Dim R As Range
Dim i As Integer
Dim LineColor As Integer
Dim PosColor As Integer
Dim NegColor As Integer
Dim LastPtColor As Integer
Dim CurrPtColor As Integer
PosColor = 4 'Green
NegColor = 3 'red
SeriesNum = 1
For i = 2 To R.Cells.Count
LastPtColor = IIf(R.Cells(i - 1).Value < 0, NegColor, PosColor)
CurrPtColor = IIf(R.Cells(i).Value < 0, NegColor, PosColor)
When you select a chart and then run the PosNegLine macro, it looks through the chart and, for
line segments between negative data point values, changes the line color to red. For line
segments connecting positive data points, the line color is set to green.
The problem with this solution is that it provides only an approximation; it only works with lines
connecting two data points, and it can either change the entire line segment or not. If the
beginning data point is positive and the ending data point is negative, it cannot change the color
of a line right as it passes into negative values.
Another approach is to format data points as different colors or shapes, based on whether they
are positive or negative. A way to accomplish this is detailed at Jon Peltier’s Web site, located
here:
http://www.peltiertech.com/Excel/Charts/ConditionalChart1.html
This can be done manually, but it is tedious at best. For 50 rows it would quickly be brutal, so it
is best to look at a macro-oriented approach. One idea is to use a macro similar to the following,
which steps through the data points in the X-Y chart and reads the label values from column A.
Sub DataLabelsFromRange()
Dim Cht As Chart
Dim i, ptcnt As Integer
ptcnt = Cht.SeriesCollection(1).Points.Count
For i = 1 To ptcnt
Cht.SeriesCollection(1).Points(i).DataLabel.Text = _
ActiveSheet.Cells(i + 1, 1).Value
Next i
End Sub
The macro assumes that the first row of the worksheet contains header information and that the
actual data begins in row 2. If the data really begins in row 1, then change “i + 1” to simply “i”.
(This macro approach is actually a variation of a macro found on pages 570-571 of John
Walkenbach's excellent book Excel 2003 Power Programming with VBA. Despite the book's
title, the macro still works just fine with later versions of Excel.)
One rather unique non-macro approach is to use Excel’s custom formats. All you need to do is
set up a bunch of custom formats that contain only the text you want to be displayed. For
example, if you have the values Age, 15, and 23 in cells A3 to C3, you can format either cell B3
or C3 to show the word “Age” even though the value will remain 15 or 23, respectively. Just
enter “Age” (including the quotation marks) for the Custom format for the cell. Then format the
chart to display the label for X or Y value.
When you do this, the X-axis values of the chart will probably all changed to whatever the
format name is (i.e., Age). However, after formatting the X-axis to Number (with no digits after
the decimal in this case) rather than General, the chart should display correctly.
This approach can obviously still take a bit of time to implement as you set up and apply a bunch
of custom formats for each value in your data series. If you don’t want to mess with writing and
testing your own macros or creating a bunch of custom formats, you can always turn to add-ins
written by others. Microsoft MVP Rob Bovey has created an excellent (free) add-in for Excel
which includes an X-Y labeling feature among several others. It can be downloaded at this
address:
http://www.appspro.com/Utilities/ChartLabeler.htm
The answer is that you cannot do this, at least not directly. To understand why this is, you must
understand how the “print in black and white” feature works. This feature only affects what is
sent to the printer driver (to your printer), it doesn’t affect the actual chart at all. Even when you
use Print Preview, you are not viewing your actual chart, but a representation of what your chart
will look like when printed. Thus, you are seeing printer output, not the real chart.
If you want to export a black and white version of your chart, there are several ways to
accomplish the task. The first is to simply view the chart in Print Preview and do a screen
capture (press ALT+PRINT SCREEN). You can then paste the screen into your favorite graphics
program and touch it up, as desired.
If you want to export the chart instead of just capturing the screen, then you should change the
colors of the chart so that they really are grayscale and contain the same patterns you would see
if you chose to print in black and white. This approach actually changes the source for the chart,
rather than relying on Excel to do a transformation of the chart when you print. Once you get
done making the formatting changes, you can even save the chart as a “chart template” so you
can use it as a pattern for other charts you create.
If desired, you can also use a macro to convert between color and grayscale chart presentation.
This approach is highly dependent on the colors you want to use in the chart, the type of chart
you are using, and the number of data series in the chart. The following is an example of a macro
that will toggle the colors in a data series between color and black and white, for up to five data
series.
Option Explicit
Public bColored As Integer
Sub ColoredToBW()
Dim cht As Chart
Dim chtSC As SeriesCollection
Dim x As Integer
Dim iSeriesCount As Integer
Dim iColors(1 To 5, 0 To 1) As Integer
Dim iColor As Integer
iColors(3, 1) = 6 'yellow
iColors(4, 1) = 8 'Turquoise
iColors(5, 1) = 13 'Violet
For x = 1 To iSeriesCount
'Define the color
iColor = iColors(x, bColored)
'Marker color
With chtSC(x)
.MarkerBackgroundColorIndex = xlNone
.MarkerForegroundColorIndex = iColor
End With
Next x
End Sub
This example will not work with all chart types; you will need to modify it to reflect your needs.
It will, however, serve as a starting point for making your own macro.
There is no way to specify a chart object size as you are creating the chart. You can, however,
resize the chart object after it is created, just as you can resize other graphic elements of your
worksheet. You could write a macro to create the object at a particular size, but doing so would
remove much of the flexibility that is inherent in Excel's chart-creation tools. For instance, when
you specify the size of the chart object being created, you also have to specify other
characteristics, such as chart type. It is easier to pick and choose such characteristics through the
tools on the ribbon than it is to do so in a macro.
You can, however, easily create a macro that will resize an existing chart object. The key
commands of such a macro would be changing the Width and Height properties for the chart
object. In VBA, these properties are specified in points. Thus, if you wanted to resize the chart
object so it was 4 inches high, you would set the Height property to 288, which is the number of
points in 4 inches (4 * 72).
The following macro gives an example of one way to step through all the chart objects on a
worksheet and make them the same size. This particular macro sets the width of each chart
object to 4 inches, and the height to 3 inches.
Sub ResizeCharts()
For j = 1 To ActiveSheet.Shapes.Count
If ActiveSheet.Shapes(j).Type = msoChart Then
ActiveSheet.Shapes(j).Width = 4 * 72
ActiveSheet.Shapes(j).Height = 3 * 72
End If
Next j
End Sub
The reason that each of the pie charts is a little bit different in size is because when you create a
chart with the default settings, Excel decides it can adjust the chart size as it sees fit. This sizing
can depend on several factors, such as available space, label sizes, number of data points, etc.
One way to improve the chances that each chart will be the same size is to create your first chart
and then use CTRL+C and CTRL+V to copy the chart the other three times. Each should be
identical, and then you can adjust the data ranges reflected in the charts so that they display the
desired ranges.
If it is not practical to copy and paste the charts (for instance, if the charts are created by
macros), then you may be interested in just using a quick macro to adjust the size of all the charts
in the worksheet. The following macro will step through each chart and adjust the Height and
Width properties to 5 centimeters.
Sub AdjChartSizes()
Dim cht As ChartObject
For Each cht In ActiveSheet.ChartObjects
cht.Chart.ChartArea.AutoScaleFont = False
cht.Height = Application.CentimetersToPoints(5)
cht.Width = Application.CentimetersToPoints(5)
Next cht
End Sub
If you find yourself using a “non-default” chart often (which means changing the appearance of
certain chart elements after the chart is created), then a great approach is to create a custom chart
and save that format in Excel. You can then use the saved format to create all your new charts,
thereby minimizing the amount of later formatting you need to do. How you save custom chart
formats has been covered in other issues of ExcelTips.
Custom chart formats may be great for the future, but it doesn’t help if you already have a whole
bunch of charts in an existing workbook. In that case, the best solution is to use a macro which
can step through all the charts in a workbook and make a desired change. You just need to decide
up front which items you wish to change, and then program the macro to specifically change
those items.
For example, the following macro changes the font color and size of the Y-axis labels. It loops
through all the charts in the workbook, both sheets and embedded charts.
Sub ChangeAllCharts1()
Dim cht As Chart
Dim sht
Dim ChtObj As ChartObject
As written here, the macro changes the font size to 20 and the color to red. If you want the macro
to change other elements, all you need to do is change the With statements to reflect the elements
you want changed, or you could use a For…Next loop to step through all the chart elements. The
following macro exhibits this technique, changing the background color of the charts in a
workbook.
Sub ChangeAllCharts2()
On Error Resume Next
NewChartAreaColor = 34
For J = 1 To ActiveWorkbook.Charts.Count
ActiveWorkbook.Charts(J).Select
For J = 1 To ActiveWorkbook.Sheets.Count
For K = 1 To Sheets(J).ChartObjects.Count
Sheets(J).Select
Sheets(J).ChartObjects(K).Activate
There are a couple of things you can do to get the file you want. If you only need to create a GIF
file once in a while, the best be would be to simply use a graphics program. For instance, you
could follow these simple steps:
If you prefer, you could modify these steps a bit (well, steps 2 and 3) to capture an entire screen
instead of just the chart. This allows you to size the chart any way you want prior to capture,
even filling the entire screen, if desired. To capture the screen, just press the PRTSCRN button on
your keyboard, which places the screen capture in the Clipboard. You can then use steps 4
through 7 to put the screen into Paint and crop it or make other edits you need.
If you need to save your charts as GIF files quite often, then the best solution is to use a macro.
The following simple macro saves the currently selected chart as a GIF file in the same directory
in which the current workbook is stored.
Sub SaveChartAsGIF()
Dim sFileName As String
sFileName = ThisWorkbook.Path & "\" & ActiveChart.Name & ".gif"
ActiveChart.Export Filename:=sFileName, FilterName:="GIF"
End Sub
The use of the Export method to save out charts is detailed in this Microsoft Knowledgebase
article:
http://support.microsoft.com/?kbid=163103
The article indicates that it is written for Excel 97, but the coding will work just fine with all
versions of Excel that use VBA.
If you want a more full-featured macro approach, this article on Jon Peltier's site is quite helpful:
http://peltiertech.com/WordPress/enhanced-export-chart-procedure/
The good news is that you don't have to go crazy quite as fast; Excel provides macros that can
make the job faster and easier. Before jumping into that discussion, however, you may want to
think long and hard before you go about putting all your photos into an Excel workbook.
When you insert a photo into Excel, the file size of your workbook is increased by at least the
file size of the photo being inserted. Thus, if your average photo is 1 MB in size (quite small
with today's cameras) and you insert 5000 such photos, then you end up with a workbook that
has at least 5 GB of photos in it. That is a huge workbook, and Excel might have a hard time
working with that much info. (How hard of a time depends on your version of Excel, how much
memory is in your system, how fast your processor is, etc.)
You might think that the solution is to scale the images as you place them in your worksheet, so
that they are smaller. While rescaling an image makes it appear smaller (it looks smaller in the
worksheet), it isn't really smaller. The full-size image is still right there in Excel. So, you don't
reduce your workbook's file size at all by scaling the photos.
The way you can reduce the file size is to scale the photos outside of Excel, using photo-editing
software, before they are inserted into Excel. In other words, you would need to load each of the
photos into the photo editing software, resize the photos to whatever thumbnail size you want,
and then save the resized photo into a new thumbnail image file. (You generally wouldn't want to
save the resized image over the top of your original photo.) You could then insert each thumbnail
into your Excel worksheet and your resultant workbook file size would be smaller, although still
directly related to the aggregate size of the thumbnail photos you add to the worksheet.
If you still want to insert all the photos into your worksheet, you can do so using a macro. The
following example, PhotoCatalog, can look for all the thumbnail photos and insert them into the
worksheet, along with a hyperlink to the full photo. It assumes four things: (1) your photos and
thumbnails are all JPG images, (2) the photos are in the directory c:\Photos\, (3) the thumbnails
are in the directory c:\Photos\Thumbnails\, and (4) the thumbnails have the same file names as
the full-size photos.
Sub PhotoCatalog()
Dim i As Double
Dim xPhoto As String
Dim sLocT As String
Dim sLocP As String
Dim sPattern As String
sLocT = "c:\Photos\Thumbnails\"
sLocP = "c:\Photos\"
sPattern = sLocT & "*.jpg"
Application.EnableEvents = False
Application.ScreenUpdating = False
Range("A1").Select
ActiveCell.FormulaR1C1 = "Description"
Range("B1").Select
ActiveCell.FormulaR1C1 = "Thumbnail"
Range("C1").Select
ActiveCell.FormulaR1C1 = "Hyperlink"
Range("A1:C1").Select
With Selection.Font
.Name = "Arial"
.FontStyle = "Bold"
.Size = 12
.ColorIndex = xlAutomatic
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.Weight = xlMedium
.ColorIndex = xlAutomatic
End With
i = 1
On Error GoTo 0
xPhoto = Dir(sPattern, vbNormal)
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
It can take quite a while for this macro to run, depending on the type of system you are using and
how many photos you are cataloging.
Hyperlinks to Charts
Excel allows you to create hyperlinks, either to resources on the Internet or to cells in other
worksheets. Excel won’t, unfortunately, allow you to create hyperlinks that display chart sheets
in your workbook. If a worksheet includes a chart object (the chart was created as an object in a
worksheet), then you can create a hyperlink that displays the worksheet on which the chart object
is located. You cannot, however, use an actual chart sheet as the target of your hyperlink.
The way to work around this problem is to create a macro that actually displays the desired chart
sheet. You can then assign the macro to the Quick Access Toolbar or a regular toolbar,
depending on your version of Excel. You would use a macro such as the following:
Sub GotoChart1()
Sheets("Chart1").Select
End Sub
This is a very simplistic version of a macro that displays a specific chart sheet. In this case, the
chart sheet is named Chart1; you can change the name to reflect your needs. You can create a
macro like this for each destination chart sheet in your workbook.
An alternative is to enhance the macro so that it accepts a parameter indicating the name of the
chart sheet you want selected. For instance, consider the following macro:
Sub GotoChart2()
Sheets(ActiveSheet.Shapes(Application.Caller) _
.TopLeftCell.Value).Select
End Sub
With this macro in place, go back to your worksheet and select the cell where you want your
hyperlink. Type the name of the chart sheet, and format it to look like a hyperlink. (Blue,
underlined text, or formatted as desired. You are simulating a hyperlink; you are not creating a
real one.)
Using the legacy form controls on the Developer tab of the ribbon, create a label object within
the same cell, and format the label to not be visible. You do this by modifying the properties of
the object so it has no lines, no text, etc. Then, right-click the label object and use the Assign
Macro choice to assign the GotoChart2 macro to the object.
Now, when someone tries to click the “hyperlink,” they are really clicking the invisible label
object, and the macro is being executed. The macro determines the name of the object that called
it (Application.caller), figures out what cell the object’s top-left corner is in, and grabs the value
of that cell. The value is then used as the destination name for the desired chart sheet.
Excel doesn’t allow you (even with VBA) to get the coordinates of the mouse pointer on a
graphic inserted as a regular picture in the worksheet. If you insert the picture using an Image
object in the Control toolbox, you have quite a bit more latitude. Indeed, you can use the
MouseDown event handler to determine the coordinates, as shown here:
This code assumes that the image is named Image1. Similar code could be used to display the
cursor coordinates in real time on the status bar:
Either (or both) of these event handlers are obviously associated with Image1, so they need to be
added to the code window for that object.
There are three parameters you can use with InputBox (each of them strings), although only the
first one is absolutely required. In this syntax, sPrompt is the text you want displayed as the user
prompt, sTitle is the text to display in the title bar of the dialog box, and sDefault is the default
text string offered to the user in the dialog box. The user can edit or accept the default string, as
desired.
As an example, the following code lines can be used to display a dialog box and ask the user for
his or her name:
These two lines, when inserted into a macro, prompt the user for input. This input is assigned to
the UserValue variable by the InputBox function. The contents of this variable are then deposited
in cell A1 of the current worksheet using the Cells method. If you prefer, you could also use the
Range object to specify a location for the value, as shown here:
There is no direct way to do this using the InputBox function; it doesn’t include the needed
functionality. There are folks who have done it using API calls and the like, but that gets rather
involved and—in all likelihood—beyond the scope of ExcelTips.
An easier approach is to create your own UserForm in VBA. (How you create UserForms in
VBA is a fairly advanced topic and has been covered in other issues of ExcelTips.) The form can
contain a TextBox, and the control includes a property you can set to function as a masking
character when someone enters a password. If you display the property window for the TextBox
control, you’ll see a property named PasswordChar. Set this to whatever character you want used
for the masking. For instance, you could put a single asterisk in the property.
When it comes time to check whether the user entered the correct password, then all you need to
do is check the value in the TextBox control; it will be “clear” (unmasked), while the on-screen
version remains masked. In other words, if someone enters “MyPass” as their password, then
that is the value associated with the control itself. However, what shows on the screen is six
asterisks (or whatever masking character you specified), one for each letter typed.
instance, if someone enters information in cell B6, then the worksheet function can’t be used for
converting the information in B6 to uppercase.
Instead, you must use a macro to do your changing for you. When programming in VBA, you
can force Excel to run a particular macro whenever anything is changed in a worksheet cell. The
following macro can be used to convert all worksheet input to uppercase:
For the macro to work, however, it must be entered in a specific place. Follow these steps to
place the macro:
2. In the Project window, at the left side of the Editor, double-click on the name of the
worksheet you are using. (You may need to first open the VBAProject folder, and then
open the Microsoft Excel Objects folder under it.)
3. In the code window for the worksheet, paste the above macro.
4. Close the VBA Editor.
Now anything (except formulas) that are entered into any cell of the worksheet will be
automatically converted to uppercase. If you don’t want everything converted, but only cells in a
particular area of the worksheet, you can modify the macro slightly:
In this particular example, only text entered in cells A1:B10 will be converted; everything else
will be left as entered. If you need to have a different range converted, specify that range in the
second line of the macro.
Excel doesn't include the same capability, but it does have ways that you can prompt the user for
input. The two primary methods are these:
• MsgBox function. This function displays a dialog box and a set of buttons. When the
user clicks on a button, an integer value is returned that indicates the button clicked.
Your program can then take action based on the value returned.
• InputBox function. This function displays a dialog box and allows the user to type a
response. Whatever the user types is returned as a string to the macro.
Both of these functions have been discussed in other issues of ExcelTips; I won’t go over them
again here. Based on the user’s input, you can modify what the macro does in any way desired.
The only drawback to the functions is that they only return a single, discrete piece of data. In
other words, they aren’t designed to allow the user to input a range of cells and then continue
processing. For instance, if you wanted to ask the user to provide five values destined for five
cells, you would need to present an InputBox five times, depositing the user’s responses into the
desired cells one after the other.
You can also add symbols to your message boxes by including one a symbol type code as part of
your MsgBox invocation. These symbols are used extensively in many Windows dialog boxes.
The following four types of symbols can be used:
Type Symbol
16 Stop sign
32 Question mark in a circle
48 Exclamation point in a circle
64 Information symbol (lowercase i in a circle)
As an example, let’s suppose you wanted to include the exclamation point symbol. This is
typically included in dialog boxes as a notice of when something important has happened or is
about to happen. To include this symbol in your message box, you would include the following
macro code:
So far the MsgBox command has been used as a statement, but you can also use it as a function.
If you do so, you can use it to get simple input from the user. To make the MsgBox function
more useful, Excel allows you to display more clickable buttons in the dialog box besides the
OK button. This is done by adjusting the type code, which was used for the symbols displayed in
the message box. The following are the different button combinations you can display in your
message box:
To use the buttons, you simply add the value of the button type to the value you want used for
the symbol. In the previous example, you used the code of 48 to display the exclamation point
symbol. If you wanted to also include the Abort, Retry, Ignore buttons, you simply change the
code to 50, which is 48 (the symbol code) plus 2 (the button code).
When using buttons in this way, the MsgBox function returns a value indicating which button the
user chose. The buttons return, from left to right, -1, 0, and 1. Thus, if you use a button code of 3,
then -1 would mean the user chose Yes, 0 would mean No, and 1 would mean Cancel.
For instance, let’s say that you wanted to displays a message whenever the information in cell C3
is changed. The following, added to the code window for a specific worksheet, will do the trick:
The Change event is called, and passes the cell range to the routine. In this case, the range is
assigned to the Target variable. The address of this range is then checked, and if it is equal to C3
(it has to be noted in absolute terms, such as $C$3), then the message box is displayed.
There is no way to do this, as the MsgBox function doesn't include any way to specify a location.
Instead, Excel displays the message box centered on the screen. If you need the capability to
position the box, then the easiest solution is to rely upon the InputBox function:
Note that you can specify both an X position and a Y position for the upper-left corner of the
box. The values assigned to these parameters are measured from the top-left corner of the screen,
and are specified in twips. (There are 1440 twips to an inch.)
An input box does, of course, expect the user to provide input, whereas a message box does not.
If you don't want to potentially confuse your users by soliciting input when none is really
needed, then you'll need to create a UserForm to simulate a message box.
The solution for most macro developers is to somehow alert users as to the progress of the
macro. There are two ways that you can do this in Excel. The simplest and most common
approach is to use the status bar to indicate what the macro is doing. All you need to do is put
together a string that contains the status message, and then assign that string to the StatusBar
property of the Application object, as shown here:
The message stays on the status bar until you overwrite it with some other message. You could
also indicate progress in a loop by giving the percentage complete:
For x = 1 to y
Application.StatusBar = Format(x/y,"0.0%") & " Complete"
' Other coding here
Next
When your routine finishes, return the status bar back to normal with the following statement:
Application.StatusBar = False
If you prefer to develop an actual progress indicator for the macro, you can do so by creating a
UserForm and then updating the form to display a “percentage bar” or some other visual
indicator. Most people who desire this type of progress indicator rely on a variation of John
Walkenbach’s solution, found at this address:
http://spreadsheetpage.com/index.php/site/tip/displaying_a_progress_indicator/
Using the .Visible property is only half of the battle; the other thing you should do is to turn off
alerts using the .DisplayAlerts property, in this manner:
Application.DisplayAlerts = False
This should stop Excel from displaying any alerts, including the dialog box you notice.
A common method of providing feedback is through the use of the status bar. Using VBA, this is
done with a code line similar to the following:
This line causes the message Updating past months... to display on the status bar of the
application program. This message remains there until another message is written to the status
bar, either by your macro or by Excel.
If you want to erase the message on the status bar, there are two ways you can do it. The first is
to write an empty string to the status bar, as in the following code:
Application.StatusBar = ""
In this case, there is nothing between the quote marks, so an empty string is displayed on the
status bar, erasing whatever was there before. The other method is to use the following line:
Application.StatusBar = False
Writing the logical value FALSE to the Application.StatusBar property erases whatever you
wrote on the status bar before and restores the default status bar text.
Using the status bar is all fine and good, as long as the status bar is turned on. Excel can be
customized, by the user, so that the status bar is turned off. If this has been done, then you cannot
display messages on the status bar. The solution is to make sure the status bar is turned on before
you try to display a message.
You can control the display of the status bar by using the Application.DisplayStatusBar property.
If you set this property to a logical value (TRUE or FALSE), it turns the status bar on or off.
As an example of how to program this sort of process, consider the following code:
bStatusState = Application.DisplayStatusBar
Application.DisplayStatusBar = True
Application.StatusBar = "Updating past months..."
'
' Rest of program goes in here
'
Application.StatusBar = False
Application.DisplayStatusBar = bStatusState
The very first line of this code assigns the current value of the status bar (TRUE or FALSE,
meaning on or off) to the variable bStatusState. This same variable is used in the last line to reset
the status bar condition to its original state. In between, the status bar is turned on and a message
is displayed and later erased.
Sub GetFName()
Dim FName As Variant
Dim Msg As String
FName = Application.GetOpenFilename()
If FName <> False Then
Msg = "You chose " & FName
MsgBox Msg
Else
'Cancel was pressed
End If
End Sub
When you run this macro, you will see the standard Open dialog box used in Excel. The user can
select a file, and when they click on Open, the file name (including the full path) is assigned to
the variable FName. If the user clicks on the Cancel button, then FName is set equal to False.
(Thus the test for that in the code.)
The following VBA function can be used to check for the existence of a file. All you need to do
is pass it the full filename as a string, and the macro returns either True (if the file exists) or
False (if it doesn't).
This function works by using the Dir function, which checks for a file in a directory. If the file
exists, then Dir returns the full path of the file. The True/False condition of the function is
derived by comparing what Dir returns against an empty string. If something is returned, the file
exists because Dir doesn’t return an empty string.
If FileThere("c:\myfile.txt") Then
'
There is no way to do this using built-in Excel commands. You can, however, create a macro that
will do the checking for you. For instance, consider the following simple user-defined function:
The routine simply returns a True or False value, based on whether the specified file exists. The
value that is passed to the function needs to include a full path and file name. For example, if the
file specification (including the path) were in cell A1, you could use the following in a cell:
=FileExists1(A1)
You may not, however, want to put the full path name into the cell. In that case, you could
specify it in the actual formula, in this way:
Of course, you could instead specify the path in the user-defined function:
With such a function you could easily create a formula in your worksheet that would “flag” any
invoices missing from the directory:
=IF(FileExists2(A1),"","Missing Invoice")
For J = 1 to NumValues
Line Input #1, UserVals(J)
Next J
Close #1
In this example you should note that the first line read from the text file (MyFile.Dat) is assumed
to contain a value that indicates how many items are to be read in from the file.
The first thing written to the file is a numeric value indicating how many individual values will
follow it. Then a For … Next loop is used to create the balance of the file.
When using a macro to write information to a text file, you may want to add information to an
existing file, rather than creating a new text file from scratch. To do this, all you need to do is
open the file for Append rather than Output. The following code shows this process:
When the file is opened for Append mode, any new information is added to the end of the file,
without disturbing the existing contents.
These program lines use the LOF function to determine the length of the file. The last line then
positions the internal file pointer half way through the file. All subsequent reading or writing of
the file will take place from that position.
You can also use Seek as a function to determine your current position within a text file. This is
what this code does:
iCurPos = Seek(1)
This command leaves the internal file pointer where it was, but sets iCurPos to a value
representing how many characters into the file the pointer is. The iCurPos value is the position at
which all subsequent reading and writing of the file will take place.
Renaming a File
Your macros can rename a file by using the Name command. This is a holdover from other
versions of BASIC. The syntax is:
where OldFile is the name of the old file, and NewFile is the name of the new file. Both
filenames must either be string variables or be enclosed in quotes. Both filenames can contain
complete path names, but both must be on the same disk drive. If the path names differ, then the
command also has the side benefit of moving the file from one directory to another.
Deleting a File
Sometimes you may use a macro to create temporary files which you later need to delete.
Similarly, you may need to just delete a file within a macro. You can accomplish this task using
the Kill command. This is a holdover from other versions of BASIC. The syntax is:
Kill File
where File is the full path and file name of the file you want to delete. When you delete a file in
this manner, the file is not moved to the Windows Recycle bin; instead, it is immediately deleted
from your drive.
If desired, you can also use wildcard characters in the File specification. For instance, if you
wanted to delete all the files in the current directory that end in the TMP extension, you could
use a command like this:
Kill "*.tmp"
Unfortunately, there is no way to change the default. However, the changing of the column data
types can be done much more easily by applying a little of the “pick and choose” features
available in most Windows programs. Follow these steps:
2. When the dialog box is displayed that allows you to change column data types, select
the first column in the table.
3. Scroll to the right in the dialog box so the last column in the table is visible.
4. Hold down the SHIFT key as you click on the last column. Now all the columns should
be selected.
5. Change the data type to Text.
6. Continue with the import, as usual.
If you prefer an even faster way of inputting the information from the comma-delimited text file,
you can do so using a macro, thereby skipping the Excel import filters entirely. The following
macro, entitled (appropriately enough) Import, will do the trick:
Sub Import()
Open "d:\data.txt" For Input As #1
R = 1
While Not EOF(1) 'Scan file line by line
C = 1
Entry = ""
Line Input #1, Buffer
Length = Len(Buffer)
i = 1
While i <= Length 'split string into cells
If (Mid(Buffer, i, 1)) = "," Then
With Application.Cells(R, C)
.NumberFormat = "@" 'Text formatting
.Value = Entry
End With
C = C + 1
Entry = ""
Else
Entry = Entry + Mid(Buffer, i, 1)
End If
i = i + 1
Wend
If Len(Entry) > 0 Then
With Application.Cells(R, C)
.NumberFormat = "@" 'Text formatting
.Value = Entry
End With
End If
R = R + 1
Wend
Close #1
End Sub
You should note that you can change the first line of the macro to represent the name of the file
you are importing. You should also understand that this macro works on the simplest of comma-
delimited text files. If the file was created with quote marks around each field (as is sometimes
the case), then the macro will not give the desired results and would need to be changed to
compensate for the quote marks. Or, as an alternative, you could simply use search for and
remove the quotes after the macro is through importing the information.
There is no way to do this in Excel; alignment of imported data is based on system defaults, such
that text is left-justified and numbers are right-justified. One option, however, would be to add a
prefix character that you could then later “parse” with a macro to apply the desired alignment.
For instance, you could use “<” for left, “^” for center, and “>” for right. When Excel imports
the CSV files, the fields are treated as text. You can then run this macro to search for the leading
alignment character and do the desired action:
Sub SetJustification()
Dim rCell As Range
The macro checks each cell in the worksheet. If the cell begins with an alignment character, then
the character is removed and the proper alignment is applied.
This works this way by design in VBA. The Excel implementation of the export routines for
VBA always use whatever the Windows regional settings are to determine how items in a CSV
should be separated. Specifically, the routine looks at the List Separator field for the delimiter.
This means that you can, if desired, change the delimiter to a semicolon by changing the List
Separator setting in your regional settings configuration.
If you don’t want to change the regional settings, then you can instead write your own macro that
will output the file in any way you desire. Consider, for a moment, the following macro, which
will output the file:
Sub CreateFile()
FName = ActiveWorkbook.Name
If Right(FName, 4) = ".xls" Then
FName = Mid(FName, 1, Len(FName) - 4)
End If
Columns(1).Insert Shift:=xlToRight
For i = 1 To Range("B65000").End(xlUp).Row
TempString = ""
For j = 2 To Range("HA1").End(xlToLeft).Column
If j <> Range("HA1").End(xlToLeft).Column Then
TempString = TempString & _
Cells(i, j).Value & ";"
Else
TempString = TempString & _
Cells(i, j).Value
End If
Next
Cells(i, 1).Value = TempString
Next
Columns(1).Select
Selection.Copy
Workbooks.Add
Range("A1").Select
ActiveSheet.Paste
Application.CutCopyMode = False
This macro takes a unique approach to creating the output file. What it does is to insert a column
at the left of your worksheet, and then concatenates all the data to the right of that column into
the newly inserted column A. It adds a semicolon between each field. Once that is done, it grabs
the information it put into column A and writes it into a new workbook. This workbook is then
saved to disk using the xlPrinter file format, which means that it is put out “as is” without any
modification whatsoever.
If you prefer a more direct approach, writing the information directly to a file without making
changes to your worksheet, take a look at the macro at this blog post:
http://www.dicks-blog.com/archives/2004/11/09/roll-your-own-csv/
The macro uses commas between each field, but it can be easily modified so that it uses
semicolons instead.
Creating a Directory
If you need to, you can create a disk-drive directory (folder) using VBA. This is done with the
MkDir command, and is a remnant from the same command in earlier versions of BASIC. The
syntax is:
MkDir DirName
where DirName is the full pathname of the directory you want to create. If you do not use a
string variable to specify the directory name, then DirName must be enclosed in quotes. You
might want to use this command if you want to create a directory where you can store temporary
files you are building with your macro.
Changing Directories
VBA provides a very rich programming environment. You can do many things with macro code
that you cannot necessarily do using Excel’s native commands. For instance, you may want to
change the current directory in the middle of a macro. This may be necessary in order to find a
particular file or to do some other file-oriented task. VBA provides the ChDir command to
change directories. The syntax is as follows:
ChDir DirName
where DirName is the full pathname of the directory to which you want to change. If you do not
use a string variable to specify the directory name, then DirName must be enclosed in quotes. If
the directory name you supply does not exist, the command fails with an error.
MyDir = CurDir
When this line is executed, MyDir (a string) will be equal to the full path of the current directory.
Removing a Directory
In an earlier issue of ExcelTips you learned how to create a directory from within a macro. There
are times that programmers (even macro programmers) create directories to store temporary
files. When they are done, the files are deleted and the directory is removed. To remove a
directory, you use the RmDir command in your macro, as shown here:
RmDir DirName
where DirName is the full pathname of the directory you want to delete. If you do not use a
string variable to specify the directory name, then DirName must be enclosed in quotes. If there
are any files in the directory or any subdirectories contained in the subdirectory, the command
fails with an error. (This means you should delete all the files and subdirectories before trying to
remove a directory with RmDir.)
ChDrive "E"
This particular statement changes the current drive to E:. You can change to a different drive by
simply changing the drive letter enclosed within the quote marks.
MyDrive = “E:”
MyFolder = “\MyDocs\ThisFolder\”
ChDrive MyDrive
ChDir MyFolder
When done, the current directory will be E:\MyDocs\ThisFolder\. VBA provides a handy
shortcut that allows you to easily specify both the drive and directory using the same
information. Consider the following:
MyPath = “E:\MyDocs\ThisFolder\”
ChDrive MyPath
ChDir MyPath
This code contains one less line (and one less variable), but it does the same thing. VBA, when
executing the ChDrive command, only pays attention to the drive letter in a path. This allows
you to easily set the single variable to your path, and then use it when both setting drives and
directories.
One way to accomplish this task is to just not use Excel. Instead, use Word’s mail merge feature
to pull information from an Excel database. This approach works best if you are creating a
document from well-defined information. If, however, you need to open a series of documents
and copy the data from the Excel database into the documents, then mail merge won’t do the
trick.
Word has a special name for using macros to work with different Office applications: Office
Automation. Creating Office Automation macros is a bit more complex than creating a macro
that will work solely within a specific application, such as Excel. One of the things you may
want to do is to download a free Help file that includes a good deal of information about Office
Automation applications. You can download the file at the following Microsoft page:
http://support.microsoft.com/?kbid=302460
The basic procedure to open a Word document from within an Excel macro is to create an object
that references the Word application, and then use that object to open the document. The
following code illustrates this concept:
Sub OpenWord()
Dim wdApp As Object
Dim wdDoc As Object
Set wdApp = CreateObject("Word.application")
Set wdDoc = wdApp.Documents.Open _
(FileName:="C:\Path\myTestDoc.doc")
wdDoc.Close savechanges:=False
Set wdDoc = Nothing
wdApp.Quit
Set wdApp = Nothing
End Sub
You’ll need to change the path and document name of the document you want to open, but this
code very nicely demonstrates what needs to be done to open the document. As written, the
Word document (indeed, the entire Word application) will not be visible on screen. If you prefer
to have the application visible, you should use this code line near the beginning of the macro:
wdApp.Visible = True
Another approach to working with a Word file from inside your Excel macro is to use DDE and
the SendKeys function to copy the information. Consider the following DDE command:
ChannelNumber=Application.DDEInitiate{ _
app:="WinWord", topic:=FullPath
The DDEInitiate method uses two properties: app and topic. The app property indicates the
application you are opening via DDE. Typical examples could be “calc” for the calculator or
“WinWord” (in this case) for the Word application. The topic property indicates the full path to
the document file you are opening. In this case, the full path is contained in the FullPath variable.
Using this method, you can open a document and then use SendKeys to copy information to that
document:
Sub PasteExcel2Word()
Dim channelNumber As String 'Application Handle
Dim FullPath As String
FullPath = 'C:\MyFolder\MyFile.Doc'
'Replace above with a file or loop of files
channelNumber = Application.DDEInitiate( _
app:="WinWord", topic:=FullPath
SendKeys "^v", False
Application.DDETerminate channelNumber
End Sub
The Copy method is used to copy information to the Clipboard, and then SendKeys uses ^v
(CTRL+V) to paste the information into the Word documented opened using DDEInitiate.
1. Open a new Lotus spreadsheet, and set up a simple keystroke macro like this one:
{d}{?}{R}{?}{R}{?}{L 2}{Branch \e}
2. Name the macro '\e. (Go into Lotus Help if you don’t know how to name your macro.)
3. Save the spreadsheet.
4. Close Lotus 1-2-3 and open Excel.
5. Display the Open dialog box. (In Excel 2010 display the File tab and click Open. In
Excel 2003 click the Office button and click Open. In older versions choose Open from
the File menu.)
6. Using the Files of Type drop-down list, choose Lotus 1-2-3 Files (*.wk?).
7. Use the controls in the dialog box to locate and select the file you created in steps 1
through 3.
8. Click on Open.
As part of opening the Lotus 1-2-3 file, Excel converted the macro so it can be run in Excel. In
this example you would press CTRL+E to start the macro. I found that I can modify the macro
right in Excel. I can also click and drag the macro to another location in the spreadsheet. What
you can’t do is rename it and copy it to a different worksheet or workbook. But you can use
many of the older keystroke commands to really make your imported macros powerful.
This does, indeed, seem to be the case, Gary. The HYPERLINK worksheet function seems to
ignore the page specification for some strange reason. There also doesn’t seem to be a way
around this problem with the function.
Fortunately, you can use a macro to do the opening, if you desire. The following macro relies
upon Internet Explorer to open the PDF and display the proper page:
Sub OpenPDFpage()
Dim myLink As String
Dim TargetPage As Double
Dim objIE As New InternetExplorer
myLink = "path/filename.pdf"
TargetPage = 7 'Page number to be shown
With objIE
.Navigate myLink & "#page=" & TargetPage
.Visible = True
End With
End Sub
The code could also be rather easily changed to a function to which you can pass the desired path
and target page.
There are several ways this problem can be approached. First, you can configure Excel so that it
doesn’t ask the question. This option affects all workbooks opened on the system. To set the
option, follow these steps if you are using a version of Excel prior to Excel 2007:
1. Choose Options from the Tools menu. Excel displays the Options dialog box.
2. Make sure the Edit tab is selected.
If you are using Excel 2007 or a later version, follow these steps instead:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
2. Make sure the Advanced option is selected at the left of the dialog box.
3. Scroll through the available options until you see the General section.
With the check box cleared, Excel no longer asks and all links are updated automatically.
If you are using Excel 2002 or Excel 2003, and you want an option that doesn’t affect all
workbooks, you can follow these steps:
4. Choose the third option, Don’t Display the Alert and Update Links.
5. Click on OK to dismiss the Startup Prompt dialog box.
6. Click on Close to dismiss the Links dialog box.
7. Save your workbook.
You can accomplish the same thing in Excel 2007 or a later version by following these steps:
Now the workbook can be opened without Excel asking about updates. This, of course, affects
just this workbook, and it affects it regardless of how it is opened. In other words, it will affect
how the workbook is opened by the macro as well as when it is opened by a user.
Perhaps the best approach is to simply make a small change in your macro—the one that opens
the workbook containing links. There is a good chance that the code to open the workbook looks
something like this:
Workbooks.Open FileName:="MyWorkbook.xlsx"
This UpdateLinks property is optional, but without it the “Do you want to update links” dialog
box is displayed. If you include the property with the setting shown, then Excel will update both
remote and external references in the workbook as it is opened.
The best way to check the data and display the desired message box is to use a macro that is
triggered by the Worksheet_Change event. This event is triggered any time the contents of a cell
are changed. It is not, however, triggered by a change in what is displayed in a cell. For instance,
if a new piece of commodity data is placed into a cell, then the event is triggered. However, if a
formula is recalculated and a new result of that formula displayed, the event is not triggered.
Why? Because the formula itself didn’t change; it was only the result of the formula (what is
displayed) that was changed.
Once the Worksheet_Change event is triggered, the macro can do anything you want it to do,
including displaying your message. For this example, let’s assume that the range to check is
A1:C5 (this is where the commodity data is being inserted) and that the criteria you want to
trigger the message is that the average of the range is 5. If the contents of any cell in the range is
changed and the average of the values in the range is 5, then a message is displayed.
It is important that this macro be placed in the sheet object for the worksheet you want to
monitor. When you display the VBA Editor, right-click on the desired worksheet in the Project
Explorer area, then choose View Code from the resulting Context menu. This code window is
where you place the macro.
The macro, again, is triggered anytime there is a change anywhere on the worksheet. The macro
then uses the Intersect function to determine if the change occurred within the desired A1:C5
range. If it did, then the average of the range is checked, and the message displayed if the result
is 5.
Even when the range is formatted as text in Excel, complete with leading zeroes, Access more
often than not converts these values to numbers. However, if the number is preceded with an
apostrophe, as for a label, Access will correctly import it as text without the leading apostrophe.
To prepare Social Security Numbers for importing in Access a quick little macro can come in
handy—one that makes sure that leading zeros are present and that the apostrophe is in place for
the cell. To use the macro, just select the range of Social Security Numbers and then run the
macro:
Sub SSN2Text()
Dim c As Range
Application.ScreenUpdating = False
'Format selected cells as text
Selection.NumberFormat = "@"
For Each c In Selection
If Left(c, 1) = "'" Then
'strip the apostrophe, if any
c = Mid(c, 2, 99)
Else
c = "'" & Right("000000000" & c, 9)
End If
Next c
Application.ScreenUpdating = True
End Sub
The solution for the ZIP Codes is similar in nature. The macro to process ZIP Codes steps
through each cell in the selection, formats it as text, adds a leading apostrophe, and plugs in any
leading zeroes. The difference is that the macro must also account for instances where there are
either five-digit or nine-digit ZIP Codes.
Sub ZIP2Text()
Dim c As Range
Application.ScreenUpdating = False
'Format selected cells as text
Selection.NumberFormat = "@"
For Each c In Selection
If Left(c, 1) = "'" Then
'strip the apostrophe, if any
c = Mid(c, 2, 99)
End If
There is no native way to do this in Excel or in Outlook. You can, however, export a list of your
contacts from Outlook and then load them into Excel. (How you do the export varies depending
on your version of Outlook. Just look for the Export command within your version and then
follow the steps that initiated once you choose the command.)
If you want to pull contact information for a single contact, based on a name you specify in
Excel, then the best solution is to use a macro. You can use a user-defined function to return
pieces of information about a contact, as shown in the following:
Application.Volatile
sNameWanted = rRng.Value
sRetValue = "Not Found"
Case 4
sRetValue = .BusinessAddressState
Case 5
sRetValue = .BusinessAddressPostalCode
Case 6
sRetValue = .BusinessTelephoneNumber
Case 7
sRetValue = .Email1Address
End Select
End If
End With
Next lItem
olA.Quit
GrabContactInfo = sRetValue
End Function
The purpose of this function is to return a piece of data about a contact whose name you specify.
All you need to do is specify the reference to a cell that contains the name and specify what piece
of information you want. For instance, if the contact’s name is in cell B3 and you wanted the
contact’s address returned, you would use this in a cell:
=GrabContactInfo(B3, 2)
There are a few things to remember with this macro. First, if it cannot find an exact match on the
name you specify, then it returns “Not Found”. Second, if your contact list in Outlook contains
more than one contact with the same name, this function will always return the information
associated with the last contact.
In addition, this macro will not work properly unless you set up Excel macros to handle
references to Outlook objects. You do that by following these steps within the VBA Editor:
1. Choose References from the Tools menu. VBA displays the References dialog box.
2. Scroll through the list of references until you see one called Microsoft Outlook Object
Library. (There may be a version number included in the reference name, such as
Microsoft Word 14.0 Object Library.)
3. Make sure the check box to the left of the object library is selected.
4. Click on OK.
There are a couple of ways you can approach this task, and which one you choose depends
largely on the nature of the data in your worksheet. If the cells contain active hyperlinks (ones
that if you click on them, the address is opened in a browser), then you can use a rather simple
macro:
Sub FollowHyperlinks1()
Dim MyRange As Range
Dim hl As Hyperlink
The macro simply looks at all the hyperlinks in the range of A1:A10 and uses the Follow method
to open each of them in your default browser. Because of the way in which your operating
system transfers information from Excel to your browser, it is a good idea to have your browser
open before you run the macro. The reason for this is because, in testing, we found that you may
actually end up with two instances of the browser open, with some addresses open in one
instance and some in the other. This apparently occurs because of the delay in opening the first
instance of the browser. If the browser is open before the macro is run, then there is no delay and
each address opens in a different tab of the same browser instance.
If the addresses in your worksheet may not be active hyperlinks, then you can't rely upon using
the Hyperlinks collection for the range. Instead, you need to look at the value of each cell in the
range:
Sub FollowHyperlinks2()
Dim MyRange As Range
Dim cell As Range
Dim sTemp As String
This approach uses the FollowHyperlink method to load the address in the sTemp variable. In
this case, it doesn't matter whether the contents of the cells are active hyperlinks or not; the code
still tries to open them in a browser.
Finally, if your data may not contain fully qualified addresses, then you'll need to use a different
approach, still. For instance, Steve mentioned having addresses such as www.example.com in
the worksheet, but such an address will not work with the examples so far. If your data is missing
http:// at the beginning (or some variant, such as https://), then the code won't open the address in
the browser. In your data has this peculiarity, then a slight modification to the macro is in order:
Sub FollowHyperlinks3()
Dim MyRange As Range
Dim cell As Range
Dim sTemp As String
Note that this example examines the contents of sTemp to see if it has the characters "://" within
it. If not, then the prefix http:// is added to the cell contents and Excel tries to use the
FollowHyperlink method to open the modified address.
What if you want to start the browser and open an HTML file from within a VBA macro,
however? There are a couple of ways that you can do this. The first is to simply open a new
Internet Explorer object within your code. A macro to do this would appear as follows:
Sub DoBrowse1()
Dim ie As Object
Set ie = CreateObject("Internetexplorer.Application")
ie.Visible = True
ie.Navigate "c:\temp\MyHTMLfile.htm"
End Sub
This macro will open the file c:\temp\MyHTMLfile.htm in a new Internet Explorer window. If
you want to instead open a Web page from over the Internet, you can do so simply by changing
where you want to navigate. (Replace the file path with a URL.)
Another way to accomplish the same task is to rely on Excel to figure out what your default
browser is and open the HTML resource. The following macro does the trick:
Sub DoBrowse2()
ActiveWorkbook.FollowHyperlink _
Address:="c:\temp\MyHTMLfile.htm", _
NewWindow:=True
End Sub
Again, the browser opens a new window and displays the specified file. You can change the
Address parameter to any URL that you desire.
There is no way to do this within Excel; a hyperlink in a worksheet, when clicked, relies on
whatever the default browser is on the system being used. There is a workaround that you can
try, however: You could create a macro that actually opens a target address using a specific
browser.
For example, consider the following macro. It automatically opens an instance of Internet
Explorer and opens a website in that browser:
Sub LaunchIE()
Dim IE As Object
Set IE = CreateObject("InternetExplorer.Application")
IE.navigate "http://excel.tips.net/"
IE.Visible = True
Set IE = Nothing
End Sub
The macro could easily be assigned to a shortcut or to a toolbar button. It isn't terribly flexible,
however, when it comes to which browser is being used (it is always Internet Explorer) and
which site is displayed (it is always the ExcelTips site). You can make it a bit more flexible in
this manner:
Sub Testing()
Call showURL("Firefox", "http://www.tips.net")
Call showURL("IE", "http://excel.tips.net")
End Sub
Note that the main routine—showURL, the one that does all the work—can work with either
Internet Explorer or Firefox. The Testing routine shows how to launch the browsers; all you need
to do is specify which browser you want and what URL you want to open in that browser.
As is the case with most tedium in Excel, the solution is to use a macro to do the conversion. To
be effective, the macro would need to step through each cell in a selected range and, if the cell is
not blank, convert the contents to a hyperlink. The following will do the trick:
Sub URL_List()
For Each cell In Selection
If cell.Value <> "" Then
If Left(cell.Value, 7) = "http://" Then
URL = cell.Value
Else
URL = "http://" + cell.Value
End If
ActiveSheet.Hyperlinks.Add Anchor:=cell, _
Address:=URL, TextToDisplay:=cell.Value
End If
Next cell
End Sub
The macro is not foolproof; it assumes that if a cell contains anything at all it is a valid URL.
What it does is to check the cell contents and, if the contents aren't prefaced by the "http://" text,
then it is added. The hyperlink is then created based on the cell contents.
There is no way to do this that we've been able to discover. The closest we can come is to check
the whether a hyperlink is followed or not and then somehow indicate that status with a condition
or value that survives a save operation. For instance, consider the following macros:
Every time a hyperlink is followed, the second macro is run. It sets the color of the cell
containing the hyperlink. Then, as the workbook is saved, the first macro is run. It checks all the
cells containing hyperlinks, and if their interior color is the "key" color (color value of 37), then
the style of the cell is set to a style named "Followed Hyperlink". This style setting for the cell
will survive the save operation; the only thing you need to do is make sure that you've defined
the style to appear as you want your followed hyperlinks to appear.
The easiest approach is to run a macro that deletes all the hyperlinks. The following macro
quickly removes all hyperlinks in a worksheet, without affecting anything else it may contain:
Sub KillLinks()
Do Until ActiveSheet.Hyperlinks.Count = 0
ActiveSheet.Hyperlinks(1).Delete
Loop
End Sub
If you want to delete these hyperlinks, you can do so by right-clicking on them and choosing
Hyperlink | Remove Hyperlink from the Context menu. Doing this with dozens or hundreds of
hyperlinks can quickly consume a huge amount of time.
To delete all the hyperlinks in the active worksheet at the same time, you can use a handy one-
line macro:
Sub DeleteHyper()
ActiveSheet.Hyperlinks.Delete
End Sub
Select the worksheet you want to affect, run the macro, and you just saved yourself tons of time!
This can be easily done with a macro. The reason is because the hyperlinks can be examined and
changed using regular string functions. The following macro provides a simple way to address
the issue.
Sub EditHyperlinks()
Dim lnkH As Hyperlink
Dim sOld As String
Dim sNew As String
sOld = "http://www.mysite.com"
sNew = "c:/documents/mycopy/"
This routine steps through all the hyperlinks in the current worksheet and makes modifications, if
necessary, to each one. Both the hyperlink and the displayed text are changed, as appropriate. All
you need to do is make changes to the sOld and sNew strings to specify what you are searching
for and what you want to replace it with.
You should note that this macro uses the Replace function, which is built into the later versions
of VBA. If you are using an older version that does not include the Replace function (you’ll
know because you’ll get an error when you try to turn the macro) then you will need to create
your own Replace function that replaces one portion of a string with another. Such functions
have been covered in other issues of ExcelTips.
At first blush it might seem that you could use Excel's regular Find and Replace feature to find
the hard drive designation (as in file://c:) and replace it with a network drive (as in
file://shareddrive). The problem is that this approach only addresses part of the problem—it only
changes the displayed portion of the hyperlink, not the underlying hyperlink itself. The only way
you can get to the hyperlink itself is through the use of a macro.
Assuming that all the hyperlinks that need changing are on the same worksheet, then you can use
the following macro:
Sub FixHyperlinks1()
All you need to do is change the values assigned to the sOld and sNew variables. If you get an
error when you try to run the macro—an error with the line containing the Replace function—it
is because the Replace function isn't available in all versions of Excel. In that case you should
use the macro in this listing, instead:
Sub FixHyperlinks2()
Dim wks As Worksheet
Dim hl As Hyperlink
Dim sOld As String
Dim sNew As String
Note that the only difference is the use of the Substitute worksheet function.
Quite simply, adding and editing hyperlinks is not allowed when using a shared workbook. The
simplest way around it is to put the links in separate cells as text and then use the HYPERLINK
formula to reference those cells.
For example, if the URL is entered into cell E2, you could use the following formula in a
different cell:
=HYPERLINK(E2, E2)
The first argument in this formula is to the cell that contains the address and the second argument
is for the text to be displayed for the hyperlink. This approach requires two additional columns
(for the HYPERLINK formulas) but will not require unsharing and resharing the workbook.
The only other option is to create a macro that can automate the process of unsharing and
resharing the workbook. The following macro will do this and convert whatever is in the selected
cell into a hyperlink.
Sub AddHyperlink()
Dim cell As Range
Application.DisplayAlerts = False
The first method is to remember that the hyperlinks for e-mail addresses all start with the text
“mailto” followed by a colon. Thus, you can use a formula that will strip out the first part of the
hyperlink. For instance, if the e-mail hyperlink is in cell A1, you can use this formula:
=RIGHT(A1,LEN(A1)-7)
This checks the length of the cell contents, and then extracts all of it except the first seven
characters, which is the “mailto:” portion. You could also use a formula that relies on the
SUBSTITUTE function:
=SUBSTITUTE(A1,"mailto:","")
If you prefer, you can use a macro to do the conversion from hyperlink to text-only e-mail
address. The following single-line macro is a user-defined function that returns the converted
hyperlink:
In order to use the macro, all you need to do is use the function in some cell of your worksheet,
in this manner:
=ExtractEmailAddress(A1)
3. Select and copy (CTRL+C) the entire URL from the Address field of the dialog box.
4. Press ESC to close the Edit Hyperlink dialog box.
5. Paste the URL into any cell desired.
Note that this is for a single hyperlink. If you have a whole bunch of hyperlinks in a worksheet
and you want to recover the URLs, you need to do this for each and every hyperlink. Obviously
this can get tedious very quickly.
The cure for tedium—like them or not—is a macro. With a macro, getting at the underlying URL
for a hyperlink is child’s play. All the macro needs to do is pay attention to the Address property
of the hyperlink. The following is an example of a macro that will find each hyperlink in a
worksheet, extract each one’s URL, and stick that URL in the cell directly to the right of the
hyperlink.
Sub ExtractHL()
Dim HL As Hyperlink
For Each HL In ActiveSheet.Hyperlinks
HL.Range.Offset(0, 1).Value = HL.Address
Next
End Sub
Instead of a “brute force” macro, you could also create a user-defined function that would extract
and return the URL for any hyperlink at which it was pointed:
In this case you can place it where you want. If you want, for example, the URL from a
hyperlink in A1 to be listed in cell C25, then in cell C25 you would enter the following formula:
=GetURL(A1)
The problem is that you’ll often get more than just the table data. If there were other objects in
the data you copied from the Web, those objects will be pasted into the worksheet, as well. It is
not uncommon to end up with all sorts of small graphics in the worksheet. If these graphics were
originally hyperlinks, you may want to actually extract the hyperlink and then delete the graphic.
This would make the data in the worksheet much more usable.
The way to do this is with a macro. Once you’ve pasted the Web information into the worksheet,
run the following macro.
Sub ConvertHLShapes()
Dim shp As Shape
Dim sTemp As String
This macro steps through each of the shapes in the worksheet. It then checks to see if the shape
has an associated hyperlink. If it does, then the address of that hyperlink (in the sTemp variable)
is placed into the cell at the top-left corner of where the shape is located. The macro deletes any
shapes that have hyperlinks; you can force it to delete all shapes in the worksheet by simply
moving the shp.Delete line to the outside of the If … End If structure.
Processing and extracting information from hyperlinks in this manner requires the use of a
macro. The following is an example of a flexible macro that examines whatever hyperlinks are in
the selected range of cells. If a hyperlink is found, the URL for the hyperlink is entered to the
right of the hyperlink and then the hyperlink itself is deleted. This leaves the display text in the
cell where the hyperlink used to be.
Sub GetHLInfo()
Dim rRng As Range
Dim cell As Range
The first thing that most people try is to use Go To Special, in this manner:
When you do this, Excel selects a number of the objects in the worksheet, and you can then press
the DELETE key to get rid of them. The problem is that this method doesn’t select all the non-data
items in the worksheet; it only selects a subset of them—those items that are considered
“objects” by Excel.
A better solution is to use a macro to select all the shapes in the worksheet and then delete them.
This is fairly simple to do, using a macro like this one:
Sub DeleteAllShapes1()
Dim shp As Shape
For Each shp In ActiveSheet.Shapes
shp.Delete
Next
End Sub
The macro just loops thru each shape on the active worksheet and deletes each one. You could
expand on the macro just a bit by having it also delete all the hyperlinks that are pasted in the
worksheet. All it takes is the addition of a single line:
Sub DeleteAllShapes2()
Dim shp As Shape
For Each shp In ActiveSheet.Shapes
shp.Delete
Next
ActiveSheet.Hyperlinks.Delete
End Sub
If, for some strange reason, these macros don’t get rid of all the non-data items you want
removed, there is another approach you can use: make a stop in NotePad before Excel. Simply
paste your Web data into a blank NotePad document, then select that information (after it is
pasted) and copy it back to the Clipboard. Then, paste it into Excel. The only thing that is left
should be straight data.
At this point, you should see a new workbook with a single worksheet in it—a copy of the
worksheet you want to send. E-mail this workbook, and you’ve accomplished what you wanted
to do. Once it is e-mailed, you can delete the workbook, as your worksheet is still in the original
workbook, as well.
If you need to routinely e-mail the current worksheet to someone else, you may want to create a
macro that will do the task for you. The macro you create will vary, depending on the e-mail
program you are using. For this reason, it is not possible to provide a comprehensive macro-
based answer in this tip. However, it may be instructive to provide an example of a macro that
can e-mail a worksheet using Outlook as the mail program.
Sub EmailWithOutlook()
Dim oApp As Object
Dim oMail As Object
Dim WB As Workbook
Dim FileName As String
Dim wSht As Worksheet
Dim shtName As String
Application.ScreenUpdating = False
FileName = WB.Worksheets(1).Name
On Error Resume Next
Kill "C:\" & FileName
On Error GoTo 0
Note that the macro does effectively what was done in the earlier steps: it copies the worksheet
to a new workbook and then e-mails that workbook. It then deletes the workbook and returns you
to your normal use of Excel.
If you are looking for a more in-depth discussion of how to e-mail a worksheet using various
programs, then you will definitely want to visit the following Web page:
http://www.rondebruin.nl/win/s1/outlook/mail.htm
You can use a macro to temporarily disable the Reviewing toolbar, but it won't permanently
disable it. The following macro will perform the temporary suppression:
Sub SuppressReviewingToolbar()
Application.CommandBars("Reviewing").Enabled = False
End Sub
You can add this to a module in your personal.xls so it is always available and, if desired, create
a keyboard shortcut to it or assign it to a toolbar button. That way you can quickly close the
toolbar whenever it pops up.
This presents a problem when Nikolas is away from his computer because it may mean that the
Web query doesn’t collect all the data it should be cause it is patiently waiting for the OK button
to be clicked when it runs into a problem. Nikolas wants a way to tell the Web query to not
display the message and just go back to waiting if it can't connection on the current attempt.
Unfortunately, there is no way to tell Excel to do what you want. When the “Unable to open the
web page...” message appears, it is virtually impossible to suppress the message. The only
solution is to try to create a macro that works around the problem. For instance, you could
develop a macro that creates an instance of Internet Explorer (which doesn’t have the problem)
to test for an error reaching the Web page. The following macro implements this approach.
Option Explicit
'Declare Sleep API
Private Declare Sub Sleep Lib "kernel32" (ByVal nMilliseconds As Long)
GetData = "N/A"
Attempt = 0
retry:
Attempt = Attempt + 1
If ieDocNew.Scripts.Length = 13 _
And ieNew.LocationName = "Microsoft Excel Tips" _
Then
Connected = True
GetData = "Data successfully captured"
'Clean up IE objects
Set ieDocNew = Nothing
ieNew.Quit
Set ieNew = Nothing
DoEvents
If Attempt < 10 And Not Connected Then GoTo retry
End Function
Note that this macro requires some configuration within the VBA interface to properly operate.
Specifically, you need to choose References from the Tools menu and make sure that the project
includes references to the Microsoft HTML Object Library and the Microsoft Internet Controls.
What the macro does is to use IE to connect to the URL passed to the function (in strStartURL)
and then grab the content that is found there. If the connection is successful, then Connected is
set to True and you can parse and use the data at the site. The function, as written, passes back
“Data successfully captured” to the calling routine, but you could just as easily pass back some
value grabbed from the remote site. That value could then be stuffed, but the calling routine, into
a worksheet.
Note, as well, that the function does some rudimentary parsing on the page it captures, and only
considers the connection successful if it finds some expected wording in the page title found at
the URL.
To get a feel for how the macro works, use some macros like the following:
Sub TEST_GetData1()
MsgBox GetData("http://excel.tips.net")
End Sub
Sub TEST_GetData2()
MsgBox GetData("http://excel.tipsxx.net")
End Sub
Sub TEST_GetData3()
MsgBox GetData("http://excel.tips.net/junk")
End Sub
The first one should work; the second two should display a message that says “N/A.”
There are many ways you can go about this. Perhaps the easiest way is to just "hide" the
information in cell A1 so it is not visible and won't print out. One way to do this is to format the
text in the cell as white, since white-on-white is quite invisible. The link would still be there and
could easily be clicked, but it wouldn't be visible.
A similar result can be had by applying a custom format to the cell. Just use the format ";;;"
(that's three semicolons, without the quote marks) and the information in the cell disappears from
view. Again, you can still click the link, even though it is quite invisible.
Another way to approach the problem is to define print areas for each of your worksheets. Just
exclude the first row of each worksheet from the print area, and it will never show up on the
printout. The added benefit to this approach is that the hyperlink is still visible on each
worksheet.
You could also put your hyperlink into a text box instead of cell A1. The text box could then be
formatted so that it doesn't print. (Select the text box, right-click and choose Size and Properties,
display the Properties tab, and uncheck the Print Object check box.)
Another approach is to not use hyperlinks in your worksheets, but instead add a form button that,
when clicked, runs a macro that takes the user to the main worksheet. (How you create form
buttons has been discussed in other issues of ExcelTips.) Form buttons aren't included when you
print your worksheets.
A rather unique approach is to use Microsoft Word to help you create the link. You can, in
Word, create a hyperlink and then format that hyperlink as Hidden text. (How you format
Hidden text can be found on the WordTips website.) Then, copy the text of the link to the
Clipboard and paste it into Excel as a Word object. The object can then retain the features of
Word—including the text being hidden—and still be "clickable" in Excel.
Finally, you could use macros to facilitate printing your worksheets. Add the following macro to
the ThisWorkbook object:
All this does is to apply, to all the worksheets in the workbook, the special custom format
described earlier in this tip. The macro is automatically run just before printing. After printing
the formatting is still in the worksheets. You can then include a second macro to apply the
General format to cell A1 in the workbook being activated:
It seems that the best way to handle this is to use an outmoded (but still available) Excel 4
function to determine the number of total printed pages in a worksheet. Then you can use the
HPageBreaks and VPageBreaks collections to figure out where the cell falls in the matrix of
pages that will be printed. The following is an example of a macro that utilizes these items:
Sub PageInfo()
Dim iPages As Integer
Dim iCol As Integer
Dim iCols As Integer
Dim lRows As Long
Dim lRow As Long
Dim x As Long
Dim y As Long
Dim iPage As Integer
iPages = ExecuteExcel4Macro("Get.Document(50)")
With ActiveSheet
y = ActiveCell.Column
iCols = .VPageBreaks.Count
x = 0
Do
x = x + 1
Loop Until x = iCols _
Or y < .VPageBreaks(x).Location.Column
iCol = x
If y >= .VPageBreaks(x).Location.Column Then
iCol = iCol + 1
End If
y = ActiveCell.Row
lRows = .HPageBreaks.Count
x = 0
Do
x = x + 1
Loop Until x = lRows _
Or y < .HPageBreaks(x).Location.Row
lRow = x
If y >= .HPageBreaks(x).Location.Row Then
lRow = lRow + 1
End If
One thing that you should keep in mind with this macro is that the HPageBreaks and
VPageBreaks collections are only considered accurate if you are viewing the worksheet in Page
Break Preview (View | Page Break Preview or, in Excel 2007 and later, by clicking Page Break
Preview on the status bar). Thus, you'll want to make sure that you are in that mode before
selecting a cell and running the macro.
There are a few ways you can go about tackling this problem, all of them involving the use of
macros. If you actually want to print all the worksheets in the current workbook and none of
those worksheets is over a single page in length (when printed), then the following macro will set
the center section of the header as requested:
Sub PageNums1()
Dim sheet As Worksheet
Dim J As Integer
J = 1
On Error Resume Next
For Each sheet In Worksheets
Sheets(J).PageSetup.CenterHeader = "Page 21" & Chr(64 + J)
J = J + 1
Next
End Sub
Note that the macro doesn't actually print anything; all it does is to change the header
information. If you really only want to print out the current worksheet and that worksheet will
require multiple pages on the printout, then the following should work just fine:
Sub PageNums2()
Dim X As Integer
Dim Y As Integer
Dim Z As Integer
Z = 1
For X = 1 To ActiveSheet.HPageBreaks.Count + 1
For Y = 1 To ActiveSheet.VPageBreaks.Count + 1
ActiveSheet.PageSetup.CenterHeader = _
"Page 21" & Chr$(64 + Z)
Worksheets.PrintOut Z, Z
Z = Z + 1
Next Y
Next X
End Sub
This macro calculates pages based on the position of the horizontal (HPageBreaks) and vertical
(VPageBreaks) page breaks on the printout. You could also try just working with the Pages
collection, in this manner:
Sub PageNums3()
Dim J As Integer
For J = 1 To ActiveSheet.PageSetup.Pages.Count
ActiveSheet.PageSetup.CenterHeader = "Page 21" & Chr(64 + J)
Worksheets.PrintOut J, J
Next J
End Sub
You should note that regardless of the approach you select, you'll run into problems if the
printout requires more than 26 pages.
There is no setting that we've been able to locate. Instead, it appears that Excel determines both
the X and Y values based on the current print job, not on worksheets or the entire workbook. In
other words, if your workbook contains three worksheets, and you print all three, the X and Y
values will be different than if you choose to print only the second or the second and third
worksheets.
The solution to this is to use a small, simple macro that steps through all the worksheets in a
workbook and prints the worksheets individually. As far as Excel is concerned, then, you are
performing multiple print jobs, thus the Y value will get reset on a worksheet-by-worksheet
basis. Here's the macro:
Sub PrintProperly()
Dim sht As Worksheet
For Each sht In ActiveWorkbook.Worksheets
sht.PrintOut copies:=1
Next sht
End Sub
Assign the macro to a shortcut key or to a button on the Quick Access Toolbar, and you'll be able
to invoke it easier.
That’s it; the paper size is now set for all the worksheets. There is a drawback to this approach,
however: If individual worksheets have differing page setup settings (different orientations,
margins, headers, footers, etc.), then following these steps will set them all the same. If you only
want to change the paper size and don’t want to change any other settings, your only recourse is
to use a macro to do the change.
Sub AllSheetsA4()
Dim sht As Variant
The macro steps through each sheet in the workbook, changing only the PaperSize property so
that the sheet will print on A4 paper.
If you are creating a macro that is used to print information from your worksheets, you may want
to display the Print dialog box programmatically. The user can then choose to print, directly from
within your macro.
bTemp = Application.Dialogs(xlDialogPrint).Show
The Show method results in the Print dialog box being displayed. When this code line is
finished, bTemp will be either True or False. If True, it means that the user clicked on OK in the
dialog box, thereby printing something. If False, then the user either clicked on Cancel or the
Close button to close the dialog box without printing.
This is relatively easy to do, depending on what you want to have printed. If you want to print
just the contents of the active worksheet, then you can use code similar to the following:
iNumCopies = Range("B11").Value
If iNumCopies < 1 Then iNumCopies = 1
ActiveSheet.PrintOut Copies:=iNumCopies
If you don’t want to print the entire worksheet, then you need to modify the PrintOut statement
just a bit. For instance, the following example presumes that the “label” to be printed in in the
range A1:A5:
Unfortunately, it doesn’t appear that this can be done because the printer drivers don’t typically
make the features of printers available in a way that can be understood and accessed from the
object model used by VBA. (Boy, was that a mouthful!) Instead, you would have to use the
actual Windows API, and even then not all features may be accessible.
There are some workarounds that can be used, however. You can use VBA to select different
printers to which you can direct your output. This means that you can create different printer
definitions—in Windows—and then use those definitions as the target for your output.
For example, you could use the Printers folder in Windows to set up a printer named HP Regular
Paper. That printer definition can be set to print on regular paper, by default. You can then set up
another printer definition named HP Glossy Paper and set it to print, by default, to a tray that
may contain glossy paper. With the two printers defined, you can then use VBA to switch
between the two. For instance, if you wanted to print to the printer definition for the glossy
paper, you could use the following in your macro:
There is a way, however, that you can have one-click printing of your worksheets on a
designated printer. To do this, simply create a macro that changes the printer and then prints the
worksheets. Here is a macro that will accomplish the task:
Sub GoodPrinter()
Application.ActivePrinter = "HP LaserJet"
ActiveWindow.SelectedSheets.PrintOut Copies:=1
End Sub
When you create this macro on your system, make sure you change the printer name in the
second line of the macro. It must exactly match the name of a printer on your system. (In this
example the printer name is set to "HP LaserJet". You should change it to match the name of the
printer you want used.)
The trick is to create one of these macros for each of the printers you use. You can add a
command for each printer to a toolbar so that each printer has its own print button. When you
then click on the command or button, the appropriate macro is run and you get output on the
desired printer.
For instance, let’s assume that you have two print ranges defined in your worksheet: Range1 and
Range2. Further, Range1 should be printed in portrait orientation and Range2 should be printed
in landscape orientation. The following macros can be used to print each of the print ranges:
Sub PrintRange1()
ActiveSheet.PageSetup.PrintArea = Range("range1").Address
ActiveSheet.PageSetup.Orientation = xlPortrait
ActiveWindow.SelectedSheets.PrintOut
End Sub
Sub PrintRange2()
ActiveSheet.PageSetup.PrintArea = Range("range2").Address
ActiveSheet.PageSetup.Orientation = xlLandscape
ActiveWindow.SelectedSheets.PrintOut
End Sub
These are very simple macros, but you get the idea—all you need to do is set up the print job in
the macro, and then print from the macro itself. You could even attach the macros to toolbar
buttons or to a menu option, as described in other issues of ExcelTips.
If you prefer to not use macros, you could also use the custom views feature of Excel. Simply set
the print area, orientation, margins, and other settings desired. Then define this as a custom view.
To define a custom view, follow these steps if you are using Excel 2007 or later:
1. Choose Custom Views from the View menu. Excel displays the Custom Views dialog
box.
You can continue to define and save additional views, as desired. Your custom views are saved
with your workbook, and you can later use them to print what you want. (Just display the custom
view and then print your worksheet.)
There could be a number of different causes for this problem. One subscriber reported that they
had the same problem but that it only cropped up after migrating their office to Windows 7 64-
bit and using Windows PrintServer. In their case, they discovered that their was a hidden
attribute on the printer queues which caused the problem and they could only get it taken care of
by talking with Microsoft support.
Others reported the problem occurring when particular add-ins were installed on the system.
(One in particular, Microsoft Office Labs Search Command, was mentioned a few times.)
Disabling the add-in solved the problem.
There is a good discussing about the problem and various fixes here:
http://answers.microsoft.com/en-us/office/forum/office_2010-excel/excel-2010-
only-prints-to-the-default-printer/5b6beddd-f85d-4fda-ab2b-56c750f2028c
You'll want to ensure that this is entered in your browser as a single URL; it is quite long.
If none of the suggested solutions work in your situation, you can try printing via macros. Why?
Because you can easily modify the designated default printer in the macro and then change it
back. It's all done through the use of the ActivePrinter property. You can determine the name of
the current default printer and assign it to a variable, change the printer, then do your printing,
and finally change the printer back:
The only thing you need to do is to make sure that you replace "XYZ SuperPrinter" with the
actual name of the printer you want to use. You can find out the name of the printer by making it
the default (in Windows) and then, within the VBE Immediate window, printing the name of the
printer:
? Application.ActivePrinter
Mark down the name, paying attention to spacing and capitalization, and that is the name you
can use in the printing macro.
A quicker way is to create a macro that will do the printing for you. The following macro starts
by asking you for a directory path. Provided that you specify a path, the macro then starts to load
each XLS (Excel) file in the directory, and then print the second and third worksheet from each
one. Once printed, the worksheet is closed.
You should note that this macro specifically searches for files that have the XLS extension. If
you are using Excel 2007 or a later version, your workbook files may use the XLSM or XLSX
extensions. If you know what they are using, just change the XLS designation in the code to the
appropriate extension. If your worksheets could have any of the three extensions (XLS, XLSM,
or XLSX), then change the "xls" designation in the code to "xls*". The asterisk will match
anything that may follow XLS, which should address all the variants.
Obviously, if you have quite a few workbooks in the directory, printing could take quite some
time. You may want to find some time when you have nothing else to do, and then just let the
macro start running.
The answer is that such a macro could be written rather easily. It would only need to figure out
how many worksheets there are, check cell G41 on each of them, and then print only if there is
something in that cell. The following macro performs just these operations.
Sub PrintMost()
Dim wks As Worksheet
For Each wks In ActiveWorkbook.Worksheets
If Not IsEmpty(wks.Range("G41")) Then
wks.PrintOut
End If
Next
Set wks = Nothing
End Sub
The macro could be easily modified to perform other operations, such as asking if any given
worksheet should be printed or asking how many copies should be printed.
When you select a range of worksheets and then choose to print, those worksheets are considered
by Excel to be a single, contiguous print job. So, for instance, if you selected 20 worksheets and
each worksheet required eight pages, that would not be treated by Excel as 20 individual print
jobs of eight pages each, but as a single 160-page print job.
Theoretically you could specify, in the Print dialog box, that you wanted to print pages 1, 2, 9,
10, 17, 18, etc., but this is prone to error and quite tedious. It gets even more difficult if the
worksheets being printed consist of varying numbers of pages.
The best solution is to write a macro that will do the printing for you. The macro can step
through however many worksheets you’ve selected and print only the first two pages of each of
those worksheets. The following macro implements this technique:
Sub PrintTwoPages()
Dim sht As Variant
First, if your purpose for printing odd and even pages is to print double-sided, you might check
out your printer driver to see if it can handle double-sided printing or if it will somehow print just
odd or even pages. This approach allows you to bypass Excel altogether.
Another way to bypass Excel is to simply create a PDF file from your output. You can then open
the PDF file and use Acrobat or Adobe Reader to print either odd or even pages.
If you want to stay within Excel, then perhaps the best way to handle the situation is to come up
with a macro that will handle the printing. Such a macro can be approached in any number of
ways. Here's a short one:
Sub PrintOddEven()
Dim Total)ages As Long
Dim StartPage As Long
Dim Page As Integer
When you run the macro, you are asked for a starting page number. In most instances you would
enter either 1 or 2, but you could actually enter any page number you want. The macro then
prints the starting page and every second page from thereon out.
You could, if desired, also print odd and even pages by creating two custom views in Excel—one
for odd pages and one for even pages. All you need to do is specify a non-contiguous range of
cells (consisting of the cells in either the odd or even pages) as your print area for each view. For
instance, if you wanted to define a print area that consisted of the cells for all the odd pages, you
could do this:
With the print area selected, save the view. Then wipe out the print area, use the same technique
to select all the even cells, and save the view. You now have two views you can print, and each
view will contain only odd or even pages. The only drawback to this approach is that Excel
numbers the printed pages sequentially (1, 2, 3, 4) instead of what they really are (1, 3, 5, 7).
Unfortunately, Excel does not remember what you select in the Print What area from one print
job to the next; it always resets the default. The easiest way to always print the entire workbook,
however, it to make a simple little macro like this:
Sub PrintItAll()
ActiveWorkbook.PrintOut
End Sub
You can then create a button on a toolbar and assign this macro to that button. When you want to
print the entire workbook, just click on the button. Easy and quick.
Perhaps the easiest way to handle this type of printing is to add a little macro that runs just before
printing. If the print area is set so that it contains 1, 2, or 3, columns, then the printout is done in
portrait orientation. Any other number of columns and landscape orientation is used. Here is the
macro; you should add it to the ThisWorkbook module:
Of course, it may be more beneficial (and flexible) if you simply use the Custom Views feature
of Excel. You can specify a view that includes your three columns or any number of columns
you want. You can even have the view include print settings, so the orientation of the page
would be included in the view. If you are using Excel 2007 or a later version, follow these steps
to set up the views:
1. Format and situate your worksheet as you want it to appear. Make sure, as well, that
you set both the print area for your three columns and set the page layout to portrait
orientation.
2. Display the View tab of the ribbon.
3. Click Custom Views in the Workbook Views group. Excel displays the Custom Views
dialog box.
4. Click on the Add button. Excel displays the Add View dialog box.
5. In the Name field, supply the name you want associated with this view.
6. In the View Includes section, select the options that reflect what you want saved with
this view. Make sure you specify that you want to include print settings.
7. When you are satisfied with your settings, click the OK button. The current view is
saved by Excel.
8. Repeat steps 1 through 7, but this time for your larger print area, making sure you set
the page layout for landscape orientation.
1. Format and situate your worksheet as you want it to appear. Make sure, as well, that
you set both the print area for your three columns and set the page layout to portrait
orientation.
2. Select Custom Views from the View menu. Excel displays the Custom Views dialog
box.
3. Click on the Add button. Excel displays the Add View dialog box.
4. In the Name field, supply the name you want associated with this view.
5. In the View Includes section, select the options that reflect what you want saved with
this view. Make sure you specify that you want to include print settings.
6. When you are satisfied with your settings, click the OK button. The current view is
saved by Excel.
7. Repeat steps 1 through 6, but this time for your larger print area, making sure you set
the page layout for landscape orientation.
Now, whenever you want to print different ways, you just call up the view you want and choose
to print—everything else is already set for you.
In this type of instance, it would be nice to print the single column as if it were multiple columns.
That way you could use more of each printed page and fewer overall pages for your print job.
Unfortunately Excel contains no intrinsic command or print setting that allows you to
automatically reformat your data so it prints better. There are workarounds, however.
One workaround that is often overlooked is just copying the single-column list to a blank Word
document. If you paste it there as plain text, you can format each page for multiple columns and
actually print the information.
If you would rather not involve Word, you can cut and paste information from the first column
into other columns to give the desired number of printing columns. This, of course, should be
done in a new worksheet or workbook, so that the original data remains undisturbed. As an
example, if you have 200 names in your original list, you can cut 40 names at a time from the list
and paste them into columns A through E of a new worksheet. Printing this worksheet requires
less pages than printing the original single-column worksheet.
Of course, if you have to do this cut-and-paste often, the chore can quickly become tiresome. In
this instance, you can use a macro that does the exact same thing: It slices and dices the original
list and pastes it into a number of columns on a new workbook.
Sub SingleToMultiColumn()
Dim rng As Range
Dim iCols As Integer
Dim lRows As Long
Dim iCol As Integer
Dim lRow As Long
Dim lRowSource As Long
Dim x As Long
Dim wks As Worksheet
When you run this macro, you are asked to select the range you want to convert, and then you
are asked to specify the number of columns you want it to be reformatted as. It creates a new
worksheet in the current workbook and copies information from the original into as many
columns as you specified.
For additional resources to solve this problem, refer to the following Web sites:
http://www.ozgrid.com/VBA/MiscVBA.htm#Print
http://www.mvps.org/dmcritchie/excel/snakecol.htm
There are a couple of ways that you can approach a solution to this problem. The first is simply
print multiple pages per sheet, using the capabilities of your printer driver. For instance, I have
an older HP LaserJet, and the printer driver allows me to specify the number of pages to print per
sheet of paper. If I wanted to print three or four single-page worksheets all on one piece of paper,
all I need to do is follow these steps:
1. Press CTRL+P. Excel displays the Print dialog box (Excel 2007 or earlier) or the printing
options (Excel 2010 and Excel 2013).
2. If you are using Excel 2007 or earlier, choose the Entire Workbook option in the Print
What area of the dialog box. If you are using a later version of Excel, use the drop-
down list immediately under the Settings heading to choose Print Entire Workbook.
3. Click the Properties button (Excel 2007 or earlier) or the Printer Properties link (later
versions of Excel). Excel displays the Properties dialog box for the printer, with the
Layout tab selected.
4. Set the Pages Per Sheet control to 4.
5. Click OK to close the Properties dialog box.
6. Click OK to actually print the worksheets.
Your printer may offer a similar capability to what is outlined here, but you may need to do some
exploring through the printer's Properties dialog box to find that capability. Of course, printing
this way can lead to some very small text on the printout, because the printer driver simply
reduces each page to occupy a proportionate area of the printed page. If you want to reduce some
of the white space, and thereby increase the size of the printed text, then you need to look for a
different solution.
Many people, to consolidate what is printed, actually create a "printing worksheet" which
contains nothing but references to the areas to be printed on the other worksheets in the
workbook. These references can either be done through formulas referring to the data on each
worksheet, or by using the camera tool in Excel. (The camera tool has been described in other
issues of ExcelTips.)
For an automated solution of amalgamating multiple worksheets into a single worksheet, you can
use a macro. The following macro will create a new worksheet at the end of your workbook and
copy the contents from all the other worksheets into it.
Sub PrintOnePage()
Dim wshTemp As Worksheet, wsh As Worksheet
Dim rngArr() As Range, c As Range
Dim i As Integer
Dim j As Integer
ReDim rngArr(1 To 1)
For Each wsh In ActiveWorkbook.Worksheets
i = i + 1
If i > 1 Then ' resize array
ReDim Preserve rngArr(1 To i)
End If
Set c = wsh.Cells.SpecialCells(xlCellTypeLastCell)
If Err = 0 Then
On Error GoTo 0
'Add temp.Worksheet
Set wshTemp = Sheets.Add(after:=Worksheets(Worksheets.Count))
'Delete temp.Worksheet?
If MsgBox("Delete the temporary worksheet?", _
vbYesNo, "ExcelTips") = vbYes Then
Application.DisplayAlerts = False
wshTemp.Delete
Application.DisplayAlerts = True
End If
End Sub
After the combined worksheet is put together, the macro displays the worksheet using Print
Preview. When you close Print Preview, it asks how many copies of the worksheet you want to
print. If you enter a number greater than zero, then that many copies are printed. Finally, the
macro offers to delete the combined worksheet for you just before finishing.
Sub GetSheets()
Dim j As Integer
Dim NumSheets As Integer
NumSheets = Sheets.Count
For j = 1 To NumSheets
Cells(j, 1) = Sheets(j).Name
Next j
End Sub
This macro will overwrite anything in a cell it needs in the current workbook, so you should
make sure you don’t need anything in column A of the worksheet. If you don’t want to overwrite
anything, make sure you create a new worksheet and then run the macro from that worksheet.
Once the list of worksheets is created, you can format it as desired, and then print it out.
In Word you have the option to print document properties, if you desire. There is no intrinsic
way to print workbook properties in Excel. Instead, you must resort to a macro that will place the
names and values of the properties into a worksheet. You can then print the worksheet and have
your workbook properties available in hardcopy format.
The following VBA macro is an example of a good way to copy all the workbook properties to a
worksheet that can be printed:
'Built in Properties
iRow = 1
Cells(iRow, 1).Value = "Built-in Properties"
Cells(iRow, 1).Font.Bold = True
iRow = iRow + 1
Worksheets(1).Activate
For Each p In ActiveWorkbook.BuiltinDocumentProperties
On Error Resume Next
Cells(iRow, 2).Value = p.Name
'If no value then Excel causes an error so ignore!
Cells(iRow, 3).Value = p.Value
iRow = iRow + 1
Next
On Error GoTo 0
'Custom Properties
iRow = iRow + 1
Cells(iRow, 1).Value = "Custom Properties"
Cells(iRow, 1).Font.Bold = True
iRow = iRow + 1
For Each p In ActiveWorkbook.CustomDocumentProperties
On Error Resume Next
Cells(iRow, 2).Value = p.Name
Cells(iRow, 3).Value = p.Value
iRow = iRow + 1
Next
On Error GoTo 0
End Sub
This macro places the comments on the second worksheet in a workbook, so if you want them on
a different worksheet (so you don’t overwrite information already on the second sheet) you will
have to make a modification to the Sheets(2) object.
Note, as well, that the macro name is Workbook_BeforePrint. This means that the macro will run
every time you go to print your worksheet.
Sub Auto_Open()
'Prevent Printing via menu
MenuBars(xlWorksheet).Menus("File").MenuItems("Print...").Delete
You can also create a special Auto_Close macro that restores the menus and toolbars when the
workbook is closed:
Sub Auto_Close()
'Reset the menu items
For Each mb In MenuBars
mb.Reset
Next mb
You should note that these macros only run when the specific workbook is opened and closed.
That means that your printing capability will be unavailable as long as the workbook is open—
even for any other open workbooks you may have.
Another approach is to cancel any printing before it starts. The following is a macro you can
place within a workbook module:
Whenever someone tries to print the workbook, the process is automatically cancelled.
Otherwise, the menu choices and toolbar buttons remain visible. (You could also change the
macro to not only cancel but to display a message box indicating that users are not allowed to
print.)
There are a couple of approaches you could use to this problem, including using Windows
Scripting to handle the printing. However, since the workbook is always open, you don't have to
resort to that. Instead, you can rely on the native macro capabilities of Excel.
The solution considered here requires two macros. The first is one that runs whenever the
workbook is first opened. It sets up the correct event handler to trigger the actual macro that does
the printing.
This particular marco sets the OnTime method to be triggered whenever 5:00 p.m. is reached. To
specify a different time of day, simply change the time (using 24-hour notation) in the macro.
When 5:00 p.m. rolls around, Excel will run the PrintMe macro:
This macro does nothing but print the second sheet in the workbook (which should contain the
summary info you want printed) and then reset the OnTime method to again be triggered at 5:00
p.m. the next day. If you want a different data range to be printed, simply change the object used
with the PrintOut method in the first line of the macro.
As Martin has discovered, there is no way to do this directly in Excel. When you select multiple
worksheets, select the area you want set as the print area, and then try to set the print area, you
quickly discover that the option to do the setting is grayed out, so you cannot select that option.
There are several things you can try, however. One is to start with a new workbook and develop
a single worksheet that contains the print area as you would want it on all worksheets. Then,
copy the worksheet however many times desired in the workbook. The copied worksheets will
have the print area set as it was in the first worksheet.
The other option is to create a macro that will do the print-area setting for you. Consider the
following macro, which will set the print area for all the selected worksheets to whatever the
print area is on the active worksheet. (When more than one worksheet is selected, the active
worksheet is the one that is visible when you run the macro.)
Sub SetPrintAreas1()
Dim sPrintArea As String
Dim wks As Worksheet
sPrintArea = ActiveSheet.PageSetup.PrintArea
For Each wks In ActiveWindow.SelectedSheets
wks.PageSetup.PrintArea = sPrintArea
Next
Set wks = Nothing
End Sub
If you prefer to have the print area set to some range that you specify, rather than needing to set
the print area on the active worksheet first, then you can make one small change to the macro so
that it uses a range for the print area:
Sub SetPrintAreas2()
Dim sPrintArea As String
Dim wks As Worksheet
sPrintArea = "A7:E22"
For Each wks In ActiveWindow.SelectedSheets
wks.PageSetup.PrintArea = sPrintArea
Next
Set wks = Nothing
End Sub
To choose a different print area for your needs, replace the range that is assigned to the
sPrintArea variable. If you figure that you may use the macro quite a bit, in a number of different
workbooks, or if you figure that you may need to change the print area regularly, you could
change the macro so that it prompts the user for a range to use:
Sub SetPrintAreas3()
Dim sPrintArea As String
Dim wks As Worksheet
The only way to handle this is through the use of a macro. VBA allows you to create macros that
are initiated when certain events occur. One of the events that can trigger macros is the “print”
event. When someone asks to print, or chooses to see a print preview, the BeforePrint event of
the Workbook object is triggered. You can create your own macro that executes when the event
is triggered.
Next
ExitHandler:
Application.EnableEvents = True
Cancel = True
Exit Sub
ErrHandler:
MsgBox Err.Description
Resume ExitHandler
End Sub
Whenever Excel gets ready to print, or whenever print preview is invoked, the BeforePrint event
is triggered and this macro runs. The macro first asks the user if he or she wants to do a print
preview. A Select Case structure is used to set the bPreview variable based on the answer to the
question. The setting of bPreview then controls what happens.
If the user clicked Cancel when asked about previewing, then the macro is exited and the
printing is canceled. Otherwise, each worksheet in the workbook is examined to either print or
preview. If the worksheet is visible, it is printed, and the Preview property is set equal to
bPreview (True means that the worksheet is previewed; False means it is actually printed).
Notice that the macro sets the EnableEvents property to False. This is done so that no other
events can trigger while printing or previewing. If EnableEvents is left “on,” then every time the
PrintOut method is used, the entire BeforePrint event is again triggered—the user would end up
in an endless loop if event handling were not turned off.
Also, note that one of the last things to occur before exiting the macro is that the Cancel property
is set to True. This is done so that the original print or print preview request that generated the
BeforePrint event is cancelled. There is, after all, no need to complete that request, and the macro
did all the print handling for the user.
There is one caveat, of course, to using this approach to printing: If macros are not enabled, the
handler will not run and the user can print as desired. (Holding SHIFT while opening the
workbook disables macros and the user most times is asked if they want to enable macros.) Other
issues of ExcelTips have discussed this fact.
Conditional Printing
Kirk asked if there is a way to conditionally control what is printed in Excel. For instance, cell
A1 contains a value, and the value controls exactly what is printed. Perhaps if A1 contains 1,
then Sheet1 is printed; if it contains 2, then Sheet1 and Sheet2 are printed.
The only way to do this is with a macro, and there are several approaches you can use. Consider
the following very simple macro, which simply uses a Select Case structure to control the
printing.
Sub PrintStuff()
Dim vShts As Variant
vShts = Sheets(1).Range("A1")
If Not IsNumeric(vShts) Then
Exit Sub
Else
Select Case vShts
Case 1
Sheets("Sheet1").PrintOut
Case 2
Sheets("Sheet2").PrintOut
Case 3
Sheets("Sheet1").PrintOut
Sheets("Sheet2").PrintOut
End Select
End If
End Sub
Run this macro with the value 1, 2, or 3 in cell A1 of the first sheet, and the macro prints
different things based on the value. If the value is 1, then Sheet1 is printed; if it is 2, then Sheet2
is printed; and if it is 3, then both Sheet1 and Sheet2 are printed. If you want different values to
print different things, just modify the Select Case structure to reflect the possible values and what
should be printed for each value.
A more comprehensive approach can be created, as well. Consider adding a “control sheet” to
your workbook. This sheet would have the name of each worksheet in the workbook listed in the
first column. If you put a value to the right of a worksheet name, in the second column, then a
macro will print the corresponding worksheet.
Sub CreateControlSheet()
Dim i as integer
Range("B1").Select
ActiveCell.FormulaR1C1 = "Print?"
Cells.Select
Selection.Columns.AutoFit
For i = 1 To ActiveWorkbook.Sheets.Count
Cells(i + 1, 1).Value = Sheets(i).Name
Next
End Sub
The macro first deletes any old control sheet, if it exists. It then adds a new worksheet named
Control Sheet, and puts headers labels in columns A and B. It then lists all the worksheets in the
workbook in column A.
With the control sheet created, you can then place an “X” or some other value (such as “Y” or 1)
into column B beside each worksheet you want to print. The following macro then examines the
control sheet and prints any worksheet that has a mark—any mark—in the cell in column B.
Sub PrintSelectedSheets()
Dim i as Integer
i = 2
Another approach is to create a macro that runs just before printing. (This is one of the events—
printing—that Excel allows you to trap.) The following macro, added to the thisWorkbook
object, is run every time you try to print or choose Print Preview.
vShts = Sheets(1).Range("A1")
If Not IsNumeric(vShts) Then
GoTo InValidEntry
ElseIf vShts < 1 Or vShts > Sheets.Count Then
GoTo InValidEntry
Else
iResponse = MsgBox(prompt:="Do you want Print Preview?", _
Buttons:=vbYesNoCancel, Title:="Preview?")
Select Case iResponse
Case vbYes
bPreview = True
Case vbNo
bPreview = False
Case Else
Msgbox "Canceled at User request"
GoTo ExitHandler
End Select
Application.EnableEvents = False
Sheets(vShts).PrintOut Preview:=bPreview
End If
ExitHandler:
Application.EnableEvents = True
Cancel = True
Exit Sub
InValidEntry:
MsgBox "'" & Sheets(1).Name & "'!A1" _
& vbCrLf & "must have a number between " _
& "1 and " & Sheets.Count & vbCrLf
GoTo ExitHandler
ErrHandler:
MsgBox Err.Description
Resume ExitHandler
End Sub
The macro checks the value in cell A1 of the first worksheet. It uses this value to determine
which worksheets should be printed. In other words, a 1 prints the first worksheet, a 2 prints the
second, a 3 prints the third, and so on.
If the value in A1 is not a value or if it is less than 1 or greater than the number of worksheets in
the workbook, then the user is informed that the value is incorrect and the macro is exited.
Assuming the value in A1 is within range, the macro asks if you want to using Print Preview.
Depending on the user’s response, the macro prints the specified worksheet or displays Print
Preview for that worksheet.
If you are responsible for a particular worksheet, you may want to somehow protect the various
print settings you’ve established so that they aren’t garbled by other users. Perhaps the easiest
way to do this is to save your print settings in a macro, and then run that macro every time the
workbook is closed. In that way, the settings can be changed back to the “defaults” you specify,
without worry that users will mess them all up.
For instance, the following macro shows how you can set all the print settings for a particular
print job:
Sub Auto_Close()
With ActiveSheet.PageSetup
.LeftHeader = ""
.CenterHeader = ""
.RightHeader = ""
.LeftFooter = ""
.CenterFooter = ""
.RightFooter = ""
.LeftMargin = Application.InchesToPoints(1)
.RightMargin = Application.InchesToPoints(1)
.TopMargin = Application.InchesToPoints(1)
.BottomMargin = Application.InchesToPoints(1)
.HeaderMargin = Application.InchesToPoints(0.5)
.FooterMargin = Application.InchesToPoints(0.5)
.PrintHeadings = False
.PrintGridlines = False
.PrintComments = xlPrintNoComments
.CenterHorizontally = False
.CenterVertically = False
.Orientation = xlPortrait
.Draft = False
.PaperSize = xlPaperLetter
.FirstPageNumber = xlAutomatic
.Order = xlDownThenOver
.BlackAndWhite = False
.Zoom = False
.FitToPagesWide = 1
.FitToPagesTall = 99
.PrintErrors = xlPrintErrorsDisplayed
.PrintArea = "MyPrintArea"
.PrintTitleRows = ""
.PrintTitleColumns = ""
End With
End Sub
To make the macro work for your particular needs, simply modify the settings to match whatever
your requirements are.
Of course, when someone else opens your workbook, the macro may be disabled automatically
or they may see a notification that there are macros in it. If they choose to disable the macros,
then your default-setting macro won’t run when the workbook is closed. The solution, of course,
is for you to open the workbook, enable the macros, and then close the workbook. This runs the
macro and your settings are again restored as you want them.
There are a couple of things you can try. First, you can set your print area and then apply
worksheet protection that allows only some of the cells in the worksheet to be selected. This will
preclude those strange changes that result in huge printouts. It won’t, however, stop someone
from changing the print area so it includes only those unprotected cells.
The only way to “protect” the print area is to use a macro that will force the desired print area.
One natural place to enforce this is just before printing. The following event handler (added to
the ThisWorkbook module) will change the print area for worksheet Sheet1 to the range A1:C25:
Worksheets("Sheet1").PageSetup.PrintArea = "A1:C25"
End Sub
This approach will only work, obviously, if the user enables macros when the workbook is
opened. You can change the specified sheet name and range as desired.
When printing out filtered data, you might want to know what slicing and dicing was done to the
original data. There are several ways you can go about displaying your filtering criteria. One
simple way is to use the advanced filtering capabilities of Excel, which require that you set up a
small criteria table for your data. If the criteria table is made part of what you print, then you can
see your filtering criteria quite easily.
If you use AutoFilter, then you need to use a different approach. One such approach is detailed at
John Walkenbach’s site:
http://j-walk.com/ss/excel/usertips/tip044.htm
This solution uses a user-defined function to return any filtering criteria in use in the current
column. The function can be used in a cell, in that column, to display the criteria. If you are
using advanced filtering, then the macro approach is a bit more complex. The following macros
(there are two of them in the listing) examine what advanced criteria are in play, and then places
the criteria in the left portion of the header.
Sub AddFilterCriteria()
Dim strCriteria As String
strCriteria = FilterCriteria()
If strCriteria = "" Then
strCriteria = "No Filtering Criteria"
Else
strCriteria = "Filter Criteria:" & Chr(10) & strCriteria
End If
FilterCriteria = ""
FilterCriteria = strCriteria
End Function
To use the macro, just run the AddFilterCriteria macro, after you have your advanced filtering
set up. The macro reads the criteria table and puts together the criteria into a string that is placed
in the left header.
There is no direct way to do this, but you can simulate such a printing by using a macro to do the
task. All you need to do is have the macro print all except your last page, then change the page
setup so that rows are not repeated, and finally print the final page of the printout. The following
macro provides an example of this approach.
Sub PrintWorksheet()
lPages = Application.ExecuteExcel4Macro("GET.DOCUMENT(50)")
With ActiveSheet.PageSetup
ActiveSheet.PrintOut From:=1, To:=lPages - 1
sTemp = .PrintTitleRows
.PrintTitleRows = ""
ActiveSheet.PrintOut From:=lPages, To:=lPages
.PrintTitleRows = sTemp
End With
End Sub
For example, one of the event handlers supported by Excel is triggered every time something in
the workbook is changed. You can create an event handler that examines which cell was
changed. If it is a specific cell, and if that cell contains a particular value, then a worksheet can
be printed.
This macro examines the contents of cell B2. If the cell contents are changed and if the cell
contains the value 1001, then the worksheet is automatically printed.
Of course, you may want the contents of a particular cell to control what is printed when
someone actually chooses to print. For instance, if the user chooses to print, you may want to
examine the contents of a cell (such as E2) and, based on the contents of that cell, automatically
modify what is printed. The following macro takes this approach:
Case 3
Worksheets("Sheet3").PrintOut
Case 4
Worksheets("Sheet4").PrintOut
Case Else
ActiveSheet.PrintOut
End Select
Cancel = True
Application.EnableEvents = True
End Sub
The macro prints Sheet1, Sheet2, Sheet3, or Sheet4 depending on whether cell E2 contains 1, 2,
3, or 4.
Special-Purpose Macros
In order to run a procedure automatically when a workbook is opened, all you need to do is name
the procedure Auto_Open(). Thus, the following procedure will be run automatically whenever
the workbook containing it is opened:
Sub Auto_Open()
Dim strMsg As String
Dim intBoxType As Integer
Dim strTitle As String
Dim intUpdate As Integer
Dim strDefault As String
Dim strOldFile As String
Dim intStatusState As Integer
In order to run a macro automatically when a workbook is closed, all you need to do is name the
macro Auto_Close(). Thus, the following example macro is run automatically whenever the
workbook containing it is closed:
Sub Auto_Close()
Dim intStatusState As Integer
intStatusState = Application.DisplayStatusBar
Application.DisplayStatusBar = True
Application.StatusBar = "Examining transactions."
DetermineTransactions
Application.StatusBar = "Posting transactions."
PostTransactions
Application.StatusBar = False
Application.DisplayStatusBar = intStatusState
End Sub
1. Activate the worksheet with which you want the macro associated.
2. Make sure the Formulas tab of the ribbon is displayed.
3. In the Defined Names area of the ribbon, click Define Name. Excel displays the New
Name dialog box.
4. In the Name field, enter a name that begins with the worksheet name, followed by an
exclamation point, Auto_Activate, and any other wording desired. Thus, if the
worksheet were named Stocks, you might enter Ledger!Auto_Activate_ALW.
5. In the Refers to box, enter a formula that points to the workbook and macro you want
automatically executed. Thus, if the macro name were Update_Accts, and the workbook
name were SALES.XLS, you would enter the formula =Sales!Update_Accts.
6. Click on the OK button.
1. Activate the worksheet with which you want the macro associated.
2. Choose Name from the Insert menu. You will see a submenu.
3. Choose Define from the submenu. You will then see the Define Name dialog box.
4. In the Names in Workbook field, enter a name that begins with the worksheet name,
followed by an exclamation point, Auto_Activate, and any other wording desired.
Thus, if the worksheet were named Ledger, you might enter
Ledger!Auto_Activate_ALW.
5. In the Refers to field, enter a formula that points to the workbook and macro you want
automatically executed. Thus, if the macro name were Update_Accts, and the workbook
name were SALES.XLS, you would enter the formula =Sales!Update_Accts.
6. Click on the OK button.
Remember that a procedure defined in this way is run every time the worksheet is activated, not
just the first time. Think about how you use Excel; it is possible to activate a worksheet several
dozen times during the course of a session.
1. Activate the worksheet with which you want the macro associated.
2. Choose Name from the Insert menu. You will see a submenu.
3. Choose Define from the submenu. You will see the Define Name dialog box.
4. In the Names in Workbook field, enter a name that begins with the worksheet name,
followed by an exclamation point, Auto_Deactivate, and any other wording desired.
Thus, if the worksheet were named Stocks, you might enter
Stocks!Auto_Deactivate_Exit.
5. In the Refers to field, enter a formula that points to the workbook and macro you want
automatically executed. Thus, if the macro name were Update_PL, and the workbook
name were PFOLIO.XLS, you would enter the formula =PFolio!Update_PL.
6. Click on the OK button.
If you are using Excel 2007 or a later version, then you define the requisite macro names in this
manner:
1. Activate the worksheet with which you want the macro associated.
2. Make sure the Formulas tab of the ribbon is displayed.
3. In the Defined Names area of the ribbon, click Define Name. Excel displays the New
Name dialog box.
4. In the Name field, enter a name that begins with the worksheet name, followed by an
exclamation point, Auto_Deactivate, and any other wording desired. Thus, if the
worksheet were named Stocks, you might enter Stocks!Auto_Deactivate_Exit.
5. In the Refers to box, enter a formula that points to the workbook and macro you want
automatically executed. Thus, if the macro name were Update_PL, and the workbook
name were PFOLIO.XLS, you would enter the formula =PFolio!Update_PL.
6. Click on the OK button.
Remember that a macro defined in this way is run every time the worksheet is deactivated, not
just the first time. Think about how you use Excel; if you spend a fair amount of time hopping
between worksheets in a workbook or between workbooks, it is possible to deactivate a
worksheet several dozen times during the course of a session.
There are actually two events you could use for this purpose. You can use the SheetDeactivate
event in the ThisWorkbook module to trigger actions whenever a user leaves any worksheet in
the workbook:
If you want to trigger actions only when they leave a particular worksheet, then you can use the
Deactivate event in the WorkSheet object:
You should know, however, that in either case the worksheet to which the user is choosing to go
will be the active worksheet after the event is completed. If you want to force the user to stay on
the worksheet, you need to specifically put them back on the worksheet, in this manner:
This assumes, of course, that the name of the worksheet you want the user to remain on is
Sheet1.
While this may sound good in theory, it won’t work in practice. Why? Because when you open a
workbook under macro control, the Auto_Open macro in the workbook being opened will not
automatically run. There are three ways around this problem.
The first is to redo your macro so that you don’t rely on Auto_Open macros in each workbook. If
the Auto_Open macro in each workbook is the same, then why not simply move the code to a
separate procedure in the controlling workbook? For instance, let’s say you were using code that
followed this process:
Sub MyMacro()
Dim J As Integer
Dim sTarget As String
Application.ScreenUpdating = False
For J = 1 To 999
sTarget = "Book" & Format(J, "000") & ".xls"
Workbooks.Open sTarget
'Auto_Open runs here
Workbooks(sTarget).Save
Next J
Application.ScreenUpdating = True
End Sub
This won’t work, for reasons already explained. One solution is to simply move the common
Auto_Open code into another procedure, and then call it after opening the workbook, as shown
here:
Sub MyMacro()
Dim J As Integer
Dim sTarget As String
Application.ScreenUpdating = False
For J = 1 To 999
sTarget = "Book" & Format(J, "000") & ".xls"
Workbooks.Open sTarget
Workbooks(sTarget).Activate
DoCommonCode
Workbooks(sTarget).Save
Next J
Application.ScreenUpdating = True
End Sub
Sub DoCommonCode()
'Common code goes here
End Sub
This approach works fine, provided the routine is the same that will be run on all your different
workbooks. If the routines are different in each workbook, then you can force VBA to run the
Auto_Open macro. This is done by using the RunAutoMacros method right after opening the
workbooks:
Workbooks.Open sTarget
ActiveWorkbook.RunAutoMacros xlAutoOpen
Given this approach, you could easily come up with a macro that would simply open each
workbook (so the Auto_Open macros could run) and then save them. Such a macro would appear
as follows:
Sub RunAutoOpenMacrosInBooks()
Dim J As Integer
Dim sTarget As String
Application.ScreenUpdating = False
For J = 1 To 999
sTarget = "Book" & Format(J, "000") & ".xls"
On Error Resume Next
Workbooks.Open sTarget
Windows(sTarget).Activate
With ActiveWorkbook
If .Name <> ThisWorkbook.Name Then
.RunAutoMacros xlAutoOpen
.Save
.Close
End If
End With
Next i
Application.ScreenUpdating = True
End Sub
A third, and even better, approach is to not rely upon Auto_Open macros in each of your
workbooks. Instead, rely on the Workbook_open event as a way to run your macro. The
Workbook_open event is triggered automatically, regardless of whether the workbook is opened
manually or in another macro. The code that the event contains is run automatically, just as you
would expect of an Auto_Open macro.
The standard way to do this is with the SelectionChange event. Every time the selection changes
in the worksheet, the event is triggered. The event doesn’t just trigger when a cell is clicked on,
but also if someone presses a cursor control key that results in a different cell being selected.
As an example, let’s say that you wanted cell B5 to contain the value 10 whenever that cell is
selected. To implement that, you could use the following:
This code is added to one of the sheet objects in the Project Explorer area of the VB Editor.
Double-click the worksheet you want the event handler to apply to, and then add the macro to the
resulting code window.
When the SelectionChange event is triggered, the target (the cell range being selected) is passed
to the handler. The macro then checks to see if the target range contains cell B5, and if it does,
stuffs the value 10 into cell B5. If you want to make sure that the macro only stuffs information
into B5 if only B5 (the single cell) is selected, you can use this version of the macro:
There are any number of ways that this can be done. For instance, you could create your own
macro that saves two versions of the same workbook. The macro could be assigned to a toolbar
button, and then clicked when you want to save both copies. (In other words, you would bypass
the normal Save function all together.)
Another approach is to make a small adjustment to how Excel saves the workbook. For instance,
the following macro would be added to the ThisWorkbook object for the workbook:
This is an event handler, and it is triggered every time you go to do a save on the workbook. At
that point, the macro is executed and a copy of the workbook is saved in the specified path on
your local hard drive.
Excel doesn’t have a built-in capability to do this, but if the various locations are well defined,
you can create a macro that will do the saving for you. The following macro is an example of
such a tool:
Sub SaveToLocations()
Dim OrigName As String
OrigName = ActiveWorkbook.FullName
ActiveWorkbook.SaveAs "G:\" + ActiveWorkbook.Name
ActiveWorkbook.SaveAs "L:\" + ActiveWorkbook.Name
ActiveWorkbook.SaveAs "K:\" + ActiveWorkbook.Name
ActiveWorkbook.SaveAs "S:\" + ActiveWorkbook.Name
ActiveWorkbook.SaveAs OrigName
End Sub
The particular example of the macro saves the active workbook to five different locations, all
using the same workbook name. The macro determines the current location of the workbook so
that it can save to the current location last. The reason this is done is so that you can continue to
use the regular Save tool and get the expected results.
If you want to use this macro on your own system, all you need to do is to make sure that you
change the drive letters of where each workbook will be saved. If one of the drives you specify is
for a location that uses removable media, and there is no media in the drive, then the macro will
generate an error and stop. You’ll then have to figure out where the workbook was originally
saved so you can manually resave it there (using Save As).
Another peculiarity of the macro is that since it uses the SaveAs method, if there is already a
workbook at each of the destinations with the same name as the current workbook, Excel will
ask if you want the existing version of the workbook overwritten. This will always be the case
with the last save, into the original location.
Understanding Add-Ins
Many features of Excel are available only through what are called add-ins. For instance, the
Analysis ToolPak is a good example of an add-in. The tools available in add-ins such as the
Analysis ToolPak are not part of the basic Excel system, but can be added to the system as needs
dictate. These add-ins are nothing more than programs which have been "added to" Excel in such
a way that they appear to be part of Excel itself.
You also know that macros are nothing more than programs that you write using a language
understood by Excel. These programs instruct Excel to perform tasks that otherwise might be
time consuming or repetitious on your part. These programs, if elaborate enough, can become
full-fledged applications that operate under Excel.
Excel allows you to translate your macro programs into add-ins, which can become part of
Excel—the same as the Analysis ToolPak and others. Eventually you might want to take
advantage of this capability. The files you convert to add-ins do not need to be elaborate, nor do
they have to be fancy. Converting them to add-ins does have several advantages, however:
In essence, add-ins are nothing but a special type of workbook which you have converted to an
add-in format that is understood by Excel.
You may want to make sure your macro code which is destined to be an add-in performs some
initializing routine that modifies, in some way, the Excel user interface. For instance, an add-in
may modify the ribbon structure used by Excel or it may add a selection to the Quick Access
Toolbar so that the functions in the add-in can be accessed. Your macros should take care of the
interface modification so that people can access your add-ins. If you don't modify the interface in
some way, then users can only get to the macro code in your add-in by directly referencing in a
worksheet formula the names of any functions in your add-in.
Creating Add-Ins
Any Excel workbook can be converted to an add-in. The steps you need to follow to create an
add-in are very precise, and may seem a bit overwhelming (particularly the first couple of times
you do it). To create a protected add-in file, follow these steps if you are using a version of Excel
prior to Excel 2007:
6. Make sure the Lock Project For Viewing check box is selected.
7. Enter a password in both fields at the bottom of the dialog box.
8. Click on OK. The dialog box closes.
9. Close the Visual Basic Editor and return to the Excel workbook.
10. Choose Properties from the File menu. This displays the Properties dialog box for your
workbook.
11. Make sure the Summary tab is displayed.
12. Make sure the Title field is filled in. What you enter here will appear in the Add-Ins
dialog box used by Excel.
13. Make sure the Comments field is filled in. What you enter here will appear in the
description area of the Add-Ins dialog box used by Excel.
14. Click on the OK button to close the dialog box.
15. Choose Save As from the File menu. This displays the Save As dialog box.
16. Using the Save As Type pull-down list, specify a file type of Microsoft Excel Add-In
(*.xla).
17. Specify a name for your add-in file in the File Name field.
18. Click on Save. Your add-in file is created.
19. Close the workbook you just saved as an add-in.
You should follow these steps, instead, if you are using Excel 2007 or a later version:
6. Make sure the Lock Project For Viewing check box is selected.
7. Enter a password in both fields at the bottom of the dialog box.
8. Click on OK. The dialog box closes.
9. Press ALT+Q. Excel close the Visual Basic Editor and returns to the Excel workbook.
10. Click the Office button, Prepare, and then Properties. Excel displays the Document
Information Panel just below the ribbon and just above the worksheet.
11. Make sure the Title field is filled in. What you enter here will appear in the Add-Ins
dialog box used by Excel.
12. Make sure the Comments field is filled in. What you enter here will appear in the
description area of the Add-Ins dialog box used by Excel.
13. Close the Document Information Panel.
14. Press F12. Excel displays the Save As dialog box.
15. Using the Save As Type pull-down list, specify a file type of Excel Add-In (*.xlam).
16. Specify a name for your add-in file in the File Name field.
17. Click on Save. Your add-in file is created.
18. Close the workbook you just saved as an add-in.
Follow these steps if you are using Excel 2010 or Excel 2013:
14. Make sure the Title field is filled in. What you enter here will appear in the Add-Ins
dialog box used by Excel.
15. Make sure the Comments field is filled in. What you enter here will appear in the
description area of the Add-Ins dialog box used by Excel.
16. Click on the OK button.
17. Press F12. Excel displays the Save As dialog box.
18. Using the Save As Type pull-down list, specify a file type of Excel Add-In (*.xlam).
19. Specify a name for your add-in file in the File Name field.
20. Click on Save. Your add-in file is created.
21. Close the workbook you just saved as an add-in.
have open, or any time you are using Excel. All you need to do to use your add-in is follow these
steps if you are using Excel 2007 or a later version:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
2. At the left side of the dialog box click Add-Ins.
3. Make sure the Manage drop-down list is set to Excel Add-ins.
4. Click the Go button. Excel displays the Add-Ins dialog box.
5. If your custom add-in is visible in the dialog box, click the check box beside it and skip
to step 9.
6. Click on the Browse button. Excel displays a standard file dialog box.
7. Use the controls in the dialog box to locate and select your custom add-in.
8. Click on OK. The add-in is loaded and made a part of Excel. (You can tell that the add-
in is available because it is now listed in the Add-Ins dialog box.)
9. Click on OK to close the Add-Ins dialog box.
If you are using an older version of Excel, follow these steps instead:
1. Choose Add-Ins from the Tools menu. This displays the Add-Ins dialog box.
2. If your custom add-in is visible in the dialog box, click the check box beside it and skip
to step 6.
3. Click on the Browse button. Excel displays a standard file dialog box.
4. Use the controls in the dialog box to locate and select your custom add-in.
5. Click on OK. The add-in is loaded and made a part of Excel. (You can tell that the add-
in is available because it is now listed in the Add-Ins dialog box.)
6. Click on OK to close the Add-Ins dialog box.
1. Load the worksheet for which you want a specific add-in loaded.
2. Press ALT+F11 to display the VBA Editor.
3. Double-click on the “This Workbook” object in the Project Explorer. Excel opens a
code window for This Workbook.
4. Place the following macros in the code window:
5. In the code, change the name of the add-in (“Add-In Name”) to the real name of the
add-in you want to use with the workbook.
6. Close the VBA Editor.
7. Save your workbook.
If you are not sure of the correct name for a particular add-in (see step 5), you can use the macro
recorder to record the process of activating an add-in. That will show you the exact name you
should use in the above macros.
Universal Searching
When you use the search feature in Excel, it searches for the first occurrence of whatever you are
searching for. Trouble is, Excel only searches on the currently displayed worksheet. What if you
want to search an entire workbook, not just a worksheet?
The answer depends on the version of Excel you are using and what you want to do. In any
version of Excel you can select multiple worksheets before you perform a search. If you are
using Excel 95 or Excel 97, only the first occurrence of your text is located, regardless of what
worksheet it is on. This may sound confusing, but it is not really. If you search for text that you
know is on three different worksheets, Excel 97 will only find the first instance of the text, and
then search no further.
If you are using Excel 2000 or a later version and select multiple worksheets, it cycles through
each occurrence of the text you are searching. This is reasonable, and is how searching should
work. That doesn’t help you, however, if you are using an earlier version of Excel. In that case
you are required to implement your own solution through the use of a macro. The following
macro, FindAcrossAll, will search all worksheets in the current workbook (selected or not) for
anything you specify.
Sub FindAcrossAll()
Dim DoIt As Boolean
Dim What As String
DoIt = True
While DoIt
What = InputBox("What are you looking for?")
For Each Sht In Worksheets
Sht.Activate
Set Found = Sht.UsedRange.Find(What)
If Not Found Is Nothing Then ' The value has been found.
FirstAddress = Found.Address
Do
Found.Activate
Msg = "Continue the search ?"
Title = "Continue ?"
Response = MsgBox(Msg, vbYesNo + vbQuestion, Title)
If Response = vbNo Then ' Doesn't want to continue
MsgBox "Search cancelled by user."
Exit Sub ' Quit the macro
End If
Set Found = Cells.FindNext(After:=ActiveCell)
Selecting the cells containing the text you want to use is rather easy; you can use the standard
Find and Replace feature to do it. Follow these steps:
1. Press CTRL+F. Excel displays the Find tab of the Find and Replace dialog box.
2. Expand the dialog box by clicking the Options button.
3. In the Find What box, enter the text you want to find.
4. Use the controls in the dialog box to limit the matches, as desired.
5. Click Find All. The dialog box is expanded to show all the matches that were located.
6. Press CTRL+A. This selects all those cells that were found.
7. Click Close to dismiss the dialog box.
That's it. As long as you didn't click on Match Entire Cell Contents in step 4, Excel selects all the
cells that contain the text you specified in step 3. You can, at that point, apply formatting to the
cells, if desired.
You could, of course, use conditional formatting to dynamically format cells that contain the text
you want to highlight. All you need to do is set up a condition that uses "Text Contains" as the
test. This won't, of course, select all the cells that contain the text, but it will highlight them so
you can pick out where they are.
You could also use a macro to select all the cells that contain the desired text. The following is a
rather simple one that accomplishes the task:
Sub selCellbasedonValue()
Dim c As Object
Dim u As Range
Dim v As Range
Dim sInpt As String
Set u = ActiveSheet.UsedRange
Set u = Nothing
End Sub
There is a problem with selecting cells that you need to recognize, however—if the cells are non-
contiguous, you cannot cut or copy the cells. If you try, you'll get an error message indicating
that the command cannot be used on multiple selections. The easiest way to copy cell contents to
a different location is to, again, use a macro:
Sub CopyFinds()
Dim sSrch As String
Dim sFirst As String
Dim rPaste As Range
Dim i As Integer
Dim iLeftC As Integer
Dim lTopR As Long
Dim c As Object
If Selection.Cells.Count = 1 Then
MsgBox "Select the range to be searched."
Exit Sub
End If
Application.ScreenUpdating = True
Cells(rPaste.Row, rPaste.Column).Select
End Sub
When you select a range of cells and run this macro, you are asked to specify what you are
searching for (case is important) and an address of where you want to copy it. The macro then
finds all cells that contain that value and copy both their address and the cell value to the starting
address you specified. The macro doesn't do a lot of error checking; it will overwrite information
if you specify a target address that has information in it already. In addition, if you specify a
target address that is within the range you are searching, the macro may run infinitely. You
should definitely specify a target that is outside of the range being searched.
Finding which workbooks contain the desired text is relatively easy. All you need to do is use the
Search capabilities of Windows to look for files, in the single folder, that contain the desired text.
While it won't tell you the cell locations, it will winnow down the list of files.
Of course, you can use a macro to do your searching for you. (It's always a good idea to use a
macro to do the long, tedious work that would otherwise be done manually.) The following will
step through all the workbooks in a folder and search for what you want to locate. It will open
any file ending in xls* (the trailing asterisk means that it will search for xls, xlsx, and xlsm files).
Sub SearchFolders()
Dim fso As Object
Dim fld As Object
Dim sfl As Object
Dim strSearch As String
Dim strPath As String
Dim strFile As String
Dim wOut As Worksheet
Dim wbk As Workbook
Dim wks As Worksheet
Dim lRow As Long
Dim rFound As Range
Dim strFirstAddress As String
'Change as desired
strPath = "c:\MyFolder"
strSearch = "Specific text"
wbk.Close (False)
strFile = Dir
Loop
.Columns("A:D").EntireColumn.AutoFit
End With
MsgBox "Done"
ExitHandler:
Set wOut = Nothing
Set wks = Nothing
Set wbk = Nothing
Set sfl = Nothing
Set fld = Nothing
Set fso = Nothing
Application.ScreenUpdating = True
Exit Sub
ErrHandler:
MsgBox Err.Description, vbExclamation
Resume ExitHandler
End Sub
To customize the routine for your needs, change the strPath variable to reflect the path to the
folder you want to process and change strSearch to reflect the text for which you are searching.
The macro creates a new worksheet and places "hits" into each row. Column A contains the
workbook name, column B the worksheet name, column C the cell address, and column D the
contents of that cell.
Obviously, any macro like this one takes quite a bit of time to run. You can shorten the time
somewhat by reducing the number of files it needs to search. The best way to do this is to use the
Windows Search approach (described at the beginning of this tip) to identify the workbooks in
which the desired text resides. Move those workbooks to their own folder and then do the macro
search on that folder.
When you first display the dialog box, Within is set to Sheet, by default. This setting is true
regardless of whether you select one worksheet or multiple worksheets prior to displaying the
dialog box.
If you want the Within drop-down list to default to Workbook (instead of Sheet), there is no way
to specify this in Excel. You can take some solace in the fact that the setting of the Within drop-
down list is persistent for the current session with Excel. In other words, if you set it to
Workbook, complete your search, and later do another search, then the Within setting is
persistent; it is still set to Workbook.
It is interesting that, at first blush, there appears to be no way tackle this issue using a macro.
This is because Excel doesn’t provide a way for a macro to easily display and modify the settings
in the Find and Replace dialog box. Many dialog boxes can be displayed using the Dialogs
collection, but not the Find and Replace. Instead, VBA allows you to display an older version of
the Find dialog box, using this code:
Sub ShowFind1()
Application.Dialogs(xlDialogFormulaFind).Show
End Sub
Unfortunately, this version of the Find dialog box does not have a control that allows you to
specify the scope of the search, as can be done with the Within drop-down list in the Find tab of
the Find and Replace dialog box.
There is a way to display the correct Find and Replace dialog box, but it isn't by using the
Dialogs collection. Instead you need to pull up the dialog box using the CommandBars
collection, which essentially displays the dialog box using a menu command. (For those using
Excel 2007 and Excel 2010, this is pretty ironic if you think about it—Excel no longer has
menus, but you can still access the CommandBars collection to display dialog boxes using
menus.) Here's how to do it:
Sub ShowFind2()
ActiveSheet.Cells.Find What:="", LookAt:=xlWhole
Application.CommandBars("Worksheet Menu Bar").FindControl( _
ID:=1849, recursive:=True).Execute
End Sub
The Find method allows you to set the different parameters in the Find and Replace dialog, and
then the CommandBars object is accessed to actually display the dialog box.
Unfortunately, there is no setting that you can specify so that Excel remembers how you want to
do your search. You can, however, use a macro to set the default searching order. Consider the
following example:
End Sub
This macro does nothing but change the search order to columns. After it is run (in other words,
after you open the workbook), subsequent searches will default to searching by column.
The fact that Excel remembers the last-used search order for all subsequent searches during the
current Excel session can be used to your advantage. The following macro does essentially the
same thing as the previous example, except it also closes the workbook:
Sub Auto_Open()
Worksheets("sheet1").Cells.Find _
What:="", _
After:=ActiveCell, _
LookIn:=xlFormulas, _
LookAt:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlNext, _
MatchCase:=True
ThisWorkbook.Close savechanges:=False
End Sub
If you put this macro into a blank workbook and then save the workbook in your xlStart folder, it
would be opened every time you start Excel. When opened, the workbook does a single search
using the settings you want, and then closes itself. The net result is that your search order is set to
columns, and subsequent searches will occur the way you want them to.
Excel doesn't allow you to specify what settings you want for a default in the Find dialog box.
There is a bit of a way around this seeming limitation, however—at least a partial way. Excel
remembers the last settings in the Find dialog box for the entire Excel session. (The settings are
not reset until you exit and restart Excel.) This means that all you need to do is create a small
macro that will set the settings you want in the dialog box.
There are two ways you can do this. The first is to create a macro that sets the options in the
dialog box directly, such as this:
Sub SetFind1()
Application.Dialogs(xlDialogFormulaFind).Show,2,2
End Sub
The second way is to utilize the Find method of the Cells object, in this manner:
Sub SetFind2()
Dim c As Range
c = Cells.Find(What:="", LookIn:=xlValues, LookAt:=xlPart)
End Sub
Either of these will work just fine, to a point. (More about that in a moment.) All you need to do
is run the macro when you first start Excel, either manually or as part of an Auto_Open macro.
The settings in the dialog box are then changed for the remainder of the Excel session, unless
you manually change them.
Now, to the point. It seems that there is no way to change the Within setting of the dialog box.
This setting defaults to looking in the Worksheet. You can manually change it to Workbook, and
Excel will dutifully remember the setting for your current session. However, you cannot seem to
change the setting within VBA. You'll note that neither of the sample macros, above, change this
particular setting. Further, if you record a macro in which you change the two settings (Within
and Look In), you end up with something that looks like this:
Sub Macro1()
'
' Macro1 Macro
'
'
Sheets("Sheet1").Select
Cells.Find(What:="", After:=ActiveCell, LookIn:=xlValues, _
LookAt:=xlPart, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False).Activate
End Sub
If you save the workbook in which this macro exists, restart Excel, and then examine the settings
in the Find dialog box (press CTRL+F), you'll note that the settings are back to the default of
searching within the worksheet and looking in formulas. Run the macro and then look at the
dialog box again; you should see that the settings are for looking in values within the worksheet;
the macro doesn't set the Within setting, even though you recorded it when you set Within to
Workbook.
The apostrophe is actually considered a “prefix character” for a cell. The possible values of the
prefix character are set by the Transition Navigation Keys setting in Excel, and the value of the
setting is saved on a workbook-by-workbook basis. You can change this setting by using the
Transition tab of the Options dialog box (in versions of Excel through 2003) or in the advanced
area of the Excel Options dialog box, at the bottom of the dialog box (in Excel 2007 or later
versions).
If the setting is cleared (the default condition for the setting), then the value of the prefix
character for each cell can either be blank or an apostrophe. If the cell contains text, then the
setting of the prefix character doesn’t really matter much. If the cell contents are not text, then
setting the prefix character to an apostrophe forces Excel to treat the cell contents as if they are
text. So, for instance, the number 123 is treated as text—not a number—and shows up in the
Formula bar as '123.
If the Transition Navigation Keys setting is selected (the check box has a check mark in it), then
the value of the prefix character for each cell can have one of five different values. These values
are consistent with the prefixes used in Lotus 1-2-3 and are, oddly enough, supported in Excel
only as a transitional aid to the regular usage in the program. The possible values are an
apostrophe (left-justified), quote mark (right-justified), carat (centered), back slash (repeated), or
blank (non-text item).
Now, back to Richard’s original question: how to search and get rid of that leading apostrophe.
You can’t use Find and Replace to do the editing because the apostrophe isn’t really part of the
cell contents. So, you must do the changing in a macro. The changing is relatively easy. First,
you’ll want to make sure that the workbook has the Transition Navigation Keys setting cleared.
Why? Because you probably don’t want to mess up the prefix character for the cells if the
workbook could be used at some future point with Lotus 1-2-3 again. You make sure that the
setting is correct, in your macro, with the following line:
Application.TransitionNavigKeys = False
You can then step through a selection of cells and check to see if the prefix character for each
cell is an apostrophe. If it is, then all you need to do is have the macro do the equivalent of
manually retyping the contents of the cell, in the following manner:
Note that the macro checks what is in the PrefixCharacter property. This property can be read in
VBA, but it cannot be changed directly. That is why the macro needs to use the seemingly
simple line to assign the value of each cell back into the cell—essentially retyping the contents.
If you would rather not use a macro to get rid of the apostrophe prefix characters, then you can
take advantage of a strange little quirk of Paste Special. Follow these general steps:
After the “adding” of the blank cell to each of the target cells, the prefix character—if any—is
removed.
The answer is to remember that you can enter any ASCII code into the “Find What” box by
holding down the ALT key and using the numeric keypad. Since the ASCII code for the line
break is 10, you can follow these steps:
1. Press CTRL+F to display the Find tab of the Find and Replace dialog box.
2. In the Find What box, hold down the ALT key as you type 0010 on the numeric keypad.
It may not look like anything is in the Find What box, but the character is there.
3. Click Find Next.
If you want to find cells containing a line break through a macro, you can use the following:
Sub FindLineBreak()
WhatToFind = Chr(10)
Cells.Select
Selection.Find(What:=WhatToFind, After:=ActiveCell, _
LookIn:=xlValues, LookAt:=xlPart, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate
End Sub
The approach you use will be dictated by the range you want to search. If you want to search on
the same worksheet on which you want the answer displayed, then you can use a formula, such
as the following:
=ADDRESS(MAX(ROW(1:5)*(A1:E5="my text")),
MAX(COLUMN(A1:E1)*(A1:E5="my text")),4)
This should be entered as an array formula (press CTRL+SHIFT+ENTER), and it only searches in
the range A1:E5. You can, if desired, change the range by adjusting the formula appropriately.
A larger search area would be to look at an entire worksheet. This can still be done using a array
formula, such as the following:
=ADDRESS(MAX(ROW(Sheet1!1:65000)*(IF(Sheet1!1:65000=$A$1,1,0))),
MAX(COLUMN(Sheet1!$1:$65000)*IF(Sheet1!1:65000=$A$1,1,0)))
The formula assumes that what you are looking for is stored in cell A1. You should change the
Sheet1 designation to the name of whatever worksheet you want searched.
If you want to search a wider range, such as all the worksheets in a workbook, then the best
solution is to use a macro that calls upon the Find function within Excel.
bFound = False
For Each wks In ActiveWorkbook.Worksheets
With wks
Set rCell = .Cells.Find _
(What:=vValue, After:=.Cells(1), _
LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not rCell Is Nothing Then
bFound = True
Exit For
End If
End With
Next
If bFound Then
FindAddr = wks.Name & "!" & _
rCell.Address(False, False)
Else
FindAddr = "Not Found"
End If
Set wks = Nothing
Set rCell = Nothing
End Function
This function is designed to be called from another macro, which passes it whatever should be
searched for in the vValue parameter. The function returns either the full address (including
worksheet name) of the first match, or it returns “Not Found” if there was no match.
There are two things you can try. First, if you are looking for an exact match for cell contents,
then you can use a formula. The basic formula is this:
=ADDRESS(MATCH(C2,A:A,0),1)
In this example, cell C2 contains the value you are looking for and column A is the range of cells
being searched. The formula returns a result regardless of the capitalization of C2 or the values
in column A. Thus, if C2 contains "apple", then the formula will match positively to cells that
contain "apple," "Apple," or "APPLE." Indeed, any mix of capitalization will match.
This formula will not return an address for a cell that contains what you are searching for amidst
other text. So if you are searching for "apple" (cell C2), it won't return the address of a cell that
contains the phrase "apple crisp." You can modify this behavior, a bit, by adding wild card
characters to the search cell. For instance, if you search for "*apple*" then the formula returns
the address of a cell that contains "apple", even if it is preceded or followed by other characters.
It should be pointed out that this formula only returns the address of the first cell in the range
which meets the criteria. If you actually want the addresses of all cells that meet the criteria, then
you'll need to rely on a macro. The following is a good example:
Application.Volatile
sSearch = LCase(y)
For Each r In x
If InStr(1, LCase(CStr(r.Value)), sSearch) > 0 Then
sResults = sResults & r.Address & ", "
End If
Next r
If Len(sResults) > 2 Then
FindMe = Left(sResults, Len(sResults) - 2)
Else
FindMe = ""
End If
End Function
You use the function by simply providing the range you want to search along with what you
want to search for:
=FindMe(A:A, "apple")
If you use a large range (as in this example—all of column A), then don't be surprised if the
function takes a noticeable amount of time to return a result. This makes sense, as it has to search
through every cell in the range, regardless of whether there is anything in the cell or not.
You also don't need to use any wildcards with this function; it assumes that a match occurs if
what you are looking for is located anywhere within the cell. It also doesn't pay attention to the
capitalization of what you are looking for or the capitalization of anything in the search range.
The short answer is that it cannot. The Find and Replace capabilities in Excel are more limited
than those in Word, where you have the capability to search for wildcards and use the “Find
What” text in what is replaced. (These are just two examples of capabilities missing in Excel’s
Find and Replace.)
One potential answer, then, is to copy your data over to Word, use Find and Replace to make the
changes, and then copy the data back. Of course, you run the risk of losing your formatting in the
round trip, losing some of your precision, and converting all your formula results to static values.
For many users, these are not acceptable risks.
Another option is to use the concatenation capabilities of Excel. For instance, if the values you
want to pre-pend is in column A (beginning with A1), then you would use a formula such as this
is column B:
="A" & A1
The result pre-pends the letter A to whatever is in A1. This works for pre-pending anything
except an apostrophe. Trying to pre-pend an apostrophe ends up with '123 or 'xyz, but the
apostrophe is visible in the cell. The result is not the same, to Excel, as typing an apostrophe
followed by 123 or an apostrophe followed by xyz. (In the case of typing, the apostrophe
indicates the cell contents should be treated as text and the apostrophe is only visible in the
Formula bar, not in the cell itself.)
If you actually want to change the values in a series of cells (which a desire to use Find and
Replace would suggest), then the only thing you can do is to use a macro to make your changes.
If you only want to pre-pend cells beginning with a set value (such as 123) with a letter (such as
A), then a simple macro will suffice.
Sub Prepend1()
ToFind = "123"
ToFindLength=Len(ToFind)
ToPrepend = "A"
Note that the ToFind variable contains the beginning text that you want to pre-pend and the
ToPrepend variable contains what you want to appear before that string. In this instance, when
you select a range of cells and run the macro, anything beginning with 123 (such as “123” or
“12345” or “123D27X”) will have the letter A added to the front of the cell.
Such a macro doesn’t help, however, when you want to add the letter to the front of every cell in
the range, not just those beginning with 123. In that case you need a different approach.
Sub Prepend2()
Dim rng As Range
Dim c As Range
Dim ToPrepend As String
ToPrepend = "A"
This macro takes a subset of whatever cells you selected before running it (only those cells
containing text and numeric values) and then adds the contents of the ToPrepend variable to the
start of the cell. If you want to change what is pre-pended, simply change the value of the
variable. (It should be noted that if you change ToPrepend to an apostrophe, then the cells to
which the apostrophe is pre-pended behave exactly as if you had typed and apostrophe followed
by the cell value.)
There is no way to accomplish this task using the Find and Replace tools in Excel. That means
that you need to use a formula or a macro to do the task. Formulas can be used to make sure that
the last two characters of a cell are uppercase:
The problem with such a formula, however, is that it is non-discriminating. As long as any cell it
is used on has a compass direction as its last two characters, there is no problem. But if some
cells don't have the compass direction, then you run into problems real fast. In that case you need
to actually have the formula check the last characters:
This formula checks the last three characters to see if they are a space followed by either ne, se,
nw, or sw. If this is the case, then those last two characters are made uppercase. The formula can
be shortened just a bit if you approach it differently:
If you prefer to not use a formula, you can easily create a macro that will do the checking and
conversion for you:
Sub CapDirections()
For Each RCell In Selection
CText = UCase(Right(RCell.Value, 3))
If CText = " NE" Or CText = " SE" _
Or CText = " SW" Or CText = " NW" Then
RCell.Value = Left(RCell.Value, _
Len(RCell.Value) - 3) + CText
End If
Next
End Sub
To use the macro, just select the cells containing the addresses, and then run it. It checks to see if
one of the four compass points are at the end of the cell value, and if it is then it makes sure that
the compass direction is uppercase.
You should note that these solutions are based upon there only being four possible compass
directions in your addresses. If your addresses have more wide-ranging compass directions (like
N or SSE) then you will definitely want to use a macro-based solution because the checking
quickly becomes very complex for a formula.
There are a couple of ways you can approach this issue. One is to use the Go To feature in Excel.
Simply follow these steps:
Note that this approach results in any error values being replaced, not just those with the #N/A
error. If you have to make the replacements quite a bit or you want to only affect #N/A errors,
you may want to use a macro to do the replacements:
Sub Replace_NAs()
Dim C
For Each C In ActiveSheet.UsedRange
If Application.WorksheetFunction.IsNA(C) Then
C.Value = 0
End If
Next
End Sub
You should note that all these options result in the formulas in the cells (those that returned the
#N/A values) being permanently replaced with a 0 or whatever value you specify. The only way
to not replace the formulas is to change those formulas to use an IF statement to check for the
error condition before applying the formula.
First, you could easily make the text in your text boxes or in your chart labels dynamic, so that it
is tied to the contents of some worksheet cells. For instance, you could do the following for your
text boxes:
1. Copy your text from each of text boxes to a range of cells on your worksheet. (For this
example, assume that you copied the contents of ten text boxes to the range Z1:Z10.)
2. Select the first text box (the one that corresponds to cell Z1) and get rid of the text box's
contents.
3. With the text box still selected, enter the following into the Formula bar: =Z1. When
you press ENTER, the text box should reflect whatever is in cell Z1.
4. Repeat steps 2 and 3 for each of your other text boxes, using the appropriate cell
reference for each in step 3.
That's it. You can use the same technique with custom chart labels—all you need to do is select
the chart label and enter a cell reference in the Formula bar. With the text boxes and chart labels
tied to worksheet cells, you can easily use Find and Replace to search for and change
information in the cells. When the changes are made, the text boxes and chart labels should
automatically reflect the changes in the cells.
The only way to actually change the text within a text box or chart label is to change it manually
or change it using a macro. The code would need to step through each text box in the worksheet
and then make your change. The following is a simple version of a macro that can make such a
change.
Sub TextBoxReplace()
Dim shp As Shape
Dim sOld As String
Dim sNew As String
'Change as desired
sOld = "Old string"
sNew = "New string"
On Error Resume Next
For Each shp In ActiveSheet.Shapes
With shp.TextFrame.Characters
.Text = Application.WorksheetFunction.Substitute( _
.Text, sOld, sNew)
End With
Next
End Sub
This macro steps through all the shapes in the worksheet (text boxes are shapes) and then
replaces whatever is in the sOld variable with whatever is in the sNew variable. Applying the
same technique to chart labels is only a bit more complex, as shown in the following macro:
Sub ChartLabelReplace()
Dim Cht As ChartObject
Dim Ser As Series
Dim scPt As Point
Dim sOld As String
Dim sNew As String
'Change as desired
sOld = "Old String"
sNew = "New String"
On Error Resume Next
For Each Cht In ActiveSheet.ChartObjects
For Each Ser In Cht.Chart.SeriesCollection
For Each scPt In Ser.Points
With scPt.DataLabel
.Text = Application.WorksheetFunction.Substitute( _
.Text, sOld, sNew)
End With
Next
Next
Next
End Sub
The macro steps through each data label for every data series on every chart and (again) replaces
any instances of whatever is in sOld with whatever is in sNew.
The find and replace capabilities of Excel are more limited than those of Word, where such
replacements are relatively easy. While you could export your information to Word, do the
replacements, and then import it back into Excel, there are some things you can do without ever
leaving Excel.
First, however, let's examine something that you might reasonably think would work, but doesn't
really. Note that the Replace tab of the Find and Replace dialog box seems to provide a way to
specify attributes for the text you want to use as the replacement. This might lead you to think
that you could do the following:
While this sounds good in theory, it won't work. You can follow the steps, including making sure
that the replacement 2 is set to be superscript. The problem, however, is that Excel applies the
superscript format to the entire cell, not just to the 2. Thus, you end up with yd2 completely as
superscript.
You could, if you wanted, skip superscripting all together and just use a typeface character that
appears superscripted. If you use the Symbol dialog box, you can find the digits 0 through 3 that
appear superscripted. If you use the superscripted digit 2 (ASCII 178) in your replacement text,
then you can get the desired appearance. Follow these steps:
1. Press CTRL+H to display the Replace tab of the Find and Replace dialog box.
Finally, if you really want to use superscripts, your best bet is going to be using a macro to do the
formatting. The simplest method is tied to the specific example provided—making the 2 in yd2
superscript.
Sub DoConvert()
Dim c As Range
To use the macro, select the cells you want to modify, then run the macro. Each cell in the
selection is stepped through and checked to see if it contains the text yd2. If it does, then the
third character (the 2) is made superscript; the rest of the cell is undisturbed.
The short answer is that there is no way to do this in Excel, as described. If you only wanted to
convert the second character of a text value from “b” to “a”, then that can be done rather easily:
=REPLACE(A1,2,1,"a")
This, however, is probably not what you want to do; you want a way to use wildcards in the
“replace with” text. The technical term for doing such string replacements is called REGEX,
which is short for Regular Expressions. REGEX started with languages like Perl but was so
powerful that many other programming languages added it on.
The VBA used in Excel is no exception. REGEX was added to Visual Basic 6.0, which means
that it made its way to Excel’s VBA in Excel 2003. The first step in using REGEX is to turn it
on. You do this in the VBA Editor by choosing Tools | References and then making sure there is
a check mark next to the Microsoft VBScript Regular Expressions 5.5 option.
Enabling this reference allows you to create REGEX objects. These objects possess a Test
method and a Pattern property. This means that you set the Pattern property, and then the Test
method checks to see if the pattern exists. A REGEX object also has a Replace method, which is
used to do replacements.
Before proceeding, it is important to understand that regular expressions can get very complex
and, well, “geeky.” There is no way around it; how to work with regular expressions has been the
subject of entire books. Fortunately, for the purposes of this tip, the expressions are rather simple
in nature. In this case we’ll use the pattern “^ab.*de$”. This pattern refers to a word that starts
(indicated by the ^) with “ab” followed by an arbitrary expression (indicated by *) consisting of
at least one character (indicated by the period) and ending (indicated by the $) with “de”.
Here is the code that implements the use of the REGEX object to do the actual replacements.
reg.IgnoreCase = True
reg.MultiLine = False
reg.Pattern = Pattern1
If reg.Test(TestString) Then
reg.Pattern = Pattern2
SearchNReplace = reg.Replace(TestString, ReplaceString)
Else
SearchNReplace = TestString
End If
End Function
To use this macro, start with the strings you want to change in column A. Assuming that the first
string is in cell A1, you could place the following into another cell in order to get the changed
text:
=SearchNReplace1("^ab.*de$","^ab","aa",A1)
This tells the macro that the pattern you want to look for is “^ab.*de$” (the first parameter), and
that you want to replace “^ab” with “aa”. This formula can be pasted down the column, and you
end up with a conversion of column A where the string “ab*de” is replaced by “aa*de”.
If you are using an older version of Excel that does not allow you to create REGEX objects, or if
you would prefer not to do so, then you can create a macro that will simply step through a group
of selected cells and look for any cell that begins with “ab” and ends with “de”, and then replaces
the beginning part with “aa”.
Sub SearchNReplace2()
Dim sFindInitial As String
Dim sReplaceInitial As String
Dim iLenInitial As Integer
Dim sFindFinal As String
Dim sReplaceFinal As String
Dim iLenFinal As Integer
Dim sTemp As String
Dim rCell As Range
sFindInitial = "ab"
sReplaceInitial = "aa"
sFindFinal = "de"
sReplaceFinal = "de"
To use this routine, simply select the cells you want to change, and then execute the macro. You
should also make changes to the sFindInitial, sReplaceInitial, sFindFinal, and sReplaceFinal
variables, as needed.
This can be done by using a macro. One of the properties your macro can access is the width of
each column. This means that you can step through the columns and check those widths against
the desired width (3.6) in the following manner:
Sub ListColumns()
Dim dColWidth As Double
Dim sMsg As String
Dim x As Integer
dColWidth = 3.6
sMsg = ""
For x = 1 To ActiveSheet.Columns.Count
If Columns(x).ColumnWidth = dColWidth Then
sMsg = sMsg & vbCrLf & x
End If
Next
If sMsg = "" Then
sMsg = "There are no columns with" & _
vbCrLf & "a width of " & dColWidth
Else
sMsg = "The following columns have" & _
vbCrLf & "a width of " & dColWidth & _
":" & vbCrLf & sMsg
End If
MsgBox sMsg
End Sub
This macro displays a message box that lists the columns that match the desired width. The
macro can be made more robust with some simple changes. For instance, the following example
prompts the user for a column width, counts the number of matches, and even compensates if the
worksheet is using R1C1 referencing mode.
Sub Find_ColumnWidth()
Dim Col As Integer ' Column (loop variable)
Dim ColsFound As Integer ' Columns Found Count
Dim Desired_Width As Double ' Column Width To Find
If Application.ReferenceStyle = 1 Then
' Using "A1" format
S = Cells(1, Col).Address(ReferenceStyle:=xlA1)
S = Mid(S, 2, Len(S) - 3)
Else
' Using "R1C1" format
S = Trim(Str(Col))
End If
OutStr = OutStr & S & vbCrLf
End If
Next
If ColsFound = 0 Then
OutStr = "No matches found"
End If
Sorting Worksheets
If you are working on a project that uses a lot of worksheets in a workbook, you may want to sort
them by worksheet name. The following short macro will do the trick very nicely.
Sub SortSheets()
Dim I As Integer, J As Integer
For I = 1 To Sheets.Count – 1
For J = I + 1 To Sheets.Count
If UCase(Sheets(I).Name) > UCase(Sheets(J).Name) Then
Sheets(J).Move Before:=Sheets(I)
End If
Next J
Next I
End Sub
This macro works if you have a relatively low number of worksheets in your workbook. If, when
you run the macro, you note that it takes a great deal of time to run, you may want to use a more
efficient sorting algorithm in the macro. For instance, the following is a version that reads the
names of all the worksheets into an array, sorts the array using the BubbleSort algorithm, and
then does the actual arranging.
Sub SortSheetsB()
Dim I As Integer
Dim sMySheets() As String
Dim iNumSheets As Integer
iNumSheets = Sheets.Count
Redim sMySheets(1 To iNumSheets)
For I = 1 To iNumSheets
sMySheets(I) = Sheets(I).Name
Next I
BubbleSort sMySheets
For I = 1 To iNumSheets
Sheets(sMySheets(I)).Move Before:=Sheets(I)
Next I
End Sub
Lower =LBound(sToSort)
Upper =UBound(sToSort)
For I =Lower To Upper – 1
K =I
ForJ = I + 1 To Upper
If sToSort(K) > sToSort(J) Then
K = J
End If
Next J
If I <> K Then
Temp = sToSort(I)
sToSort(I) = sToSort(K)
sToSort(K) = Temp
End If
Next I
End Sub
Anyone who has programmed for some time knows that BubbleSort is a good general-purpose
sorting routine, but there are faster ones available. For instance, if you have quite a few
worksheets and they start out very disorganized, you may find that the QuickSort algorithm is
more beneficial. All you would need to do to change the above to use QuickSort is add the
QuickSort algorithm as a subroutine (you can find the algorithm in any good Visual Basic
programming book) and then call the procedure from within the main SortSheets macro. (This
means changing the line where BubbleSort is now called.)
There is another difference between this second macro and the first. The first macro does not pay
attention to the case of the text used to name your worksheets. Thus, My Worksheet would be
viewed the same as MY WORKsheet. The second macro does pay attention to text case, and
sorts accordingly.
Non-standard Sorting
It is not unusual in an office environment to work with Excel files created by other people. Some
of these files can be pretty different than the files you might create. For instance, you might
inherit a file in which the first column contains a person’s first name on the first line, then their
last name on the second line. (The user pressed ALT+ENTER to separate the first name from the
second name within the same cell.) What if you need to sort the rows in the worksheet based on
the last name of the person?
Perhaps the best way to complete such a task is to insert a new column in the worksheet—
column B. (This column could be hidden so it doesn’t show up when normally working with the
worksheet or when printing it out.) The following formula should then be placed in each cell of
column B:
=RIGHT(A2,LEN(A2)-FIND(CHAR(10),A2))
Obviously the cell references will change when placed in column B. In this formula the FIND
portion determines the position of the ALT+ENTER character (the character code of this character
is 10). The RIGHT function returns the characters in the cell starting at the character following
the ALT+ENTER character. This solution results in column B containing the information on the
second line of the first column. You can then easily sort based on the information in column B.
There is one assumption made in this solution—that there are only two lines in each cell of
column A. If there are more, or less, then the solution becomes more difficult. If that is the case,
the best (and easiest) solution may be to reformat the worksheet so that the sort key is in a
column all by itself. If that is not possible (for whatever reason), then the following user-defined
VBA function can be used:
B1 = InStr(x, Chr(10))
B2 = InStr(B1 + 1, x, Chr(10))
If (B1 + B2) > 0 Then
If B2 > 0 Then
SecLine = Mid(x, B1 + 1, B2 – B1 - 1)
Else
SecLine = Mid(x, B1 + 1)
End If
End If
End Function
To use this routine, simply include the following in the cells in column B:
=SecLine(A2)
Regardless of how many lines there are in cell A2 (in this instance), the function returns a string
representing the value of the second line.
If you are using Excel 2002 or a later version the answer is quite easy: These versions of Excel
allow you to specify what users can and cannot do with a protected worksheet. When you choose
Tools | Protection | Protect Sheet (Excel 2002 and Excel 2003) or display the Review tab of the
ribbon and click Protect Sheet in the Changes group (Excel 2007 and later versions), Excel
displays the Protect Sheet dialog box. At the bottom of the dialog box is a long list of check
boxes. All you need to do is select what the user should be able to do with the worksheet. One of
the options (you need to scroll down a bit) is Sort. If you select this option, then users can sort
protected data.
If you are using an older version of Excel, the solution is to create a macro that unprotects the
worksheet, sorts the data, and then protects the worksheet again. The following is a simple
example:
Sub Sorting()
ActiveSheet.Unprotect
Range("A1:D100").Sort Key1:=Range("A1"), _
Order1:=xlAscending, Header:=xlGuess, _
OrderCustom:=1, MatchCase:=False, _
Orientation:=xlTopToBottom
ActiveSheet.Protect
End Sub
This example sorts the data in the range A1:D100 based on the contents of column A. The macro
illustrates the general concept behind this approach, but you will need to modify it to reflect the
needs of your data and your users.
If you go the macro route, you need to assign the macro to either a toolbar button or a menu
command. If you don’t the user will never be able to use it, since the Macros menus are disabled
in a protected document.
Sorting by Colors
Excel allows you to sort the data in your worksheets by any number of attributes. One of the
things that you cannot intrinsically sort by, however, is the color of cells. (Well, you can't sort by
color in versions of Excel older than Excel 2007. In the newer versions you can sort by color.)
For some applications this could be a very handy feature. The following macro, SortByColor,
will sort a table based on the color with which a cell is formatted.
Sub SortByColor()
On Error GoTo SortByColor_Err
Application.ScreenUpdating = False
SortByColor_Exit:
Application.ScreenUpdating = True
Set rngSort = Nothing
Exit Sub
SortByColor_Err:
MsgBox Err.Number & ": " & Err.Description, _
vbOKOnly, "SortByColor"
Resume SortByColor_Exit
End Sub
The macro works by first asking you the beginning cell of the range you want to sort. This
should be the top-most cell in the range. The macro then inserts a column (just temporarily) in
which color values can be stored. It then steps through each cell in the range defined by the
starting cell you specified.
SortByColor assumes your data table doesn't have a header row. If it does, you should change
the actual sorting command. Simply change Header:=xlNo to Header:=xlYes.
You should note that the SortByColor macro will only sort cells based on the explicit color
applied to the cell, it will not sort if the color of the cell is the result of conditional formatting.
Assuming the fill colors are in the cells of column A, all you need to do is make sure there is an
empty column B. Then place the following formula in cell B2 and copy it down for each record:
=GetFillColor(A2)
When you are done, column B will contain the index values of each fill color used in column A.
You can then sort by column B, which has the result of grouping all the like fill colors together.
If you need to get more elaborate, for instance, if you need to sort in a particular order (yellow
first, red second, green third, etc.), then you cannot rely solely on the fill color's index value. In
such an instance you must rely on a different method of returning a color. Consider the following
macro:
Application.Volatile
lngColor = rngSource.Interior.ColorIndex
This macro works differently than the last one. It requires two ranges to work properly. The first
range is basically a color table that indicates the order in which you want colors sorted. For
instance, cells E1 through E9 could contain the nine colors you want to use for sorting, in the
order that you want them sorted. You would then place the following formula in cell B2 and
copy it down for each record:
=GetColor($E$1:$E$9,A2)
The result is that column B will contain the values 1 through 9, representing the colors in your
color table. If the color in a cell does not have a corresponding color in the color table, then the
function returns the value of 99. When you sort the records in your table, you end up with them
sorted as you want.
There is a huge drawback to using merged cells, however: You can’t sort tables that include
them. If you try, you’ll get a message that says “The operation requires the merged cells to be
identically sized.”
The most obvious solution to the problem is to not use merged cells. Let’s say, for instance, that
you have a worksheet in which each “record” actually consists of two rows, and that the first
column of the worksheet contains merged cells. (Each two-row record starts with two merged
cells spanning the two rows. This merged cell contains a project name.)
It is better to unmerge the cells in the first column, but then you may wonder how to make the
records sort properly in the worksheet; how to keep the row pairs together during a sort. You can
do this by putting your project name in the first row and the project name appended with “zz” in
the second row. For instance, if the first row contains “Wilburn Chemical” (the project name),
then the second row could contain “Wilburn Chemicalzz”. Format the second row’s cell so the
name doesn’t show up (such as white text on a white background), and you can then successfully
sort as you want to.
Another solution is to use a macro to juggle your worksheet and get the sorting done. Assuming
that the merged cells are in column A (as previously described), you can use the following macro
to sort the data by the contents of column A:
Sub SortList()
Dim sAddStart As String
Dim rng As Range
Dim rng2 As Range
Dim lRows As Long
Application.ScreenUpdating = False
sAddStart = Selection.Address
Set rng = Range("A1").CurrentRegion
With rng
lRows = .Rows.Count - 1
.Cells(1).EntireColumn.Insert
.Cells(1).Offset(0, -1) = "Temp"
.Cells(1).Offset(1, -1).FormulaR1C1 = _
"=+RC[1]&"" ""&ROW()"
.Cells(1).Offset(2, -1).FormulaR1C1 = _
"=+R[-1]C[1]&"" ""&ROW()"
Set rng2 = .Cells(1).Offset(1, -1).Resize(lRows, 1)
Range(.Cells(2, 0), .Cells(3, 0)).AutoFill _
Destination:=rng2
rng2.Copy
rng2.PasteSpecial Paste:=xlValues
.Columns(1).MergeCells = False
.CurrentRegion.Sort _
Key1:=Range("A2"), Order1:=xlAscending, _
Header:=xlYes, OrderCustom:=1, _
MatchCase:=False, Orientation:=xlTopToBottom
rng2.EntireColumn.Delete
The macro inserts a temporary column, reads the items from the first column of the list, appends
the row number, copies it down the temporary column, unmerges the cells, sorts the list, deletes
the temporary column, and re-merges column A. (That’s a lot of work just to sort a table with
merged cells!)
This macro is very specific to a particular layout of your data, and therefore would need to be
tested and probably modified to make sure it would work with data formatted in any other way.
Unfortunately, Excel doesn’t have a built-in way to determine the sorting criteria used for a
range of data. You could theoretically write a macro that would check each column and see if it
were in ascending or descending order. This will tell you if that single column was sorted, but
that doesn’t necessarily mean that the entire data table was sorted by that column—it could just
be coincidence that the column is in sorted order, and the sort was done by some other column.
The task of checking gets even trickier when you start considering secondary and tertiary sorts.
There is one thing you can try, however, to determine if a particular column is sorted and
whether it is sorted in ascending or descending order. (Remember: this won’t tell you if the
particular column was the primary column used for sorting, it will only tell you if the column is
sorted.)
The idea behind the macro is to copy the contents of the column to a temporary worksheet, two
times. For instance, if you want to check out column F, the macro copies column F to columns A
and B on the temporary worksheet. The macro then sorts column B in ascending order and
compares it to column A. If the sorted and unsorted columns are the same, then the original
column was in ascending order. Then column B is sorted in descending order and the comparison
done again. Again, if the columns are equal then the column is in descending order.
Sub TestIfSorted(i)
Dim CColumn as Number
Dim CSheet as String
Dim FlagSort as String
Sheets("TempSort").Select
Range("A1").Select
ActiveSheet.Paste
Range("B1").Select
ActiveSheet.Paste
Application.CutCopyMode = False
Once it is determined whether the original column was in ascending or descending order, then
the first cell of the column in the original worksheet is set to yellow or orange, respectively.
Finally, the temporary worksheet is deleted.
This macro could be modified so that it was called once for each column in a data table. Running
the macro for an entire table wouldn’t take that long, but would provide a colorful representation
as to whether individual columns are sorted in ascending or descending order.
Of course, any macro like this is not trivial, so it may just be easier for you to figure out how you
want to sort the data, and then sort it that way from the get-go.
There are a couple of ways that you can approach this issue. The first is to create custom views
(described in other issues of ExcelTips) that include your data sorted in a desired manner. You
can always store and recall the view to see it sorted as you want.
Perhaps the most flexible approach, however, is to perform your sorting in a macro instead of by
using the Sort dialog box. You can easily use the macro recorder to set up and execute your sort;
later running the macro will sort the same area over again, using the same criteria.
A more general macro would be one like what is shown below. It sorts columns A, B, and C in
descending order. All you need to do is select the data you want sorted before running the macro.
(You should, of course, make sure that the range you select includes columns A, B, and C.)
Sub SortMyData()
Selection.Sort _
Key1:=Range("A1"), Order1:=xlAscending, _
Key2:=Range("B1"), Order2:=xlAscending, _
Key3:=Range("C1"), Order3:=xlAscending, _
Header:=xlGuess, OrderCustom:=1, _
MatchCase:=False, Orientation:=xlTopToBottom, _
DataOption1:=xlSortNormal, _
DataOption2:=xlSortNormal, _
DataOption3:=xlSortNormal
End Sub
The only way that this can be done is by using a macro that is triggered whenever something new
is entered in the worksheet. You can, for instance, add a macro to the code for a worksheet that is
triggered when something in the worksheet changes. (You can view the code window by right-
clicking the worksheet tab and choosing View Code from the resulting Context menu.) The
following is an example of one such simple macro:
The macro assumes that you want to sort on the data in column A and that there is a header in
cell A1. If the names are in a different column, just change the cell A2 reference to a different
column, such as B2, C2, etc.
Of course, sorting anytime that any change is made can be bothersome. You might want to limit
when the sorting is done so that it only occurs when changes are made to a specific portion of
your data. The following version of the macro sorts the data only when a change is made in
column A.
OrderCustom:=1, MatchCase:=False, _
Orientation:=xlTopToBottom
End If
End Sub
There are some drawbacks to using a macro to automatically sort your data. First, since you are
using a macro to sort, the operation is essentially "final." In other words, after the sorting you
can't use CTRL+Z to undo the operation.
A second drawback is that data entry might become a bit disconcerting. For instance, if you use
any of the above macros and you start to put names into the worksheet, they will be sorted as
soon as you finish what is in column A. If your data uses five columns and you start your entry
in row 15, as soon as you get done entering the name into column A (and before you enter data
into columns B through E), your data is sorted into the proper order. This means that you will
need to find where it was moved in the sort, select the proper cell in column B, and then enter the
rest of the data for the record. Of course, the way around this is to add your data in an unnatural
order—simply make sure that the name in column A is the very last thing you enter for the
record.
Macro Cookbook
The AutoFill tool in Excel has a few standard sequences it will fill automatically, such as dates
and numeric sequences. The very powerful part of AutoFill, however, is that you can create
custom lists that the tool uses just as easily as the built-in sequences. In order to create a custom
list manually, you can follow these steps if you are using a version of Excel prior to Excel 2007:
1. Choose Options from the Tools menu. Excel displays the Options dialog box.
2. Make sure the Custom Lists tab is selected.
If you are using Excel 2007 or later, then you create your custom list in this manner:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
2. Make sure the Popular option is selected at the left side of the dialog box.
3. Click Edit Custom Lists. Excel displays the Custom Lists dialog box.
You’ve now created your custom list, and you can close any open dialog boxes. To use the
custom list, just type one or two letters you want to start the sequence with, select those cells,
and use the AutoFill handle to drag over as many cells as you want to fill.
There’s another way to create the custom list that may be a bit easier, just in case you don’t want
to type twenty-six letters in the dialog box. Instead, if you already have the letters of the alphabet
in twenty-six cells, simply select those cells and follow these steps if you are using a version of
Excel prior to Excel 2007:
1. Choose Options from the Tools menu. Excel displays the Options dialog box.
2. Make sure the Custom Lists tab is selected. The range of cells you selected should be
shown in the Import List from Cells box.
3. Click Import.
If you are using Excel 2007 or later, follow these steps with the twenty-six cells selected:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
2. Make sure the Popular option is selected at the left side of the dialog box.
3. Click Edit Custom Lists. Excel displays the Custom Lists dialog box.
4. Click Import.
Now you can close the dialog boxes and use the custom list as you desire.
Of course, there is one drawback with using a custom AutoFill list, especially when it comes to
creating word searches: the letters added to blank squares are always in a predictable sequence,
which could make finding the actual words a bit easier than you want. To make the puzzles a bit
more challenging, it would be better to fill the non-word squares with random letters.
One easy way to get random letters is to use the following formula:
=CHAR(RANDBETWEEN(65,90))
This formula works because the RANDBETWEEN function returns a random numeric value
between the two boundary values provided. In this case, it will return a value between 65 and 90,
which are the ASCII values of the letters A and Z, respectively. The CHAR function is then used
to convert this random numeric value into an actual letter.
In versions of Excel before Excel 2007 the RANDBETWEEN function is part of the Analysis
ToolPak, an add-in that many people have installed. (Choose Tools | Add-ins to see if you have it
installed.) If you prefer to not have the add-in enabled, then you can rely upon a more basic
formula, such as the following:
=CHAR((65+(90-65)*RAND()))
The CHAR function should look familiar; the only difference is the use of the RAND function to
generate the random value instead of RANDBETWEEN.
If you create a lot of word search puzzles, then you may want to use a macro to fill a range of
cells with random letters of the alphabet. There are any number of ways that such a macro could
be put together; the following is one that is particularly flexible. It will work with either a pre-
selected range (a range selected when you run the macro) or you can select a range after you run
the macro.
Sub AlphaFill()
Dim Cell, CellChars
Dim Default, Prompt, Title
Dim rangeSelected As Range
Dim UpperCase As Boolean
UpperCase = True
Randomize
For Each Cell In rangeSelected
CellChars = Chr(64 + Int((Rnd * 26) + 1))
If Not UpperCase Then CellChars = LCase(CellChars)
Cell.Value = CellChars
Next
End Sub
The macro code, as written, inserts the uppercase letters into whatever range you specify. If you
want to use lowercase letters instead, then all you need to do is set the UpperCase variable to
False rather than True.
NumberToWords will convert any number between 0 and 999,999. To use it, simply select the
cell (or cells) whose contents you want to convert, then run it. You should note that the cells
must contain whole number values, not formulas that result in whole number values. The actual
contents of the compliant cells are changed from the original number to a text representation of
that number. In other words, this is not a format change, but a value change for those cells.
Sub NumberToWords()
Dim rngSrc As Range
Dim lMax As Long
Dim bNCFlag As Boolean
Dim sTitle As String, sMsg As String
Dim vCVal As Variant
Dim lNumber As Long, sWords As String
bNCFlag = False
For lCtr = 1 To lMax
vCVal = rngSrc.Cells(lCtr).Value
sWords = ""
If IsNumeric(vCVal) Then
If vCVal <> CLng(vCVal) Then
bNCFlag = True
Else
lNumber = CLng(vCVal)
Select Case lNumber
Case 0
sWords = "Zero"
Case 1 To 999999
sWords = SetThousands(lNumber)
Case Else
bNCFlag = True
End Select
End If
Else
bNCFlag = True
End If
If sWords > "" Then
rngSrc.Cells(lCtr) = sWords
End If
Next lCtr
If bNCFlag Then
sTitle = "lNumberToWords Macro"
sMsg = "Not all cells converted. May not be whole number or may be too
large."
MsgBox sMsg, vbExclamation, sTitle
End If
End Sub
TeensArray(7) = "Seventeen"
TeensArray(8) = "Eighteen"
TeensArray(9) = "Nineteen"
Dim iTemp1 As Integer
Dim iTemp2 As Integer
Dim sTemp As String
iTemp1 = Int(lNumber / 10)
iTemp2 = lNumber Mod 10
sTemp = TensArray(iTemp1)
If (iTemp1 = 1 And iTemp2 > 0) Then
sTemp = TeensArray(iTemp2)
Else
If (iTemp1 > 1 And iTemp2 > 0) Then
sTemp = sTemp + " " + SetOnes(iTemp2)
End If
End If
SetTens = sTemp
End Function
Sub DoPhone()
The DoPhone procedure tries to convert the information in any cell that does not contain a
formula. All you need to do is select the cell (or cells) you want to convert, and then run the
procedure. The result is that any text in the cells is converted to its digit equivalent on a phone.
Thus, 598-Tips becomes 598-8477.
You should note one small peculiarity of DoPhone, and you may want to change it. Some phones
recognize the letters Q and Z as the digits 7 and 9, respectively. Others simply leave these digits
out, or they are converted to 0. DoPhone, as written here, converts these letters to 7 and 9. You
can change the appropriate places in the Select Case structure, as desired, so they are changed to
numbers according to your needs. (The appropriate places are commented in the listing.)
One solution, of course, is to simply change the display format used for ZIP Code cells. This
may work for the display, but the underlying data is still missing the leading zeroes. A better
solution is to use a macro that goes through and adds leading zeroes to the information in a cell.
The following macro does just that:
Sub MakeZIPText()
Dim ThisCell As Range
Application.ScreenUpdating = False
'Make sure format is text
Selection.NumberFormat = "@"
For Each ThisCell In Selection
'Strip the leading apostrophe, if any
If Left(ThisCell, 1) = "'" Then
ThisCell = Mid(ThisCell, 2, 99)
End If
'It's a 5-digit ZIP Code
If Len(ThisCell) <= 5 Then
ThisCell = "'" & Right("00000" & ThisCell, 5)
Else
ThisCell = "'" & Right("00000" & ThisCell, 10)
End If
Next ThisCell
Application.ScreenUpdating = True
End Sub
To use the macro, simply select the range of cells containing the ZIP Codes, then run the macro.
The macro actually changes the cell contents—no longer will the cells contain numeric values
(the cause of the original problem), but they will contain text values. This allows the leading
zeroes to appear at the beginning of the ZIP Codes.
This is a rather easy task to accomplish, since all you need to do is strip everything after the fifth
digit in the ZIP Code. Follow these steps:
1. Insert a new column, just to the right of the existing ZIP Code column.
2. Assuming the ZIP Codes are in column G and you added a new column H, you can
enter the following in cell H3:
=Left(G3, 5)
If you have an empty column to the right of your ZIP Codes, you can also use Excel's Text to
Columns feature:
At this point you have the first five digits of the ZIP Code in the original column, and the last
four digits (if any) in the previously empty column to the right. You can delete the column
containing the four digits, if desired.
If you need to truncate ZIP Codes quite often, you may be more interested in a macro-based
approach. The following macro will do the trick:
Sub ZIPShorter()
For Each cell In Selection
cell.Value = Left(cell.Value, 5)
Next
End Sub
All you need to do is select the cells containing the ZIP Codes, and then run the macro.
Your best bet is to keep the different units of whatever imperial measurement you want in
different cells. For instance, a distance of 3 miles, 428 feet, and 7 inches could be kept in three
cells, one for miles, one for feet, and the other for inches. You could then write the formulas
necessary to convert to whatever measurement system you desire. There are also Excel add-ins
available around the Internet (a quick search will find them) that can allow you to use this
technique to work with linear measurements.
Another approach is to develop a custom function or macro that would convert a value into a
linear measurement and display it as text. You couldn't use the result in math functions, but it
may give you want you want for your workbook. Consider, for example, the following simple
macro:
x1 = x
Distances = Array(63360, 36, 12, 1)
FinalAnswer = ""
For Each Item In Distances
FinalAnswer = FinalAnswer & " " & Int(x1 / Item)
x1 = x1 - Item * Int(x1 / Item)
Next
N2MYFI = Trim(FinalAnswer)
End Function
This function returns four numbers, in a string, that represent the number of miles, yards, feet,
and inches (MYFI) in a raw value. It is assumed that the value fed to the function is in inches,
such as the following:
=N2MYFI(100)
This returns the string "0 2 2 4", which means there are 0 miles, 2 yards, 2 feet, and 4 inches in
100 inches. The function could easily be changed to return the values in any format desired.
If the latitude and longitude pairs were really just points on a grid, then calculating the distance
between them would be easy. The problem is that they are really points on a sphere, which
means that you can’t use flat-grid calculations to determine distance. In addition, there are many
ways that you can calculate distances: shortest surface distance, optimum flight path (“as the
crow flies”), distance through the earth, driving distance, etc.
Obviously this could be a complicated question. In the space available, I’ll examine a couple of
ways to determine the great circle distance (“as the crow flies”), and then provide some
references for additional information on the other types of calculations.
The first thing you need to figure out is how the latitude and longitude of each point will be
represented in Excel. There are several ways it could be represented. For instance, you could
enter the degrees, minutes, and seconds in individual cells. Or, you could have them in a singe
cell as DD:MM:SS. Either way is acceptable, but they will need to be treated differently your
formulas. Why? Because if you enter latitude and longitude as DD:MM:SS, then Excel will
convert them internally into a time value, and you just need to take that conversion into account.
What you are going to need to do, no matter what, is convert your latitude and longitude into a
decimal value in radians. If you have a coordinate in three separate cells (degrees, minutes, and
seconds), then you can use the following formula to do the conversion to a decimal value in
radians:
=RADIANS((Degrees*3600+Minutes*60+Seconds)/3600)
The formula uses named ranges for your degrees, minutes, and seconds. It converts those three
values into a single value representing total degrees, and then uses the RADIANS function to
convert this to radians. If you start with a value of 32 degrees, 48 minutes, and 0 seconds, the
formula ends up looking like this:
=RADIANS((32*3600+48*60+0)/3600)
=RADIANS((115200+2880+0)/3600)
=RADIANS(118080/3600)
=RADIANS(32.8)
=0.572467995
If you are storing your coordinates in the format of DD:MM:SS in a single cell (in this example,
cell E12), then you can use the following formula to convert to a decimal value in radians:
=RADIANS((DAY(E12)*86400+HOUR(E12)*3600+MINUTE(E12)*60+SECOND(E12))/3600)
Assuming that cell E12 contains 32:48:00, then the formula ends up looking like this:
=RADIANS((1*86400+8*3600+48*60+0)/3600)
=RADIANS((86400+28800+2880+0)/3600)
=RADIANS(118080/3600)
=RADIANS(32.8)
=0.572467995
With your coordinates in radians, you can use a trigonometric formula to calculate distance along
the surface of a sphere. There are many such formulas that could be used; the following formula
will suffice for our purposes:
=ACOS(SIN(Lat1)*SIN(Lat2)+COS(Lat1)*COS(Lat2)*COS(Lon2-Lon1))*180/PI()*60
In this formula, each of the latitude (Lat1 and Lat2) and longitude (Lon1 and Lon2) coordinates
must be a decimal value, in radians, as already discussed. The formula returns a value in nautical
miles, which you can then apply various formulas to in order to convert to other units of
measure, as desired.
You should realize that the values you come up with by using any formula that calculates
distance on the surface of a sphere will give slightly erroneous results. Why? Because the Earth
is not a perfect sphere. Thus, the distances should only be considered approximate. If you want
to get a bit more accurate, then you can use the following formula to determine your nautical
miles:
=ACOS(SIN(Lat1)*SIN(Lat2)+COS(Lat1)*COS(Lat2)*COS(Lon2-Lon1))*3443.89849
This formula substitutes the radius of the earth (3443.89849 nautical miles) for the radius of a
sphere (180/PI()*60, or 3437.746771). Either way, the answer should still be considered
approximate.
As you can tell, the formula to calculate distances is quite long. You may find it easier to develop
your own user-defined function that will do the calculation for you. The following function takes
four values (the two pairs of latitudes and longitudes, in degrees), and then returns a result in
nautical miles:
If you would like to see a more in-depth discussion of latitudes and longitudes, and the math
involved, you can find a good selection of articles at this site:
http://mathforum.org/library/drmath/sets/select/dm_lat_long.html
With the math under your belt, then you can start to look about at various formulas you can use.
There is an interesting one in VBA at this Web page:
http://www.freevbcode.com/ShowCode.asp?ID=5532
A good general-purpose discussion can also be found at Chip Pearson’s site, here:
http://www.cpearson.com/excel/LatLong.aspx
As is often the case with Excel, there are a number of different approaches you can take. Each
approach examined in this tip assumes that the names you need to select from are listed in cells
A1 through A1000. Of course, your range of names could be shorter or longer, but the point is
that they are in contiguous cells in column A. The examples also assume that you need to select
15 names at random from the list.
The first approach is to use the INDEX function. Enter the following formula in cells B1:B15:
=INDEX(A:A,INT((RAND()*1000)+1),1)
=OFFSET($A$1,ROUNDUP(RAND()*1000,0),0,1,1)
It is possible, but not probable, that you will get the same name twice in the resulting list. (The
improbability comes because of the size of the original list. The larger the list, the less probable
there will be duplicates in the extracted list.) If you do get a duplicate name, then simply force a
recalculation of your worksheet by pressing F9. Each time your recalculate, the list of extracted
names is regenerated.
Another potential approach requires the use of multiple columns. Simply follow these steps if
you are using Excel 2007 or later:
=RANK(B1,$B$1:$B$1000)
13. Click on OK. The table (range A1:C1000) is sorted according to the values in column
C.
The steps are pretty much the same for older versions of Excel. The primary difference is in how
you display the Paste Special dialog box (step 7); you do so by choosing Paste Special from the
Edit menu. Also, to display the Sort dialog box (step 12) you simply choose Sort from the Data
menu.
The result is that column C now contains a ranking of all the random numbers in column B. The
first 15 rows contain your random names.
In this approach you could also have left out column C completely and simply sorted your list
based on the static random values in column B. Again, the top 15 would be your random names.
Of course, there are any number of macro solutions you could use for this problem. The coding
of any macro will be similar, relying on VBA's RND function to generate random numbers. Of
all the possible macro solutions, perhaps the following is the most unique and offers some
advantages not available with the workbook solutions discussed so far:
Sub GetRandom()
Dim iRows As Integer
Dim iCols As Integer
Dim iBegRow As Integer
Dim iBegCol As Integer
Dim J As Integer
Dim sCells As String
iRows = Selection.Rows.Count
iCols = Selection.Columns.Count
iBegRow = Selection.Row
iBegCol = Selection.Column
To use this macro, just select the names from which you want to select the 15 random names. In
the examples thus far, you would select the range A1:A1000. The macro then pulls 15 names at
random from the cells, and puts them in the Clipboard. When you run the macro, you can then
paste the contents of the Clipboard where ever you want. Every time the macro is run, a different
group of 15 is selected.
Entire books have been written on putting together testing suites for software. How rigorous you
are in compiling test data depends, in large part, on the needs of your audience and the nature of
your worksheet. Unfortunately, there is no quick cure-all that will automatically figure out what
you need and generate the data for you. There are tools in Excel, however, that you can use
toward this end.
First, if you need to generate random numeric values, you can use the RAND or
RANDBETWEEN worksheet functions. The difference is that RAND generates a value between
0 and 1, and RANDBETWEEN generates integer values between any bounds you set.
Random data may not be appropriate for your testing needs, however. This is particularly true
when you are testing boundaries of your formulas. For instance, testing with large values, small
values, or a combination of large and small values. Likewise, you may want to test for
inappropriate values, such as using text as input rather than numbers (or vice versa). There are a
whole contingent of conditions you need to think through, and then pick the type of data that is
right for your needs.
If you prefer, you can use macros to generate testing data. The following macro fills a selected
range of cells with a random numeric value, between whatever boundaries (minimum and
maximum) that you set.
Sub RandNums()
Dim MyRange As Range
Dim lMin As Long, lMax As Long
Dim dRand As Double
Randomize
Application.ScreenUpdating = False
c.Value = dRand
Next c
Application.ScreenUpdating = True
End Sub
To use the macro, just select a range of cells that you want to contain random numeric values,
and then run the macro. If you must use integer values in the cells, then you can “uncomment”
the noted line within the macro.
If you want to fill a range of cells with random dates, then a slight modification to the
RandNums macro results in the following.
Sub RandDates()
Dim MyRange As Range
Dim dtMin As Date, dtMax As Date
Dim dtRand As Date
Randomize
Application.ScreenUpdating = False
c.Value = dtRand
' Change format for cell, below, as desired
c.NumberFormat = "m/d/yyyy"
Next c
Application.ScreenUpdating = True
End Sub
Again, just select a range and then run the macro. You can modify the initial values set to the
dtMin and dtMax variables in order to specify the boundaries for the dates desired. You can also,
if desired, change the formatting applied to the cells after the random date is stored within the
cells.
If you want the ability to zoom in or out easily using the keyboard, the only way to get it is to
create a macro and then assign the macro to a keyboard combination. The following VBA macro
(MyZoomIn) allow you to zoom in to (enlarge) a worksheet by 10%:
Sub MyZoomIn()
Dim ZP As Integer
ZP = Int(ActiveWindow.Zoom * 1.1)
If ZP > 400 Then ZP = 400
ActiveWindow.Zoom = ZP
End Sub
Notice that the macro only allows you to zoom in up to 400%. This is because Excel allows you
to only zoom that high, and any higher would generate an error. A slight variation on the same
theme results in a macro I call MyZoomOut. It zooms out of (reduces) a worksheet by 10%:
Sub MyZoomOut()
Dim ZP As Integer
ZP = Int(ActiveWindow.Zoom * 0.9)
If ZP < 10 Then ZP = 10
ActiveWindow.Zoom = ZP
End Sub
This macro sets the bottom boundary at 10%, which is the smallest you can go. Any smaller, and
Excel would generate an error again.
The final trick to make these macros really useful is to assign them to a keyboard combination.
You can then quickly zoom in or out by 10% with a simple keystroke. The following are the
steps you can use to assign a macro to a keyboard combination if you are using Excel 2007 or a
later version:
1. Press ALT+F8. Excel displays the Macro dialog box, which includes a list of your
defined macros. (MyZoomIn and MyZoomOut should be among them.)
2. Select the MyZoomIn macro.
3. Click on Options. Excel displays the Macro Options dialog box.
4. In the Shortcut Key box, specify the shortcut you want to use. For instance, if you want
to use CTRL+I, you would enter an I in the Shortcut Key box.
5. Click on OK.
6. Select the MyZoomOut macro.
7. Click on Options. Excel again displays the Macro Options dialog box.
8. In the Shortcut Key box, specify the shortcut you want to use. For instance, if you want
to use CTRL+O, you would enter an O in the Shortcut Key box.
9. Click on OK.
10. Click on Cancel to close the Macro dialog box.
Here are the steps if you are using an older version of Excel:
1. Press ALT+F8. Excel displays the Macro dialog box, which includes a list of your
defined macros. (MyZoomIn and MyZoomOut should be among them.)
2. Select the MyZoomIn macro.
3. Click on Options. Excel displays the Macro Options dialog box.
4. In the Shortcut box, specify the shortcut you want to use. For instance, if you want to
use CTRL+I, you would enter an I in the Shortcut box.
5. Click on OK.
6. Select the MyZoomOut macro.
7. Click on Options. Excel again displays the Macro Options dialog box.
8. In the Shortcut box, specify the shortcut you want to use. For instance, if you want to
use CTRL+O, you would enter an O in the Shortcut box.
9. Click on OK.
10. Click on Cancel to close the Macro dialog box.
The problem is that the zoom factor is saved with the workbook. Thus, when Wanda saves the
workbook and hands it off to you, when you open it, the workbook is still displayed at whatever
zoom factor Wanda last used. If you don’t have the same size monitor as Wanda, then the
workbook may be almost illegible on your system.
There are only two possible solutions to this problem. First, you can simply adjust the zoom
factor once you open the workbook. There are a multitude of ways to do this, but the easiest
involve the Zoom setting on the Formatting toolbar, or using the scroll wheel on your mouse.
(On some systems you may need to hold down the CTRL key in order for the scroll wheel to
adjust the zoom factor.)
The second workaround is to create a macro that gets saved with the workbook. The macro can
run every time the workbook is opened, and thereby set the zoom factor. (This macro should be
added to the This Workbook code window in the VBA editor.)
The only problem with a macro such as this, of course, is that whenever Wanda (your colleague)
opens the workbook on her system, the zoom factor is also set and she’ll get just as frustrated
with you as you were with her.
Perhaps a solution is to create a more involved macro—one that checks the current screen
resolution and then sets the zoom factor accordingly. For instance, the following macro could be
used to make the adjustments based on resolution:
lResWidth = GetSystemMetrics32(0)
lResHeight = GetSystemMetrics32(1)
sRes = lResWidth & "x" & lResHeight
Select Case sRes
Case Is = "800x600"
ActiveWindow.Zoom = 75
Case Is = "1024x768"
ActiveWindow.Zoom = 125
Case Else
ActiveWindow.Zoom = 100
End Select
End Sub
This routine checks the screen resolution and adjusts the window accordingly. Other resolutions
and zooms may be added easily. To make the routine run automatically, just use a
Workbook_Open event handler in the This Workbook code window to trigger the macro:
After execution of this line, Even will be True (-1) if UserNum was even, or False (0) if
UserNum was odd.
There obviously is no built-in Excel function to provide this capability, so you are left to work
with macros. Fortunately, such a macro is not terribly difficult to create. The following macro
will do the trick nicely:
Sub FillRand()
Dim nums() As Integer
Dim maxval As Integer
Dim nrows As Integer, ncols As Integer
Dim j As Integer, k As Integer
Dim Ptr As Integer
Randomize
Set s = Selection
maxval = s.Cells.Count
nrows = s.Rows.Count
ncols = s.Columns.Count
ReDim nums(maxval, 2)
This macro uses a two-dimensional array (nums) to figure out which numbers to use and the
order in which they should be used. Near the beginning of the macro the array is filled with a
static number (1 through the number of cells) and a random number between 1 and the number
of cells. This second number is then used to sort the array. Once the array is stored, it is a simple
matter to place the original numbers in the cells.
By the way, the reason a two-dimensional array is used is because the Rnd function that VBA
uses to generate random numbers can return duplicate values. Thus, even through the second
dimension of the array can have duplicates in it, when the array is finally sorted, the first
dimension will not have duplicates.
To use the macro, start by selecting the cells you want to have filled with sequential values in a
random order. When you run the macro, that range is filled. For instance, if you select ten cells
and then run the macro, then those cells are filled with the numbers 1 through 10, in random
order.
There are a number of ways you can get the desired information. One is to use this type of
formula:
=LEN(SUBSTITUTE(B3&C3&D3&E3&F3&G3&H3,"L",""))
This formula calculates the number of non-L characters in row 3—in other words, the number of
wins. It does this by concatenating the contents of B3:H3, and then using the SUBSTITUTE
function to remove all the Ls. This leaves the Ws, which are counted by the LEN function. You
could also use the CONCATENATE function, in the following manner, for the same result:
=LEN(SUBSTITUTE(CONCATENATE(B3,C3,D3,E3,F3,G3,H3),"L",""))
To calculate the number of losses, simply replace “L” in each formula with “W”.
You can also use an array formula, which allows you to specify a range of cells to examine,
rather than needing to specify every single cell:
=SUM(LEN(SUBSTITUTE(B3:H3, "L","")))
This array formula, entered by pressing SHIFT+CTRL+ENTER, returns the number of wins (W
characters) in the range B3:H3.
Finally, you can use a user-defined function to return the occurrences of a specific character
within a given range. The following macro will do the trick:
Application.Volatile
CharNums = 0
For Each c In r.Cells
strX = c.Value
For J = 1 To Len(strX)
If Mid(strX, J, 1) = chr Then CharNums = CharNums + 1
Next J
Next c
End Function
To use the function, you would us a formula like this in your worksheet:
=CharNums(B3:H3;"W")
The function returns the number of uppercase W characters in the range. All other characters
(including lowercase w characters) are ignored. To count losses, simply substitute L for W in the
formula.
Making Squares
One of the (many) frustrating things about Excel is that it uses different units of measurement to
specify the height of rows and the width of columns.
This leads to problems if you want the height and width of a particular cell to match, thereby
making a square. Fortunately, with a little macro wizardry you can bypass this oddity of Excel
and achieve the desired results. Consider the MakeSquare macro, which follows.
Sub MakeSquare()
Dim WPChar As Double
Dim DInch As Double
Dim Temp As String
This macro prompts you for the dimension of the square you want to create, and then calculates
exactly how wide and high to set each cell. You can run the macro with a single cell selected, or
you can make a larger selection set.
The “math magic” is done in the calculating of the WPChar variable. This is set to a value
derived by dividing the width of the column in points (returned by the Width property) by the
width of the column in characters (returned by the ColumnWidth property). This value, which is
the number of points in a character at the current settings, is then used to calculate how many
characters should be used to set the width in the next program line.
Counting Words
Words are normally associated with a word processor, such as Microsoft Word. However, many
people also work with words in their spreadsheet program. (I had a coworker once who used
Excel to write memos all the time.) There may be times when you want to count the number of
words in a worksheet that you receive from someone. There are native abilities to perform such a
task in Word, but not in Excel.
One solution, of course, is to load your workbook into Word, perform the word count there, and
then close the file. This is not nearly as flexible, however, as creating a macro to count words
within Excel itself. The following macro, CountWords, counts the number of words in any range
you select in a worksheet:
Sub CountWords()
Dim MyRange As Range
Dim CellCount As Long
Dim TotalWords As Long
Dim NumWords As Integer
Dim Raw As String
End If
While InStr(Raw, " ") > 0
Raw = Mid(Raw, InStr(Raw, " "))
Raw = Trim(Raw)
NumWords = NumWords + 1
Wend
TotalWords = TotalWords + NumWords
End If
Next CellCount
MsgBox "There are " & TotalWords & " words in the selection."
End Sub
Notice that the macro steps through each cell in the range you select. It then ignores any cell that
contains a formula. In all other cells it essentially counts the number of spaces in the cell. (One
or more spaces are assumed to separate words.) The word count is then displayed in a message
box for your edification.
The macro is pretty quick on relatively small ranges. If you pick a large range (such as the entire
worksheet), then the macro can take a great deal of time to finish its work. The point of this is to
make sure that you only select the actual range you want to analyze before invoking the macro.
This macro will always display a worksheet named StartSheet. You will obviously need to
change the worksheet name to something different; it should exactly match the name of the
desired worksheet.
For this macro to work properly, it has to be associated with the workbook object. Follow these
steps:
1. Make sure you have only a single Excel workbook open. While this isn’t exactly
mandatory, it will make creating the macro a bit easier.
2. Press ALT+F11 to display the VBA Editor.
3. In the Project Explorer window you will see a list of the open workbooks and templates.
If the Project Explorer is not visible on your screen, choose Project Explorer from the
View menu.
4. Locate your current workbook in the Project Explorer. It will be named something like
VBAProject (MyWorkbook), where “MyWorkbook” is the name of the actual
workbook.
5. If there is a plus sign to the left of the current workbook in the Project Explorer, click
on it. You should see a list of worksheets appear underneath the workbook.
6. If you don’t see a list of worksheets, but instead see a list of folders with plus signs to
their left, click on the plus sign to the left of Microsoft Excel Objects. Now you should
see the worksheets.
7. At the bottom of the list of worksheets is the ThisWorkbook object. Double-click on it.
A code window is opened.
8. In the code window, paste or create the macro shown above. Make sure you name it
exactly as shown.
9. Close the VBA Editor.
10. Save your workbook.
Now, whenever you open the workbook, the specified worksheet will be displayed.
What if you want to limit access to the worksheets entirely, however? What if you don’t even
want an unauthorized user to see the other worksheet? This need is a bit trickier to accommodate,
but it can be done. The basic approach would be as follows:
1. Set up a workbook that has three worksheets: One that will always be open, one for user
1, and the third for user 2.
2. Hide the worksheets for user 1 and user 2.
3. Create a form that appears whenever the workbook is opened, asking for a user name
and password.
4. Create macro code that unlocks and displays the proper worksheet based on the user
name and password.
5. Protect the entire workbook (Tools | Protection | Protect Workbook or, in Excel 2007
and later versions, Review | Changes | Protect Workbook).
Steps 1, 2, and 5 are easy enough to do, and have been covered in other issues of ExcelTips. The
crux of this approach, however, is steps 3 and 4. You can create a user form by following these
steps:
With your user form created you are ready to associate macro code with the controls you just
placed. Make sure the user form is selected and press F7 to display the Code window for the
form. The window may contain a line or two of automatically generated code. Replace this with
the following code:
bOK2Use = False
bError = True
If Len(txtUser.Text) > 0 And Len(txtPass.Text) > 0 Then
bError = False
Select Case txtUser.Text
Case "user1"
sSName = "u1sheet"
If txtPass.Text <> "u1pass" Then bError = True
Case "user2"
sSName = "u2sheet"
If txtPass.Text <> "u2pass" Then bError = True
Case Else
bError = True
End Select
End If
If bError Then
MsgBox "Invalid User Name or Password"
Else
'Set document property
bSetIt = False
For Each p In ActiveWorkbook.CustomDocumentProperties
If p.Name = "auth" Then
p.Value = sSName
bSetIt = True
Exit For
End If
Next p
If Not bSetIt Then
ActiveWorkbook.CustomDocumentProperties.Add _
Name:="auth", LinkToContent:=False, _
Type:=msoPropertyTypeString, Value:=sSName
End If
Sheets(sSName).Visible = True
Sheets(sSName).Unprotect (txtPass.Text)
Sheets(sSName).Activate
bOK2Use = True
Unload UserForm1
End If
End Sub
The above code does several things. Notice that there are two procedures: a longer one that runs
when the user clicks on the OK button in the form, and another that runs when the form is
terminated. When the user clicks on the OK button, the procedure checks to make sure that the
combination of the user name and password is correct. If it is not, then the user is notified. If it is,
then the authorized sheet name is stored in a document variable and the appropriate sheet is
displayed and unprotected.
If you want to change the acceptable user names, sheet names, and passwords, you can do so by
making the desired changes in the Select Case structure near the top of this macro code.
The second macro in this code (UserForm_Terminate) comes into play if the user tries to simply
dismiss your form without entering a user name and password. In this instance, if the
authorization process was not previously completed, then the workbook is simply closed.
In addition to the above code, you will also need to add the following macros to the workbook
itself. These open the user form when the workbook is opened, and protect the worksheet when
the workbook is closed.
bSaveIt = False
For Each w In Worksheets
If w.Visible Then
Select Case w.Name
Case "u1sheet"
w.Protect ("u1pass")
w.Visible = False
bSaveIt = True
Case "u2sheet"
w.Protect ("u2pass")
w.Visible = False
bSaveIt = True
End Select
End If
Next w
If bSaveIt Then
ActiveWorkbook.CustomDocumentProperties("auth").Delete
ActiveWorkbook.Save
End If
End Sub
When the user chooses to close the workbook—they are done with their work—the applicable
worksheets are again protected and hidden. (If you change user sheet names and passwords, you
will need to change them in the Select Case structure here, as well.) The macro then deletes the
appropriate document property and saves the workbook.
Since protection is done at a worksheet level, it can be major pain to step through each worksheet
in a workbook and either protect or unprotect them. If you have 25 worksheets, you must activate
each worksheet, do the protect or unprotect, and move on to the next one.
Sub ProtectAllSheets()
Dim ws As Worksheet
Dim sOrigSheet As String
Dim sOrigCell As String
Dim J As Integer
Application.ScreenUpdating = False
sOrigSheet = ActiveSheet.Name
sOrigCell = ActiveCell.Address
Application.GoTo Reference:=Worksheets("" _
& sOrigSheet & "").Range("" & sOrigCell & "")
Application.ScreenUpdating = True
End Sub
Sub UnProtectAllSheets()
Dim ws As Worksheet
Dim sOrigSheet As String
Dim sOrigCell As String
Dim J As Integer
Application.ScreenUpdating = False
sOrigSheet = ActiveSheet.Name
sOrigCell = ActiveCell.Address
Application.GoTo Reference:=Worksheets("" _
& sOrigSheet & "").Range("" & sOrigCell & "")
Application.ScreenUpdating = True
End Sub
While these macros will work just fine, there are a couple of caveats. First, you need to make
sure that the Password variable in each macro is set to the proper password for your worksheets.
(This assumes, of course, that all the worksheets use the same passwords.) The second caveat is
that since the macro has to include the password, the overall security of your workbook may be
compromised—anyone that can display the macros will know what the passwords are for your
workbooks.
As a solution to this last problem, you could modify the macros so that they ask for a password
to use in their work. The following would be the version of the macro that protects worksheets:
Sub ProtectAllSheetsPass()
Dim ws As Worksheet
Dim sOrigSheet As String
Dim sOrigCell As String
Dim J As Integer
Dim sPWord As String
Application.ScreenUpdating = False
sOrigSheet = ActiveSheet.Name
sOrigCell = ActiveCell.Address
The macro displays an input box asking for the password. The same password is then used to
protect every worksheet in the workbook. The same sort of change can be done to the macro that
unprotects all the worksheets.
This sort of repetitive work is a natural for a macro. The following macro, called BreakItUp,
creates individual workbook files based on the worksheets in the current workbook. Thus, if the
current workbook contains 25 worksheets, running this macro results in 25 individual Excel
workbook files being created. Each workbook has a single worksheet, and the name of the
workbook is the same as that of the worksheet.
Sub BreakItUp()
Dim sht As Worksheet
Dim NFName As String
Const WBPath = "C:\"
The BreakItUp macro stores the new workbooks in the root directory on the C: drive. If you want
your workbooks saved in a different place, you can simply change the line in which the WBPath
constant is created.
You should also know that it is relatively easy to crash this macro. For instance, if you use a
character in a worksheet name that is not “legal” for a file name, the macro will rudely stop when
it tries to create the file. Of course, you could easily make the modifications to the macro to
check for and replace such illegal characters.
Another potential pitfall for the macro is that it will stop running if a file already exists that has
the same name as a worksheet. For instance, let’s suppose you have a worksheet named
MySheet1. If there is already a file on disk called MySheet1.xls, then the macro will stop when it
tries to overwrite the file. You can get around this by making sure there are no file name
conflicts in the directory where the workbooks are being saved.
There are any number of reasons why this could happen, so it is very hard to narrow down to a
definitive answer. There are a few things to check, however.
Toolbar customizations are stored in a file with an extension .XLB. The main portion of the file
name varies based on your version of Excel and Windows. The name may be something like
excel10.xlb or some name containing your user ID and a version number. It is usually on the C:\
drive and often in Windows folder or in a personal settings folder (on my system it is
C:\Documents and Settings\Allen L. Wyatt\Application Data\Microsoft\Excel\Excel11.xlb.)
The XLB file may become corrupt for various reasons. Because of this, it is a good idea to make
a backup of the file so that if it ever gets modified unintentionally or somehow gets corrupted,
you can just delete the old one and rename the backup.
The XLB file can be “lost” in various ways. Most often it is due to sloppy programming. Some
programmers may decide to make changes to the toolbars to add their own customizations, or
they may decide to change the configuration of Excel’s menus. All these changes get written to
the XLB file, and this is not normally a problem. It can become a problem, however, if Excel
ends abnormally and the programmer’s code doesn’t restore the toolbars or menus in the way it
should.
Even if Excel doesn’t exit abnormally, there could still be problems introduced by the
programmer’s code. Excel does not include a command to restore the toolbars to some saved
configuration. Programmers often write code to check out the status of all the toolbars and then
reverse the steps to get back to that condition, but if the code has errors in it, then the toolbars
may be left in an unstable or (worse yet) unusable condition. This is bad.
The solution is to rely on your backup of the XLB file. You need to make sure that you save the
file so that you can restore it if you detect a problem with the toolbars or menus. If you make any
changes that you want to keep, make a new backup of the XLB file. This is nothing but cheap
insurance.
The following macro was developed to help in these situations. It checks the names of the
worksheets in your workbook, renaming them to the months of the year if they begin with the
letters “Sheet”. If there are not enough sheets in the workbook, it adds sheets, as necessary, for
each month of the year.
Sub DoMonths()
Dim J As Integer
Dim K As Integer
Dim sMo(12) As String
sMo(1) = "January"
sMo(2) = "February"
sMo(3) = "March"
sMo(4) = "April"
sMo(5) = "May"
sMo(6) = "June"
sMo(7) = "July"
sMo(8) = "August"
sMo(9) = "September"
sMo(10) = "October"
sMo(11) = "November"
sMo(12) = "December"
For J = 1 To 12
If J <= Sheets.Count Then
If Left(Sheets(J).Name, 5) = "Sheet" Then
Sheets(J).Name = sMo(J)
Else
Sheets.Add.Move after:=Sheets(Sheets.Count)
ActiveSheet.Name = sMo(J)
End If
Else
Sheets.Add.Move after:=Sheets(Sheets.Count)
ActiveSheet.Name = sMo(J)
End If
Next J
For J = 1 To 12
If Sheets(J).Name <> sMo(J) Then
For K = J + 1 To Sheets.Count
If Sheets(K).Name = sMo(J) Then
Sheets(K).Move Before:=Sheets(J)
End If
Next K
End If
Next J
Sheets(1).Activate
End Sub
The last step in the macro is that it places the worksheets in proper order, for the months 1
through 12. The result is that if you have any other worksheets left in the workbook (in other
words, you had some that did not begin with the letters “Sheet”, then those worksheets end up at
the end of the workbook, after the 12 months.
=Today()
Place the formula into a cell, and you end up with today’s date. Format the cell using a custom
format, and you end up with the full month name. The custom format is applied by using these
steps:
=Text(Today(),"mmmm")
No special formatting is required; the formula returns the text of the full month name for
whatever today is. Finally, you could use an even longer formula that simply picks the month
name from a list of months:
=CHOOSE(MONTH(NOW()),"January","February",
"March","April","May","June","July",
"August","September","October","November",
"December")
Which brings us, finally, to the macros. If you want a macro that returns the month name in the
current cell, you are looking for a user-defined function:
Function MonthName()
Application.Volatile
MonthName = Format(Date, "mmmm")
End Function
This simple two-line macro dynamically returns the month name for whatever the current date is.
Just put this formula in a cell:
=MonthName()
Remember—since you’ve just added a macro to your workbook, you’ll be asked whenever you
open your workbook if you want to enable macros. If you don’t want to see this question all the
time, you should use one of the formulaic approaches presented earlier.
If you have a need to create such a workbook, you know that individually creating and naming
all the worksheets can be a real hassle. This is where a macro would come in handy. The
following macro will add the appropriate number of worksheets, and then rename all of the
worksheets according to week number (01 through 52).
Sub YearWorkbook1()
Dim iWeek As Integer
Dim sht As Variant
Application.ScreenUpdating = False
Worksheets.Add After:=Worksheets(Worksheets.Count), _
Count:=(52 - Worksheets.Count)
iWeek = 1
For Each sht In Worksheets
sht.Name = "Week " & Format(iWeek, "00")
iWeek = iWeek + 1
Next sht
Application.ScreenUpdating = True
End Sub
If you instead need a way to create worksheets that show the ending date of each week for a year,
then a different macro is needed.
Sub YearWorkbook2()
Dim iWeek As Integer
Dim sht As Variant
Dim sTemp As String
Dim dSDate As Date
Application.ScreenUpdating = False
Worksheets.Add After:=Worksheets(Worksheets.Count), _
Count:=(52 - Worksheets.Count)
For Each sht In Worksheets
sht.Name = Format(dSDate, "dd-mmm-yyyy")
dSDate = dSDate + 7
Next sht
Application.ScreenUpdating = True
End Sub
This version of the macro asks you for a beginning date. It then uses that date to start naming the
different worksheets in the workbook. If you enter a value that cannot be translated to a date,
then the macro will generate an error.
1. Right-click on the scrolling arrows at the left side of the tabs. Excel displays a Context
menu that includes many of the worksheet tab names.
2. Click on More Sheets option. Excel displays the Activate dialog box. All of your
worksheet names are listed in the dialog box.
3. Press the first letter of the worksheet name you want. The first worksheet that begins
with that letter is selected.
4. Continue pressing the same letter to select the next worksheet beginning with that letter.
5. When the desired worksheet name is selected, press ENTER.
The interesting thing about this approach is that you don’t need to have the worksheets in
alphabetical order to use it. Each time you press a letter (steps 3 and 4), Excel selects the next
worksheet that begins with that letter.
While this approach is pretty fast to use, some people may object because it involves the use of
both the mouse (two clicks) and the keyboard. Some people prefer to strictly use the keyboard. In
this case, it is best if you sort your worksheets alphabetically (as covered in other issues of
ExcelTips) and then use a macro to pull up the desired worksheet area. The following macro will
do the trick:
Sub GoToSheet()
Dim iTemp As Integer
Dim sSheet As String
Dim sThisOne As String
Now, assign a shortcut key to the macro, such as CTRL+G. From now on, you can simply press
CTRL+G, type a letter, and then press ENTER. The first worksheet that starts with the letter you
specified is selected.
A final solution is to create your own “index” or “TOC” to your worksheets. Insert a blank
worksheet at the beginning of the workbook, then add hyperlinks to the various other worksheets
in your workbook. Someone could click on the hyperlink, which would then display the
worksheet referenced by the hyperlink.
Setting up hyperlinks in this manner is definitely more work, but it does have advantages not
offered by the other methods described so far. First, users don’t need to know the worksheet
name at all. Second, you can use multiple “keywords” as links, each leading to the same
worksheet. In this way the overall workbook becomes more accessible to different users. Finally,
the sheets can be in any order desired, instead of putting them in alphabetical order.
Flipping Data
Many people use Excel as a simple database manager, entering information in different rows of a
worksheet. As you are working with your data tables, you may come across a need to reverse the
order of the rows in the table. Thus, if you have a table with ten rows, the rows would go from
ten to one instead of one to ten.
There is no intrinsic function in Excel that allows you to flip data in this manner. However, you
can use the sorting capabilities of Excel to accomplish the same thing by following these general
steps:
5. In the Sort By drop-down list, indicate you want to sort by your newly created column.
6. Click Descending as the type of sort.
7. Click on OK. Excel reorders your data in the reverse order of what it was.
If you have to do a lot of data flipping on a daily basis, using the above steps can get rather
tiring. In this case, you may want to create a macro to do the job for you. The following macro,
FlipRows, will do the trick.
Sub FlipRows()
Dim vTop As Variant
In order to use this macro, all you need to do is select the rows you want flipped and run it. The
macro will not change your data, other than flipping the rows. In other words, it will not add any
columns of information.
An interesting feature of this approach is that you can quickly adapt it to flipping columns of
data. All you need to do is change all occurrences of the word “Rows” to “Columns.” Thus, the
following becomes the new macro.
Sub FlipColumns()
Dim vTop As Variant
Dim vEnd As Variant
Dim vTopTemp As Variant
Dim vEndTemp As Variant
Dim iStart As Integer
Dim iEnd As Integer
Application.ScreenUpdating = False
iStart = 1
iEnd = Selection.Columns.Count
Do While iStart < iEnd
vTop = Selection.Columns(iStart)
vEnd = Selection.Columns(iEnd)
Selection.Columns(iEnd) = vTop
Selection.Columns(iStart) = vEnd
iStart = iStart + 1
iEnd = iEnd – 1
Loop
Application.ScreenUpdating = True
End Sub
Again, simply select the columns you want to flip and then run the macro.
Format Cells dialog box.) At some point you may want to know how many cells in a range are
shaded.
There is no worksheet formula in Excel that will allow you to count shaded cells. Instead, you
must develop your own macro to do this. The following macro is an example of a way to
approach this problem. It counts the number of shaded cells in the range of A1 through J20, and
places the count in cell A1.
Sub CountColor()
Dim irow, icol As Integer
Cells(1, 1) = 0
For irow = 1 To 20
For icol = 1 To 10
If Cells(irow, icol).Interior.ColorIndex _
<> xlColorIndexNone Then
Cells(1, 1) = Cells(1, 1) + 1
End If
Next icol
Next irow
End Sub
Notice that the heart of the routine is the comparison that is done between the ColorIndex of each
cell and the pre-defined xlColorIndexNone constant. If they are not equal, then the cell has been
shaded in some way.
This same basic technique can be easily adapted to a custom function. Notice in the following
that the same comparison is done on a cell-by-cell basis:
In order to use this function, simply use it in a cell, as a formula, and specify a range in the
formula:
= FindShades(B7:E52)
104-120
104-101
104-119
104-120
In this case there are three unique values. There is no intuitive worksheet function that will return
a count of unique values, which makes one think that a user-defined function would be the
logical approach. However, you can use an array formula to very easily derive the desired
information. Follow these steps:
1. Define a name that represents the range that contains your list. (This example assumes
the name you define is MyRange.)
2. In the cell where you want the number of unique values to appear type the following
formula, but don’t press ENTER yet:
=SUM(1/COUNTIF(MyRange,MyRange))
3. Instead of pressing ENTER, press CTRL+SHIFT+ENTER. This informs Excel that you are
entering an array formula. The formula shown in the formula bar should now appear as
follows (notice the addition of the surrounding braces, indicative of array formulas):
{=SUM(1/COUNTIF(MyRange,MyRange))}
That’s it! The cell now contains the number of unique name values in the specified range. This
approach is not case-sensitive, so if you have two values that differ only in their capitalization
(ThisName vs. THISNAME), they are both counted as a single unique value. In addition, there
can be no blank cells in the range. (Having a blank cell returns a #DIV/0 error from the formula.)
If your particular needs require that your list contain blanks (but you don’t want them counted)
and you want the evaluation to be case-sensitive, then you must turn to a macro. The following
macro, CountUnique, will do the trick:
iNumCells = MyRange.Count
ReDim sUCells(iNumCells) As String
iUVals = 0
For Each Cell In MyRange
If Cell.Text > "" Then
For J = 1 To iUVals
If sUCells(J) = Cell.Text Then
Exit For
End If
Next J
If J > iUVals Then
iUVals = iUVals + 1
sUCells(iUVals) = Cell.Text
End If
End If
Next Cell
CountUnique = iUVals
End Function
=CountUnique(MyRange)
The value returned is the number of unique values, not counting blanks, in the range.
If the text you want to evaluate is in column A, starting at cell A1, you could use the following
formula in cell B1:
=IF(A1>"",IF(EXACT(UPPER(A1),A1),"Upper",
IF(EXACT(LOWER(A1),A1),"Lower","Mixed")),"")
The formula checks to see if there is anything in A1. If there is, then it uses the EXACT function
to compare the contents to various conversions of the cell’s contents. The formula returns an
empty string if cell A1 is empty or the words Upper, Lower, or Mixed.
Copy the formula down column B as far as you need to, and then you can use the following type
of formula to determine the count:
=COUNTIF(B:B,"Upper")
To find the count of lowercase or mixed-case cells, replace “Upper” with “Lower” or “Mixed”.
Obviously, using formulas in this manner involves adding a column to your worksheet. There is
another formula approach you can use that doesn’t involve the use of an intermediate column in
this manner. Consider the following formula, which returns the number of cells in the range
A1:A100 that contain only uppercase letters:
=SUMPRODUCT(--(EXACT(A1:A100,UPPER(A1:A100))),--(A1:A100<>""))
A variation on this formula can be used to return the number of lowercase cells. The only thing
that is changed in the following is the use of the LOWER function instead of the UPPER
function:
=SUMPRODUCT(--(EXACT(A1:A100,LOWER(A1:A100))),--(A1:A100<>""))
To determine cells containing mixed case, you need to come up with a mix of the two
SUMPRODUCT-based formulas:
=SUMPRODUCT(--(NOT(EXACT(A1:A100,UPPER(A1:A100)))),--
(NOT(EXACT(A1:A100,LOWER(A1:A100)))),--(A1:A100<>""))
There are some drawbacks to these formulas, drawbacks that aren’t evident in the earlier
formulas. First, if a cell contains a numeric value, then these formulas count the cell as
uppercase. Second, if a cell contains an error value, then the formula returns an error.
If you have the need to count case quite often, then you would probably be better served by
creating a user-defined function that does the counting for you. There are many ways that such a
function could be written, but the general guidelines are the following:
The following macro is one example of how the above can be implemented:
End Select
End Function
Determining if a cell is upper, lower, or mixed case is obviously the crux of a macro such as this.
Making such a determination uses the same process as was done in the worksheet formulas:
compare the contents of the cell to the uppercase or lowercase conversion of those contents. In
this macro the value of the cell (vValue) is compared to vValue transformed with either the
UCase or LCase function.
The function also ignores cells that it doesn’t make sense to evaluate. It ignores cells containing
numeric values, Boolean values, error values, empty cells, and cells that contain only spaces. If a
numeric value is formatted as text, then the function counts that cell as uppercase. To use this
user-defined function, use a formula such as the following in your worksheet:
=COUNTCASE(A1:A100, "L")
For the first argument you use the range you want evaluated. The second argument is a single
character—L, M, or U—indicating which count you want returned. If you use some other value
for the second argument, then the function returns an error.
There are two ways you can solve this issue. The first is to create a simple formula that would be
placed in cell E5:
=MAX(A1:A3,E5)
The MAX function examines the various values it references and then returns the maximum out
of them—exactly what is wanted. However, since this formula is being placed in cell E5 and it
also references E5, it will return an error. This is because the formula creates a circular reference.
Excel can handle those, but you need to make a small configuration change to do it. Follow these
steps in Excel 2007 or later:
1. Display the Excel Options dialog box. (In Excel 2007 click the Office button and then
click Excel Options. In Excel 2010 and Excel 2013 display the File tab of the ribbon
and then click Options.)
1. Choose Options from the Tools menu. Excel displays the Options dialog box.
2. Display the Calculation tab.
Now Excel will handle circular references, such as the simple formula you've put in cell E5.
The second approach is to use a macro to perform the calculation. This approach may be
preferred because you may not want (for some reason) to enable circular references in your
workbook. The following is actually an event handler, added to the code for the worksheet.
(Easiest method: Right-click on the sheet tab, display the code window from the resulting
Context menu, and add the macro to that code window.)
The macro is triggered every time the worksheet is recalculated. It grabs the maximum of A1:A3
and compares it to what is in E5. Only if it is larger is that value then placed into E5.
There are several ways you can accomplish this task. The first is through the use of VLOOKUP.
This worksheet function works great, provided your lists of clients are arranged in alphabetical
order. One way to use the function is to add a status column to your "all clients" worksheet. First,
make sure you select your active clients and name them "Active". (How you define a name for a
selected range of cells is covered in other ExcelTips.) Then, in your full list of clients, add a
column (named Status) to the right of your existing data. In the cells of the Status column, use
the following formula:
=IF(ISNA(VLOOKUP(A2,Active,1,FALSE)),"Inactive","Active")
This formula assumes that the client's name is in column A of the current worksheet. The result
of the formula is either "Active" or "Inactive," depending on whether there is a match between
the name at A2 and the names in the Active list.
Once the Status column is in place, you can use the AutoFilter capability of Excel to filter your
list based on the status column. You can then easily display the inactive clients, as desired.
It should be noted that while the above example uses the VLOOKUP worksheet function, you
could just as easily compose other formulas that use functions such as HLOOKUP and MATCH.
Which you use depends on your personal preferences and the way in which your data is laid out.
Another solution is to use a macro to compare each name on the "all clients" list with the names
on the "active clients" list. If no match is found, then the name can be safely added to the
"inactive clients" list. The following macro does just that:
Sub ListInactive()
Dim cell As Range
Dim SearchRng As Range
End If
Next cell
End Sub
The macro makes several assumptions about the data being examined. First, it assumes that the
"all clients" worksheet is the first worksheet, and that the "active clients" worksheet is the
second. Also, it is assumed that the third worksheet is blank and will end up containing the list of
inactive clients. Further, the assumption is that column A contains a unique client ID number and
column B contains the name of the client. When the macro is finished, the third worksheet
contains the client numbers and names of all the inactive clients.
The easiest way to do this would be to create a macro that exported each of your macros to a .bas
text file, then read that text file to determine the shortcut keys. Why this is easiest is because the
shortcut keys are not directly available to VBA, but they are written as part of the text file
whenever you export macros.
For instance, when you export a macro to a .bas file, it will include lines similar to the following:
Your macro could read the .bas file (it is nothing but a text file) and parse what is written there to
extract the macro’s name and shortcut keys. In the above example, "Module1" is the module the
code is in, "MyMacro" is the procedure name, "My Description" is the description for the
procedure and "q" is the shortcut key. (The "\n14" seems to be there for all macros; what it
signifies is unclear.)
Your code, to be effective, would need to loop through all the modules in a workbook, doing the
export and parse steps to get the desired information.
Sound complex? It can be. It’s a good thing that there is already a macro available that does all
of this. Ivan Moala has written a free Excel add-in that does exactly what is described above. The
add-in is available here:
http://www.xcelfiles.com/GetShortCutKeys.html
The code is password protected but Moala has indicated elsewhere on the Internet that the
password to access the code is “test.” By unlocking the code, you can see exactly how he
achieved his results and then create your own macro, or you can just modify his to make sure
that whatever is printed matches your needs. For instance, if you want the macro to print the
workbook name, module name, macro name, and shortcut key, then you could modify the code
to do exactly that.
There is one gottcha that could bite you when using macros of this sort, as well. You need to
make sure that you have your macro security set to the proper level to allow the type of access
required for the macro to do its work. You do this by choosing, within Excel, Tools | Options |
Security | Macro Security. (In Excel 2007 and later versions you display the Developer tab of the
ribbon, then click Macro Security in the Code group.) You can then set the security level to the
necessary setting: Trust Access VB Projects or, in Excel 2007 and Excel 2010, Trust Access to
the VBA Project Object Model.
The code described so far allows you to get the desired information for a specific workbook. The
next step, of course, is to make sure that the code is applied to each open workbook. This is
relatively easy. If your code to process the macros in a single workbook is named something like
ListKeys, then you can create a different macro that looks like this:
Sub ListAllKeys()
Dim wkb As Workbook
The macro steps through all the open workbooks, passing each one’s name to the ListKeys
macro. This name can then be used by your ListKeys procedure to determine where it grabs its
information from.
• If you are using Excel 2007 or later you can change between workbook windows by
displaying the View tab of the ribbon, clicking the Switch Windows tool, and choosing
the workbook name from the resulting list.
• If you are using an older version of Excel you can change between workbook windows
by choosing the workbook name from the Window menu.
You may not know, however, that there is a shortcut for changing windows. You can cycle
through your documents by pressing either CTRL+F6 or SHIFT+CTRL+F6. The difference is that
CTRL+F6 cycles through the list of windows in a forwards direction, while SHIFT+CTRL+F6
cycles backwards.
Another shortcut is to use CTRL+TAB and SHIFT+CTRL+TAB to switch windows. These function
just the same as CTRL+F6 and SHIFT+CTRL+F6.
If you would rather not take your hands off the mouse, you can create a macro which cycles
through the windows. This macro can then be assigned to a toolbar button. The following macro
will do the trick:
Sub ChangeWin()
On Error GoTo ChangeWinErr
Set nw = ActiveWindow.ActivateNext
If Windows.Count > 1 Then
nw.Activate
Exit Sub
End If
ChangeWinErr:
Windows(1).Activate
End Sub
Unfortunately, there is no formatting that will do the trick; all display formatting seems to be
dependent on the position of the decimal point. You can format a display for a specific number
of digits after the decimal point, but that number of digits will be used regardless of how many
digits appear before the decimal point.
Several ExcelTips subscribers came up with suggestions that involve using formulas to display
the number as desired. For instance, the following formula will display the value in A1 using six
digits:
=FIXED(A1,IF(ABS(A1)<1,5,5-INT(LOG(ABS(A1)))),TRUE)
Other readers provided formulas that relied on converting the number to a text string and
displaying it as such. Converting a number to its textual equivalent, however, has the distinct
drawback of no longer being able to use the number in other formulas. (Remember—it is text at
this point, not a number.) The above formula does not have that limitation.
If you wanted to, you could also use a macro to set the formatting within a cell that contains a
value. The advantage to such a macro is that you don’t have to use a cell for a formula, as shown
above. The drawback to a macro is that you need to remember to run it on the cells whenever
values within them change. The following macro is an example of such an approach:
Sub SetFigures()
Dim iDecimals As Integer
sFormat = "0"
If bCommas Then sFormat = "#,##0"
If iDecimals < 0 Then sFormat = "General"
If iDecimals > 0 Then sFormat = sFormat & _
"." & String(iDecimals, "0")
TestCell.NumberFormat = sFormat
Next TestCell
End Sub
In order to use the macro, simply select the cells you want to format, then execute it. Each cell in
the range you selected is set to display six digits, unless the number in the cell is too large or too
small.
Engineering Calculations
In an engineering environment, it is not unusual to need to “normalize” numbers in some
manner. For instance, you may need to show numeric values normalized to multiples of 10^3,
such that 7340 is expressed as 7.34 and 73400 is expressed as 73.4.
It is possible in Excel to use a custom number format to express information in scientific notation
that will normalize the display of a number to a multiple of 10^3. To do this, you would follow
these steps:
Now, when you enter a number such as 7340 into the cell, Excel displays it as 7.3E+3. Because
of the way the cell format was entered, the portion after the E will always be a multiple of 3.
This is fine and good, but what if you want just the 7.3 in the cell, and then a metric prefix with a
unit in an adjoining cell, such as kilograms? This is a bit more complex, but it can be done using
formulas. For instance, let's assume you have your original number in cell A2, you wanted the
normalized number in cell B2, and the metic prefix and unit name in cell C2. All you would need
to do is enter the following formula in cell B2:
=IF(OR(A2>=1,A2<=-1),SIGN(A2)*(ABS(A2)/(10^(3*INT(LOG(ABS(A2))/3)))),
IF(A2=0,0,SIGN(A2)*(ABS(A2)*10^(-3*INT(LOG(ABS(A2))/3)))))
Assuming the units you are working with are an imaginary unit called a foo, in cell C2 you
would use a different formula, as follows:
These formulas may seem a bit long, and they are. However, they will work for any number
between approximately -9.99999E-18 to 9.99999E+20. For instance, if you put the number
.000125 in cell A2, then cell B2 will contain 125 and cell C2 would contain Millifoos.
If you prefer to not use longer formulas such as these in your workbooks, you can develop a
couple of VBA functions to do the trick. The following function, MySciNum, returns a
normalized number. Thus, you would use =MySciNum(A2) in cell B2 to get the same results as
noted above:
This function only returns a number. To return the units with the appropriate metric prefix, you
would use the following function. All you need to do is pass it the cell reference and the name of
a single unit. For instance, you could use =MySciPre(A2, "foo"). The macro is as follows:
Pref = 0
OrigNum = BaseNum
Select Case BaseNum
Case Is >= 1
While Abs(BaseNum) > 1000
BaseNum = BaseNum / 1000
Pref = Pref + 1
Wend
Case 0
Pref = 99
Case Else
While Abs(BaseNum) < 1
BaseNum = BaseNum * 1000
Pref = Pref - 1
Wend
End Select
MySciPre = Temp
End Function
The short answer, Ken, is that you cannot. Why? Because Excel always transforms everything to
15 significant digits internally. This, if you enter 12 or 12.0 or 12.00000, Excel always considers
these the same as 12.0000000000000, even though this may not be technically correct in the
truest mathematical sense. Further, since Excel converts everything to 15 significant digits, it
truncates any trailing zeros in any numbers displayed using the General number format.
(It should be obvious from the above explanation that the terms “significant digits,” “precision,”
and “number of decimal places” mean entirely different things. For instance, the number 12.000
has three decimal places, but has five significant digits. (I’m sure the math theorists in the
audience will be all too glad to correct me if I am wrong.)
You can use a custom numeric format for individual cells, which will display your information
using the proper number of significant digits, but this will not affect how Excel works internally.
For instance, you could format a cell to display 12 or 12.0 or 12.00000, but this does not change
the fact that Excel still considers all the numbers to be 12.0000000000000.
One way around this problem is to enter your numbers as text, by putting an apostrophe in front
of them. In other words, instead of entering 12.0 you would enter ’12.0. Excel then parses the
entry as text (because of the apostrophe) and displays 12.0 in the cell. The only drawback to this
approach is that you cannot directly reference the cell in all mathematical functions; some of
them will return an error because you are including what Excel considers as text in the function.
The easiest way to handle this is to make sure you include the =VALUE function as part of your
overall formula. For instance, if you enter a number as text into cell B7, and then need to
reference it in a formula, you could do it in this way:
=VALUE(B7) * 100
This works great for formulas with single-cell references. It does not work well for range
references, however. If you need to do a sum or an average on a range of values entered as text,
Excel will choke every time, returning an error based on what the function is trying to do.
This brings us to the second potential solution: maintaining either the significant digits or the
number of decimal places separate from the actual value. For many technical mathematical
purposes, this is the best way to handle the situation. For example, in column C you could enter
your numbers, as numbers. Excel, of course, converts them internally to 15 significant digits. In
column D you could enter various numbers that represent the number of decimal places you want
applied to the values in column C. You could then use the values in column D to help in
formulas that refer to the values in column C.
A final potential solution is to simply format each individual cell so that it shows only the
number of decimal places that you want displayed. With the caveat noted above (that such
formatting does not affect the internal representation of numbers by Excel), this might be the best
solution. Of course, individually adjusting the display formatting of a couple of cells is easy, but
doing so to a hundred or five hundred cells is a different story. This is where a macro can come
in handy. Consider the following macro:
Sub SigPlaces()
Dim c As Range
Dim strcell As String, NumFormat As String, DecSep As String
Dim DecPtLoc As Integer, stringLen As Integer
Dim numDecPlaces As Long
DecSep = "."
strcell = c.Value
If IsNumeric(strcell) Then
NumFormat = "#0"
DecPtLoc = InStr(strcell, DecSep)
If DecPtLoc > 0 Then
NumFormat = NumFormat & DecSep
stringLen = Len(strcell)
numDecPlaces = stringLen - DecPtLoc
NumFormat = NumFormat & String(numDecPlaces, "0")
End If
c.Value = Val(strcell)
c.NumberFormat = NumFormat
End If
Next c
End Sub
If you enter your numbers as text (with the apostrophe, mentioned above), you can then select
the cells and run this macro. The text entries are converted to numbers, and the formatting
applied to those cells is changed so that the same number of decimal places is displayed as you
entered in the text entry.
27, 22, 22, 30, 32, 18, 22, 23, 28, 39, 24, 27, 35, 25, 21
If he wants to know the number of groupings where the members of those groupings were under
26, the answer would be 4. Note that this is the groupings of consecutive values below 26, not
the number of individual values below 26. Thus, in this case, the four groupings would be shown
by the brackets in the following:
27, [22, 22], 30, 32, [18, 22, 23], 28, 39, [24], 27, 35, [25, 21]
Ronald is wondering what sort of formula he can use to figure out the number of groupings that
fall below some arbitrary threshold he might specify.
There are actually several different ways you can approach this. The first is to use a “results
column” that essentially notes changes in threshold and sequence grouping. For instance, if you
had the above values in column A of a worksheet (starting at cell A2) and the threshold value in
cell E1, then you could use the following formula in every cell to the right of a value in column
A:
=IF(A2>=$E$1,B1,IF(A1<$E$1,B1,B1+1))
The formula keeps a running sum of the groups below the threshold. The max (or last value) of
column B provides the total number of groups below the threshold. The formula checks to see
whether the value immediately to the left, in column A, is above or below the threshold. If it’s
above, or if not and the previous value in column A was also below, then it doesn't increment the
running sum. Otherwise, it does increment because a new grouping is starting.
A related way of doing the count is to use this formula in column B, instead:
=IF(A2>=$E$1,0,IF(A1<$E$1,0,1))
This results in column B containing a series of 0 or 1 values. The only time that a 1 value occurs
is at the start of a series that is below the threshold. This makes it easy to sum all the values in
column B, which provides the count of groupings.
If you don’t want to use the results column, you can use an array formula to figure out the count.
The following formula assumes, again, that the values to be analyzed are in column A, beginning
at A2, and that the threshold value is in cell E1. Remember, as well, that array formulas are
entered by pressing CTRL+SHIFT+ENTER.
=SUM(IF((A2:A16<$E$1)*((A2:A16<$E$1)*1<>((A1:A15<$E$1)*ISNUMBER(A1:A15))),1))
The formula basically does what the previous results-column formula did (determines a 0 or 1
based on whether a below-threshold grouping is starting) and then sums those values.
Of course, if you do these types of comparisons a lot, you may want to develop your own user-
defined function (a macro) to figure the count of groupings for you. The following is an example
of such a function.
Application.Volatile
iCount = 0
bInGroup = False
For Each Cell In MyRange
If Application.IsNumber(Cell) Then
If Cell < Threshold Then 'Less than the threshold?
If Not bInGroup Then 'Only count if starting new group
iCount = iCount + 1
bInGroup = True 'Mark as being in group
End If
Else
bInGroup = False 'No longer in a group
End If
End If
Next
CountGroups = iCount
End Function
The function looks through each cell in a range and calculates if it is the start of a new below-
threshold group or not. You use the function by using a formula such as the following in your
worksheet:
=CountGroups(A2:A16,E1)
This code does several things. First, it opens the external workbook and selects the range of cells
you want to copy. The Copy method is then invoked, so the source range is now in the
Clipboard. The macro then switches to the target workbook and select the range there. Finally,
the PasteSpecial method is used to paste only the format of the source cells.
If you decide to use code like this, you can place it in the Auto_Open macro for the target
workbook. Of course, you need to modify the code so that it refers to the proper path and
workbook names, along with the desired source and target ranges.
Sub SwitchStuff()
Dim rngSrc As Range
Dim lMax As Long, lCtr As Long
bDoIt = True
sFrom = InputBox("Search for this:", "SwitchStuff")
If Len(sFrom) = 0 Then bDoIt = False
If bDoIt Then
sTo = InputBox("Replace with this:", "SwitchStuff")
End If
If Len(sTo) = 0 Then bDoIt = False
If bDoIt Then
Set rngSrc = ActiveSheet.Range(ActiveWindow.Selection.Address)
lMax = rngSrc.Cells.Count
Start by selecting the cell or cells you want to change. When you run the macro, it asks what you
want to search for. Enter the characters you want to search for and click on OK. It then asks what
you want to replace it with. Enter that, click on OK, and the changes are made.
You should understand that this is a straight text replacement; it does not adjust the relativity of
cell references (if that is what you are searching for). For instance, if you search for C123 and
replace it with C321, then all occurrences of C123 are changed. If one of the cells in the range
has a reference to C124, it is not changed.
Doing the conversion, of course, isn't the real issue. The problem is that if someone enters a
value in A1, that value will overwrite any formula that may be in that cell, and mean that any
subsequent value entered in cell C1 would not give the required conversion in the previously
overwritten A1.
There are a couple of different ways that this could be approached. If you don’t mind expanding
your worksheet design to include two more cells, those cells could be used strictly for input and
cells A1 and C1 could be used strictly for output. One of the input cells could contain the value
to be converted and the other could contain the measurement unit of the input value (in or mm,
for instance).
Of course, if you want to really limit yourself to two cells, then you will need to resort to using
macros to do the actual conversion. You can use a worksheet event that is triggered every time a
cell value is changed, and the event handler could check to see if the cell being changes is either
A1 or C1. The following macro gives an example of how this could work:
Note that you don’t have to have any formulas in cells A1 or C1; the formulas are in the macro
itself. If there is a change in cell A1 (inches are entered by the user), then the value in cell C1 is
changed by the macro. Likewise, if there is a change in cell C1 (millimeters are entered by the
user), then the value in cell A1 is changed by the macro. A change in any other cell besides A1
or C1 is ignored by the macro.
The following macro will help you tremendously in this situation. All you need to do is select the
first row in the data table. When you run the macro, it asks you how many blank rows you want
to insert between the original rows. When you provide a number, the macro steps through the
table and starts inserting blank rows. The macro stops when the first blank cell after the original
table is detected.
Sub SpreadOut()
Dim iBlanks As Integer
Dim J As Integer
Within the quote marks you can place the full path name and file name of the file you want to
execute. On some systems you may experience problems if you use a path name with the file
specification. (This seems to crop its ugly head if you have complex path names or if the path
name includes spaces.) If you have this problem, then simply use the ChDir command just prior
to Shell in order to change the directory used by Excel. You can then execute Shell using just a
file name.
The mode indicator simply tells Excel how you want the window opened for the file to appear.
The mode indicator can be any of the following:
The Shell command returns a value that indicates the program ID of the file you executed, or else
a zero. If a zero is returned, then there was an error executing the file.
You should remember that when you use Shell, the target file is executed right away, and it is
executed independently of Excel. This means that the next macro command, in your Excel
macro, is immediately executed without waiting for the Shell target file to finish. Unfortunately,
there is no way around this behavior.
There are a couple of ways that this could be handled. One way is to avoid having to do the
deletion all together. Instead, have your macro create a new workbook and then transfer, to that
workbook, a copy of the data you need. You could then slice and dice the data in the new
workbook and save it as your purchase order. The macros in the existing workbook are never
copied to the new workbook during the process, so you don’t need to worry about deleting them.
Copying all the worksheets in the current workbook to a new workbook is very easy to do. The
following macro shows how it is done:
Sub CopyThisWorkbook()
Dim CopiedWB As String
Sheets.Copy
ActiveWorkbook.SaveAs Filename:=CopiedWB, _
FileFormat:=xlNormal
End Sub
The Copy method, when applied to the Sheets collection, copies all the worksheets in the active
workbook to a new workbook and makes the new workbook active. The final command saves
the new workbook under a new name.
Of course, if you need some of your macros to be in the new workbook, but not all of them (such
as the SelectionChange event handler), then you are probably best to delete what you don’t need
and simply save under a new name. The following example macro shows how to delete the
SelectionChange event handler from the worksheet code for Sheet1.
Sub DeleteProcedure()
Dim VBCodeMod As CodeModule
Dim StartLine As Long
Dim HowManyLines As Long
After the macro is completed, the workbook could be saved, and the desired macro won’t be in
the saved file. This macro is adapted from information provided at Chip Pearson’s Web site,
which you should reference if you need additional information on this technique:
http://www.cpearson.com/excel/vbe.aspx
Self-Deleting Macros
Patrick is writing a macro, and he wants the macro to delete itself after a specific expiration date
is reached. There are a couple of ways that this task can be approached. First, you could write a
macro that would only function before a specific date, in the following manner:
Sub MyMacro()
ExpirationDate = #6/1/2013#
If Now() < ExpirationDate Then
End if
End Sub
The idea is that if (in this case) the current date is prior to June 1, 2013, then the main body of
the macro will execute. If it is June 1 or later, then the macro will not execute. This approach, of
course, does not actually delete the macro; it simply checks to see that the macro is being
executed before a certain date.
To actually get rid of the macro code, you need to take a different approach:
End Sub
This code was adapted from a macro originally written by Chip Pearson, available on his site at
the following address:
http://www.cpearson.com/excel/vbe.aspx
To make the macro work, you’ll need to make sure that there is a reference to Microsoft Visual
Basic for Applications Extensibility. (You do this by choosing, in the VB Editor, Tools |
References and then choosing Microsoft Visual Basic for Applications Extensibility in the
available references.)
The macro runs when the workbook is opened, and if the date is greater than or equal to June 1,
2013, then each component of the VBProject is deleted. This means that the macro is very
powerful, because it deletes everything, not just a single procedure or module.
There are a couple of things to keep in mind with this macro, of course. First, if the user chooses
to not enable macros when the workbook is opened, then this code will never run and the macro
won’t be deleted. Second, deleting macros in this way obviously introduces changes to the
workbook. That means that when the workbook is closed, the user will be asked if they want to
save their changes. If they choose not to, then the deletions will not be saved and the macro will
again run the next time the workbook is opened.
The good news is that if you know how to delete macros within a module, you can apply the
same technique to delete it within a sheet. The difference is that you would use the sheet name
rather than the module name when referring to the component you want to delete.
For instance, if you are referring to code in a module in a workbook, you normally do it by
referencing the containing module in this manner:
ActiveWorkbook.VBProject.VBComponents("Module1")
To refer to code contained within a worksheet, you would use this syntax, instead:
ActiveWorkbook.VBProject.VBComponents("Sheet1")
For other ideas about how to reference VBA code in various ways from within other macros,
refer to the following page at Chip Pearson’s site:
http://www.cpearson.com/excel/vbe.aspx
When you reference a cell in a macro, such as using Range("B6"), then VBA treats that reference
as absolute, meaning that it doesn’t change. Even if you add or delete cells that affect where the
info that was in B6 is now located, the macro reference will remain the same.
The way around this is to not use direct references to cells in your macros. Instead, rely on
named ranges. In Excel, define a name for cell B6 (such as “MyData”), and then use that name in
the reference, as in Range("MyData"). This approach works because VBA looks up the name in
order to determine which cell is being referenced, and Excel makes sure the named range
references remain up-to-date as you add or delete cells.
Sub DoCamera()
Dim MyPrompt As String
Dim MyTitle As String
Dim UserRange As Range
Dim OutputRange As Range
Application.ScreenUpdating = True
This macro prompts you to specify a range to be copied, it then copies it to the Clipboard as a
picture, and prompts you for where to paste it. When pasted, the final line of the macro is the key
to making the "photo" dynamic, just as is done manually with the Camera tool. The PasteSpecial
command actually pastes the picture, and the pasted picture remains selected. Setting the
Formula property for the selection (the picture) results in the dynamic nature of the graphic.
This is rather easy to do with the built-in Subtotals feature of Excel. All you need to do is follow
these steps:
1. Make sure your table contains column labels. For instance, if column A contains the
department names, then cell A1 could contain a label such as “Department.” Make sure
all the columns have labels.
2. Sort the data in your table, using the department column as the key.
3. With any cell within the table still selected, choose Subtotals from the Data menu. Excel
displays the Subtotal dialog box. (Display the Subtotal dialog box in Excel 2007 and
Excel 2010 by displaying the Data tab of the ribbon and clicking the Subtotal tool in the
Outline group.)
If, for some reason, you don’t want to use the Subtotals feature, you can always write a macro
that will remove all the page breaks in your worksheet, then add new page breaks at the
appropriate places. The following macro will do the trick:
Sub PageBreak()
Dim CellRange As Range
Dim TestCell As Range
To use the macro, simply select the cells you want to use as your key for doing the splits, minus
the top cell. For instance, if the departments are in column A, rows 2 through 37, you would
select the range in A3 through A37. Run the macro, and any old page breaks are removed and
new ones added.
This approach is difficult to implement in Excel because Excel is not very good at searching
backwards—up a column. If the premise could be reversed, then the task becomes much simpler.
For instance, if a formula in cell B246 could return the value 105, indicating the interval until the
next occurrence of the value in cell A246, instead of calculating the last occurrence. The
following formula calculates the next occurrence of the value in cell A1:
=MATCH(A1,A2:$A$65536,0)
Place this formula in cell B1 and copy it down however many cells are necessary. If the value in
column A does not occur again in the column, then the formula returns the #N/A error. If you
would rather have the formula return 0, then the following works:
=IF(ISNA(MATCH(A1,A2:$A$65536,0)),0,MATCH(A1,A2:$A$65536,0))
If you absolutely must count upwards (find the previous occurrence instead of the next
occurrence), then the easiest way to do it is with a user-defined function. The following function,
RowInterval, will look backward through a range you specify and return the desired interval:
Application.Volatile
varValue = TestCell.Value
In order to use the function, you would put the following formula in cell B2, and then copy the
formula down the number of desired cells:
=RowInterval(A2,A$1:A1)
Self-Aware Macros
For some macros you may need to determine if there is a way to determine the particular
machine on which the macro is operating. For instance, you may have a desktop PC that has a
particular directory at D:\OraNT\Plus33, while your notebook PC has the directory at
C:\OraNT\Plus33. The macro, of course, needs to detect which machine is in use so that it knows
which directory to use for its processing.
There are different ways that this task can be approached. It is possible to create an Excel macro
that actually accesses the Windows API and determines the name of the computer on which it is
running. Such an approach can get quite involved, however.
An easier way is to just use VBA’s DIR command to determine where the desired directory
exists. The following will do the trick:
Sub OracleQueries()
Dim sTemp As String
Dim sGoodPath As String
sGoodPath = "D:\OraNT\Plus33\"
sTemp = Dir("D:\OraNT\Plus33\nul")
If sTemp = "" Then
sGoodPath = "C:\OraNT\Plus33\"
sTemp = Dir("C:\OraNT\Plus33\nul")
End If
Notice how the DIR function is used in this example. Normally DIR returns the name of the first
file it finds in the requested directory. If the directory is empty, however, DIR returns an empty
string—even if the directory actually exists. Since all we want to do is find out if the directory
exists (not if there are files in it), it is necessary to append the letters “nul” at the end of the
directory path used by DIR. This causes DIR to return an empty string if the directory is not
located, or else the characters “nul” if it is (even if the directory is empty).
Toward the end of the macro, sTemp will be empty if neither directory could be located. If one
of them was located, then sTemp will not be empty, and sGoodPath will be set to the directory
name that can be used in further processing.
references within the selection. The answer, of course, is that it depends. (Don’t you just love
that about Excel?) Let’s take a look at how you can both copy and move selections in Excel.
If you are copying a selection, then Excel will update all relative references within the selection
when you paste it. The solution, of course, is to make sure that all the references within the
selection are absolute before doing the copy and paste. Making the changes to the formulas by
hand is tedious. You can use the following macro to convert all the formulas in the selection to
their absolute equivalent:
Sub ConvertToAbsolute()
Dim c As Variant
Application.ScreenUpdating = False
For Each c In Selection
c.Value = Application.ConvertFormula(c.Formula, _
xlA1, , xlAbsolute)
Next c
Application.ScreenUpdating = True
End Sub
Once this macro is run, you can copy and paste the selection without Excel doing any updating to
references. Once the pasting is done, you can change the references in the selection (and in the
original range, if it still exists) by selecting the range and applying this macro:
Sub ConvertToRelative()
Dim c As Variant
Application.ScreenUpdating = False
For Each c In Selection
c.Value = Application.ConvertFormula(c.Formula, _
xlA1, , xlRelative, c)
Next c
Application.ScreenUpdating = True
End Sub
This macro will change all formulas in the selected range to their relative equivalent. Remember
that this will affect all formulas—which means that if the formulas in the range contained both
relative and absolute references, when this macro is done, they will all be relative.
If you are moving a selection, then Excel does not update cell references in the move. You can
move either by selecting the range and using the keyboard (pressing CTRL+X to cut and then
CTRL+V to paste the selection) or the mouse (dragging the selection to a new location). In either
case, Excel leaves the references in the selection exactly the same—relative or not—without
updating.
So far I have discussed what Excel does with the references in the selection being copied or
moved. What about references to the information in the selection? If you are copying, then Excel
leaves references pointing to the original range. If you are moving a selection, then Excel
updates references to that selection, regardless of whether they are relative or absolute. If you
don’t want the information updated during a move, then the solution is to make a copy of the
range and then delete the original.
Word users know that they can, within Word, use the Go To dialog box to jump to various pages,
but no such feature exists in Excel. There are a couple of ways you can approach the problem,
however.
One approach is to select the cell that appears at the top of a page. (For instance, that cell that
appears at the top-left of page 5.) You can then define a name for the cell, such as Page05. Do
this for each page in your worksheet, and you can then use the features within Excel to jump to
those names.
Another way you can do this is to use the page break preview mode. (To switch to page break
preview, choose View | Page Break Preview or, in Excel 2007 and Excel 2010, display the View
tab of the ribbon and click the Page Break Preview tool.) You can then see where the page
breaks are, select a cell on the page you want, and then return to normal view.
It is possible to also create a macro that will let you jump to a specific page, but it isn't as easy as
you might think. The reason has to do with the possible use of hard page breaks, which can
change where pages start and end. The following macro might do the trick for you, however. It
prompts the user for a page number and then selects the top-left cell on the page entered.
Sub GotoPageBreak()
Dim iPages As Integer
Dim wks As Worksheet
Dim iPage As Integer
Dim iVertPgs As Integer
Dim iHorPgs As Integer
Dim iHP As Integer
Dim iVP As Integer
Dim iCol As Integer
Dim lRow As Long
Dim sPrtArea As String
Dim sPrompt As String
Dim sTitle As String
Else
iHP = Int((iPage - 1) / iVertPgs)
iVP = ((iPage - 1) Mod iVertPgs)
End If
If iVP = 0 Then
If sPrtArea = "" Then
iCol = 1
Else
iCol = wks.Range(sPrtArea).Cells(1).Column
End If
Else
iCol = wks.VPageBreaks(iVP).Location.Column
End If
If iHP = 0 Then
If sPrtArea = "" Then
lRow = 1
Else
lRow = wks.Range(sPrtArea).Cells(1).Row
End If
Else
lRow = wks.HPageBreaks(iHP).Location.Row
End If
wks.Cells(lRow, iCol).Select
Set wks = Nothing
End Sub
There are many ways that this can be approached. In considering solutions, I examined only
those solutions that avoid intermediate answers, which occupy additional columns. The first
solution involves using the COUNTIF function in this manner:
=COUNTIF(A1:A9,"*apple*")+COUNTIF(A1:A9,"*seed*")
-COUNTIF(A1:A9,"*seed*apple*")-COUNTIF(A1:A9,"*apple*seed*")
The formula counts all the cells that contain either "apple" or "seed" and then subtracts all the
cells that contain "seed" followed by "apple" (both words are in the cell) or "apple" followed by
"seed" (the same words in reverse order).
Another solution, this one a bit shorter, relies on the COUNTA and FIND functions, as shown
here:
=COUNTA(A1:A9)-SUMPRODUCT(--(ISERROR(FIND("apple",A1:A9)))
*--ISERROR(FIND("seed",A1:A9)))
The formula counts the cells containing values and then subtracts all those cells that don't contain
either "apple" or "seed".
You can also, if you prefer, use one of Excel's database functions. Provided you have a column
heading for your original phrases, this is not that difficult to do and it results in the shortest
formula. All you need to do is set up a corresponding criteria table. For instance, let's say your
data is in A1:A9, and the first cell in the column contains a header such as "My Phrases". In
another column you should put the same header and then, in the two cells directly under it, place
these two formulas:
*apple*
*seed*
The criteria specify that you want to match any cells that contain "apple" or "seed" within the
cell. With this set up (I'm assuming you placed the criteria table in D1:D3), you can use the
following formula:
=DCOUNTA(A1:A9,1,D1:D3)
Of course, you could also use an array formula (entered by pressing CTRL+SHIFT+ENTER) to get
your answer. The following is one such formula that relies, again, on the phrases being checked
to be in A1:A9:
=SUM(--((ISNUMBER(FIND("apple",A1:A9))+ISNUMBER(FIND("seed",A1:A9)))>0))
If you lean more towards working with macros, you could create a user-defined function that
returns the count for you. The following is an example of one that will work:
=FindTwoStrings(A1:A9,"apple","seed")
There is no Excel command that allows you to update all PivotTables, but you can create a short
macro that will do the job for you. The following macro, RefreshAllPivots, steps through each
worksheet in a workbook, checks to see if there are any PivotTables, and then updates them if
there are.
Sub RefreshAllPivots()
Dim wks As Worksheet
Dim pt As PivotTable
If you do a lot of work with multiple PivotTables, you may want to assign the macro to a
shortcut key, a toolbar button, or to a menu option so that you can run it easier. (Information on
how to assign macros to toolbars, shortcut keys, and menus is covered in other issues of
ExcelTips.)
The answer is that it would be easier to use a macro. (That is what macros are for—to take care
of the tedious things you tire of.) Rather than reinvent the wheel, however, a good solution is to
consider the following code, adapted from Microsoft MVP Debra Daglisesh's site:
Sub NoSubtotals()
'turns off subtotals in pivot table
'.PivotFields could be changed to
'.RowFields or .ColumnFields
Dim pt As PivotTable
Dim pf As PivotField
Just display the PivotTable you want to affect, and then run the macro. The subtotals for all the
fields in the PivotTable are suppressed at once. The original for this code is available here, at
Debra's site:
http://www.contextures.com/xlPivot03.html#Subtotals
The site also contains some other good information for working with PivotTables.
If your workbook contains multiple PivotTables, all based on a single data source, Excel may
create an intermediate dataset for each PivotTable, instead of using one intermediate dataset.
This, of course, could increase the size of your workbook very rapidly.
You can control how Excel creates the intermediate dataset by modifying the options you choose
in the PivotTable Wizard that puts your PivotTable together. If you have one PivotTable in your
workbook, and when running the PivotTable Wizard a second time you specify the same data
source that you used in the existing PivotTable, Excel informs you that “Your new report will
use less memory if you base it on your existing report.” If you click Yes, you will save memory
because Excel will use the same intermediate data as it used for your other PivotTable.
You can also instruct Excel to not save your intermediate data tables in the same disk file with
the workbook. This will make the size of your workbook file much, much smaller, but it will also
require that PivotTables be refreshed every time you open your workbook. Follow these steps if
you are using Excel 2007 or a later version:
If you are using an older version of Excel, follow these steps insead:
1. Run the PivotTable Wizard to create your PivotTable as you normally would.
2. When you get to the final screen of the PivotTable Wizard (the one with the checkered
flag on it), click the Options button to display the PivotTable Options dialog box.
You don’t need to choose the Refresh on Open check box (step 4) if you don’t want to, but if you
don’t, you will need to remember to manually refresh the PivotTable every time you open the
workbook.
If you already have quite a few PivotTables in your workbook, and you don’t want to go through
the process of creating them again, you can use a macro to step through the PivotTables and
modify the caching index and turn off the saving of the intermediate data to disk. The following
macro will accomplish these tasks:
Sub PTReduceSize()
Dim wks As Worksheet
Dim PT As PivotTable
End Sub
Once the macro runs (it won’t take long), you should save your workbook using the Save As
option. This will write a new workbook file, and you will be able to compare how much this
change reduced the size of your workbook. Remember, however, that with the intermediate data
not being saved to disk, the refreshing of the PivotTables takes longer when you first open the
workbook.
There is no way to set this default, but it is possible to make the process a bit less painful. I
created a PivotTable and left it empty, with nothing defined in the various sections of the
PivotTable. With the PivotTable sheet visible, I turned on the macro recorder and recorded just
the steps that Nancy mentioned, above. Here's what was recorded:
Sub Macro1()
'
' Macro1 Macro
'
'
With ActiveSheet.PivotTables("PivotTable1")
.InGridDropZones = True
.RowAxisLayout xlTabularRow
End With
End Sub
As you can see, there isn't much (programmatically) to changing back to classic layout—all you
need to do is issue two statements that affect the PivotTable. This macro can be improved just a
bit, however, by making it more "universal."
Sub PivotTableClassic()
Dim pt As PivotTable
This version of the macro steps through each of the PivotTables on the current worksheet (if any)
and applies those two statements that set them to classic layout.
The best idea we've been able to come up with is to assign this macro to the Quick Access
Toolbar or to a shortcut key. Immediately after creating the macro, you can click the QAT button
or press the shortcut key and Excel makes all the PivotTables on the sheet classic.
Resizing Checkboxes
When working in VBA, one of the things you can create is known as a “user form.” These forms
provide you with the ability to essentially create your own dialog boxes. You can add many
different types of controls to a user form, if desired. For instance, you can add labels, text boxes,
drop-down lists, radio buttons, and many other controls. Some of the controls you can resize;
others you cannot. One that you cannot resize is a checkbox. While you can modify the font size
used for the label next to the checkbox, you cannot resize the checkbox itself.
If you find the checkboxes in your user forms too small for your taste, you can “work around”
them by simulating a checkbox. You do this by actually creating a label instead of a checkbox.
Then, change the properties of the label so that it has a transparent background, and that the font
being used is Wingdings. You should also make sure that the font is set to a large size, such as 20
or 26 points.
Now, double-click on your label, which should open a code window. The event that you are
programming is the Click event for the label, which means it will be executed whenever the label
is clicked. Use this as your code:
In the Wingding font, Chr(254) is box with a checkmark, and Chr(168) is a box with no
checkmark. When you execute the user form and click on the label, it switches between an empty
box and a checked box. You can also add other code to the Click event that performs other tasks,
as necessary.
In a perfect world, Excel would allow you to easily split the numbers from the street names.
Since this option doesn't exist, you have a couple of choices. The most time-consuming option
involves adding an additional column and retyping the data. If, however, you would like to save
some time, you can use a variety of formulas to accomplish the task.
Assuming the list of addresses is in column A (beginning in cell A1), you could use a formula
similar to the following to pull out the numeric portion of the address:
=IF(ISERROR(VALUE(LEFT(A1,1))),"",LEFT(A1,FIND(" ",A1)-1))
Assuming you put the formula in cell B1, you could then use a different formula to derive the
non-numeric portion of the address:
=TRIM(RIGHT(A1,LEN(A1)-LEN(B1)))
Note that this approach does have a limitation. Some addresses, especially in major metropolitan
areas, use a format such as 152-33 Bell Blvd. The formulas above will work for these addresses,
but if the alternative, 152 33 Bell Blvd., is used, the formula will parse incorrectly. Unless you
want to buy a professionally developed address-parsing program, the formulas above and a quick
eyeball scan of the results should be adequate.
Another formula works in this case. Assuming your address is in cell A2, enter the following
formula into cell B2:
=IF(ISNUMBER(VALUE(LEFT(A2,1))),VALUE(LEFT(A2,FIND(" ",A2)-1)),"")
This formula is saying, "If the first character is not a number, leave the cell blank. Otherwise,
give me all of the characters on the left out to, but not including, the first space." You can then
use the result of this formula to pull out the non-numeric portion of the address:
=IF(B2="",A2,MID(A2,FIND(" ",A2)+1,99))
Another approach is to use an array formula. Here again, assuming your address is in cell A2,
you can use the following:
=IF(ISNUMBER(1*MID(A2,ROW($1:$1),1)) = TRUE,
LEFT(A2,FIND(" ",A2,1)),"")
=IF(ISNUMBER(1*MID(A2,ROW($1:$1),1)) = TRUE,
RIGHT(A2,LEN(A2)-FIND(" ",A2,1)),A2)
Finally, the following macro can be used to break out the street address from the street
name.
Sub GetStreetNum()
Dim sStreet As String
Dim J As Integer
Dim iNum As Integer
To use this macro, simply select the range of cells that contain your addresses and then run it.
The leading numeric portion of the address will appear in the cell to the right of each address and
the balance of the address will be placed in the cell to the right of that. (So you should make sure
that there are two blank columns to the right of the addresses you select.)
There needs to be a few assumptions made about the data in order to make any
recommendations. Let's assume, for example, that all the data is in this format:
My Town, CA 98765-4321
The portion from the dash onward (the trailing part of the ZIP Code) is optional, but the position
of the comma is static—it always follows the name of the town—and the state always consists of
two characters. In this case it is easy to devise two formulas that extract the state abbreviation
and the first five digits of the ZIP Code:
=MID(A1,FIND(",",A1)+2,2)
=MID(A1,FIND(",",A1)+5,5)
Both formulas key on the comma; it serves as a delimiter between the city and the two items
really want. If there is no comma in the data or if there are multiple commas, then the formulas
won't return the desired information.
The formulas also assume that there are no extra spaces in your data; at most there is a single
space after the comma and between the state and ZIP Code. This is, of course, easy enough to
enforce—just use Find and Replace to replace two spaces with a single space anywhere in your
worksheet.
Of course, if your data is this structured, you can still rely on the Text to Columns tool to do your
work. All you need to do is run the tool and split your data based on the comma. This will leave
the city in one cell and put the state and ZIP Code together in the next cell. Then you can use
Text to Columns again, this time on the second cell (not the city name) and divide the contents
based on the space.
If your data is not that structured—perhaps it has multiple commas in the address or extra
spaces—then an entirely different approach is called for. To deal with this the basic technique
involves trimming the data to remove extraneous spaces (leading, trailing, and internal), then
determining the location of the last space and the second-to-last space.
You can pull out the five digits in the ZIP Code, which is defined as immediately following the
last space in the data, by using this formula:
=MID(TRIM(A1),FIND(CHAR(1),SUBSTITUTE(TRIM(A1)," ",
CHAR(1),LEN(TRIM(A1))-LEN(SUBSTITUTE(TRIM(A1)," ",""))))+1,5)
The two-character state abbreviation can be returned by pulling out the two characters
immediately following the second-to-last space:
=MID(TRIM(A1),FIND(CHAR(1),SUBSTITUTE(TRIM(A1)," ",CHAR(1),
LEN(TRIM(A1))-LEN(SUBSTITUTE(TRIM(A1)," ",""))-1))+1,2)
If your data is even less structured—perhaps it includes addresses that don't all have two-
character state abbreviations (N.J. instead of NJ)—then you would best be served to use a macro
to divide up the data. The reason for this is that VBA has a much richer set of text handling
functions than what you can do using Excel formulas. The following macro creates a user-
defined function that can return either the state or ZIP Code:
Application.Volatile
rstrAddress = Trim(rstrAddress)
If Len(rstrAddress) = 0 Then Exit Function
sZIP = "?"
Else
sState = arr(UBound(arr) - 1)
sZIP = arr(UBound(arr))
End If
End With
If iAction = 1 Then
GetStateZIP1 = sState
End If
If iAction = 2 Then
GetStateZIP1 = sZIP
End If
End Function
To use this function, simply provide a cell reference and either 1 (if you want the state) or 2 (if
you want the ZIP Code). Here is an example of requesting the ZIP Code for whatever address is
in cell A1:
=GetStateZIP1(A1,2)
If you get an error when you try to use this function, it could be because you are using an older
version of Excel that doesn't support the Split function in VBA. If this is the case, use this
version of the function instead:
Application.Volatile
rstrAddress = Trim(rstrAddress)
If Len(rstrAddress) = 0 Then Exit Function
sState = "?"
sZIP = "?"
For J = Len(rstrAddress) To 1 Step -1
If Mid(rstrAddress, J, 1) = " " And sZIP = "?" Then
sZIP = Mid(rstrAddress, J + 1, 5)
rstrAddress = Trim(Left(rstrAddress, J))
For K = Len(rstrAddress) To 1 Step -1
If Mid(rstrAddress, K, 1) = " " And sState = "?" Then
sState = Mid(rstrAddress, K + 1, 20)
rstrAddress = Trim(Left(rstrAddress, K))
End If
Next K
End If
Next J
If iAction = 1 Then
GetStateZIP2 = sState
End If
If iAction = 2 Then
GetStateZIP2 = sZIP
End If
End Function
There are several ways that a solution to this problem can be approached. If your table design is
flexible, you can "simplify" things by changing the way your table is laid out. Instead of putting
months across the columns, you can simply have each column be a meeting date. Then, each cell
could contain some sort of indicator (a number or a character) that indicates the person attended
the meeting on that particular date. It would be a relatively easy process to figure out who had
not met with whom:
1. Choose the key member, the one you want to check, and move him/her to the top of
your data table.
2. Sort the data table horizontally on the key member row, so all the meetings that the key
member attended are in the left-most columns.
3. Sort everyone except the key member vertically on the first three meeting dates.
Everyone who met the key member in those three meetings is now at the top of the data
table, just below the key member.
4. Move down the data table and select everyone who has not yet met the key member and
sort on the next three meeting dates.
5. Repeat steps 3 and 4 until all meeting dates have been sorted.
6. Everyone remaining at the bottom of the data table (those not selected in steps 3 and 4)
has never met the key member.
If you cannot change the format of your table, then a macro solution is called for. There are
many approaches that could be used in a macro, but the following is perhaps the most direct:
Sub PeopleNotMet()
Dim rTable As Range
Dim rOutput As Range
Dim iCols As Integer
Dim iCol As Integer
Dim iRows As Integer
Dim iRow As Integer
Dim iCompRow As Integer
Dim sNotMet As String
Dim sMet As String
Application.ScreenUpdating = False
With rTable
iRows = .Rows.Count
iCols = .Columns.Count
.Columns(1).Copy
With rOutput
.PasteSpecial
.PasteSpecial Transpose:=True
Application.CutCopyMode = False
Range(.Offset(1, 1), .Offset(iRows - 1, _
iRows - 1)).Value = sNotMet
Range(.Offset(1, 1), .Offset(iRows - 1, _
iRows - 1)).HorizontalAlignment = xlCenter
End With
End With
With rTable.Cells(1)
For iRow = 1 To iRows - 1
For iCol = 1 To iCols - 1
For iCompRow = 1 To iRows - 1
If Not (IsEmpty(.Offset(iRow, iCol))) Then
If Not (IsEmpty(.Offset(iCompRow, iCol))) Then
If .Offset(iRow, iCol).Value = _
.Offset(iCompRow, iCol).Value Then _
rOutput.Offset(iRow, iCompRow).Value = sMet
End If
End If
Next
Next
Next
End With
This macro assumes a couple of things. First, it assumes that Bob's original data table is on
Sheet1, starting in cell A1. Second, it assumes that the "who has not met with whom" table
should be on Sheet2, beginning at cell A1. If these assumptions are correct, then when you run
the macro, the table created on Sheet2 shows names down the left side and names across the top.
The intersecting cells will contain either nothing (which means that the people have met) or a
capital X (which means they have not met).
This is a relatively straightforward task; all you need to do is have your macro step thorough the
cells and (1) find out if the cell contains a formula. If it does, then check to see if the formula
contains an exclamation point. Exclamation points are used in formula references, such as the
following:
=Sheet2!A1
So, if the formula contains an exclamation point, you can ignore it. If it doesn’t contain an
exclamation point then you can replace it with its value.
Sub ConvertFormulas1()
Dim c As Variant
Dim frm As String
There is one drawback to this approach: the exclamation point will appear in all formulas
external to the current worksheet, including those that are in other workbooks. If you truly want
to only replace formulas to other worksheets in the current workbook but ignore formulas that
reference sheets on other workbooks, then you need to add some additional logic. The logic
makes itself apparent when you look at how Excel references those other workbooks:
=[OtherWorksheet.xls]Sheet1'!$C$9
Note that the name of the other workbook is contained within brackets. Thus, after testing for the
exclamation point (which informs you that the reference is to another worksheet, you need to
check for the presence of a left bracket. If it is there, then the reference is not to a cell within the
current workbook.
Sub ConvertFormulas2()
Dim c As Variant
Dim OtherSheet As Boolean
Dim frm As String
End Sub
It should be pointed out that it would be relatively easy to modify the formula used in this macro
so that it got rid of all external references while leaving the references to the current worksheet
intact. In fact, all you need to do is get rid of the checking for the bracket and then get rid of the
“Not” keyword in the structure that checks the OtherSheet variable.
One simple way is to simply step through the range you want to examine and derive both the
lowest value and the address of the cell being examined, as in the following:
Note that this approach doesn’t rely upon the MIN worksheet function at all. There is a drawback
to it, however—it doesn’t differentiate between cells that contain numeric values and those that
don’t. In other words, if the range passed to the function contains a blank cell, that cell is
considered to contain a zero value, which may very well be the lowest value in the range.
One way around this is to rely upon worksheet functions from within the macro. The following
macro uses both the MIN and MATCH worksheet functions to determine the location of the
minimum value and then the index (offset) of that cell within the range.
Application.Volatile
With Application.WorksheetFunction
dMin = .Min(rng)
lIndex = .Match(dMin, rng, 0)
End With
GetAddr = rng.Cells(lIndex).Address
End Function
It should be noted that if you are using the macro only to discover the address because you
figured there was no way to derive the desired information without the macro, then you can do
away with the macro entirely by using a worksheet formula. For instance, if you want to
determine the address of the lowest-valued cell in the named range MyRange, you could use the
following:
=ADDRESS(ROW(MyRange)+MATCH(MIN(MyRange),MyRange,0)-1,COLUMN(MyRange))
Assuming that the column contains only words (no spaces, punctuation, or phrases), you can
manually check the list in this manner:
1. Make a copy of column A into column B. You now have two identical columns.
2. Select column B and run spell check.
3. Every time a spelling change is suggested, accept it. When done, you should have
column A as your original and column B as a spell-checked version of column A.
4. In column C, enter the formula =IF(A1=B1,B1,"") and copy the formula down. This
formula only shows a word in column C if the original word matches the spell-checked
version of the word.
5. Copy all the words in column C and use Paste Special to paste Values into another
location. You now have a list of validly spelled words.
If you need to perform the validation process regularly, you may want to use a macro to instead
create your final list. The following macro steps through the word list in column A and clears
any cells that contain words not in the dictionary. After checking all the words, it then deletes all
the cleared cells.
Sub ExtractDictionaryWords()
Dim rWords As Range
Dim rCell As Range
Application.ScreenUpdating = False
Set rWords = Range(Range("A1"), _
Range("A65536").End(xlUp))
For Each rCell In rWords
If Not Application.CheckSpelling(rCell.Value) Then
rCell.Clear
End If
Next
Remember—this macro is intentionally destructive in its behavior, meaning that it clears out
cells. If you have any need for the original data, you’ll want to run the macro on a copy of the
data, not on your only copy.
First, you can replace the “large amount” with a “small amount.” (Excel never asks if you want
to discard a small amount.) To do this, just select a single cell in your worksheet and copy it to
the Clipboard. The contents of the single cell replace the large amount of data on the Clipboard,
and you can exit Excel without seeing the message.
If you’d rather have a macro approach, you can do the exact same thing in a macro—just select
cell A1 and have your macro copy it to the Clipboard:
Sub GoAway1()
ActiveSheet.Range("A1").Copy
End Sub
Another approach is to use a single-line macro that basically “disables” the Clipboard by
canceling any current copy operation:
Sub GoAway2()
Application.CutCopyMode = False
End Sub
You can easily use the Text to Columns feature in Excel to pull your data apart. Just follow these
steps:
3. Choose whether the text you have selected is fixed width or delimited. (In the case of a
space between first and last name, the text would be delimited.)
4. Click on Next.
5. Specify the delimiters you want Excel to recognize. In the case of pulling apart names,
you should make sure that you use spaces as delimiters.
6. Click on Finish.
Excel pulls apart the cells in your selected range, separating all the text at the delimiter you
specified. Excel uses however many columns are necessary to hold the data.
If you don’t want to spread your data completely across the columns, then you will need to use a
macro. For instance, if a cell contains “John Davis, Esq.”, then using the Text to Columns feature
will result in the data being spread into three columns: the first containing “John”, the second
containing “Davis,” (with the comma), and the third containing “Esq.” If you would rather have
the data split into two columns (“John” in one and “Davis, Esq.” in the other, then the following
macro will be helpful:
Sub PullApart()
Dim FirstCol As Integer, FirstRow As Integer
Dim RowCount As Integer
Dim ThisRow As Integer
Dim j As Integer, k As Integer
Dim MyText As String
FirstCol = ActiveWindow.RangeSelection.Column
FirstRow = ActiveWindow.RangeSelection.Row
RowCount = ActiveWindow.Selection.Rows.Count
For j = 1 To RowCount
ThisRow = FirstRow + j - 1
MyText = Cells(ThisRow, FirstCol).Text
k = InStr(MyText, " ")
If k > 0 Then
Cells(ThisRow, FirstCol + 1).Value = Mid(MyText, k + 1)
Cells(ThisRow, FirstCol).Value = Left(MyText, k - 1)
End If
Next j
End Sub
This macro examines each cell and leaves everything up to the first space in the selected cell, and
moves everything after the space into the column to the right. The only "gottcha" with this macro
is to make sure you have nothing in the column to the right of whatever cells you select when
you run it.
Reorganizing Data
If you import a data list into Excel, it is not unusual to end up with a lot of data in column A. In
fact, it is not unusual to have nothing in any of the other columns. (This all depends on the nature
of the data you are importing, of course.) As part of working with the data in Excel, you may
want to "reorganize" the data so that it is pulled up into more columns than just column A.
As an example, imagine that you imported your data, and it ended up occupying rows 1 through
212 of column A. What you really want is for the data to occupy columns A through F, of
however many rows are necessary to hold the data. Thus, A2 needs to be moved to B1, A3 to C1,
A4, to D1, A5 to E1, A6 to F1, and then A7 to A2, A8 to B2, etc.
To reorganize data in this manner, you can use the following macro. Select the data you want to
reorganize, and then run the macro. You are asked how many columns you want in the
reorganized data, and then the data shifting begins.
Sub CompressData()
Dim rSource As Range
Dim rTarget As Range
Dim iWriteRow As Integer
Dim iWriteCol As Integer
For J = 1 To rSource.Cells.Count
rTarget.Cells(J) = rSource.Cells(J)
If J > (rSource.Cells.Count / iTargetCols) Then _
rSource.Cells(J).Clear
Next J
End If
End Sub
The macro transfers information by defining two ranges: the source range you selected when you
ran the macro and the target range defined by the calculated size based on the number of
columns you want. The source range is represented by the rSource variable object, and the target
range by rTarget. The For … Next loop is used to actually transfer the values.
There is no built-in way to get rid of these unused names. You can, however, create a macro that
will do the trick for you. This is most easily done by using the Find method to figure out which
names have references that can be “found.” If the reference cannot be found, then the name is not
in use.
Sub RidOfNames()
Dim myName As Name
Dim fdMsg As String
The macro steps through all the elements of the Names collection and does a search for each
name. If the name cannot be found, then the name is deleted. When the macro is completed, it
displays a message box that lists the names that were removed from the workbook.
If you would rather not create your own macro, you can opt to use a free add-in by Jan Karel
Pieterse. The add-in, called Name Manager, allows you to (guess what?) manage names better
than you can do with native Excel. One of the functions it provides is the ability to get rid of
names that are no longer needed. You can find the add-in here:
http://www.jkp-ads.com/OfficeMarketPlaceNM-EN.asp
To get rid of these characters you can try to use the Find and Replace feature of Excel. Try these
steps:
1. Within the cell that contains one of the small boxes, highlight the box and press
CTRL+C. This copies the character to the Clipboard.
2. Press CTRL+H. Excel displays the Replace tab of the Find and Replace dialog box.
3. With the insertion point in the Find What box, press CTRL+V. This pastes the contents
of the Clipboard (the offending character) into the Find What box. The character will
most likely not look like the small box you selected and copied in step 1.
4. If nothing was pasted in step 3, then close the dialog box and try the steps again. If
nothing is still pasted, then you won’t be able to use Find and Replace to get rid of the
non-printing characters, and you can skip the rest of these steps.
5. If you want to just delete the characters, make sure there is nothing in the Replace With
box. If you want to replace the characters with spaces, put a single space in the Replace
With box.
6. Click on Replace All.
This approach may or may not work, depending mostly on Excel and whether it let you
accurately copy the offending character in step 1. If it does work, then you have learned a
valuable technique for getting rid of the bad characters. If it doesn’t work, then you should try a
different approach.
One thing to try is to use Word in your “clean up” operations. Copy the data from Excel to a
Word document (paste it as regular text), and then replace the offending characters. You can then
paste the data back into Excel. Some people report that they get exactly the results they want by
using this round-trip approach to working with the data.
You can, of course, use a macro to get rid of the offending characters. It isn’t too difficult to
create your own version of the CLEAN worksheet function that, instead of simply removing
non-printing characters, replaces them with spaces. Consider the following example macro:
ReplaceClean = sText
End Function
You use this function in the following manner within your worksheet:
=ReplaceClean1(B14)
In this case, all non-printing characters in cell B14 are replaced with a space. If you want the
characters replaced with something else, just provide the text to replace with. The following
example replaces the non-printing characters with a dash:
=ReplaceClean1(A1,"-")
The following usage simply removes the non-printing characters, the same as the CLEAN
function:
=ReplaceClean1(A1,"")
If you look back at the ReplaceClean1 macro presented earlier, you see that it uses the Replace
function. This VBA function is not available in all the versions of VBA used with the different
versions of Excel. If you try the macro and you get an error on one of the lines that use the
Replace function, then use this version of the ReplaceClean macro instead:
If the month name you want to change is stored as text within various worksheets, you can use
Excel’s find and replace feature to make the changes. Just follow these steps:
1. Click on the tab of the first worksheet in which you want to make changes.
2. Hold down the SHIFT key as you click on the tab of the last worksheet in which you
want to make changes. All of the worksheets you want to change should now be
selected.
3. Press CTRL+H to display the Replace tab of the Find and Replace dialog box.
4. In the Find What box, enter the old month’s name.
5. In the Replace With box, enter the new month’s name.
6. Click on Replace All.
7. Close the Find and Replace dialog box.
If these steps do not change a particular month name as it appears in your workbook, it could be
because the month name is not actually text, but a date value formatted to show only the month.
In that case, you cannot use Find and Replace; instead you must simply change the date value
stored in the cell.
If you want a quick way to change the month names in the worksheet tabs, that is a bit more
tricky. Excel’s find and replace feature won’t find or replace the text in tab names. Normally
they need to be done by hand, but if you have many of them, you may want to create a macro
that will do the changing for you. The following macro prompts you for the text you are
searching for and the text you want to replace it with. Then, it steps through each worksheet tab
and makes the changes for you.
Sub TabReplace()
Dim I As Integer, J As Integer
Dim sFind As String
Dim sReplace As String
Dim sTemp As String
For I = 1 To Sheets.Count
sTemp = Sheets(I).Name
J = InStr(sTemp, sFind)
While J > 0
sTemp = Left(sTemp, J - 1) & sReplace _
& Mid(sTemp, (J + Len(sFind)))
J = InStr(sTemp, sFind)
Wend
Even though the steps (and macro) presented here can make the job of updating your workbook
easier, it may be easier still to simply rethink how you do your workbook. It may be easier to set
up a cell to contain the current month’s name, and then reference that name in the appropriate
cells throughout the workbook. Then, all you need to do is change the month name in a single
cell, and it will be changed elsewhere, automatically. In other ExcelTips you even learned how
you can dynamically change a tab name based on the contents of a particular cell.
Microsoft knows about this problem. In fact, you can find more information about it in the
Knowledge Base, here:
http://support.microsoft.com/?kbid=172832
There are essentially several ways you can work around the problem. First, you could upgrade to
a newer version of Excel. I’m not exactly sure which version it was corrected in, but I do know
that in Excel 2003 selecting something from a data validation drop-down does trigger the
Worksheet_Change event.
The second option is to use some other method of changing the worksheet data than with data
validation. For instance, you could use a combo box from the Controls toolbox. Setting one up is
a bit more difficult than using data validation, but much more versatile.
Finally, you could create a formula reference to the validated cell, and then trigger your macro
from the Worksheet_Calculate event. For instance, if your validated cell is A7, then you could
use the formula =A7 in a different cell. When the value in A7 changes (the user selects from the
drop-down list), then a recalculation is begun because the results of the formula change. This, of
course, triggers the Worksheet_Calculate event, where you could implement your macro. (Since
the Worksheet_Calculate event can be triggered by lots of changes, you would need to add some
checking code to your handler to make sure that the validation change is what ultimately
triggered the event.)
the feature is to allow it to recognize characters within the cells and use those characters to
trigger where the split should take place. This type of splitting is referred to as a delimited split.
You may be wondering how you can perform a delimited text-to-columns operation in a macro
you may be writing. This is easy enough to do by using the TextToColumns method on a
selection you set up. Consider the following very simple macro:
Sub ExampleSplit1()
Selection.TextToColumns _
Destination:=Range("A2"), _
DataType:=xlDelimited, _
TextQualifier:=xlDoubleQuote, _
ConsecutiveDelimiter:=False, _
Tab:=True, _
Semicolon:=False, _
Comma:=False, _
Space:=False, _
Other:=True, _
OtherChar:="-"
End Sub
Notice all the variables that you can set for the TextToColumns method. Most of these variables
are only necessary because this is a delimited split; the variables set what is used as a delimiter
by the method. Beginning with the Tab line, the variables correspond directly to the settings you
would make in Step 2 of the Convert Text to Columns Wizard, if you were manually using the
feature. You can set Tab, Semicolon, Comma, and Space to either True or False, depending on
whether you want that character used as a delimiter.
You can also set the Other variable to True or False, depending on whether you want to have a
"user defined" delimiter. If you set it to True, then you should set the OtherChar variable equal to
the character you want used as a delimiter.
If you use the TextToColumns method multiple times in the same macro, the only thing you
need to do on invocations subsequent to the first is to change variables that differ from the
previous invocation. For instance, let's say that you are calling the method twice in the same
macro, and the first time you want the split to be on an instance of the dash character, but the
second you want it to be on any instance of a lowercase x. You can put the macro together like
this:
Sub ExampleSplit2()
Dim objRange1 As Range
Dim objRange2 As Range
Space:=False, _
Other:=True, _
OtherChar:="-"
The reason for this behavior is that Excel VBA "speaks" American, and some actions done using
a recorded macro don't work as expected due to that fact. Because American Excel expects the
decimal separator to be a period, interpreting a "number" in VBA with another separator (such as
a comma) will cause Excel to consider the value to be text.
The workaround is not to use find and replace, but to use a different trick. Consider the following
short macro:
Sub ConvertNumbers()
Dim oConRange As Range
Set oConRange = ActiveSheet.UsedRange.Cells.SpecialCells(xlConstants)
oConRange.Value = oConRange.Value
End Sub
This macro defines a range that consists of all the cells that contain constants. Then, it sets the
value of each cell in the range equal to itself. In the process of doing this, Excel re-evaluates the
contents of each cell and converts it to the appropriate numeric value. In other words, numbers
that contain decimal points are converted to numbers that contain decimal commas.
There are other ways you can process the cells using a macro, but the above procedure seems to
work the best and the quickest.
themselves) into a worksheet and separate the names at the dash. Thus, the example filename
would actually occupy four cells in a single row. Carol figures this will take a macro to
accomplish, but she’s not sure how to access the filenames in that macro.
You can, of course, use a macro to do this, but you don’t need to use a macro. You can, instead,
use an old DOS-era trick to get what you need. At the command prompt (accessible through
Windows: Start | All Programs | Accessories | Command Prompt), navigate until you are in the
directory that contains the files. Then enter the following:
This creates a text file (filelist.txt) that contains a list of all the files within the current directory.
Now, within Excel, you can follow these steps:
1. Within Excel, display the Open dialog box. (How you do this varies based on the
version of Excel you are using. You should use whatever method you prefer to display
it.)
2. Using the Files of Type drop-down list at the bottom of the dialog box, indicate that you
want to open Text Files (*.prn; *.txt; *.csv).
3. Navigate to and select the filelist.txt file you created at the command prompt.
4. Click on Open. Excel starts the Text Import Wizard, displaying the Step 1 of 3 dialog
box.
5. Make sure the Delimited choice is selected, then click on Next. Excel displays the Step
2 of 3 dialog box.
6. Make sure you specify a dash as your delimiter. (You’ll need to click on Other and then
enter a dash as the delimiter.)
7. Click on Finish. Your file is imported and broken at the dashes, just as you wanted.
The above steps are fairly easy to accomplish, particularly if you only need to get the file listing
into Excel once in a while. If you need to do it more routinely, then you should probably seek a
way to do it using a macro. The following macro will work very quickly:
Sub GetFileNames()
Dim sPath As String
Dim sFile As String
Dim iRow As Integer
Dim iCol As Integer
Dim splitFile As Variant
iRow = 0
sFile = Dir(sPath)
Do While sFile <> ""
iRow = iRow + 1
splitFile = Split(sFile, "-")
When you run the macro, make sure that there is nothing in the current worksheet. (Anything
there will be overwritten.) Also, you should change the directory path that is assigned to the
sPath variable near the beginning of the macro.
If you get an error when you run the macro, chances are good that you are using Excel 97. The
Split function (used to break the filename apart at the dashes) was not added to VBA until Excel
2000. If you are using Excel 97, then you can use the following routine to emulate what the Split
function does:
Indx = 0
sTemp = Raw
J = InStr(sTemp, Delim)
While J > 0
Indx = Indx + 1
ReDim Preserve vAry(1 To Indx)
vAry(Indx) = Trim(Left(sTemp, J))
sTemp = Trim(Mid(sTemp, J, Len(sTemp)))
J = InStr(sTemp, Delim)
Wend
Indx = Indx + 1
ReDim Preserve vAry(1 To Indx)
vAry(Indx) = Trim(sTemp)
Split = vAry()
End Function
The short answer is no, you can’t do that in Excel. There are a number of different techniques
you can apply that will provide the desired result, however. The first method is to use a program
such as WinZip to combine all the workbooks into a single zip file. This file can be password
protected (in WinZip) so that not everyone can open it. You could then open the zip file (using
your password) and double-click on any workbook in it in order to modify it with Excel. The
result, for all intents and purposes, is that you have a “folder” (the zip file) that is protected,
while the individual files it contains are not.
Another approach is to place the workbook folder on a network drive and then have the network
admin protect the folder. Most network operating systems allow administrators to control who
can have access to specific folders and their contents.
A third approach is to use a third-party program to protect the folder. A quick search of the Web
will no doubt turn up several candidates, such as the following:
http://www.folder-password-expert.com
You can also use an Excel macro to protect the workbooks. While it does not offer true folder-
level protection, it does allow you to protect all the workbooks in the folder in as easy a manner
as possible.
Sub ProtectAll()
Dim wBk As Workbook
Dim sFileSpec As String
Dim sPathSpec As String
Dim sFoundFile As String
sPathSpec = "C:\MyPath\"
sFileSpec = "*.xls?"
Make sure you change the sPathSpec and sFileSpec variables, near the beginning of the code, to
reflect the folder containing the workbooks and the pattern for the names of the workbooks you
want protected. The macro assumes that all the workbooks are unprotected; if any are not, the
macro will prompt for the workbook’s password.
The problem is, the macro could be finished on an entirely different worksheet than where the
user started, and ActiveCell.Address only gives a cell address, not a worksheet name and
definitely not a range. John wonders about the best way to store what he needs so he can return
to the user’s original location at the end of the macro.
For the best chance of getting someone back to where they started, there are three elements:
workbook, worksheet, and cell. Actually, this last element (cell) may be a bit simplistic, as the
user will always have a cell selected (this is what is returned by ActiveCell.Address), but may
additionally have a range selected.
When you want to later return the user to where they were, you can use this type of code:
Workbooks(sOrigWB).Activate
Sheets(sOrigWS).Select
rngOrigSelection.Select
rngOrigCell.Activate
Of course, Excel always has multiple ways that you can accomplish any given task. In this case,
you could shorten your code by only remembering the active cell and selected range:
When you want to restore the user to the location, you rely upon the Parent object available in
VBA:
rngOrigSelection.Parent.Parent.Activate
rngOrigSelection.Parent.Select
rngOrigSelection.Select
rngOrigCell.Activate
The Parent object of the selection range you saved is the worksheet in which that range is
located, and the Parent of that Parent object is the workbook in which the worksheet is located.
Another approach is to simply create, within your macro, a named range that refers to whatever
the user had selected:
After you do your processing, when you are ready to return to what the user had selected, you
use this code:
Application.Goto Reference:="MyOrigPlace"
ActiveWorkbook.Names("MyOrigPlace").Delete
The first line returns to the selection and the second line then deletes the named range. The only
drawback to this approach is that the active cell is not retained; the code assumes that you want
the upper-left cell in the range to be the active cell when it is done. You should also be aware
that if your processing deletes the cells that make up the named range, then the code may not
work properly—Excel can’t go to a place that no longer exists.
Of course, you may not have to remember any location at all, if you code your macro correctly.
While VBA allows you to “move around” and select different areas of your worksheets and
workbook, in most cases this isn’t necessary. You could, for instance, simply work with different
ranges and then do your work on those ranges, without ever changing the current selection or
active cell. Indeed, VBA allows you to change, reformat, sort, delete, and do almost anything
you can imagine to cells without actually needing to select them.
1. Right-click the sheet tab for the sheet on which you want to place a limit.
2. In the resulting Context menu, choose View Code. The VBA editor appears, displaying
the code window for the worksheet whose tab you right-clicked.
3. If the Properties window is not visible, press F4.
4. In the Properties Window, place the insertion point in the box to the right of the Scroll
Area property.
5. Enter the range in which you want navigation possible. For instance, if you want the
user to only be able to access the cells in the range A3:D15, then enter that range.
6. Close the VBA Editor.
That’s it; you can no longer move to or select cells outside the range you specified in step 5. The
range you enter must be a contiguous range; you cannot enter a non-contiguous group of cell
addresses.
The reason for this is simple—when you refresh information in a workbook from an external
source (such as an Oracle database query), Excel won’t wait around. This is contrasted with
internal events in Excel, which can be easily waited upon. To overcome this difference, you need
to change the way you write the macros. Essentially, you need to write two separate macros. The
first macro basically initiates the refresh from the external source, and the second macro is
executed once the refresh is completed.
How do you know when you can run the second macro? You could do it manually after visually
inspecting the worksheet to make sure everything loaded, but that ties you up. Instead, you can
tie a macro to the AfterRefresh event. This event is triggered when (as its name suggests) the
refresh is complete. For more information on how to use this event in your programming, visit
the Microsoft Knowledge Base articles at these addresses:
http://support.microsoft.com/?kbid=182735
http://support.microsoft.com/?kbid=213187
These Knowledge Base articles are for Excel 97 and Excel 2000, but the information they
contain will also work with later versions of Excel. (Yes, even with Excel 2010 and Excel 2013.)
There is no way to do this without resorting to using macros. If you just want to make a beep
sound, you can use something like this:
All this user-defined function does is to play a sound (which will vary depending on the system
you are using) and then return an empty string. You can use the function in your worksheet in
this manner:
=IF(A12>300,BeepMe(),"")
If you want to play some sound other than the default system beep, you’ll need to use the
Windows API PlaySound function. The following code creates a user-defined function that will
play the default “tada” sound so prevalent in many versions of Windows.
=IF(A12>300,SoundMe(),"")
If you want to play a different WAV file, simply change the file specification in the SoundMe
function.
You need to start by placing some code in the Sheet object for the workbook. (Right-click the tab
for the worksheet and choose View Code from the Context menu.) Declare the function
"playsound" using the following code:
Next you can create a short little macro that will actually play the sound file. Assuming that the
sound file is in the same directory as the workbook, the following code will work. (You should
modify the code so that it contains the proper filename and location.)
Sub PlayWAV()
WAVFile = ThisWorkbook.Path & "\MyAudioFile.wav"
Call PlaySound(WAVFile, 0&, SND_ASYNC Or SND_FILENAME)
End Sub
Finally, establish the criteria when the file is to be played. In this case you want the sound file to
play whenever the value in the target cell exceeds the threshold value of 999. The following will
check for that condition in cell C5 and, if warranted, play the sound file:
Now, whenever the value in Cell C5 changes and exceeds 999, the audio file will play one time.
If the values is changed to less than 999, nothing plays. If the value changes to another value that
exceeds 999, the sound file will play again.
For additional ideas on playing audio files, check out these sites:
http://www.j-walk.com/ss/excel/tips/tip87.htm
http://www.cpearson.com/excel/PlaySound.aspx
You should note, as well, that you can get Excel to play a system sound by using data validation.
Simply set up the validation criteria (described in other issues of ExcelTips) and then, on the
Error tab, specify whether you want Excel to stop, warn, or inform the user. When a value is
entered in the cell that does not fit the criteria, a dialog box is displayed and the system sound is
heard.
I'd like your feedback about this book, as feedback is helpful in planning the future of ExcelTips.
If you have something you'd like to share with me, just drop me a line. Here's my contact
information:
Allen Wyatt
Sharon Parq Associates, Inc.
PO Box 794
Orem, UT 84059
PS: If you have a formal "testimonal" about ExcelTips: The Macros, send it my way. I have an
uneven and inconsistent habit of rewarding concise, pithy, and glowing testimonials. (I've even
been known to reward negative feedback. It's all important to me.)
ExcelTips Archives
Your best source of usable Excel information!