0% found this document useful (0 votes)
149 views

ExcelTips The Macros, Seventh Edition

This document is the seventh edition of a book titled "ExcelTips: The Macros" that provides information about creating and managing macros in Excel. It was published by Sharon Parq Associates, Inc. in Orem, UT and discusses topics such as recording macros, writing macros, editing macros, debugging macros, assigning macros to keyboard shortcuts, and using variables, functions, and other programming structures in macros. The book has been revised several times since its initial publication in 2002.

Uploaded by

irasok
Copyright
© © All Rights Reserved
0% found this document useful (0 votes)
149 views

ExcelTips The Macros, Seventh Edition

This document is the seventh edition of a book titled "ExcelTips: The Macros" that provides information about creating and managing macros in Excel. It was published by Sharon Parq Associates, Inc. in Orem, UT and discusses topics such as recording macros, writing macros, editing macros, debugging macros, assigning macros to keyboard shortcuts, and using variables, functions, and other programming structures in macros. The book has been revised several times since its initial publication in 2002.

Uploaded by

irasok
Copyright
© © All Rights Reserved
You are on page 1/ 889

ExcelTips:

The Macros
Seventh Edition

Compiled from the pages of ExcelTips,


your free weekly productivity newsletter
ExcelTips: The Macros,
Seventh Edition

Published by:

Sharon Parq Associates, Inc.


PO Box 794
Orem, UT 84059

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.

ISBN: 978-1-61359-200-7 (e-book)


ISBN: 978-1-61359-201-4 (e-book on CD-ROM)

Produced and published in the United States of America

Revision history:

8 March 2002: First edition


26 August 2003: Second edition
4 November 2003: Second edition (modified styles of listings)
19 July 2005: Third edition
20 August 2005: Third edition (corrected links)
21 August 2007: Fourth edition
4 August 2009: Fifth edition
3 August 2011: Sixth edition
7 August 2013: Seventh edition

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

Creating and Managing Macros 6


UNDERSTANDING MACROS 6
RECORDING A MACRO 7
RELATIVE REFERENCES WHEN RECORDING MACROS 8
WRITING A MACRO FROM SCRATCH 9
EDITING MACROS 10
CONTINUING MACRO LINES 12
EXCEL 2007 VBA DIFFERENCES 12
OFFERING OPTIONS IN A MACRO 14
RENAMING A MACRO 15
AUTOMATING COPYING MACROS 15
DEBUGGING A MACRO 16
FRIENDLY AND INFORMATIVE ERROR HANDLING 16
STEPPING THROUGH A MACRO WITH A WORKSHEET VISIBLE 17
MACRO RUNS SLOWLY, BUT STEPS QUICKLY 17
GETTING BIG MACROS TO RUN 18
WORKING WHILE A MACRO IS RUNNING 19
ASSIGNING A MACRO TO A KEYBOARD COMBINATION 20
ONE SHORTCUT FOR TWO MACROS 21
REMOVING A MACRO FROM A SHORTCUT KEY 22
RELATIVE VBA SELECTIONS 23
RUNNING MACROS IN THE BACKGROUND 23
ABORTING A MACRO AND RETAINING CONTROL 24
DEVELOP MACROS IN THEIR OWN WORKBOOK 26
MACROS IN TEMPLATE FILES 27
FORCING A MACRO TO RUN WHEN A WORKSHEET IS RECALCULATED 28
DELETING A MACRO 29
GENERATING A LIST OF MACROS 30
CLEAN UP YOUR MACRO LIST 31
UNDERSTANDING PHANTOM MACROS 32
GETTING RID OF THE "ENABLE MACROS" NOTICE 33
REMOVING ALL MACROS 34
EASILY DEPLOYING CUSTOMIZATIONS 35
LIBRARY NOT REGISTERED ERROR 36
HIDING MACROS 38

ExcelTips: The Macros Page iii


DISABLED MACROS 40
MAXIMUM LENGTH LIMIT FOR A MACRO 42
EXCLUDING A SPECIFIC ADD-IN AT STARTUP 43
USING MACROS IN PROTECTED WORKBOOKS 44
CTRL+BREAK WON'T WORK TO STOP A MACRO 45
RECOVERING MACROS FROM CORRUPTED WORKBOOKS 46
TROUBLE RECORDING PASTE SPECIAL FORMULA 47
DIGITAL SIGNATURES FOR MACROS 47
MOVING MACROS FROM THE PERSONAL WORKBOOK 49
AUTOMATICALLY HIDING THE PERSONAL WORKBOOK 50
UPGRADING A PERSONAL.XLS WORKBOOK 50
OPENING PERSONAL.XLSB IN EXCEL 2007 51
DOCUMENTING CHANGES IN VBA CODE 51
OUT OF MEMORY ERRORS WHEN ACCESSING THE VBA EDITOR 52
AUTOMATICALLY CHANGING REFERENCES TO VBA LIBRARIES 52
ITEM NOT AVAILABLE IN LIBRARY 53
AUTOMATICALLY OPENING MACRO WORKBOOKS WHEN USING A SHORTCUT KEY 54

Functions and Subroutines 56


UNDERSTANDING SUBROUTINES 56
UNDERSTANDING FUNCTIONS 57
MAKING COMMON FUNCTIONS AVAILABLE 58
WORKSHEET EVENTS 58
WORKBOOK EVENTS 59

Structures Used in Macros 60


UNDERSTANDING THE FOR … NEXT STRUCTURE 60
EXITING A FOR ... NEXT LOOP EARLY 61
UNDERSTANDING THE IF ... END IF STRUCTURE 61
UNDERSTANDING THE WHILE...WEND STRUCTURE 62
UNDERSTANDING THE SELECT CASE STRUCTURE 62

Working with Variables 64


UNDERSTANDING VARIABLES IN MACROS 64
DECLARING VARIABLES 65
SWAPPING THE VALUES IN TWO VARIABLES 66
COMPARING STRINGS 66
CONVERTING NUMBERS TO STRINGS 66
CONVERTING STRINGS TO NUMBERS 67
QUICKLY DUMPING ARRAY CONTENTS 67

Commands and Statements 69


DERIVING AN ABSOLUTE VALUE IN A MACRO 69
DETERMINING A RANDOM VALUE 69
DETERMINING AN ANSI VALUE IN A MACRO 70
DETERMINING THE HOUR OF THE DAY 70
DETERMINING THE DAY OF THE MONTH 70

ExcelTips: The Macros Page iv


DETERMINING DIFFERENCES BETWEEN DATES 71
CREATING A STRING IN A MACRO 71
DISSECTING A STRING 72
TRIMMING SPACES FROM STRINGS 72
DETERMINING THE LENGTH OF A STRING 72
DETERMINING AN INTEGER VALUE 73
ERROR USING ATAN2 FUNCTION IN MACRO 73
USING BIN2DEC IN A MACRO 74
USING SUM IN A MACRO 75
LARGE NUMBERS IN THE MOD FUNCTION 76
EOMONTH FUNCTION IS FLAKEY 78
MAKING VLOOKUP TRIGGER A MACRO 79
MAKING VLOOKUP CASE SENSITIVE 80

Controlling Data Entry 82


REQUIRING INPUT 82
DON'T ALLOW EMPTY CELLS 83
CONTROLLING ENTRY ORDER ON UNPROTECTED CELLS 84
ACCEPTING ONLY A SINGLE DIGIT 85
SETTING A LENGTH LIMIT ON CELLS 86
ENTERING DATA AS THOUSANDS 88
CREATING A SHORTCUT FOR PASTING VALUES 91
AUTOMATICALLY PROTECTING AFTER INPUT 94
LOCKING ALL NON-EMPTY CELLS 95
MAINTAINING THE ACTIVE CELL 98
UNIQUE NAME ENTRY, TAKE TWO 101
INSERTING A RADICAL SYMBOL 101
CHECKING FOR PROPER ENTRY OF ARRAY FORMULAS 102
POSITIONING A COLUMN ON THE SCREEN 104
JUMPING TO THE START OF THE NEXT DATA ENTRY ROW 105
LIMITING WHO CAN DELETE DATA 105

Selecting Cells and Ranges 107


MAGNIFYING ONLY THE CURRENT CELL 107
DISPLAYING THE SELECTED CELL'S ADDRESS 109
SELECTING A CELL IN THE CURRENT ROW 110
SELECTING A RANGE OF CELLS RELATIVE TO THE CURRENT CELL 111
SELECTING THE FIRST CELL IN A ROW 112
SELECTING A SPECIFIC CELL IN A MACRO 112
HIGHLIGHTING THE ROWS OF SELECTED CELLS 113
CHOOSING DIRECTION AFTER ENTER ON A WORKBOOK BASIS 114
AUTOMATICALLY MOVING FROM CELL TO CELL WHEN ENTERING DATA 117
USING A MACRO TO SELECT A MODIFIED TABLE BODY 119

Affecting Worksheet Data 120


INSERTING WORKSHEET VALUES WITH A MACRO 120
FILLING A RANGE OF CELLS WITH VALUES 120

ExcelTips: The Macros Page v


COPYING NAMED RANGES 121
COPYING TO VERY LARGE RANGES 122
CLEARING EVERYTHING EXCEPT FORMULAS 123
CONVERTING TEXT CASE 125
CONVERTING CELLS TO PROPER CASE 126
MODIFYING PROPER CAPITALIZATION 126
CAPITALIZING JUST A SURNAME 128
CONVERTING TEXT TO NUMBERS 128
CONVERTING IMPORTED INFORMATION TO NUMERIC VALUES 129
CONVERTING TO ASCII TEXT 130
GETTING RID OF 8-BIT ASCII CHARACTERS 131
CREATING A PLUS/MINUS BUTTON 132
FINDING THE LOWEST NUMBERS 133
FINDING THE SMALLEST EVEN VALUE 134
FINDING ODD VALUES GREATER THAN 50 135
CONVERTING FROM RELATIVE TO ABSOLUTE 138
REVERSING CELL CONTENTS 139
REVERSING NAMES IN PLACE 139
CONCATENATING RANGES OF CELLS 140
CONCATENATING VALUES FROM A VARIABLE NUMBER OF CELLS 141
FINDING THE FIRST NON-DIGIT IN A TEXT VALUE 143
IDENTIFYING DIGIT-ONLY PART NUMBERS EXCLUDING SPECIAL CHARACTERS 144
FINDING THE NTH OCCURRENCE OF A CHARACTER 145
EXTRACTING A PATTERN FROM WITHIN TEXT 146
SHORTCUT TO MERGE CELLS 148
IDENTIFYING MERGED CELLS 148
GETTING RID OF SPACES IN CELLS 152
REMOVING DUPLICATE CELLS 153
DELETING DUPLICATE TEXT VALUES 154
REMOVING DUPLICATES BASED ON A PARTIAL MATCH 156
DELETING EVERYTHING UP TO A CHARACTER SEQUENCE 158
EXTRACTING FIRST AND LAST WORDS 159
REMOVING CELLS FROM A SELECTED RANGE 161
DELETING OLD DATA FROM A WORKSHEET 162
DELETING UNWANTED STYLES 163
CONDITIONAL PAGE BREAKS 164
MOVING SUBTOTALS 165
FORMATTING SUBTOTAL ROWS 166
DETERMINING COMBINATIONS TO MAKE A TOTAL 171
PUTTING ADDRESSES ON STATE-SPECIFIC WORKSHEETS 173
SELECTIVELY IMPORTING RECORDS 175
IMPORTING HUGE DATA FILES 177
MERGING MANY WORKBOOKS 178
IMPORTING MULTIPLE FILES TO A SINGLE WORKBOOK 179
GETTING RID OF EXTRA QUOTE MARKS IN EXPORTED TEXT FILES 181
GETTING RID OF ALPHABETIC CHARACTERS 182
ADJUSTING VALUES WITH FORMULAS 184

ExcelTips: The Macros Page vi


RETURNING ZERO WHEN A REFERENCED CELL IS BLANK 185
COUNTING COMMAS IN A SELECTION 186
COUNTING CONSECUTIVE NEGATIVE NUMBERS 187
SUMMING DIGITS IN A VALUE 188
SUMMING ABSOLUTE VALUES 189
SUMMING BASED ON FORMATTING IN ADJACENT CELLS 191
SUMMING BASED ON PART OF THE INFORMATION IN A CELL 192
SUMMING ONLY VISIBLE VALUES 194
DISPLAYING A COUNT OF ZEROS ON THE STATUS BAR 195
INSERTING DASHES BETWEEN LETTERS AND NUMBERS 196
REMOVING SPACES 198
GENERATING RANDOM STRINGS OF CHARACTERS 198
GENERATING UNIQUE, SEQUENTIAL NAMES 200
RANDOMLY ASSIGNING NAMES TO ITEMS 201
PULLING APART CHARACTERS IN A LONG STRING 202
BREAKING UP VARIABLE-LENGTH PART NUMBERS 203
SPLITTING TEXT TO MULTIPLE CELLS 206
SPLITTING CELLS BY CASE 208
ADDING DASHES BETWEEN LETTERS 210
REMOVING DASHES FROM ISBN NUMBERS 212
ADDING A MISSING CLOSING BRACKET 213
PULLING INITIAL LETTERS FROM A STRING 214
MAKING PROPER SKIP CERTAIN WORDS 215
DETERMINING "HIGHEST SINCE" OR "LOWEST SINCE" 217
MAKING CHANGES IN A GROUP OF WORKBOOKS 219
THREE-DIMENSIONAL TRANSPOSITIONS 221
LISTING COMBINATIONS 223
PUTTING AN X IN A CLICKED CELL 226
DELETING EVERYTHING EXCEPT FORMULAS 227
GETTING RID OF EVERYTHING EXCEPT NUMBERS 228
NUMBER OF TERMS IN A FORMULA 229
FINDING THE NUMBER OF SIGNIFICANT DIGITS 230
LEAVING A CELL VALUE UNCHANGED IF A CONDITION IS FALSE 232

Affecting Cell Formatting 234


ADDING DROP SHADOWS TO CELLS 234
HIGHLIGHTING VALUES IN A CELL 235
CREATING A CENTER ACROSS SELECTION BUTTON 237
MAKING A CELL'S CONTENTS BOLD OR ITALIC 237
COUNTING CELLS WITH TEXT COLORS 238
OFFICIAL COLOR NAMES IN VBA 239
WORKING WITH COLORS IN A MACRO 240
SHOWING RGB COLORS IN A CELL 241
DETERMINING THE RGB VALUE OF A COLOR 242
REPLACING BACKGROUND COLORS IN CELLS 244
CREATING SUPERSCRIPT AND SUBSCRIPT BUTTONS 248
AUTOMATICALLY COPYING FORMATTING 250

ExcelTips: The Macros Page vii


MATCHING FORMATTING WHEN CONCATENATING 251
CHECKING ALL CELL FORMATTING IN VBA 254
REPLACING CELL FORMATS 254
DETERMINING FONT FORMATTING 258
CHANGING FONTS IN MULTIPLE WORKBOOKS 258
MAKING ALL OCCURRENCES BOLD 260
SEPARATING CELLS BASED ON TEXT COLOR 264
SHORTCUT KEY FOR FORMAT PAINTER 265
SELECTING CELLS FILLED WITH A PARTICULAR COLOR 266
COUNTING COLORS OF CELLS 270
COUNTING EMPTY COLORED CELLS 271
FLASHING CELLS 275
USING A CUSTOM FORMAT TO ADD DASHES 277
MOVING CUSTOM FORMATS TO NUMBER FORMATTING CATEGORIES 278
COLORING CELLS WITH FORMULAS 279
COLORING IDENTICAL COMPANY NAMES 281
COLORS IN AN IF FUNCTION 284
CONDITIONALLY HIGHLIGHTING CELLS CONTAINING FORMULAS 286
CONDITIONAL FORMATS THAT DISTINGUISH BLANKS AND ZEROES 289
CHANGING FONT FACE AND SIZE CONDITIONALLY 294
DIAGONAL BORDERS IN A CONDITIONAL FORMAT 296
PROTECTING CONDITIONAL FORMATTING 297
DETECTING ERRORS IN CONDITIONAL FORMATTING FORMULAS 297
REMOVING CONDITIONAL FORMATS, BUT NOT THE EFFECTS 298

Affecting Rows and Columns 302


REMOVING DUPLICATE ROWS 302
CONDITIONALLY DELETING ROWS 303
DELETING EVERY X ROWS 304
HIDING ROWS BASED ON A CELL VALUE 305
HIDING ROWS BASED ON TWO VALUES 306
DETECTING HIDDEN ROWS 307
SKIPPING HIDDEN ROWS IN A MACRO 308
EASILY ADDING BLANK ROWS 308
INSERTING AND COPYING ROWS 310
MOVING AND SELECTING ROWS 311
SETTING ROW HEIGHT IN A MACRO 312
AUTOMATIC ROW HEIGHT FOR WRAPPED TEXT 312
ADJUSTING ROW HEIGHT FOR A NUMBER OF WORKSHEETS 313
SPLITTING INFORMATION INTO ROWS 314
SHADING ROWS FOR EASE IN READING OUTPUT 317
SUMMING EVERY FOURTH CELL IN A ROW 317
CONDENSING SEQUENTIAL VALUES TO A SINGLE ROW 319
HIDING A HUGE NUMBER OF ROWS 321
GETTING RID OF EMPTY ROWS AFTER IMPORTING 322
FLOATING INFORMATION IN A FROZEN ROW 324
ROWS IN A PIVOTTABLE 325

ExcelTips: The Macros Page viii


ALPHABETIC COLUMN DESIGNATION 326
UNHIDING A SINGLE COLUMN 327
SETTING COLUMN WIDTH IN A MACRO 328
DELETING DUPLICATE COLUMNS 329
DELETING BLANK COLUMNS 330
COMBINING COLUMNS 331
COMBINING MULTIPLE ROWS IN A COLUMN 332
CHANGING SHADING WHEN A COLUMN VALUE CHANGES 333
SELECTING COLUMNS IN VBA WHEN CELLS ARE MERGED 334
HIDING COLUMNS BASED ON A CELL VALUE 336

Filtering Data and Working with Filtered Data 338


TOGGLING AUTOFILTER 338
QUICKLY IDENTIFYING APPLIED AUTOFILTERS 339
FILTERING FOR COMMENTS 340
CLEARING ONLY FILTERING SETTINGS 341
CHANGING AUTOFILTER DROP-DOWN ARROW COLORS 343
EASY FILTERING SPECIFICATIONS FOR A PIVOTTABLE 344
MACRO FAILS AFTER AUTOFILTER 345

Working with Dates and Times 346


MODIFYING DEFAULT YEAR FOR DATES 346
ENTERING DATES WITHOUT SEPARATORS 347
ENTERING OR IMPORTING TIMES WITHOUT COLONS 350
PARSING NON-STANDARD DATE FORMATS 351
CONVERTING AN UNSUPPORTED DATE FORMAT 352
DECIPHERING A CODED DATE 354
LIMITING ENTRY OF PRIOR DATES 355
ADJUSTING DATE VALUES BY KEYPRESS 358
HIDING COLUMNS NOT WITHIN A DATE RANGE 359
GETTING EXCEL DATES INTO OUTLOOK'S CALENDAR 360
ADDING ORDINAL NOTATION TO DATES 361
TOMBSTONE DATE MATH 363
FINDING THE DATE ASSOCIATED WITH A NEGATIVE VALUE 364
AUTOMATICALLY ADVANCING BY A MONTH 365
WEEKDAYS IN A MONTH 366
PULLING ALL FRIDAYS 368
THE LAST BUSINESS DAY 371
EXPIRATION DATE FOR EXCEL PROGRAMS 373
INSERTING THE CURRENT TIME WITH SECONDS 374
ENTERING LARGE TIME VALUES 374
CHECKING FOR TIME INPUT 376
AUTOMATICALLY CONVERTING TO GMT 377
USING EXCEL FOR TIMING 379
CALCULATING TV TIME 380
RECORDING A DATA ENTRY TIME 382
CONVERTING NUMERIC VALUES TO TIMES 383

ExcelTips: The Macros Page ix


IS DAYLIGHT SAVINGS TIME IN EFFECT? 385

Working with Worksheets 387


CREATING WORKSHEETS WITH A MACRO 387
CREATING AND NAMING A WORKSHEET USING A MACRO 388
JUMPING TO A SPECIFIC WORKSHEET 389
SHORTCUT TO MOVE BETWEEN TWO WORKSHEETS 390
DISPLAYING THE FIRST WORKSHEET IN A MACRO 391
OPENING A WORKBOOK TO A SPECIFIC WORKSHEET 392
TELLING WHICH WORKSHEETS ARE SELECTED 392
DETERMINING A WORKSHEET'S NUMBER 393
DERIVING THE WORKSHEET NAME 394
GETTING THE NAME OF THE WORKSHEET INTO A CELL 395
RETRIEVING WORKSHEET NAMES 396
DYNAMIC WORKSHEET TAB NAMES 397
ORDERING WORKSHEETS BASED ON A CELL VALUE 398
COPYING WORKSHEETS IN A MACRO 399
CREATING A COPY WITHOUT FORMULAS 400
RELATIVE WORKSHEET REFERENCES 403
SHEETS FOR DAYS 404
REFERENCING WORKSHEET TABS 405
REFERENCING A WORKSHEET NAME 406
LOCKING WORKSHEET NAMES 407
FREEZING WORKSHEET TABS 408
RUNNING MACROS ON HIDDEN WORKSHEETS 410
SPELL-CHECKING IN A PROTECTED WORKSHEET 410
PREVENTING SOMEONE FROM RECREATING A PROTECTED WORKSHEET 411
FIXING MACRO BUTTON BEHAVIOR IN PROTECTED WORKSHEETS 412
VISUALLY SHOWING A PROTECTION STATUS 413
FORCING A WORKSHEET TO BE PROTECTED AGAIN 414
UNHIDING MULTIPLE WORKSHEETS 416
DETECTING TYPES OF SHEETS IN VBA 417
SELECTING ALL VISIBLE WORKSHEETS IN A MACRO 419
DELETING WORKSHEETS IN A MACRO 420
CONDENSING MULTIPLE WORKSHEETS INTO ONE 421
COMBINING WORKSHEETS FROM MANY WORKBOOKS 423
COUNTING THE TIMES A WORKSHEET IS USED 425
GENERATING UNIQUE NUMBERS FOR WORKSHEETS 426
DISABLING MOVING BETWEEN WORKSHEETS 428
RANDOM WIDTH AND HEIGHT CHANGES 429
IDENTIFYING THE LAST CELL CHANGED IN A WORKSHEET 430
TRANSFERRING DATA BETWEEN WORKSHEETS USING A MACRO 431
VIEWING SAME CELLS ON DIFFERENT WORKSHEETS 432
TESTING FOR AN EMPTY WORKSHEET 434
TURNING OFF DISPLAY OF ZEROS FOR ALL WORKSHEETS 435
COPYING WORKSHEET CODE AUTOMATICALLY 435
FINDING THE SIZE OF INDIVIDUAL WORKSHEETS 436

ExcelTips: The Macros Page x


Interacting with the Excel Environment 439
SWITCHING WINDOWS IN A MACRO 439
SETTING PROGRAM WINDOW SIZE IN A MACRO 440
TURNING OFF SCREEN UPDATING 441
DISABLING SHIFT KEY USE WHEN OPENING A WORKBOOK 442
OPENING A WORKBOOK BUT DISABLING MACROS 442
FORCING EDITING TO BE DONE IN A CELL 444
SYNCHRONOUS SCROLLING WITH MORE THAN TWO WINDOWS 446
USING GO TO TO JUMP TO A CHART SHEET 447
DISPLAYING A SET COLUMN RANGE 448
SELECTING VISIBLE CELLS IN A MACRO 448
STEPPING THROUGH A NON-CONTIGUOUS RANGE OF CELLS 449
HIDING EXCEL IN VBA 450
PRESERVING THE UNDO LIST 450
CLEARING THE UNDO STACK 451
CALCULATING ONLY THE ACTIVE WORKBOOK 452
SETTING THE CALCULATION DEFAULT 455
COUNTING ALL CHARACTERS 456
UNHIDING OR LISTING ALL OBJECTS 458
USING NAMED RANGES IN A MACRO 460
DELETING ALL NAMES BUT A FEW 461
DEFAULT CELL MOVEMENT WHEN DELETING 462
FORCING MANUAL CALCULATION FOR A WORKBOOK 462
PULLING FORMULAS FROM A WORKSHEET 463
SAVING NON-EXISTENT CHANGES 469
COUNTING PRECEDENTS AND DEPENDENTS 470
ACCESSING DEPENDENT AND PRECEDENT INFORMATION 471
DISCOVERING DEPENDENT WORKBOOKS 472
TURNING OFF AUTOFILL FOR A WORKBOOK 473
NOTING THE WORKBOOK CREATION DATE 474
DISPLAYING THE "LAST MODIFIED" DATE 475
NOTING WHEN A WORKBOOK WAS CHANGED 476
DATE LAST EDITED 479
FINDING THE PATH TO THE DESKTOP 480
GRABBING A USER'S NAME FROM EXCEL 481
INSERTING THE USER'S NAME IN A CELL 481
GRABBING THE MRU LIST 483
SEEING ALL OPEN WORKBOOK NAMES 483
TESTING IF A WORKBOOK IS OPEN 485
GETTING RID OF "COPY OF" 486
PULLING CELL NAMES INTO VBA 487
SIMULTANEOUS SCROLLING 487
LOOKING UP NAMES WHEN KEY VALUES ARE IDENTICAL 488
DETERMINING IF CAPS LOCK IS ON 490
DISABLING PAGE LAYOUT VIEW 491
DISABLING A FUNCTION KEY 492
DISABLING THE F1 KEY 493

ExcelTips: The Macros Page xi


DISABLING EXCEL'S HELP SYSTEM 493
RETRIEVING DRIVE STATISTICS 495
FULL PATH NAMES IN EXCEL 496
AUTOMATICALLY CLOSING A WORKBOOK 496
FINDING UNKNOWN LINKS 497
GETTING RID OF WORKBOOK LINKS 499
REPLACING LINKS WITH VALUES 500
TASK PANE DOESN'T APPEAR PROPERLY 502
FINDING THE LAST-USED CELL IN A MACRO 503
JUMPING TO THE REAL LAST CELL 504
DETERMINING IF CALCULATION IS NECESSARY 505
ITERATING CIRCULAR REFERENCES 506
TRACKING DOWN INVALID REFERENCES 508
DETERMINING HOW MANY WINDOWS ARE OPEN 510
SAVING COMMON FORMULAS 510
TURNING OFF TRACK CHANGES WITHOUT UNSHARING 514
CHECKING IF A WORKBOOK IS ALREADY OPEN 516
SAVING ALL OPEN WORKBOOKS 517
SAVING A WORKBOOK IN A MACRO 517
FINDING THE SIZE OF A WORKBOOK 518
FINDING WORKBOOKS CONTAINING MACROS 520
SAVING CHANGES WHEN CLOSING 521
CLOSING A READ-ONLY WORKBOOK 521
FORCING A WORKBOOK TO CLOSE AFTER INACTIVITY 522
USING A SINGLE PASSWORD FOR MULTIPLE WORKBOOKS 524
FINDING OTHER INSTANCES OF EXCEL IN A MACRO 525
WHO HAS THE FILE OPEN? 526
MACROS RUN SLOWER IN NEWER EXCEL? 527

The Ribbon, Menus, and Toolbars 529


DISPLAYING EXCEL'S DEVELOPER TAB 529
TOOLS ON DEVELOPER TAB ARE UNAVAILABLE 531
ADDING A MACRO TO A TOOLBAR 532
ADDING A MACRO TO THE QUICK ACCESS TOOLBAR 533
CONTROLLING DISPLAY OF TOOLBAR BUTTONS 535
CHANGING THE SHORTCUT MENU 536
ADDING ITEMS TO A CONTEXT MENU 537
REMOVING ITEMS FROM A CONTEXT MENU 538
PROBLEM WITH MISSING CONTEXT MENU OPTION 539

Working with Comments 541


COPYING COMMENTS TO CELLS 541
INSERTING WORKBOOK COMMENTS INTO A CELL 542
MOVING COMMENT BACKGROUND PICTURES TO CELLS 542
SETTING THE DEFAULT FONT SIZE FOR COMMENT BALLOONS 543
COPYING COMMENTS WHEN FILTERING 546
ADDING A COMMENT TO MULTIPLE CELLS 547

ExcelTips: The Macros Page xii


LINKING COMMENTS TO MULTIPLE CELLS 549
EDITING A COMMENT CLOSE TO ITS CELL 549
FINDING AND REPLACING TEXT IN COMMENTS 550
PLACING FORMULA ANSWERS IN A COMMENT 551
COUNTING COMMENTS IN A WORKSHEET 553

Working with Headers and Footers 555


DYNAMIC HEADERS AND FOOTERS 555
FULL PATH NAMES IN HEADERS OR FOOTERS 556
ADDING A FILE PATH AND FILENAME 558
EXTRACTING FILE NAMES FROM A PATH 560
FIRST AND LAST NAMES IN A PAGE HEADER 561
MULTIPLE LINE HEADERS AND FOOTERS 562
PUTTING A DIFFERENT DATE IN A HEADER 563
INSERTING THE SAVED DATE IN A HEADER OR FOOTER 563
LAST SAVED DATE IN A FOOTER 564
SPECIFYING DATE FORMATS IN HEADERS 566
SELECTIVE HEADERS AND FOOTERS 567
SWITCHING HEADERS IN A FROZEN ROW 568
PUTTING CELL CONTENTS IN FOOTERS 570
COPYING HEADERS AND FOOTERS 571
HEADER AND FOOTER BACKGROUND COLOR 575
USING A DIFFERENT FOOTER ON SECONDARY PAGES 577
LEADING ZEROS IN PAGE NUMBERS 578
CHANGING PAGE NUMBER FORMAT 579
ROMAN NUMERALS FOR PAGE NUMBERS 580
CHANGING SECTION HEADERS 581
FIND AND REPLACE IN HEADERS 582

Working with Graphics and Charts 584


ASSIGNING MACROS TO GRAPHICS 584
PASTING A GRAPHIC TO MULTIPLE WORKSHEETS 585
POSITIONING A GRAPHIC IN A MACRO 586
COPYING PICTURES WITH A MACRO 586
POP-UP COMMENTS FOR GRAPHICS 587
SCREENTIP FOR AN IMAGE 588
SIZING TEXT BOXES AND CELLS THE SAME 590
RESIZING A TEXT BOX IN A MACRO 591
FINDING TEXT IN TEXT BOXES 593
PLACING TEXTBOX TEXT INTO A WORKSHEET 596
ADDING AUTOSHAPES 597
SHIFTING OBJECTS OFF A SHEET 598
REMOVING PICTURES FOR A WORKSHEET IN VBA 599
SNAPSHOTS OF EXCEL WORKSHEETS FOR POWERPOINT 600
CREATING CHARTS IN VBA 601
AUTOMATICALLY CREATING CHARTS FOR INDIVIDUAL ROWS IN A DATA TABLE 602
UNLOCKING CHARTS 606

ExcelTips: The Macros Page xiii


POSITIVE AND NEGATIVE COLORS IN A CHART 607
LABELING X-Y SCATTER PLOTS 608
EXPORTING BLACK AND WHITE CHARTS 610
SPECIFYING THE SIZE OF CHART OBJECTS 611
SPECIFYING CHART SIZES 612
CHANGING ELEMENTS IN LOTS OF CHARTS AT ONE TIME 613
CONVERTING CHARTS TO GIF FILES 614
CREATING A PHOTO CATALOG FROM A FOLDER OF PHOTOS 615
HYPERLINKS TO CHARTS 617
DETERMINING MOUSE CURSOR COORDINATES ON A GRAPHIC 618

Working with the Outside World 619


GETTING USER INPUT IN A DIALOG BOX 619
USING INPUTBOX TO GET DATA 619
HIDING ENTRIES IN AN INPUTBOX 620
FORCING INPUT TO UPPERCASE 620
PAUSING MACROS FOR USER INPUT 622
USING MESSAGE BOXES 623
CONDITIONALLY DISPLAYING A MESSAGE BOX 624
SPECIFYING LOCATION FOR A MESSAGE BOX 625
PROGRESSION INDICATOR IN A MACRO 625
HIDING AN EXCEL 2007 PROGRESS INDICATOR 626
USING THE STATUS BAR 626
GETTING A FILE NAME 628
DETERMINING IF A FILE EXISTS 628
CHECKING FOR THE EXISTENCE OF A FILE 629
DETERMINING THE LENGTH OF A TEXT FILE 630
GETTING INPUT FROM A TEXT FILE 630
SAVING INFORMATION IN A TEXT FILE 630
USING SEEK IN A MACRO 631
RENAMING A FILE 632
DELETING A FILE 632
FASTER TEXT FILE CONVERSIONS 632
ALIGNING CELLS WHEN IMPORTING FROM CSV 634
SPECIFYING A DELIMITER WHEN SAVING A CSV FILE IN A MACRO 634
CREATING A DIRECTORY 636
CHANGING DIRECTORIES 636
DETERMINING THE CURRENT DIRECTORY 636
REMOVING A DIRECTORY 637
CHANGING THE DEFAULT DRIVE 637
EASILY CHANGING THE DEFAULT DRIVE AND DIRECTORY 637
WORD DOCUMENTS FROM EXCEL MACROS 638
USING OLD LOTUS MACROS 639
LINKING TO A SPECIFIC PAGE IN A PDF FILE 640
UPDATING AUTOMATICALLY WHEN OPENING UNDER MACRO CONTROL 641
DISPLAYING MESSAGES WHEN AUTOMATIC DATA CHANGES 644
PREPARING DATA FOR IMPORT INTO ACCESS 645

ExcelTips: The Macros Page xiv


GETTING CONTACT INFORMATION FROM OUTLOOK 646

Web and Online 648


OPENING SITES IN A BROWSER 648
OPENING AN HTML PAGE IN A MACRO 649
SPECIFYING A BROWSER IN A HYPERLINK 650
CONVERTING A RANGE OF URLS TO HYPERLINKS 651
SHOWING VISITED HYPERLINKS 652
GETTING RID OF MANY HYPERLINKS 653
GETTING RID OF ALL HYPERLINKS 653
CHANGING PORTIONS OF MANY HYPERLINKS 653
CHANGING HUGE NUMBERS OF HYPERLINKS 654
CONVERTING TO HYPERLINKS IN A SHARED WORKBOOK 655
EXTRACTING E-MAIL ADDRESSES FROM HYPERLINKS 656
EXTRACTING URLS FROM HYPERLINKS 657
EXTRACTING URLS FROM HYPERLINKED IMAGES 659
EXTRACTING HYPERLINK INFORMATION 660
GET RID OF WEB STUFF 660
SENDING SINGLE WORKSHEETS VIA E-MAIL 661
SUPPRESSING THE REVIEWING TOOLBAR ON E-MAILED WORKBOOKS 663
RETRIEVING WEB QUERY DATA WITHOUT INTERRUPTION 664
HIDING A HYPERLINK ON A PRINTOUT 666

Working with the Printer 668


PAGE NUMBERS IN VBA 668
CUSTOM PAGE NUMBERS ON PRINTOUTS 669
SPECIFYING THE Y VALUE IN X OF Y PAGE NUMBERING 670
CHANGING PAPER SIZE FOR A COMPLETE WORKBOOK 671
DISPLAYING THE PRINT DIALOG BOX IN A MACRO 673
SPECIFYING PRINT QUANTITY IN A CELL 673
CONTROLLING THE PRINTER IN A MACRO 674
WORKING WITH MULTIPLE PRINTERS 674
USING MULTIPLE PRINT SETTINGS 675
CAN ONLY PRINT TO DEFAULT PRINTER 677
PRINTING SELECTED WORKSHEETS 678
PRINTING ONLY NON-BLANK WORKSHEETS 679
PRINTING LIMITED PAGES FROM A RANGE OF WORKSHEETS 679
PRINTING ODD OR EVEN PAGES 680
PRINTING AN ENTIRE WORKBOOK BY DEFAULT 681
AUTOMATIC SELECTION OF PORTRAIT OR LANDSCAPE 681
PRINTING A SINGLE COLUMN IN MULTIPLE COLUMNS 683
PRINTING MULTIPLE WORKSHEETS ON A SINGLE PAGE 684
PRINTING A WORKSHEET LIST 687
PRINTING WORKBOOK PROPERTIES 687
ADJUSTING COMMENT PRINTOUTS 688
DISABLING THE PRINT OPTION 689
AUTOMATICALLY PRINTING A RANGE 690

ExcelTips: The Macros Page xv


SETTING PRINT RANGES FOR MULTIPLE WORKSHEETS 691
PRINTING ALL OR NOTHING 692
CONDITIONAL PRINTING 693
PROTECTING PRINT SETTINGS 696
LOCKING THE PRINT AREA 697
SHOWING FILTER CRITERIA ON A PRINTOUT 698
REPEATING ROWS ON A PRINTOUT EXCEPT ON THE LAST PAGE 699
PRINTING BASED ON CELL CONTENTS 700

Special-Purpose Macros 702


RUNNING A MACRO WHEN A WORKBOOK IS OPENED 702
RUNNING A MACRO WHEN A WORKBOOK IS CLOSED 703
RUNNING A MACRO WHEN A WORKSHEET IS ACTIVATED 703
RUNNING A MACRO WHEN A WORKSHEET IS DEACTIVATED 705
TRIGGERING AN EVENT WHEN A WORKSHEET IS DEACTIVATED 706
RUNNING A MACRO IN A NUMBER OF WORKBOOKS 707
MOUSE CLICK EVENT IN VBA 709
SAVING IN TWO LOCATIONS 709
SAVING IN MULTIPLE LOCATIONS 710
UNDERSTANDING ADD-INS 711
CREATING ADD-INS 712
USING CUSTOM ADD-INS 716
AUTOMATICALLY LOADING ADD-INS 718

Searching and Sorting 720


UNIVERSAL SEARCHING 720
SEARCHING FOR ALL 721
SEARCHING THROUGH MANY WORKBOOKS 724
SEARCHING A WORKBOOK BY DEFAULT 726
SEARCHING BY COLUMNS, BY DEFAULT 727
CHANGING DEFAULT SEARCH SETTINGS 728
SEARCHING FOR LEADING APOSTROPHES 729
SEARCHING FOR LINE BREAKS 731
SEARCHING FOR A VALUE USING A FUNCTION 732
WHERE IS THAT TEXT? 733
USING FIND AND REPLACE TO PRE-PEND CHARACTERS 734
REPLACING CHARACTERS AT THE END OF A CELL 736
FINDING AND REPLACING ERROR VALUES 737
FINDING AND REPLACING IN TEXT BOXES 739
SUPERSCRIPTS IN FIND AND REPLACE 740
WILDCARDS IN 'REPLACE WITH' TEXT 742
FINDING COLUMNS OF A CERTAIN WIDTH 744
SORTING WORKSHEETS 745
NON-STANDARD SORTING 747
SORTING DATA ON PROTECTED WORKSHEETS 748
SORTING BY COLORS 749
SORTING BY FILL COLOR 750

ExcelTips: The Macros Page xvi


SORTING DATA CONTAINING MERGED CELLS 751
DETERMINING SORTING CRITERIA 752
STORING SORTING CRITERIA 754
AUTOMATICALLY SORTING AS YOU ENTER INFORMATION 755

Macro Cookbook 757


AUTOFILLING WITH THE ALPHABET 757
NUMBERS SPELLED OUT 761
CONVERTING PHONE NUMBERS 763
ADDING LEADING ZEROES TO ZIP CODES 764
SHORTENING ZIP CODES 765
WORKING WITH IMPERIAL LINEAR DISTANCES 768
CALCULATING THE DISTANCE BETWEEN POINTS 769
SELECTING RANDOM NAMES 771
GENERATING RANDOM TESTING DATA 774
ZOOMING WITH THE KEYBOARD 776
ALWAYS OPEN AT 100% ZOOM 778
DETERMINING IF A NUMBER IS ODD OR EVEN 779
PLAYING WITH A FULL DECK 780
COUNTING WINS AND LOSSES 781
MAKING SQUARES 782
COUNTING WORDS 783
DEFAULT WORKSHEET WHEN OPENING 784
PROTECTING INDIVIDUAL WORKSHEETS, BY USER 785
UNPROTECTING GROUPS OF WORKSHEETS 788
CREATING INDIVIDUAL WORKBOOKS 790
DISAPPEARING TOOLBAR BUTTONS FOR MACROS 791
SHEETS FOR MONTHS 792
MACRO FOR MONTH NAME 793
NAMING TABS FOR WEEKS 795
JUMPING TO ALPHABETIC WORKSHEETS 796
FLIPPING DATA 798
COUNTING SHADED CELLS 799
COUNTING UNIQUE VALUES 800
COUNTING CELLS ACCORDING TO CASE 802
ONLY SHOWING THE MAXIMUM OF MULTIPLE ITERATIONS 804
FINDING DIFFERENCES BETWEEN LISTS 807
LIST OF MACRO SHORTCUTS IN ALL OPEN WORKBOOKS 808
QUICKLY CHANGING WINDOWS 809
USING AN EXACT NUMBER OF DIGITS 810
ENGINEERING CALCULATIONS 811
MAINTAINING ACCURACY OF SIGNIFICANT DIGITS 814
COUNTING GROUPINGS BELOW A THRESHOLD 816
REFERENCING EXTERNAL CELL COLORS 818
AUTOMATICALLY EDITING FORMULAS 818
DEVELOPING RECIPROCAL CONVERSION FORMULAS 819
SPREADING OUT A TABLE 820

ExcelTips: The Macros Page xvii


DOS FROM MACROS 821
DELETING MACROS FROM WITHIN A MACRO 822
SELF-DELETING MACROS 823
DELETING WORKSHEET CODE IN A MACRO 824
CHANGING MACRO CELL REFERENCES BASED ON EDITS 825
USING THE CAMERA IN VBA 825
CONDITIONAL PAGE BREAKS 826
CALCULATING THE INTERVAL BETWEEN OCCURRENCES 828
SELF-AWARE MACROS 829
PASTING WITHOUT UPDATING REFERENCES 829
JUMPING TO A SPECIFIC PAGE 831
CHECKING FOR EITHER OF TWO TEXT VALUES 832
UPDATING MULTIPLE PIVOTTABLES AT ONCE 834
REMOVING SUBTOTALS FROM MANY PIVOTTABLE FIELDS 834
REDUCING FILE SIZES FOR WORKBOOKS WITH PIVOTTABLES 835
USING CLASSIC PIVOTTABLE LAYOUT AS THE DEFAULT 838
RESIZING CHECKBOXES 839
EXTRACTING STREET NUMBERS FROM AN ADDRESS 839
EXTRACTING A STATE AND A ZIP CODE 841
COMBINATIONS FOR MEMBERS IN MEETINGS 844
REPLACING SOME FORMULAS WITH THE FORMULA RESULTS 845
FINDING THE ADDRESS OF THE LOWEST VALUE IN A RANGE 847
EXTRACTING PROPER WORDS 848
CLEARING LARGE CLIPBOARD ENTRIES 849
PULLING APART CELLS 849
REORGANIZING DATA 851
FINDING UNUSED NAMES 852
GETTING RID OF NON-PRINTING CHARACTERS INTELLIGENTLY 853
CHANGING MONTHS IN A WORKBOOK 855
TRIGGERING A MACRO FOR DROP-DOWN LIST CHANGES 857
DELIMITED TEXT-TO-COLUMNS IN A MACRO 857
REPLACING AND CONVERTING IN A MACRO 859
PULLING FILENAMES INTO A WORKSHEET 859
PROTECTING AN ENTIRE FOLDER OF WORKBOOKS 862
STORING A USER’S LOCATION BEFORE RUNNING A MACRO 863
LIMITING SCROLL AREA 865
WAITING FOR UPDATE COMPLETION 866
CONDITIONALLY MAKING A SOUND 867
CONDITIONALLY PLAYING AN AUDIO FILE 868

ExcelTips: The Macros Page xviii


Introduction

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.

A Few Words about Versions


This book is written for users of the VBA programming language provided with modern versions
of Excel. This means that it will work with versions beginning with Excel 97. At the time of this
writing Excel 2013 was the latest version of Excel and, no doubt, there will be other versions in
the future that make use of VBA.

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.

ExcelTips: The Macros Page 1


Introduction

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.

How to Use the Macros in This Book


You may want to use the macros in this book in your own computer system. This is a great way
to add features to your system and to use the techniques shown in this book. You may be curious
how you can copy the macros in this book and paste them directly into the VBA Editor.

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.

ExcelTips: The Macros Page 2


Introduction

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.

Need a Good Macro Tutorial?


Different people learn in different ways. You may be the type of person who flourishes with the
information provided in this book. I know that I enjoy "tip based" learning, as it allows me to
poke about and figure out, at my own pace, how to do things and make Excel do what I think it
should do.

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

Need More Tips?


Remember that this document contains the tips presented in past issues of ExcelTips. If you want
to stay at the top of your Excel form, you should consider subscribing to ExcelTips. All you need
is an e-mail account, and you can receive a weekly dose of tips related to Excel. To subscribe to
ExcelTips visit the ExcelTips Web site:

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

ExcelTips: The Macros Page 3


Introduction

ExcelTips is part of the Tips.Net network, a collection of websites


designed to bring you helpful tips, tricks, and ideas that can save you
time, effort, and money. You can see the full breadth of the Tips.Net
network by visiting our website:

http://www.tips.net

Want to Learn About VBA Programming in Word?


Once you learn how great it is to use macros in Excel, you may be tempted to try your hand at
programming macros in Excel’s sister application: Word. Many of the skills you use in Excel
VBA programming can be applied to working in Word, but there are many tasks in Word that do
not have corresponding tasks in Excel. Thus, while the skills may be similar, the techniques of
applying those skills can be different.

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

Sharing this Document


There was a lot of work that went into putting this document together. I can’t tell you how many
countless hours are spent putting together the weekly ExcelTips newsletter from which this
information was compiled. That means that this information has value, and your friends,
neighbors, and co-workers may want you to share it with them.

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

ExcelTips: The Macros Page 4


Introduction

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/

ExcelTips: The Macros Page 5


Creating and Managing Macros

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.

ExcelTips: The Macros Page 6


Creating and Managing Macros

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:

1. Display the Developer tab of the ribbon.


2. Click the Record Macro tool. Excel displays the Record Macro dialog box.

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.

Here are the steps for earlier versions of Excel:

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.

ExcelTips: The Macros Page 7


Creating and Managing Macros

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.

Relative References when Recording Macros


One of the most common ways of creating a macro is to use the macro recorder built into Excel.
The recorder allows you to record your keystrokes and play them back again later. When you
record your macros, Excel is very literal about recording what you do. For instance, if you start
recording while cell B7 is selected, and then you press the DOWN ARROW key, cell B8 is now
selected.

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.

ExcelTips: The Macros Page 8


Creating and Managing Macros

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.

Writing a Macro from Scratch


Many of the tips used in ExcelTips rely upon macros in order to run. Some readers may not know
how to enter a macro from scratch in Excel. There are actually two ways you can create macros.
First you can record a macro, which is appropriate when you want to record a series of steps you
perform quite often. The second method of creating a macro, writing one from scratch, is much
more powerful.

To create a macro from scratch, follow these steps:

1. Press ALT+F8 to display the Macro dialog box.

ExcelTips: The Macros Page 9


Creating and Managing Macros

The Macro dialog box.

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.

ExcelTips: The Macros Page 10


Creating and Managing Macros

The Macro dialog box.

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.

ExcelTips: The Macros Page 11


Creating and Managing Macros

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.

Continuing Macro Lines


When you are creating a macro, you may run into some very long lines. The VBA Editor will
handle long lines, but it is usually a pain to scroll the screen left and right to review a line. Some
programming languages (such as C or Perl) allow you to continue program lines simply by
pressing ENTER and continuing with the line.

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:

MsgBox "Please revise the entry in A1." & Chr(13) _


& "It appears to contain one or more " & Chr(13) _
& "illegal characters." & Chr(13)
Range("A1").Activate

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.

Excel 2007 VBA Differences


Deon wonders if anyone has compiled a list of differences between Excel 2003 VBA and Excel
2007 VBA. Such a list would be very helpful as he is working on updating older macros to work
with the latest version of Excel.

The easiest way to find this information is to follow these steps in Excel 2007:

1. Press ALT+F11 to display the VBA Editor.


2. Press F1. The VBA Help system starts.

ExcelTips: The Macros Page 12


Creating and Managing Macros

The VBA Help system.

3. Click What’s New.


4. Click Object Model Changes Since Microsoft Office 2003. (You could click one of the
other options to see changes since earlier versions, if desired.)

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

ExcelTips: The Macros Page 13


Creating and Managing Macros

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.

Offering Options in a Macro


If you are just starting out developing macros, you may be looking for a simple way to offer a set
of choices to a user, and then take an action based on the user’s response. This is a relatively
simple task, if you use the InputBox function along with a Select Case structure.

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:

Prompt = "1. This is your first choice" + vbCrLf


Prompt = Prompt + "2. This is your second choice" + vbCrLf
Prompt = Prompt + "3. This is your third choice" + vbCrLf
Prompt = Prompt + "4. This is your fourth choice" + vbCrLf
Prompt = Prompt + "5. This is your fifth choice"

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:

UserResp = InputBox(Prompt, "The Big Question")


UR = Val(UserResp)

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

Prompt = "1. This is your first choice" + vbCrLf


Prompt = Prompt + "2. This is your second choice" + vbCrLf
Prompt = Prompt + "3. This is your third choice" + vbCrLf
Prompt = Prompt + "4. This is your fourth choice" + vbCrLf
Prompt = Prompt + "5. This is your fifth choice"
UR = 0
While UR < 1 Or UR > 5
UserResp = InputBox(Prompt, "The Big Question")
UR = Val(UserResp)
Wend
Select Case UR
Case 1

ExcelTips: The Macros Page 14


Creating and Managing Macros

'Do stuff for choice 1 here


Case 2
'Do stuff for choice 2 here
Case 3
'Do stuff for choice 3 here
Case 4
'Do stuff for choice 4 here
Case 5
'Do stuff for choice 5 here
End Select
End Sub

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:

1. Press ALT+F8 to display the Macros dialog box.


2. From the list of macros displayed, select the one you want to rename.
3. Click on Edit. The VBA Editor is displayed, with the code for the selected macro
visible.
4. At the top of the macro is the keyword "Sub" followed by the macro name, then a pair
of parentheses.
5. Change the macro name as desired, but leave "Sub" there, as well as the parentheses.
6. Close the VBA Editor.

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.

Automating Copying Macros


Sreekanth asked if there is a way to automate the copying of macros from one workbook to
another. It seems that Sreekanth has to create a new "distribution" workbook each month that
contains a PivotTable that analyzes data, and the workbook needs to contain certain macros.

ExcelTips: The Macros Page 15


Creating and Managing Macros

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.

Friendly and Informative Error Handling


Other people use my workbooks and I have a simple technique that I use when writing error
handlers that makes it easier to cope with problems if macros fail. Every procedure that is at all
risky contains a local string variable, sOp, whose value is set during the macro code as follows:

Dim sOp As String


...
<code>
...
sOp = "opening target file"
...

ExcelTips: The Macros Page 16


Creating and Managing Macros

<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:

MsgBox "Procedure MyMacro failed while " + vbCrLf + sOp

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.

Stepping Through a Macro with a Worksheet


Visible
Ted asked if there was a way to step through VBA code while viewing a worksheet, so he could
view the effects on the worksheet as each step in his macro is executed.

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.

Macro Runs Slowly, but Steps Quickly


Fredric wrote about a problem he was having with a macro. When he is running the macro in the
VB Editor using F8 (stepping through the macro), it completes in just a few minutes. When he

ExcelTips: The Macros Page 17


Creating and Managing Macros

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

Getting Big Macros to Run


An ExcelTips subscriber wrote about problems he was having with a macro running on a very
powerful computer system. It seems that the macro would always crash and freeze the machine,
always with an out of memory error. Paul thought that if the macro could run in the background,
that it wouldn’t use so many resources and wouldn’t crash.

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

ExcelTips: The Macros Page 18


Creating and Managing Macros

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.

Working while a Macro is Running


Macros are great for doing the mundane (or not so mundane) processing that is often necessary
with Excel data. After you start to use them, you may find that running macros can consume
quite a bit of time. While you are running them, there is very little else that you can do, since
Excel won’t allow you to do any other work while the macro is chunking away.

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.

ExcelTips: The Macros Page 19


Creating and Managing Macros

Assigning a Macro to a Keyboard Combination


Excel allows you to assign macros to specific key combinations. These key combinations are
referred to as shortcut keys, and when used they result in the macro being executed. If you want
to assign or change a key combination associated with a macro, you can follow these steps:

1. Press ALT+F8 to display the Macro dialog box.

The Macro dialog box.

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.

ExcelTips: The Macros Page 20


Creating and Managing Macros

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.

One Shortcut for Two Macros


William has two workbooks, each containing macros. Even though the macros are different, they
are invoked by using the same keyboard shortcut. When only one of the workbooks is open, the
shortcut works great. When both workbooks are open at the same time, William never knows
exactly what will run. He wonders if there is a way for Excel to treat the shortcuts independently
so that if both workbooks are open the keyboard shortcuts will work harmoniously.

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.

ExcelTips: The Macros Page 21


Creating and Managing Macros

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.

Removing a Macro from a Shortcut Key


Excel allows you to assign macros to specific key combinations. These key combinations are
referred to as shortcut keys, and when used they result in the macro being executed. You learn
how to assign a shortcut key to a particular macro in other issues of ExcelTips.

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:

1. Press ALT+F8 to display the Macro dialog box.


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.

The Macro Options dialog box.

4. Remove any characters 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.

ExcelTips: The Macros Page 22


Creating and Managing Macros

Relative VBA Selections


It is a common thing to need to select cells in a macro. What if you want to select a range of cells
relative to your current location, however? It so happens that there are several ways you can
accomplish this task. For instance, if you want to select a single cell, relative to your current
location, you can use the Offset method. As an example, if you want to select the cell that is two
rows down and one column to the right of your current location, you could use the following:

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:

StartCell = ActiveCell.Offset(2, 1).Address


EndCell = ActiveCell.Offset(5, 3).Address
Range(StartCell, EndCell).Select

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

Running Macros in the Background


When you run a macro in Excel, the program turns its full attention to completing the macro.
(Sounds almost anthropomorphic, doesn’t it?) This means that if the macro does quite a bit of
heavy-duty processing of your data, it can seem as if your system has “locked up” during the
processing of the macro.

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

ExcelTips: The Macros Page 23


Creating and Managing Macros

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.

Aborting a Macro and Retaining Control


When you are developing a macro for others to use, you may want to add a method for the user
to exit your macro before it ends, and still retain control of what the macro does. CTRL+BREAK
will stop a macro, but it doesn’t exit gracefully, as it allows the user to view the code in the VBA
Editor.

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 ...

' your code goes here

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.

ExcelTips: The Macros Page 24


Creating and Managing Macros

The Protection tab of the Project Properties dialog box.

3. Choose the Lock Project for Viewing check box.


4. In the Password box, enter a password you want used to protect the macro.
5. In the Confirm Password box, enter the same password a second time.
6. Click OK.

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

ExcelTips: The Macros Page 25


Creating and Managing Macros

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.

Develop Macros in Their Own Workbook


Excel includes VBA as a powerful programming language that you can use to develop all sorts
of macros. It is not unusual, as you are developing macros, to go through many iterations and
make wholesale changes to your macros. You may want to keep in mind, however, that doing so
can cause problems in your worksheets.

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.

ExcelTips: The Macros Page 26


Creating and Managing Macros

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.

Macros in Template Files


Kay wrote to describe a problem she was having with an Excel 2003 template, a macro, and a
toolbar button. It seems that Kay created a macro, saved it in the workbook, and assigned the
macro to a toolbar button. When she later saved the workbook as a template, the toolbar button
no longer worked properly if she deleted the original workbook.

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

ExcelTips: The Macros Page 27


Creating and Managing Macros

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.

Forcing a Macro to Run When a Worksheet is


Recalculated
When you write a macro, it is designed to be run whenever you choose to run it. What if you
need to develop a macro that will run whenever something changes in your worksheet? What if
you want the macro to run automatically? This is particularly necessary if you are creating a
custom function that you want to use within the cells of the worksheet.

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:

Function CountCells(MyRange As Range)


Dim iCount As Integer
iCount = 0
For Each cell In MyRange
If cell.HasFormula Then
iCount = iCount + 1
End If
Next cell
CountCells = iCount
End 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:

Function CountCells(MyRange As Range)


Dim iCount As Integer
Application.Volatile
iCount = 0
For Each cell In MyRange
If cell.HasFormula Then
iCount = iCount + 1

ExcelTips: The Macros Page 28


Creating and Managing Macros

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:

1. Press ALT+F8 to display the Macro dialog box.

The Macro dialog box.

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.

ExcelTips: The Macros Page 29


Creating and Managing Macros

Generating a List of Macros


Once you start writing Excel macros, it is easy to get quite a few of them in a workbook. At
some point you may want to generate a list of macros in your workbook. There is no intrinsic
way within Excel to create a list of macros. You can, however, create a macro that will list your
macros. (Sort of sounds redundant, doesn't it?)

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"

For Each VBComp In ThisWorkbook.VBProject.VBComponents


Set VBCodeMod =
ThisWorkbook.VBProject.VBComponents(VBComp.Name).CodeModule
With VBCodeMod
StartLine = .CountOfDeclarationLines + 1
Do Until StartLine >= .CountOfLines

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.

ExcelTips: The Macros Page 30


Creating and Managing Macros

The References dialog box.

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.

Clean Up Your Macro List


Whenever you use the macro recorder to record a macro, Excel assigns it a name of MacroN,
where N is the next available macro number. Thus, your first macro recorded would be Macro1,
the second would be Macro2, and so on. (Although Excel lets you pick a different name when
you record the macro, it is my experience that most people do not take advantage of this for
quick-and-dirty macros.)

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.

ExcelTips: The Macros Page 31


Creating and Managing Macros

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.

Understanding Phantom Macros


Are you up for an experiment? Try the following: open a brand new workbook in Excel; one that
has no macros in it. Record a quick macro, and then delete it. Save the workbook, close it, and
reopen it. If all went as expected, Excel should have warned you about the workbook when you
reopened it, and asked you if you wanted to disable the macros.

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.

To fix this situation, you must follow these steps:

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.

ExcelTips: The Macros Page 32


Creating and Managing Macros

Getting Rid of the "Enable Macros" Notice


The VBA programming language included with Excel allows you to create very powerful
macros. It is not uncommon to record a couple of macros for a workbook, each designed to
accomplish a quick little task. When you create the macros, Excel adds what is called a module
to your workbook. This module is used to store the macros that you record or create.

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:

1. Press ALT+F11 to display the Visual Basic Editor.


2. 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.
3. 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.
4. Right-click on a module in the folder. A Context menu is displayed.
5. Choose the Remove option from the Context menu. You are asked if you want to export
the module before removing it.
6. Click on the No button. The module is removed.
7. Repeat steps 5 through 7 for each module in the Modules folder.
8. Close the Visual Basic Editor.
9. Resave your workbook.

At this point your workbook contains no modules, and you will not get any notification when
you subsequently open it.

ExcelTips: The Macros Page 33


Creating and Managing Macros

Removing All Macros


Gerald asked if there was a way to get rid of all the macros in an Excel workbook, without the
need to individually delete them. There are two ways you can accomplish this task. The first
approach is used if you don’t want to mess with the macros at all. Just follow these steps:

1. Unhide any worksheets that may be hidden.


2. Select all the worksheets in the workbook. (Click on the first worksheet tab, then hold
down SHIFT as you click on the last worksheet tab.)
3. Right click on one of the worksheet tabs. Excel displays a Context menu.
4. Choose Move or Copy from the Context menu. Excel displays the Move or Copy dialog
box.

The Move or Copy dialog box.

4. Using the To Book drop-down list, choose (new book).


5. Make sure the Create Copy check box is not selected.
6. Click on OK.
7. Rehide any worksheets you unhid in step 1.

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:

1. Press ALT+F11 to display the VBA Editor.


2. In the Project Explorer (upper-left corner of the Editor), right-click on a module that
you want to delete. (Remember that macros are stored in modules, and that you should

ExcelTips: The Macros Page 34


Creating and Managing Macros

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.

Easily Deploying Customizations


When it comes to customizing Excel, your imagination is pretty much the limit. If you can
imagine it, you can probably find a way to customize Excel to reflect your desires. You may
even want to share your customizations with others.

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:

ExcelTips: The Macros Page 35


Creating and Managing Macros

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/

Library Not Registered Error


When you start Excel, it goes through quite a few procedures to make sure that everything is
running properly. If it detects an error, you may see an error message in a dialog box. Sometimes
the messages you see in the dialog box are not that clear, and a few are downright cryptic.

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:

1. Press ALT+F11 to display the VBA Editor.


2. Choose References from the Tools menu. You see the References dialog box.

ExcelTips: The Macros Page 36


Creating and Managing Macros

The References dialog box.

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:

1. Make sure that Excel is not running (exit the program).


2. Click the Start button to display the Start menu.
3. From the Start menu, choose Run. Windows displays the Run dialog box.

ExcelTips: The Macros Page 37


Creating and Managing Macros

The Run dialog box.

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):

"c:\Program Files\Microsoft Office\Office\Excel.exe" /regserver

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.

ExcelTips: The Macros Page 38


Creating and Managing Macros

• 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:

Private Sub MySub()


MsgBox "We are running the macro"
End Sub

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:

Function MySub() As Boolean


MsgBox "We are running the macro"
End Function

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:

Sub MySub(Void As Integer)


MsgBox "We are running the macro"
End Sub

ExcelTips: The Macros Page 39


Creating and Managing Macros

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.

ExcelTips: The Macros Page 40


Creating and Managing Macros

The Macro Settings in the Trust Center dialog box.

5. Choose an available security setting.


6. Click on OK.

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.

ExcelTips: The Macros Page 41


Creating and Managing Macros

The Security Level tab of the Security dialog box.

2. Choose an available security setting.


3. Click on OK.

Maximum Length Limit for a Macro


Vasant has written a very long macro in Excel—over 1,400 lines. When he tries to run the
macro, Excel refuses to run it and says that it is too long.

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.

ExcelTips: The Macros Page 42


Creating and Managing Macros

Excluding a Specific Add-In at Startup


Peter asked if there is a way to specify, at Excel startup, that a particular add-in should not be
loaded. The add-in he has in mind takes a lot of time to load, and he doesn’t need it all the time.
Disabling the add-in would help start Excel quicker for those instances when it wasn’t needed.

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:

AddIns("Big Add-in").Installed = True

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:

AddIns("Big Add-in").Installed = False

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:

Private Sub Workbook_Open()


With Application
.OnKey "{TAB}", "InstallMyAddIn"
.OnTime (Now + TimeValue("0:00:05")), "DisableTABProc"
End With
End Sub

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}", ""

ExcelTips: The Macros Page 43


Creating and Managing Macros

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:

Private Sub Workbook_Close()


AddIns("Big Add-in").Installed = False
End Sub

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.

Using Macros in Protected Workbooks


Lori had a problem with a workbook she wanted to share with others. The workbook contained a
macro, but whenever the workbook is protected to prevent others from making changes to the
workbook, Lori reports that “the macro is disabled.”

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"

'work on the worksheet here

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”

ExcelTips: The Macros Page 44


Creating and Managing Macros

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.

Ctrl+Break Won't Work to Stop a Macro


Russell just switched to Excel 2007 running under Vista. His problem is that it now seems
CTRL+BREAK no longer stops the execution of a macro. Either the macro continues as if he had
done nothing or Excel hangs up and he has to close it and start over. Russell checked and
clicking the Stop Macro button (parallel line) in the VB menu doesn't work either, so this is not a
problem of linkages to keys. He wonders if anyone else experienced this and if there is an easy
fix.

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

ExcelTips: The Macros Page 45


Creating and Managing Macros

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.

Recovering Macros from Corrupted Workbooks


Devarajan ran into a situation where a workbook became corrupted, but he wanted to recover the
macro module that was associated with the workbook. (The macros represented quite a bit of
development time.) Devarajan wondered how the module could be recovered.

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:

http://support.microsoft.com/?kbid=142117 (for Excel 97)


http://support.microsoft.com/?kbid=179871 (for Excel 2000)
http://support.microsoft.com/?kbid=820741 (for Excel 2002 and 2003)
http://www.jkp-ads.com/Articles/CorruptFiles.asp

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:

1. Open Excel, but not the problem workbook.


2. Set the calculation mode to manual (Tools | Options | Calculation tab | Manual or, in
Excel 2007, Office button | Excel Options | Formulas | Manual or, in Excel 2010 and
2013, File tab | Excel Options | Formulas | Manual).
3. Set the security setting to High (Tools | Macro | Security | High or, in Excel 2007,
Office button | Excel Options | Trust Center | Trust Center Settings | Macro Settings |
Disable All Macros without Notification or, in Excel 2010 and 2013, File tab | Excel
Options | Trust Center | Trust Center Settings | Macro Settings | Disable All Macros
without Notification).
4. Open the troublesome workbook. If it opens successfully, you should see a notice that
the macros were disabled. (If the workbook doesn’t open, then you might as well shut
Excel down; this series of steps won’t work.)
5. Press ALT+F11 to display the VBA Editor.
6. In the Project Explorer, locate the module you want to save.
7. Right-click the module name and choose Export File.
8. Provide a name and location of where to save the module.
9. Close the VBA Editor and get out of Excel.
10. With the module saved in its own file, you can now import it into another workbook, as
desired.

ExcelTips: The Macros Page 46


Creating and Managing Macros

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.

Trouble Recording Paste Special Formula


Joan ran into a problem on a new computer system she got. On an older system she had a macro
that would essentially perform "Paste Special—Formula." When she tried to record the same
macro on her new system, Excel generated an error message and wouldn't record the macro.

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.

Digital Signatures for Macros


When you create macros and share them with others, the availability of those macros depends on
the security settings on the user's machine. If the security level is set high enough, the user may
not even be able to use the macros at all.

ExcelTips: The Macros Page 47


Creating and Managing Macros

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.

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.

ExcelTips: The Macros Page 48


Creating and Managing Macros

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

Moving Macros from the Personal Workbook


It is not uncommon to place commonly used macros in the Personal workbook. By placing them
there, you are able to have the macros available all the time while you are using Excel. At some
point, however, you may want to move the macros to a different workbook. For instance, you
may want to place them in a workbook so they are easily accessible by anyone else opening the
workbook.

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.

ExcelTips: The Macros Page 49


Creating and Managing Macros

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.

Automatically Hiding the Personal Workbook


Ken is having a problem with his Personal workbook. When he needs to work on it, he unhides
it. If he forgets to rehide it when he is done, then the next time he starts Excel the Personal
workbook is immediately visible. Since it looks like a new, blank worksheet, he often starts
typing in it and this messes up his Personal workbook. He wonders if there is a way to
automatically force the Personal workbook to be hidden if he forgets to hide it manually.

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:

Private Sub Workbook_BeforeClose(Cancel As Boolean)


Windows("PERSONAL.XLS").Visible = False
ThisWorkbook.Save
End Sub

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.

Upgrading a Personal.xls Workbook


Barbara updated to Office 2007, but her company has dictated that files created in Excel 2007 be
saved in Excel 2003 format since many of their clients have not yet upgraded. Barbara is
wondering if she should continue to use Personal.xls as the personal macro workbook or copy all
macros to Personal.xlsb.

ExcelTips: The Macros Page 50


Creating and Managing Macros

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.

Opening Personal.xlsb in Excel 2007


Roger uses Excel 2007. Every time he starts the program, it is supposed to open a blank
worksheet. However, Roger notes that Excel always opens Personal.xlsb instead of a fresh
worksheet.

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.

Documenting Changes in VBA Code


Phil is a member of his bank’s MIS department. The department creates a lot of management
reports using Excel. In doing so they write a lot of macros to automate the reports as much as
possible. Because of the Sarbanes-Oxley act the bank is required to track changes to the VBA
code. Phil wonders if there are any products or methods to track the changes in the VBA code
that would highlight what was changed and then preserve those changes for documentation
purposes.

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

ExcelTips: The Macros Page 51


Creating and Managing Macros

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.)

Out of Memory Errors when Accessing the VBA


Editor
Chris wrote about a problem he is having with VBA. It seems that he’s getting the error message
"out of memory" when attempting to access the VBA editor. He’s tried closing all other
applications, checked for background programs, and he has ample disk space. The error message
even occurs in workbooks with relatively small procedures.

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

Automatically Changing References to VBA


Libraries
Mark is in a work environment that uses two versions of Excel—2002 and 2003. A problem
arises when he opens a workbook with a macro in Excel 2003, which installs the VB library of
Office 11, and then later someone tries to open the same workbook in Excel 2002, which gives
an error when the macro tries to run because it cannot find the correct VBA library reference.

ExcelTips: The Macros Page 52


Creating and Managing Macros

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:

If Application.Version = "10.0" Then 'Excel 2002


Dim oExcel As Object 'Apply late binding
Set oExcel = CreateObject("Excel.Application")
End If
If Application.Version = "11.0" Then 'Excel 2003
'
' Add whatever references you want for Excel 2003
'
End If

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

Item Not Available in Library


Ravi has placed some VBA controls and code in a worksheet. When he shares the workbook
with other users and they open it, the functions don't work and report an error "item not available
in library." Ravi is wondering how he can get rid of this error.

ExcelTips: The Macros Page 53


Creating and Managing Macros

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.

Automatically Opening Macro Workbooks when


Using a Shortcut Key
Inna notes that Excel allows her to assign shortcut keys to my macros. However, it looks like the
shortcuts will only work if they refer to a macro in an open workbook. She usually has her
macros stored in a separate workbook. If a macro is assigned to a toolbar button (or an option on
the Quick Access toolbar), the workbook containing the macro is automatically opened so it can
be run. This does not happen if Inna uses a keyboard shortcut for the same macro; pressing the
shortcut won't load the workbook that contains the macro. She wonders if there is a way around
this.

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

ExcelTips: The Macros Page 54


Creating and Managing Macros

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.)

ExcelTips: The Macros Page 55


Functions and Subroutines

Functions and Subroutines

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

ExcelTips: The Macros Page 56


Functions and Subroutines

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

Function RoundIt(X) As Integer


RoundIt = Int(X + 0.5)
End Function

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.

ExcelTips: The Macros Page 57


Functions and Subroutines

Making Common Functions Available


If you work in a networked environment, you may have a need to make a common set of custom
functions available to all the users on your network. For instance, your company may have some
specialized functions that perform some financial calculations in a particular way. You may be
wondering how to best supply these functions to users on your network, without allowing them
to modify the functions themselves.

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:

1. Press ALT+F11 to display the VBA Editor.


2. In the Project Explorer window (upper-left corner of the VBA Editor), find the project
(workbook) that you are working on.
3. Expand the project, if necessary, by clicking the plus sign to the left of the project
name. You should see all the worksheets in the project listed.
4. Double-click the worksheet you want to work with. A code window should appear for
the worksheet.
5. At the top of the worksheet’s code window are two drop-down lists. In the left-hand
drop-down list, choose Worksheet.

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

ExcelTips: The Macros Page 58


Functions and Subroutines

• 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:

1. Press ALT+F11 to display the VBA Editor.


2. In the Project Explorer window (upper-left corner of the VBA Editor), find the project
(workbook) that you are working on.
3. Expand the project, if necessary, by clicking the plus sign to the left of the project
name. You should see all the worksheets in the project listed.
4. Double-click the ThisWorkbook item. A code window should appear for the workbook.
5. At the top of the workbook’s code window are two drop-down lists. In the left-hand
drop-down list, choose Workbook.

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.

ExcelTips: The Macros Page 59


Structures Used in Macros

Structures Used in Macros

Understanding the For … Next Structure


Macros in Excel are written in a language called Visual Basic for Applications (VBA). Like any
other programming language, VBA includes certain programming structures that are used to
control how the program executes. One of these structures is the For … Next structure. The most
common use of this structure has the following syntax:

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

ExcelTips: The Macros Page 60


Structures Used in Macros

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.

Exiting a For ... Next Loop Early


If you use For ... Next loops in your macro programming (who doesn’t?), then you should know
that they can take a great deal of time. You can minimize this by only checking what you need.
For instance, consider the following code, which checks an array to see if a value exists. If it
doesn’t, then it adds the value to the end of the array. If it does, then the value is not added.

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.

Understanding the If ... End If Structure


Macros in Excel are written in a language called Visual Basic for Applications (VBA). Like any
other programming language, VBA includes certain programming structures which are used to
control how the program executes. One of these structures is the If ... End If structure. The most
common use of this structure has the following syntax:

If condition Then

ExcelTips: The Macros Page 61


Structures Used in Macros

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.

Understanding the While...Wend Structure


Macros in Excel are written in a language called VBA. Like other programming languages, VBA
includes certain programming structures that are used to control how the program executes. One
of these structures is the While...Wend structure. This structure has the following syntax:

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.

Understanding the Select Case Structure


Macros in Excel are written in a language called Visual Basic for Applications (VBA). Like any
other programming language, VBA include certain programming structures which are used to
control how the program executes. One of these structures is the Select Case structure. This
structure has the following syntax:

Select Case expression


Case expression
program statements
Case expression

ExcelTips: The Macros Page 62


Structures Used in Macros

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:

Select Case DayOfWeek


Case 1
DayName = "Monday"
Case 2
DayName = "Tuesday"
Case 3
DayName = "Wednesday"
Case 4
DayName = "Thursday"
Case 5
DayName = "Friday"
Case 6
DayName = "Saturday"
Case 7
DayName = "Sunday"
Case Else
DayName = "Unknown day"
End Select

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."

ExcelTips: The Macros Page 63


Working with Variables

Working with Variables

Understanding Variables in Macros


Excel allows you to write macros in a language called Visual Basic for Applications (VBA). This
is a specialized version of the BASIC programming language, and as such, allows you to use
variables. Variables are nothing but names that represent other data. During the course of your
macro you can even change the data to which the name applies.

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:

• Byte. A numeric variable within the range of 0 to 255.


• Boolean. A variable with two possible values: True (-1) or False (0).
• Integer. A numeric variable designed for whole numbers in the range of -32,768 to
32,767.
• Long. A numeric variable designed for very large whole numbers.
• Currency. A numeric variable designed for calculations involving monetary values.
• Single. A numeric variable designed for single-precision floating-point values; accurate
to about six or seven decimal places.
• Double. A numeric variable designed for double-precision floating-point values;
accurate to about 15 decimal places.
• Date. A numeric variable designed to represent a date and time as a real number. The
value to the left of the decimal point is the date, and that portion to the right of the
decimal point is the time.
• String. A variable that can contain any type of text or character you desire. You can
assign a maximum of approximately 63,000 characters to a string variable.
• Object. A variable that contains a pointer to a defined object within VBA.
• Variant. A variable that can contain any type of data.

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

ExcelTips: The Macros Page 64


Working with Variables

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:

Dim MyVar As Integer

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

sTemp = "x is type " & VarType(x) & vbCrLf


sTemp = sTemp & "y is type " & VarType(y) & vbCrLf
sTemp = sTemp & "z is type " & VarType(z)

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:

Dim x As Integer, y As Integer, z As Integer

ExcelTips: The Macros Page 65


Working with Variables

Swapping the Values in Two Variables


If you do any serious macro programming, there will eventually come a time when you want to
swap the values stored in two variables. In some versions of BASIC, there are commands that
handle this. VBA leave you to our own devices, however. The following technique should do the
trick for most people:

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.

If LCase(sUserIn) = "yes" then bDoIt = True

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.

Converting Numbers to Strings


You already know that you can use variables in your macros, and that there are two very basic
types of variables: string variables (containing characters) and numeric variables (containing
numeric values). You can quickly and easily convert a number into a string in your macros. This
is the done with the Str() function. The way you use this function is as follows:

A = Str(B)

ExcelTips: The Macros Page 66


Working with Variables

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:

Function ToNum(X as Variant) as String


Dim A as String

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.

Converting Strings to Numbers


There are many times when writing macros that you need to convert strings to numbers. You can
do this with the Val() function. This function returns the value of a string, up to the first
nonnumeric character. The following are examples:

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.

Quickly Dumping Array Contents


If you have done any programming in VBA, you know the value of using variable arrays to store
information. It is not uncommon to start working with large arrays in your macros. For instance,
you might declare a 100-element string array, as follows:

Dim MyText(99) As String

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:

ExcelTips: The Macros Page 67


Working with Variables

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.

ExcelTips: The Macros Page 68


Commands and Statements

Commands and Statements

Deriving an Absolute Value in a Macro


VBA provides a function to return the absolute value of an expression. For those who might not
remember from math class, an absolute value is the positive equivalent of any expression. Thus,
if a formula would normally result in a negative value, such as –27, the absolute value of that
formula would result in the positive equivalent, or 27.

The syntax for the absolute value function is as follows:

x = Abs(y)

where x is the result and y is a value or an expression that evaluates to a value.

Determining a Random Value


VBA provides a function to return a random value. You wouldn’t necessarily use this function
by itself, but as part of a larger macro that may require the use of random values. The syntax for
the function is as follows:

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.

ExcelTips: The Macros Page 69


Commands and Statements

Determining an ANSI Value in a Macro


When creating a macro, you can use the Asc function to determine the ANSI value of the first
letter of a string. In early versions of BASIC, Asc returned the ASCII value, but Excel uses only
ANSI values. The function uses the following format:

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.

Determining the Hour of the Day


If you are writing macros for Excel, you may have a need to determine the hour represented by a
particular date and time value. For instance, you might want to know the hour of the day in
which the macro is running. You can ascertain this information by using the HOUR function, as
follows:

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.

Determining the Day of the Month


When creating macros in VBA, you may have a need to know the specific day of the month
represented by a particular date. For instance, you may want to determine the day of the month
on which the macro is being executed. The following code will do the trick:

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.

ExcelTips: The Macros Page 70


Commands and Statements

Determining Differences Between Dates


When you are programming Excel macros, you should know that dates are stored internally,
within variables, as serial numbers. The serial number represents the number of days elapsed
since a starting “base date,” specifically since 1 January 100. This means that you can perform
math with the serial numbers, if desired. You can, for instance, find the number of days between
two dates by simply subtracting the dates from each other.

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:

iNumWeeks = DateDiff("ww", dFirstDate, dSecondDate)


iNumMonths = DateDiff("m", dFirstDate, dSecondDate)

The first line determines the number of weeks between the two dates, and the second determines
the number of months between them.

Creating a String in a Macro


What do you do if you need a string of 80 equal signs or 25 spaces in your macro? Use the String
function. This function is used to create strings of repeating characters. Consider the following
examples:

sNew1 = String(25, 32)


sNew2 = String(25, " ")
sNew3 = String(80, "=")
sNew4 = String(20, "=*")

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.)

ExcelTips: The Macros Page 71


Commands and Statements

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.

Trimming Spaces from Strings


It is often necessary to trim spaces off of strings when programming macros. For instance, let's
say you used the InputBox function to get some user input. The function returns a string, but you
find out that the user hit the space bar a few times before typing a response. Thus, you end up
with a string such as " My String," complete with leading spaces.

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.

Determining the Length of a String


It is hard to imagine a function used more often with strings than the Len function. This simple
little function returns the length of any string. The following are a few examples:

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).

ExcelTips: The Macros Page 72


Commands and Statements

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.

Determining an Integer Value


VBA, true to its BASIC roots, provides a function to return the integer value of an expression.
This means that anything to the right of the decimal point is truncated. Thus, if a formula would
normally result in a value such as 18.73, then the integer value of that formula would result in
18.

The syntax for the integer function is as follows:

x = Int(y)

where x is the result and y is a value or an expression that evaluates to a value.

Error Using ATAN2 Function in Macro


Lars ran into a problem using the ATAN2 function in a macro. He developed a rather
complicated set of instructions, only to have VBA generate an error when it tried to use the
ATAN2 function. He was able to simplify the macro so he could recreate the problem:

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.

ExcelTips: The Macros Page 73


Commands and Statements

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).

Using BIN2DEC In a Macro


Most of Excel’s worksheet functions can be accessed in VBA by using the WorksheetFunction
object. Some functions may not seem to be available, however. One such function is BIN2DEC,
which converts a binary value to a decimal value. The reason it isn’t available is that BIN2DEC
isn’t really an Excel worksheet function—it is part of the Analysis ToolPak add-in.

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.

ExcelTips: The Macros Page 74


Commands and Statements

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.

Function Bin2Dec(sMyBin As String) As Long


Dim x As Integer
Dim iLen As Integer

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.

Using SUM In a Macro


Bob has a need to use the SUM function in a macro in order to find the sum of all the values in a
column. The problem is that the number of cells to be summed will vary; for one run of the
macro it could be 100 cells, while on the next it could be 300 and on the third only 25.

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:

Public Sub Sum_Demo()


Dim myRange
Dim Results
Dim Run As Long

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

ExcelTips: The Macros Page 75


Commands and Statements

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:

myRange = ActiveSheet.Range("A1", Range("A1").End(xlDown))


Range("B1") = WorksheetFunction.Sum(myRange)

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.

Large Numbers in the MOD Function


Cesarettin noted that the MOD worksheet function cannot produce a result when the number is
being evaluated is 268,435,456 or larger and the divisor is 2. If the number is less than this, there
is no problem. For example, if the function is MOD(268435455, 2) there is no problem. He
wonders if there is a way to use the MOD function with larger numbers and a divisor of 2?

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)

ExcelTips: The Macros Page 76


Commands and Statements

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.

Function DblMod(Dividend, Divisor)


' Declare two double precision variables
Dim D1 As Double
Dim D2 As Double

' Copy function arguments to local variables


D1 = Dividend
D2 = Divisor

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.

ExcelTips: The Macros Page 77


Commands and Statements

EOMONTH Function is Flakey


Joe uses the EOMONTH function quite frequently, but since he moved up to Excel 2007 he has
had problems. Joe has the Analysis ToolPak active and the EOMONTH function works, at first.
However, when he saves his workbook and opens it later, the cells containing the EOMONTH
function look fine until you click on them. Then the formula bar shows #N/A. The formula has
disappeared. Analysis ToolPak is still active when this happens. This does not happen on every
occasion, but it is too frequent to ignore. Also, Joe has a colleague who is experiencing the same
problem with his workbooks.

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:

Function LastOfMonth(Any_Date As Date) As Date


' Returns the date of the last day of
' the month of the passed date argument

LastOfMonth = DateAdd("d", -1, _


DateAdd("m", 1, Month(Any_Date) _
& "/1/" & Year(Any_Date)))
End Function

ExcelTips: The Macros Page 78


Commands and Statements

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")

Making VLOOKUP Trigger a Macro


Mike uses VLOOKUP regularly in his worksheets, but wonders if there is a way to make the
function run a macro if it fails to return a value.

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:

Private Sub Worksheet_Calculate()


If IsError(Range("A1")) Then Call Macro1
End Sub

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:

Private Sub Worksheet_Calculate()


On Error GoTo myMac
Worksheets(1).Select
If Range("A1").Value Then
Exit Sub
End If
myMac:
Macro1 'macro to run if VLOOKUP fails
End Sub

ExcelTips: The Macros Page 79


Commands and Statements

Making VLOOKUP Case Sensitive


Robin asked if there is a way to do a VLOOKUP that is case sensitive. Her lookup table/range
has entries that are similar (AbC and aBC) with the only difference being the case of the letters.
She can't change the values (make them all upper or lower case) since the unique values are vital.

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:

Function CaseVLook(compare_value, table_array As Range, _


Optional col_index As Integer = 1)
Dim c As Range
Dim rngColumn1 As Range

Application.Volatile

Set rngColumn1 = table_array.Columns(1)


CaseVLook = "Not Found"

ExcelTips: The Macros Page 80


Commands and Statements

'Loop first column


For Each c In rngColumn1.Cells
If c.Value = compare_value Then
CaseVLook = c.Offset(0, col_index - 1).Value
Exit For
End If
Next c
End Function

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

ExcelTips: The Macros Page 81


Controlling Data Entry

Controlling Data Entry

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.

Private Sub Workbook_BeforeClose(Cancel As Boolean)


If Cells(1, 1).Value = "" Then
MsgBox "Please fill cell A1"
Cancel = True
End If
End Sub

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.

Private Sub Workbook_BeforeClose(Cancel As Boolean)


Cancel = ForceDataEntry()
End Sub

Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, _


Cancel As Boolean)
Cancel = ForceDataEntry()
End Sub

Sub ForceDataEntry() As Boolean

ExcelTips: The Macros Page 82


Controlling Data Entry

Dim rng As Range


Dim c As Variant
Dim rngCount As Integer
Dim CellCount As Integer

Set rng = Range("Mandatory")


rngCount = rng.Count

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.

Don't Allow Empty Cells


Merle is trying to use the Data Validation feature of Excel to limit what a user can choose in a
cell. When the user activates a cell he only wants an answer of Yes, No, or N/A; he does not
want the user to be able to get out of the cell and leave it blank (empty). Merle has set up a list
for the acceptable values (Yes, No, and N/A) and has unchecked the Ignore Blanks check box
when setting up the Data Validation.

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).

ExcelTips: The Macros Page 83


Controlling Data Entry

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


Dim rng As Range
Set rng = Range("C22")
If Application.Intersect(Target, rng) Is Nothing Then
MsgBox "You must select the answer from the list"
End If
End Sub

Controlling Entry Order on Unprotected Cells


Rob has a number of worksheets that are used to score assessments. The first worksheet has cells
for name, date, etc., then several columns to enter the multiple-choice responses. The sheet is
protected, so only input cells can be changed. When the user finishes the last cell in a column,
the focus will jump to the next unprotected cell, which may be the first cell in the next column,
or it might be the "date" cell. Rob wonders how he can control the focus so that when the value
is entered into the last (bottom) cell in a column, it will then move to a cell that he specifies.

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:

Private Sub Worksheet_Change(ByVal Target As Range)


Application.EnableEvents = False
If Target.Address = "$C$10" Then Range("D5").Select
If Target.Address = "$D$10" Then Range("E5").Select
Application.EnableEvents = True
End Sub

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.

ExcelTips: The Macros Page 84


Controlling Data Entry

Accepting Only a Single Digit


Rich wonders how he can configure Excel so that when he enters a single digit it will
automatically advance to the next cell. He wants to eliminate hitting ENTER or TAB to get to the
next cell. The value of the entry for a range of cells will always be a single positive digit.

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.

Private Sub Worksheet_Change(ByVal Target As Range)


If IsNumeric(Target.Value) Then
CRow = Target.Row
CColumn = Target.Column - 1
Entry = Target.Value
For i = 1 To Len(Entry)
Cells(CRow, CColumn + i).Value = Mid(Entry, i, 1)
Next
End If
End Sub

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

ExcelTips: The Macros Page 85


Controlling Data Entry

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.

Setting a Length Limit on Cells


Craig is developing a worksheet and wants to know if there is a way to specify the maximum
number of characters that can be entered in any given cell. He doesn’t want to use Data
Validation to impose the limitation.

ExcelTips: The Macros Page 86


Controlling Data Entry

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:

=IF((LEN(C1)>15),"Cell is Too Long","")

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.

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


For Each cell In UsedRange
If Len(cell.Value) > 15 Then
MsgBox " Can't enter more than 15 characters"
cell.Value = ""
End If
Next
End Sub

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.

Private Sub Worksheet_Change(ByVal Target As Excel.Range)


Dim rng As Range
Dim rCell As Range
Dim iChars As Integer
On Error GoTo ErrHandler

'Change these as desired


iChars = 15
Set rng = Me.Range("A1:A10")

If Not Intersect(Target, rng) Is Nothing Then


Application.EnableEvents = False
For Each rCell In Intersect(Target, rng)
If Len(rCell.Value) > iChars Then
rCell.Value = Left(rCell.Value, iChars)
MsgBox rCell.Address & " has more than" _
& iChars & " characters." & vbCrLf _
& "It has been truncated."
End If
Next
End If

ExitHandler:
Application.EnableEvents = True
Set rCell = Nothing
Set rng = Nothing

ExcelTips: The Macros Page 87


Controlling Data Entry

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.

Entering Data as Thousands


Richard would like to type a value into a cell and have Excel assume that the value is thousands.
For instance, he would like to enter "3" into a cell and have Excel treat the value as 3,000; if he
enters "14" into the cell, then Excel should treat it as 14,000.

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.

ExcelTips: The Macros Page 88


Controlling Data Entry

The advanced options of the Excel Options dialog box.

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.

ExcelTips: The Macros Page 89


Controlling Data Entry

The Edit tab of the Options dialog box.

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:

Private Sub Worksheet_Change(ByVal Target As Range)


Application.EnableEvents = False
Target = Target * 1000
Application.EnableEvents = True
End Sub

ExcelTips: The Macros Page 90


Controlling Data Entry

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.

Creating a Shortcut for Pasting Values


One of the most often-used commands in Excel is the Paste Special option from the Edit menu,
where you can figure out exactly how you want information pasted into a worksheet. On the
Paste Special dialog box, the Values selection is undoubtedly the one used the most. Since
pasting only values in this manner is used so often, you might think that Microsoft would
provide a shortcut key to, well, just paste values.

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).

ExcelTips: The Macros Page 91


Controlling Data Entry

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.

ExcelTips: The Macros Page 92


Controlling Data Entry

The Commands tab of the Customize dialog box.

3. In the list of Categories, select the Edit category.


4. In the list of Commands, select Paste Values.
5. Use the mouse to drag the Paste Values command from the Commands list to its new
location on the toolbar. When you release the mouse button, the new icon appears on
the toolbar.
6. Click on Close to dismiss the Customize dialog box.

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

Now, follow these steps:

1. Press ALT+F8 to display the Macro dialog box.

ExcelTips: The Macros Page 93


Controlling Data Entry

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.

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.

Automatically Protecting After Input


Excel offers protection for your worksheets, meaning that you can protect the contents of cells so
they cannot be changed. Exactly how you use this protection has been discussed in other issues
of ExcelTips.

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:

Private Sub Worksheet_Change(ByVal Target As Range)


Dim MyRange As Range

ExcelTips: The Macros Page 94


Controlling Data Entry

Set MyRange = Intersect(Range("A1:D100"), Target)


If Not MyRange Is Nothing Then
Sheets("Sheet1").Unprotect password:="hello"
MyRange.Locked = True
Sheets("Sheet1").Protect password:="hello"
End If
End Sub

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.

Locking All Non-Empty Cells


Sandeep has a worksheet that has hundreds of rows and columns. Some of the cells have
information in them and some are empty. The empty cells are used for data entry. He would like
a way to easily lock all the non-empty cells in a selected range and then lock 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:

1. Select the range you want to affect.


2. Press F5. Excel displays the Go To dialog box.

ExcelTips: The Macros Page 95


Controlling Data Entry

The Go To dialog box.

3. Click Special. Excel displays the Go To Special dialog box.

The Go To Special dialog box.

4. Select the Blanks radio button.


5. Click OK.
6. Press CTRL+1. Excel displays the Format Cells dialog box.
7. Make sure the Protection tab is selected.

ExcelTips: The Macros Page 96


Controlling Data Entry

The Protection tab of the Format Cells dialog box.

8. Clear the Locked check box.


9. Click OK.

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:

1. Select the range you want to affect.


2. Press CTRL+1. Excel displays the Format Cells dialog box.
3. Make sure the Protection tab is selected.

ExcelTips: The Macros Page 97


Controlling Data Entry

4. Make sure the Locked check box is selected.


5. Click OK.
6. Press F5. Excel displays the Go To dialog box.
7. Click Special. Excel displays the Go To Special dialog box.
8. Select the Blanks radio button.
9. Click OK.
10. Press CTRL+1. Excel displays the Format Cells dialog box.
11. Make sure the Protection tab is selected.
12. Clear the Locked check box.
13. Click OK.

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

Set myCell = Selection


Cells.Select
Selection.Locked = True
myCell.Select
Selection.SpecialCells(xlCellTypeBlanks).Select
Selection.Locked = False

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.

Maintaining the Active Cell


An Excel workbook can contain any number of individual worksheets. As you move around
within the various worksheets, Excel keeps track of which cell is selected in which worksheet.
When you switch to a new worksheet, Excel makes active the cell that was last active within that
worksheet. Thus, if you last selected cell F9 in the worksheet, that is the one that is selected
when you display the worksheet again, regardless of what was selected in the previous
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

ExcelTips: The Macros Page 98


Controlling Data Entry

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.

Selecting a group of worksheets

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.

Remembering to use the CTRL-click-click-CTRL sequence can be cumbersome, at best. It is also a


sequence that can be fraught with danger, because if you forget to do step 3, you could end up
making unintended changes on your worksheets. (While you are working with grouped
worksheets, any change you make on one sheet is similarly changed on all the sheets 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:

Public sAddress As String

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:

Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, _


ByVal Target As Excel.Range)
sAddress = ActiveCell.Address
End Sub

Private Sub Workbook_SheetActivate(ByVal Sh As Object)


On Error Resume Next
If sAddress > "" Then Sh.Range(sAddress).Select

ExcelTips: The Macros Page 99


Controlling Data Entry

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:

Public WithEvents gobjXLApp As Excel.Application


Private mstrAddress As String

Private Sub gobjXLApp_WorkbookActivate(ByVal Wb As Excel.Workbook)


On Error Resume Next
If mstrAddress > "" Then ActiveSheet.Range(mstrAddress).Select
End Sub

Private Sub gobjXLApp_SheetActivate(ByVal Sh As Object)


On Error Resume Next
If mstrAddress > "" Then Sh.Range(mstrAddress).Select
End Sub

Private Sub gobjXLApp_SheetSelectionChange(ByVal Sh As Object, _


ByVal Target As Excel.Range)
mstrAddress = Selection.Address
End Sub

Next, open the "ThisWorkbook" module of the Personal workbook and copy the following code
to it:

Private mobjXLApp As New ClassXLApp

Private Sub Workbook_Open()


Set mobjXLApp.gobjXLApp = Excel.Application
End Sub

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.

ExcelTips: The Macros Page 100


Controlling Data Entry

Unique Name Entry, Take Two


Chris uses a data validation technique that successfully stops non-unique information from being
entered in a column. (This technique was described in previous issues of ExcelTips.) He
rightfully notes that there is still a problem with data validation, however: Someone can paste
information into a cell and successfully bypass all the checks in place.

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:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


On Error Resume Next
For Each TmpRng In Target
TmpVal = TmpRng.Validation.Type
If TmpVal > 0 Then
If Application.CutCopyMode = 1 Then
MsgBox "You cannot paste into validated cells."
Application.CutCopyMode = False
Exit Sub
End If
End If
Next
End Sub

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.

Inserting a Radical Symbol


A radical is a mathematical symbol used to denote “roots” of a value. The most common radical
is used to denote a square root. The typical method of inserting a radical is to hold down the ALT
key as you type 251 on the numeric keypad. Release the ALT key, and the symbol appears.

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

ExcelTips: The Macros Page 101


Controlling Data Entry

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.)

Checking for Proper Entry of Array Formulas


Jeffrey’s company has a number of reports that use an extensive number of CSE
(CTRL+SHIFT+ENTER) array formulas. When someone forgets to hold CTRL and SHIFT when
pressing ENTER, the resulting formulas do not equal the correct answer. Auditing each cell,
looking for the { } brackets is both tedious and time consuming. Jeffrey wonders if there is a
quick way to find the “missing brackets” or raise an error flag if CTRL+SHIFT+ENTER is not
pressed when it should be?

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

For Each rCell In Selection


rCell.FormulaArray = rCell.Formula
Next rCell
End Sub

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

ExcelTips: The Macros Page 102


Controlling Data Entry

Dim rCell As Range


Dim rArea As Range

Set rng = Range("CSERange")


For Each rArea In rng.Areas
For Each rCell In rArea.Cells
If rCell.HasArray = False Then
rCell.FormulaArray = rCell.Formula
End If
Next rCell
Next rArea
End Sub

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:

Function NoCellArray1(rng As Range) As Boolean


NoCellArray1 = Not rng.HasArray
End Function

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:

Function NoCellArray2(rng As Range) As Boolean


NoCellArray2 = (Evaluate(rng.FormulaArray) <> rng.Value)
End Function

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:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


If Right(Selection.FormulaArray, 5) = "(""{"")" Then
ActiveCell.Select

ExcelTips: The Macros Page 103


Controlling Data Entry

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("{").

Positioning a Column on the Screen


Riek encountered a problem while developing a macro that sets up the screen for user input.
Columns A:G always need to stay on the screen, so his macro freezes those columns. He then
issues a command to move to column Z to start input. This places columns T:Z to the right of the
frozen columns A:G. What Riek really wants is for columns Z:AF to appear to the right of A:G,
but he doesn’t know how to accomplish that.

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

ExcelTips: The Macros Page 104


Controlling Data Entry

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.

Jumping to the Start of the Next Data Entry Row


Do you need to always jump to the first cell right after all the data you’ve already put in your
worksheet? For instance, if you have a worksheet that contains data in A1:G251, do you ever
need to jump to cell A252 so that you can start entering data?

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.

Limiting Who Can Delete Data


Jim has a workbook that is used by multiple people in his company. He wonders if there is a way
to allow everyone to add data to a group of cells, yet restrict who can delete the data from the
cells. He has a group of about 50 that he wants to be able to add data, but he wants to give the
delete capability to just 2 individuals.

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:

Private Sub Worksheet_Change(ByVal Target As Range)


Dim sPassCheck As String
Dim rng As Range
Dim sTemp As String
Dim sPassword As String

ExcelTips: The Macros Page 105


Controlling Data Entry

sPassword = "Password"
sTemp = "You must enter the password to delete data"

'Use to set a single cell if more than one cell is


'in the target range
If Target.Count > 1 Then
Set rng = Target.Cells(1, 1)
Else
Set rng = Target
End If

If rng.Value = "" Then


sPassCheck = InputBox(sTemp, "Delete check!")
Application.EnableEvents = False
If sPassCheck <> sPassword Then Application.Undo
End If

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.)

ExcelTips: The Macros Page 106


Selecting Cells and Ranges

Selecting Cells and Ranges

Magnifying Only the Current Cell


Brian asked if there is a way in Excel to magnify the contents of the current cell. He’s working
on a worksheet which needs to be at a low zoom setting (30% or so) to see the whole sheet. As
different scenarios are run, cells change color depending on the result. Brian can easily see which
cells he needs to investigate, but he can't read them because of the zoom setting. He normally
changes the zoom, reads the answer, and zooms back out to run another scenario. It would be
much easier if only the current cell (the one selected) were magnified to a readable level.

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:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


MsgBox ActiveCell.Address & ": " & ActiveCell.Value
End Sub

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%.

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


FontSize = ActiveCell.Font.Size
LargeSize = FontSize * 5
Cells.Font.Size = FontSize
ActiveCell.Font.Size = LargeSize
End Sub

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.

ExcelTips: The Macros Page 107


Selecting Cells and Ranges

Still another approach is to create your own zoomed-in picture of each cell as it is selected:

Private Sub ZoomCell(ZoomIn As Single)


Dim s As Range
Set s = Selection

'Get rid of any existing zoom pictures


For Each p In ActiveSheet.Pictures
If p.Name = "ZoomCell" Then
p.Delete
Exit For
End If
Next

'Create a zoom picture


s.CopyPicture Appearance:=xlScreen, _
Format:=xlPicture
ActiveSheet.Pictures.Paste.Select
With Selection
.Name = "ZoomCell"
With .ShapeRange
.ScaleWidth ZoomIn, msoFalse, _
msoScaleFromTopLeft
.ScaleHeight ZoomIn, msoFalse, _
msoScaleFromTopLeft
With .Fill
.ForeColor.SchemeColor = 9
.Visible = msoTrue
.Solid
End With
End With
End With
s.Select
Set s = Nothing
End Sub

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:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


ZoomCell 6
End Sub

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

'Get rid of any existing zoom pictures


For Each p In ActiveSheet.Pictures
If p.Name = "ZoomCell" Then

ExcelTips: The Macros Page 108


Selecting Cells and Ranges

p.Delete
Exit For
End If
Next

'Create a zoom picture


s.CopyPicture Appearance:=xlScreen, _
Format:=xlPicture
ActiveSheet.Pictures.Paste.Select
With Selection
.Name = "ZoomCell"
With .ShapeRange
.ScaleWidth ZoomIn, msoFalse, _
msoScaleFromTopLeft
.ScaleHeight ZoomIn, msoFalse, _
msoScaleFromTopLeft
With .Fill
.ForeColor.SchemeColor = 9
.Visible = msoTrue
.Solid
End With
End With
End With
s.Select
Set s = Nothing
End Sub

Displaying the Selected Cell's Address


Excel allows you to easily see the location of the currently selected cell by examining the
contents of the Name Box, to the left of the Formula Bar. This is fine and good, but there are
times when you would like to have the address of a cell actually in a cell. For instance, you may
want cell A1 to contain the address of the currently selected cell. This means that if cell E4 were
selected, then A1 would contain its address, or $E$4. If you then pressed the right-arrow key,
then the contents of A1 would change to $F$4.

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:

Public Function CurrentCell() As String


Application.Volatile
CurrentCell = ActiveCell.Address
End Function

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

ExcelTips: The Macros Page 109


Selecting Cells and Ranges

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:

1. Display the VBA Editor by pressing ALT+F11.


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, click on the Object drop-down list and choose
Worksheet. When you do, the Procedure should change to SelectionChange, and the
framework for the event handler should appear in the code window.
4. Change the event handler so it appears as follows:
Private Sub Worksheet_SelectionChange(ByVal Target As Excel.Range)
Range("A1").Value = ActiveCell.Address
End Sub

5. Close the VBA Editor.

Now, as you move about this single sheet, the contents of A1 should be constantly updated to
reflect your location.

Selecting a Cell in the Current Row


If you are developing Excel macros, you may wonder how you can select a cell relative to the
one in which you are located. For instance, if you are using Excel and you press the HOME key,
the cell at the left side of the current row is selected. Unfortunately, using the macro recorder to
record this does not help in this situation, since it records destination of the action, instead of the
your actual action. For instance, if you press HOME and you are on the fourth row in a worksheet,
Excel doesn’t record the Home action, but instead records the destination, as follows:

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:

Range("A" & (ActiveCell.Row)).Select

ExcelTips: The Macros Page 110


Selecting Cells and Ranges

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.

Selecting a Range of Cells Relative to the Current


Cell
Sometimes in a macro it is helpful to select cells relative to whichever cell is currently selected.
For instance, let’s say you want to select the first three cells of the current row. You can do that
by using the following VBA code:

Range(Cells(Selection.Row, 1), Cells(Selection.Row, 3)).Select

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:

Range(ActiveCell.Offset(-3, 5), ActiveCell.Offset(0, 10)).Select

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.

ExcelTips: The Macros Page 111


Selecting Cells and Ranges

Selecting the First Cell In a Row


If you need to select the first cell in a row from within your macro, you can do it with the Select
method, as follows:

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.

Selecting a Specific Cell in a Macro


It is often necessary to select a particular cell in a macro. It is harder, however, to select that cell
if it is in a different workbook. For instance, consider the following two lines of code:

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:

ExcelTips: The Macros Page 112


Selecting Cells and Ranges

Sub CellSelect4()
Application.Goto _
Reference:=Workbooks("pwd.xls").Sheets("Sheet3").[A18] _
Scroll:=True
End Sub

Highlighting the Rows of Selected Cells


Sometimes it is easy to lose track of where the selected cell is located in a worksheet. There are
several ways you can locate the cell, but sometimes it would be handy to just have a way to
highlight the whole row of the selected cell.

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:

Sub Worksheet_SelectionChange(ByVal Target As Excel.Range)


Static rr
Static cc

If cc <> "" Then


With Columns(cc).Interior
.ColorIndex = xlNone
End With
With Rows(rr).Interior
.ColorIndex = xlNone
End With
End If

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

ExcelTips: The Macros Page 113


Selecting Cells and Ranges

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.

Choosing Direction After Enter On a Workbook


Basis
When you press ENTER after typing information into a cell, Excel normally saves your
information and then moves to the next cell beneath the one where you pressed ENTER. You can
modify this behavior, however. 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.)
2. At the left of the dialog box click Advanced.

ExcelTips: The Macros Page 114


Selecting Cells and Ranges

The advanced options of the Excel Options dialog box.

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.

Follow these steps in older versions of Excel:

1. Choose Options from the Tools menu. Excel displays the Options dialog box.
2. Click on the Edit tab.

ExcelTips: The Macros Page 115


Selecting Cells and Ranges

The Edit tab of the Options dialog box.

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:

Private Sub Workbook_WindowActivate(ByVal Wn As Excel.Window)


bMove = Application.MoveAfterReturn
lMoveDirection = Application.MoveAfterReturnDirection

Application.MoveAfterReturn = True
Application.MoveAfterReturnDirection = xlUp
End Sub

Private Sub Workbook_WindowDeactivate(ByVal Wn As Excel.Window)


Application.MoveAfterReturn = bMove
Application.MoveAfterReturnDirection = lMoveDirection
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

ExcelTips: The Macros Page 116


Selecting Cells and Ranges

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.

Public lMoveDirection As Long


Public bMove As Boolean

Automatically Moving from Cell to Cell when


Entering Data
Sheila has a worksheet in which a series of four-digit numbers needs to be entered. She would
like a way where ENTER or TAB doesn't need to be pressed between each entry. In other words,
after each fourth digit is pressed, Sheila wants Excel to automatically advance to the next cell.

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

str = InputBox("Enter string")


y = 0
For x = 1 To Len(str) Step 4
ActiveCell.Offset(0, y) = "'" & Mid(str, x, 4)
y = y + 1
Next
End Sub

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.

ExcelTips: The Macros Page 117


Selecting Cells and Ranges

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:

Private Sub vText_Change()


If Len(vText) = 4 Then
ActiveCell = vText
ActiveCell.Offset(0, 1).Activate
UserForm1.vText.Value = ""
End If
End Sub

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:

Private Sub Cancel_Click()


Unload UserForm1
End Sub

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

ExcelTips: The Macros Page 118


Selecting Cells and Ranges

The user form appears and you can start typing away. When you are done, just click the button
and the user form is closed.

Using a Macro to Select a Modified Table Body


Mike has an Excel table defined (via insert | table) and he wants to select just the data portion of
the table using VBA. He knows he can use the DataBodyRange.Select method, but this just
seems to select everything apart from the header row. In Mike’s table the first row contains
headings, the last row and last column contain formulas, and the first column contains row
headings, so he wants to exclude these from the selection. The table can expand both by rows
and columns, so he needs some way to select this data dynamically. Any thoughts on how this
can be done?

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.

ExcelTips: The Macros Page 119


Affecting Worksheet Data

Affecting Worksheet Data

Inserting Worksheet Values with a Macro


Inserting values into a cell is done quite often in macros. In order to insert information into a cell,
you use the Value property. For instance, you could use the following to insert a number (23)
into cell A1:

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"

Filling a Range of Cells with Values


Jonathan is creating a macro and needs to fill a range of cells with values. For instance, if he
needs to fill the range A1:C1, it currently takes three statements to fill that range:

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:

ExcelTips: The Macros Page 120


Affecting Worksheet Data

sOne = "Apples"
sTwo = "Oranges"
sThree = "Artichokes"
Range("A1:C1") = Array(sOne, sTwo, sThree)

You can also work with straight variables, if you prefer:

Dim sMyStrings(2) As String


sMyStrings(0) = "Apples"
sMyStrings(1) = "Oranges"
sMyStrings(2) = "Artichokes"
Range("A1:C1") = sMyStrings

The above code could also be rewritten, as follows:

Dim sMyStrings(2) As String


sMyStrings = Array("Apples", "Oranges", "Artichokes")
Range("A1:C1") = sMyStrings

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"))

Copying Named Ranges


Graeme has a workbook that has a large number (120+) named ranges defined within it. He
would like to copy the range names and definitions to a different workbook. Thus, after copying,
the range named MyRange1 which refers to the range C7:H22 in the original workbook will
exist in the target workbook and refer to the same range, in the target workbook. Nothing else
should be copied from the original workbook to the target—just the range names and definitions.

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

Set Source = ActiveWorkbook


Set Target = Workbooks("Book2.xlsx")

For Each n In Source.Names


Target.Names.Add Name:=n.Name, RefersTo:=n.Value
Next
End Sub

ExcelTips: The Macros Page 121


Affecting Worksheet Data

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.

Copying to Very Large Ranges


Chris wonders if there is a fast way to copy a cell to a very large range. He knows how to use the
mouse to scroll in order to select the target range, but if he's copying to thousands (or tens of
thousands) of cells, it takes an awfully long time to scroll through screen after screen.

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:

1. Select cell A3.


2. Press CTRL+C to copy its contents to the Clipboard.
3. Click once in the Name box, above column A. (Before you click, the Name box
contains "A3," which is the cell you just copied.)
4. Type C3:C55000 and press ENTER. The range is selected.
5. Press CTRL+V.

Easy, huh? A similar approach to selecting large ranges could also be used with the Go To box,
in this manner:

1. Select cell A3.


2. Press CTRL+C to copy its contents to the Clipboard.
3. Press F5 to display the Go To dialog box.

ExcelTips: The Macros Page 122


Affecting Worksheet Data

The Go To dialog box.

4. In the Reference box type C3:C55000.


5. Click OK. The range is selected.
6. Press CTRL+V.

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.

Clearing Everything Except Formulas


Roni wants want to clear everything in a workbook except for cells which may contain formulas.
This task can be completed either manually or through the use of a macro.

If you want to do the clearing manually, you can follow these steps:

ExcelTips: The Macros Page 123


Affecting Worksheet Data

1. Press F5. Excel displays the Go To dialog box.

The Go To dialog box.

2. Click the Special button. Excel displays the Go To Special dialog box.

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.)

ExcelTips: The Macros Page 124


Affecting Worksheet Data

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

For Each wks In Worksheets


'ignore errors in case there is only formulas
On Error Resume Next
wks.Cells.SpecialCells _
(xlCellTypeConstants, 23).ClearContents
On Error GoTo 0
Next
Set wks = Nothing
End Sub

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.

Converting Text Case


If you use worksheets that have quite a bit of text in them, there may be times you long for a
function like Word has that easily converts between upper and lower case. Excel contains such
functions, but they are designed to be used in macros, not as commands from the menus.

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

Set MyRange = ActiveSheet.Range(ActiveWindow.Selection.Address)


For CellCount = 1 To MyRange.Cells.Count
If Not MyRange.Cells(CellCount).HasFormula Then
MyText = MyRange.Cells(CellCount).Value
MyRange.Cells(CellCount).Value = LCase(MyText)
End If
Next CellCount
End Sub

ExcelTips: The Macros Page 125


Affecting Worksheet Data

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).

Converting Cells to Proper Case


Have you ever run into people who insist on typing everything with the CAPS LOCK key on? In
some worksheets, that may not be acceptable. Yet, there you are, with a worksheet full of text
cells that are all in uppercase. How do you convert everything to upper- and lowercase, without
the need to retype?

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

Set rngSrc = ActiveSheet.Range(ActiveWindow.Selection.Address)


lMax = rngSrc.Cells.Count

For lCtr = 1 To lMax


If Not rngSrc.Cells(lCtr).HasFormula Then
rngSrc.Cells(lCtr) = Application.Proper(rngSrc.Cells(lCtr))
End If
Next lCtr
End Sub

Modifying Proper Capitalization


Like many people, Kirk copies information into Excel worksheets that originates in other places.
The information that Kirk copies typically is all in CAPS, and he wants to convert it to what
Excel refers to as “proper case” (only the first letter of each word is capitalized). The problem is,
the PROPER worksheet function, which does the conversion, doesn’t pay attention to the words
it is capitalizing. Thus, words like a, an, in, and, the, and with are all initial-capped. Kirk doesn’t
want those words (and perhaps some others) capitalized.

There are several ways you can approach this problem. One is to use a rather long formula to do
the conversion:

ExcelTips: The Macros Page 126


Affecting Worksheet Data

=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.

Function MyProper(str As String)


Dim vExclude
Dim i As Integer
vExclude = Array("a", "an", "in", "and", _
"the", "with", "is", "at")

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.

Function ProperSpecial(cX As Range)


' rng = target Cell

Dim c As Range
Dim sTemp As String

sTemp = Application.WorksheetFunction.Proper(cX.Value)
For Each c In Worksheets("WordList").Range("MyList")

ExcelTips: The Macros Page 127


Affecting Worksheet Data

sTemp = Application.WorksheetFunction.Substitute( _
sTemp, Application.WorksheetFunction.Proper( _
" " & c.Value & " "), (" " & c.Value & " "))
Next c

ProperSpecial = sTemp
End Function

Capitalizing Just a Surname


Cheryl is using a worksheet that has, in column A, client names in the format "Smith, Jane." She
would like to capitalize only the surname, as in "SMITH, Jane", leaving the rest of the name
unchanged.

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.

Converting Text to Numbers


If you are using Excel to grab information from an external source, it is possible that you could
end up with some pretty strange information in your cells. For instance, let’s say that you have
cells that contain numbers such as 1,234.5-. These are formatted as text cells in Excel, and
therefore cannot be used in calculations.

ExcelTips: The Macros Page 128


Affecting Worksheet Data

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

Set MyRange = ActiveSheet.Range(ActiveWindow.Selection.Address)


For CellCount = 1 To MyRange.Cells.Count
MyText = MyRange.Cells(CellCount).Value
If VarType(MyText) = vbString Then
MyText = Trim(MyText)
If Right(MyText, 1) = "-" Then
MyText = "-" & Left(MyText, Len(MyText) - 1)
MyRange.Cells(CellCount).Value = MyText
End If
End If
Next CellCount
End Sub

Converting Imported Information to Numeric


Values
Garrett asked if there was a way to quickly convert text data to numerical data. He is importing a
text file that uses spaces in the thousands place (1 256) instead of a comma (1,256).

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," ","")

ExcelTips: The Macros Page 129


Affecting Worksheet Data

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

For Each c In Selection


tmpText = "" 'Initialize

'Check each character to see if it's a space


'If it isn't, add it to tmpText
For i = 1 To Len(c.Text)
If Mid(c.Text, i, 1) <> " " Then
tmpText = tmpText & Mid(c.Text, i, 1)
End If
Next i

'tmpText is now the cell contents without spaces


'If tmpText is a number, assign its value to
'the current cell
If IsNumeric(tmpText) Then
c.Value = tmpText
End If
Next c
End Sub

Converting to ASCII Text


Brenda has a lot of information that has been imported or pasted into a worksheet. Sometimes
the text in the worksheet will contain "foreign" and strange characters. She wonders if there is a
way to easily convert the data so that it contains no non-ASCII characters and, perhaps, some
foreign characters are converted to regular ASCII values (such as converting accented letters to
non-accented letters).

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:

ExcelTips: The Macros Page 130


Affecting Worksheet Data

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.

Getting Rid of 8-Bit ASCII Characters


Bill often has to import information into a worksheet so that he can process it for his company.
In Bill’s situation, one of the first steps he needs to do is to remove all the 8-bit ASCII characters
that may be in the imported data. These characters don’t need to be replaced with anything; they
just need to be deleted so that only 7-bit ASCII characters remain. Bill wonders if there is an
easy way to do this, perhaps with a macro of some type.

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

ExcelTips: The Macros Page 131


Affecting Worksheet Data

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

For Each rngCell In Selection


strCheckString = rngCell.Value
strClean = ""
For intChar = 1 To Len(strCheckString)
strCheckChar = Mid(strCheckString, intChar, 1)
intCheckChar = Ascw(strCheckChar)
If intCheckChar < 128 Then
strClean = strClean & strCheckChar
End If
Next intChar
rngCell.Value = strClean
Next rngCell
End Sub

Note that the macro uses the Ascw function instead of the traditional Asc function so that it looks
at Unicode values.

Creating a Plus/Minus Button


On some calculators there is a little button that can come in very handy: the plus/minus button.
This button, when pressed, will switch whatever value is on the display between its positive and
negative values. For instance, if the display shows the number 57, then pressing the button will
change the display to -57. Pressing it again will switch the value back to 57.

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

For Each cell In Selection

ExcelTips: The Macros Page 132


Affecting Worksheet Data

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.

Finding the Lowest Numbers


You may have a need at some point to find the lowest numbers in a list of values. This is
relatively easy to do if you use the SMALL worksheet function. The function takes two
parameters: the range of the values to be evaluated and an indicator of which smallest number
you want. For instance, the following will return the second lowest number in the range of
A1:A100:

=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.

Function SMALLn(rng As Range, n)


Application.Volatile
SMALLn = False
If n < 1 Then Exit Function
Dim i As Long, j As Long, k As Long, min, arr, arr2
arr = Application.Transpose(rng)
ReDim arr2(n - 1)
min = Application.WorksheetFunction.Min(arr)
j = UBound(arr)
k = 0
arr2(k) = min
For i = 1 To j
If Application.Small(arr, i) <> arr2(k) Then
k = k + 1
arr2(k) = Application.Small(arr, i)
If k = n - 1 Then
SMALLn = arr2(k)
Exit For

ExcelTips: The Macros Page 133


Affecting Worksheet Data

End If
End If
Next i
End Function

This user-defined function is used in the following manner:

=SMALLn(A1:A100,2)

When called like this, the function returns the second lowest unique value in the specified range.

Finding the Smallest Even Value


Emin has a range of cells in which there can be either text or numbers. He needs a way to
determine the smallest even number in the range. Emin wonders if this can be done with a
formula, or if he needs a user-defined function.

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:

Function MinEven(rng As Range)


Dim rCell As Range
Dim bNotFound As Boolean

Application.Volatile
MinEven = 9.99 * 10 ^ 307
bNotFound = True

ExcelTips: The Macros Page 134


Affecting Worksheet Data

For Each rCell In rng


If Application.WorksheetFunction.IsNumber(rCell) Then
If rCell Mod 2 = 0 Then
If rCell < MinEven Then
MinEven = rCell
bNotFound = False
End If
End If
End If
Next
If bNotFound Then MinEven = CVErr(xlErrNum)
End Function

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.

Finding Odd Values Greater Than 50


Amol has 1,000 values in an Excel worksheet, occupying 100 rows of 10 columns each. Each
value in this range is an integer value between 0 and 99. Amol needs a way to count and display
all the values which are odd and greater than 50.

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:

1. Select the cells that contain your data.


2. Display the Home tab of the ribbon.
3. Click the Conditional Formatting tool in the Styles group. Excel displays a palette of
options related to conditional formatting.
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 Use a Formula To
Determine Which Cells to Format.

ExcelTips: The Macros Page 135


Affecting Worksheet Data

The New Formatting Rule dialog box.

6. In the formula box enter the formula =AND(MOD(A1,2),A1>50).


7. Click the Format button. Excel displays the Format Cells dialog box.

ExcelTips: The Macros Page 136


Affecting Worksheet Data

The Format Cells dialog box.

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)))

ExcelTips: The Macros Page 137


Affecting Worksheet Data

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

Converting from Relative to Absolute


Excel allows you to easily edit formulas. In doing so, you can quickly change a cell reference or
a range reference from relative to absolute. What if you have a large number of cells in which
you need to change from relative to absolute referencing? In this instance, the nature of the
problem is well-suited to being solved through a macro.

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.)

ExcelTips: The Macros Page 138


Affecting Worksheet Data

Reversing Cell Contents


Did you ever want to reverse the contents of what is contained in a cell? Using the Reverse
macro, you can easily change "My text" to "txet yM." The macro is instructive in techniques to
access and modify the contents of a cell.

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.

Reversing Names In Place


George often has to work with data provided by other people. In working with this data he may
need to convert a name, say Joe Bloggs, so that the last name is first, as in Bloggs, Joe. George
understands that he can use a formula to do the name reversal, but he needs to do it in the same
cell in which the name resides. He wonders if there is a built-in command that will perform this
task.

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

For Each rCell In Selection


sCell = rCell.Value
x = InStr(sCell, " ")
If x > 0 Then
sFirst = Left(sCell, x - 1)
sLast = Mid(sCell, x + 1)
rCell.Value = sLast & ", " & sFirst
End If
Next
Set rCell = Nothing
End Sub

ExcelTips: The Macros Page 139


Affecting Worksheet Data

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.

Concatenating Ranges of Cells


Excel provides one workbook function and one operator that both have the same purpose—to
combine strings into a longer string. The CONCATENATE function and the ampersand (&)
operator have essentially the same purpose.

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:

Function Concat1(myRange As Range, Optional myDelimiter As String)


Dim r As Range

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:

Function Concat2(myRange As Range, Optional myDelimiter As String)


Dim r As Range

Application.Volatile
For Each r In myRange
If Len(r.Text) > 0 Then

ExcelTips: The Macros Page 140


Affecting Worksheet Data

Concat = Concat & r & myDelimiter


End If
Next r
If Len(myDelimiter) > 0 Then
Concat = Left(Concat, Len(Concat) - Len(myDelimiter))
End If
End Function

Concatenating Values from a Variable Number of


Cells
Pam has two columns of data. In column A there are simple identifiers, such as A, B, C, etc. In
column B there are a series of integer values. She can sort the data by the identifier and,
secondarily, by the integer values. Now she wants, in column C, to have a formula that will
concatenate all the integer values for a particular identifier. Thus, if A1:A4 all contain the
identifier A, then in cell C1 she would like to have all the values in B1:B4 concatenated and
divided by commas, such as "11, 17, 19, 25". Since the number of rows for each identifier can be
different, Pam isn't sure how to go about the concatenation.

The easiest way to accomplish this is to use a macro, which can be created as a user-defined
function. Here's an example:

Function CatSame(c As Range) As String


Application.Volatile
sTemp = ""
iCurCol = c.Column
If iCurCol = 3 Then
If c.Row = 1 Then
sLast = ""
Else
sLast = c.Offset(-1, -2)
End If
If c.Offset(0, -2) <> sLast Then
J = 0
Do
sTemp = sTemp & ", " & c.Offset(J, -1)
J = J + 1
Loop While c.Offset(J, -2) = c.Offset(J - 1, -2)
sTemp = Right(sTemp, Len(sTemp) - 2)
End If
End If
CatSame = sTemp
End Function

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:

ExcelTips: The Macros Page 141


Affecting Worksheet Data

=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:

Function VLookupAll(vValue, rngAll As Range, _


iCol As Integer, Optional sSep As String = ", ")
Dim rCell As Range
Dim rng As Range
On Error GoTo ErrHandler

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

If VLookupAll = "" Then


VLookupAll = CVErr(xlErrNA)
Else
VLookupAll = Right(VLookupAll, Len(VLookupAll) - Len(sSep))
End If
ErrHandler:
If Err.Number <> 0 Then VLookupAll = CVErr(xlErrValue)
End Function

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:

ExcelTips: The Macros Page 142


Affecting Worksheet Data

=B1

This formula should go into cell C2:

=IF(A2=A1,C1 & ", " & B2, B2)

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.

Finding the First Non-Digit in a Text Value


Tony has a bunch of data in a worksheet that consists of digits and other characters. For instance,
he might have a cell that contains “1234567Blue.” Tony wants to be able to figure out the
character position at which the first non-digit character occurs. In the example of the text
“1234567Blue” Tony wants some way to figure out that the first non-digit character is at position
8.

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

ExcelTips: The Macros Page 143


Affecting Worksheet Data

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:

Function FirstNonDigit(str As String)


Dim iChar As Integer
Dim iPos As Integer
Dim J As Integer

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.

Identifying Digit-Only Part Numbers Excluding


Special Characters
Chris has a large number of cells that contain part numbers. These cells can contain either digits
or characters, in any combination. They can also contain special characters such as dashes,
slashes, and spaces. Chris needs a way to identify if a cell contains only digits, without taking the
special characters into account. Thus, a cell containing 123-45 would show as containing only
digits, while 123AB-45 would not.

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:

=IF(IFERROR(INT(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(A1,"-", ""),"/", "")," ",


"")),FALSE), TRUE, FALSE)
=OR(ISNUMBER(SUBSTITUTE(A1,"-
","")+0),ISNUMBER(SUBSTITUTE(A1,"/","")+0),ISNUMBER(SUBSTITUTE(A1," ","")+0))
=ISNUMBER(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(A1," ",""),"/",""),"-","")*1)

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:

ExcelTips: The Macros Page 144


Affecting Worksheet Data

Function DigitsOnly(sRaw As String) As Boolean


Dim X As Integer
Const sAllowed As String = "0123456789 -/"

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.

Finding the Nth Occurrence of a Character


Barry often finds himself wanting to identify the Nth occurrence of a character within a text
string. He knows he can use the SEARCH and FIND worksheet functions for finding an initial
occurrence, but is unsure how to find, say, the 3rd occurrence of the letter "B" within a text
string.

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.

A more flexible formula would be the following:

=FIND(CHAR(1),SUBSTITUTE(A1,"B",CHAR(1),3))

ExcelTips: The Macros Page 145


Affecting Worksheet Data

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.

Function FindN(sFindWhat As String, _


sInputString As String, N As Integer) As Integer
Dim J As Integer

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)

Extracting a Pattern from within Text


Tom has a worksheet that contains about 20,000 cells full of textual data. From within these cells
he needs to extract a specific pattern of text. The pattern is ##-##### where each # is a digit. This
pattern does not appear at a set place in each cell. Tom wonders if there is a way to extract the
desired information.

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:

ExcelTips: The Macros Page 146


Affecting Worksheet Data

=IF(ISERROR(INT(MID(A1, FIND("-", A1, 1)-2, 2)) & INT(MID(


A1, FIND("-", A1, 1)+1, 5))), "", MID(A1, FIND("-", A1)-2, 8))

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

For Each c In Selection.Cells


If c.Value Like "*##-#####*" Then
sRaw = c.Value
iPos = InStr(sRaw, "-")
Do While iPos > 0
If iPos < 3 Then
sRaw = " " & sRaw
iPos = iPos + 2
End If
sTemp = Mid(sRaw, iPos - 2, 8)
sRaw = Mid(sRaw, iPos + 6, Len(sRaw))
If sTemp Like "##-#####" Then
TargetSheet.Cells(iTargetRow, 1) = sTemp
iTargetRow = iTargetRow + 1
Else
sRaw = Mid(sTemp, 4, 5) & sRaw
End If
iPos = InStr(sRaw, "-")
Loop
End If
Next c
End Sub

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.

ExcelTips: The Macros Page 147


Affecting Worksheet Data

Shortcut to Merge Cells


Excel allows you to merge cells together in two ways: using the Merge and Center tool on the
ribbon or toolbar, or by using the controls in the Alignment tab of the Format Cells dialog box. If
you merge cells quite a bit, you might long for a shortcut that will merge whatever cells you’ve
selected.

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

Identifying Merged Cells


Alan asked if there is a way to quickly and easily identify which cells are merged in a workbook
created by someone else. There are, in reality, several different ways you can go about
identifying these cells.

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.

ExcelTips: The Macros Page 148


Affecting Worksheet Data

The Find tab of the Find and Replace dialog box.

3. Make sure the Find What box is empty.


4. With the insertion point in the Find What box, click the Format button. Excel displays
the Find Format dialog box.
5. Make sure the Alignment tab is displayed.

ExcelTips: The Macros Page 149


Affecting Worksheet Data

The Alignment tab of the Find Format dialog box.

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

ExcelTips: The Macros Page 150


Affecting Worksheet Data

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:

Function FindMerged3(rCell As Range)


FindMerged3 = rCell.MergeCells
End Function

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

ExcelTips: The Macros Page 151


Affecting Worksheet Data

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.

Getting Rid of Spaces in Cells


Carole imports information into Excel from a different program, and this often leaves extra
spaces in some cells. The spaces are the only things in the cells, so they appear to be empty but
really aren’t. Carole wondered about the best way to get rid of these unnecessary spaces.

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

ExcelTips: The Macros Page 152


Affecting Worksheet Data

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

Set rText = Cells.SpecialCells( _


xlCellTypeConstants, _
xlTextValues)
For Each rCell In rText
If Trim(rCell.Value) = "" Then
rCell.ClearContents
End If
Next
Set rText = Nothing
Set rCell = Nothing
End Sub

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.

Removing Duplicate Cells


I can't tell you the number of times I have received raw data from some program or from some
person, and the first thing I need to do is remove duplicates from the list. If you find yourself in
the same situation, the following macro will be a huge help:

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

'Start wiping out duplicates


For J = ThisRow To (ThatRow - 1)
If Cells(J, ThisCol) > "" Then
For K = (J + 1) To ThatRow
If Cells(J, ThisCol) = Cells(K, ThisCol) Then

ExcelTips: The Macros Page 153


Affecting Worksheet Data

Cells(K, ThisCol) = ""


End If
Next K
End If
Next J

'Remove cells that are empty


For J = ThatRow To ThisRow Step -1
If Cells(J, ThisCol) = "" Then
Cells(J, ThisCol).Delete xlShiftUp
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 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.

Deleting Duplicate Text Values


Everybody runs into the need at one time or another—to delete duplicate entries from a list of
text entries. Suppose you have the text values in column A of a worksheet, and they run for about
500 rows. If you want to delete any duplicates in the list, you may be looking for the easiest way
to do it.

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:

ExcelTips: The Macros Page 154


Affecting Worksheet Data

Sub CreateUniqueList()
Dim rData As Range
Dim rTemp As Range

Set rData = Range(Range("a1"), Range("A65536").End(xlUp))


rData.EntireColumn.Insert
Set rTemp = rData.Offset(0, -1)
rData.AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=rTemp, _
Unique:=True

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

'Start wiping out duplicates


For J = ThisRow To (ThatRow - 1)
If Cells(J, ThisCol) > "" Then
For K = (J + 1) To ThatRow
If Cells(J, ThisCol) = Cells(K, ThisCol) Then
Cells(K, ThisCol) = ""
End If
Next K
End If
Next J

'Remove cells that are empty


For J = ThatRow To ThisRow Step -1
If Cells(J, ThisCol) = "" Then
Cells(J, ThisCol).Delete xlShiftUp
End If
Next J
Application.ScreenUpdating = True
End Sub

ExcelTips: The Macros Page 155


Affecting Worksheet Data

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.

Removing Duplicates Based on a Partial Match


Farris has a worksheet that contains addresses. Some addresses are very close to the same, such
that the street address is the same and only the suite number portion of the address differs. For
instance, one row may have an address of "85 Seymour Street, Suite 101" and another row may
have an address of "85 Seymour Street, Suite 412." Farris is wondering how to remove the
duplicates in the list of addresses based on a partial match—based only on the street address and
ignoring the suite number.

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.)

ExcelTips: The Macros Page 156


Affecting Worksheet Data

The Convert Text to Columns wizard.

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

ExcelTips: The Macros Page 157


Affecting Worksheet Data

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.

Function NearMatch(vLookupValue, rng As Range, iNumChars)


Dim x As Integer
Dim sSub As String

Set rng = rng.Columns(1)


sSub = Left(vLookupValue, iNumChars)
For x = 1 To rng.Cells.Count
If Left(rng.Cells(x), iNumChars) = sSub Then
NearMatch = rng.Cells(x).Address
Exit Function
End If
Next
NearMatch = CVErr(xlErrNA)
End Function

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.

Deleting Everything Up to a Character Sequence


Steven has a worksheet that has lots of text in it. In the cells in column A he wants to delete
everything that may occur before a given sequence of characters, such as everything before

ExcelTips: The Macros Page 158


Affecting Worksheet Data

=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

Set rCell = Nothing


End Sub

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:

rCell.Value = Mid(rCell, x + Len(sSeq))

Extracting First and Last Words


Reggie has a cell that contains three or more words. (The number of words could vary.) He needs
a formula that allows him to extract either the first word of the cell or the last word of the cell.
For instance, if the cell contains the phrase "Reggie was here in 2012", then he needs a formula
to extract "Reggie" and one to extract "2012".

ExcelTips: The Macros Page 159


Affecting Worksheet Data

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:

1. Count the number of spaces


2. Change the last space to a different character (which is not elsewhere in the phrase)
3. Then find that “different character”
4. Take the portion of the phrase to the right of that “different character”

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:

SUBSTITUTE(A1," ",CHAR(1),LEN(A1)-LEN(SUBSTITUTE(A1," ","")))

You can then FIND the location of char(1) in that string:

FIND(CHAR(1),SUBSTITUTE(A1," ",CHAR(1),LEN(A1)-
LEN(SUBSTITUTE(A1," ",""))))

The first character of the last word is 1 character past this:

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:

ExcelTips: The Macros Page 160


Affecting Worksheet Data

Function FirstWord(c As String)


Dim arr

arr = Split(Trim(c), " ")


FirstWord = arr(LBound(arr))
End Function

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:

Function LastWord(c As String)


Dim arr

arr = Split(Trim(c), " ")


LastWord = arr(UBound(arr))
End Function

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)

Removing Cells from a Selected Range


Let's say that you've selected a large range of cells, such as A7:R182. You want to perform some
sort of operation on all the cells in this range, except a few. You might wonder how to remove a
couple of cells within the range from the selection set, so you hold down the CTRL key as you
click on those cells. That doesn't work; Excel simply unselects the range you previously selected.

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

Set rSelect = Selection


Set rUnSelect = Application.InputBox( _
"What cells do you want to exclude?", Type:=8)

For Each rCell In rSelect

ExcelTips: The Macros Page 161


Affecting Worksheet Data

If Intersect(rCell, rUnSelect) Is Nothing Then


If rNew Is Nothing Then
Set rNew = rCell
Else
Set rNew = Union(rNew, rCell)
End If
End If
Next
rNew.Select

Set rCell = Nothing


Set rSelect = Nothing
Set rUnSelect = Nothing
Set rNew = Nothing
End Sub

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

The tool applicable to this tip is the Select tool.

Deleting Old Data from a Worksheet


Gene is looking for a way to quickly delete data from a worksheet based on the date in a
particular column. If the date is older than today (the date is passed) then the row should be
deleted.

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

iCol = 7 'Filter all on Col G

For x = Cells(Cells.Rows.Count, iCol).End(xlUp).Row To 2 Step -1


If Cells(x, iCol).Value < Date Then
Cells(x, iCol).EntireRow.Delete
End If
Next
End Sub

ExcelTips: The Macros Page 162


Affecting Worksheet Data

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

'Format dates as text


Range("Dates").NumberFormat = "@"
'Today’s date in number format
currDate = CDbl(Date)
Range("Dates").AutoFilter Field:=1, _
Criteria1:="<" & currDate
nRows = Range("Dates").Rows.Count
Rows("2:" & nRows).Select
Selection.Delete Shift:=xlUp
Range("Dates").AutoFilter
Range("Dates").NumberFormat = "m/d/yyyy"
Range("C2").Select
End Sub

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.

Deleting Unwanted Styles


When you work with other people who use Excel, it is not unusual to copy worksheets from their
workbooks into your own workbook. When you do so, the worksheet isn’t the only thing that is
copied—Excel also copies their formatting styles to your workbook. Manually deleting the
unwanted styles can be a hassle, depending on the number of styles. Removing user-defined
styles is very easy, though, if you use a macro. The following macro will quickly delete the
unwanted styles:

Sub StyleKill()
Dim styT As Style
Dim intRet As Integer

For Each styT In ActiveWorkbook.Styles


If Not styT.BuiltIn Then
intRet = MsgBox("Delete style '" & styT.Name & "'?", vbYesNo)
If intRet = vbYes Then styT.Delete
End If
Next styT
End Sub

ExcelTips: The Macros Page 163


Affecting Worksheet Data

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.

Conditional Page Breaks


Excel is a handy tool for keeping track of all sorts of data. Many people use it at work to create
ad-hoc reports for different departments or projects. As you work with your data, you may
wonder how you can automatically insert page breaks when the contents of a certain column
change. For instance, you might have a column that contains department names, and you may
want each department to start on a new page.

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.

The Subtotal dialog box.

4. Using the At Each Change In drop-down list, select Department.

ExcelTips: The Macros Page 164


Affecting Worksheet Data

5. Using the Use Function drop-down list, select Count.


6. Using the Add Subtotal To list, select the name of the column where you want your
subtotal to appear.
7. Make sure the Page Break Between Groups check box is selected.
8. Click on OK. Excel adds the subtotals and the page counts, as directed.

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

Set CellRange = Selection


For Each TestCell In CellRange
ActiveSheet.Rows(TestCell.Row).PageBreak = xlPageBreakNone
If TestCell.Value <> TestCell.Offset(-1, 0).Value Then
ActiveSheet.Rows(TestCell.Row).PageBreak = xlPageBreakManual
End If
Next TestCell
End Sub

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

ExcelTips: The Macros Page 165


Affecting Worksheet Data

Dim rng As Range


Dim iCol As Integer
Dim iOffset As Integer

iCol = 19 '19 is Column S


iOffset = 1 'Positives go right, negatives go left
Set rng = Intersect(Selection.CurrentRegion, Columns(iCol))
For Each rCell In rng
If InStr(rCell.Formula, "SUBTOTAL") Then
rCell.Offset(0, iOffset).Formula = _
rCell.Formula
rCell.ClearContents
End If
Next
End Sub

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.

Formatting Subtotal Rows


When you add subtotals to a worksheet, Excel automatically formats the subtotals using a bold
font. You, however, may want to have some different type of formatting for the subtotals, such
as shading them in yellow or a different color.

If you use subtotals sparingly, and only want to apply a different format for one or two
worksheets, you can follow these general steps:

1. Apply your subtotals, as desired.


2. Select the entire data table, including the subtotals.
3. Using the Outline area at the left of the screen, collapse the detail in your worksheet so
that only the subtotals are showing.
4. Press F5 to display the Go To dialog box.

ExcelTips: The Macros Page 166


Affecting Worksheet Data

The Go To dialog box.

5. Click Special to display the Go To Special dialog box.

The Go To Special dialog box.

6. Select the Visible Cells Only option button.


7. Click OK. Now, only the visible subtotal rows are selected.
8. Apply your formatting, as desired.

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:

ExcelTips: The Macros Page 167


Affecting Worksheet Data

1. Before applying your subtotals, select your entire data table.


2. Choose Conditional Formatting from the Formatting menu. Excel displays the
Conditional Formatting dialog box.
3. In the left-most drop-down list, choose Formula Is. The dialog box changes to reflect
your choice.
4. In the formula box, just to the right of the drop-down list used in step 3, enter the
following formula: =ISNUMBER(FIND("Grand Total",$A1))

The Conditional Formatting dialog box.

5. Click Format to display the Format Cells dialog box.


6. Using the controls in the dialog box, set the formatting as you want it applied to the
Grand Total row.
7. Click OK to dismiss the Format Cells dialog box.
8. Click Add. Excel adds a second conditional format.
9. In the left-most drop-down list of the second condition, choose Formula Is. The dialog
box changes to reflect your choice.
10. In the formula box, just to the right of the drop-down list used in step 9, enter the
following formula: =ISNUMBER(FIND("Total",$A1))
11. Click Format to display the Format Cells dialog box.

ExcelTips: The Macros Page 168


Affecting Worksheet Data

The Format Cells dialog box.

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.

ExcelTips: The Macros Page 169


Affecting Worksheet Data

The New Formatting Rule dialog box.

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.

ExcelTips: The Macros Page 170


Affecting Worksheet Data

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

For Each rCell In Selection


If Right(rCell.Value, 5) = "Total" Then
Rows(rCell.Row).Interior.ColorIndex = 36
End If

If Right(rCell.Value, 11) = "Grand Total" Then


Rows(rCell.Row).Interior.ColorIndex = 44
End If
Next
End Sub

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.

Determining Combinations to Make a Total


Suppose you have a worksheet with three columns of data. The first column has in sequential
order each letter of the alphabet, A through Z. The second column contains a number of
occurrences that correlates with the letter in the alphabet. The third column contains a number of
hours that correlates with the letter in the alphabet.

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.

This is actually a well-known problem in the field of discrete mathematics. A variety of


algorithms have been developed to provide solutions, and there are certain programming
languages (such as LISP) that greatly facilitate creating tree structures that can “search” for
optimal solutions.

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.

Function DoDist(sRaw As Range, _


iTCol As Integer, _
iBuckets As Integer, _
iWanted As Integer, _
iRetCol As Integer) As String

ExcelTips: The Macros Page 171


Affecting Worksheet Data

Dim lGTotal As Long


Dim lPerBucket As Long
Dim lCells() As Long
Dim sRet() As String
Dim lBk() As Long
Dim sBk() As String
Dim lTemp As Long
Dim sTemp As String
Dim J As Integer
Dim K As Integer
Dim L As Integer

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

lPerBucket = lGTotal / iBuckets


For J = 1 To sRaw.Rows.Count
L = iBuckets
For K = iBuckets To 1 Step -1
If lBk(K) <= lBk(L) Then L = K
Next K
lBk(L) = lBk(L) + lCells(J)
sBk(L) = sBk(L) & sRet(J) & ", "
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

ExcelTips: The Macros Page 172


Affecting Worksheet Data

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)

Putting Addresses on State-Specific Worksheets


Linda has a worksheet containing 250 addresses from all over the country. She wants to separate
the addresses, by state, to different worksheets so that each worksheet contains addresses only
from a specific state.

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

ExcelTips: The Macros Page 173


Affecting Worksheet Data

Dim rPT As Range


Dim rCell As Range
Dim iCol As Integer
Dim sHeader As String

On Error GoTo Errhandler


Application.ScreenUpdating = False
iCol = 5 'Filter all on Col E

Set wOri = ActiveSheet


With wOri
'Save Autofilter status
bAutoFilter = .AutoFilterMode
If Not bAutoFilter Then 'turn on autofilter
.Range("a1").AutoFilter
End If
If .FilterMode Then .ShowAllData

'use a PivotTable on a temp


'sheet to get a unique list
Set PT = .PivotTableWizard _
(SourceType:=xlDatabase, _
SourceData:=.Range("a1").CurrentRegion, _
TableDestination:="", _
TableName:="PivotTable1")
sHeader = .Cells(1, iCol)
With PT
.AddFields RowFields:=sHeader
.PivotFields(sHeader).Orientation = xlDataField
.ColumnGrand = False
End With
Set wPT = ActiveSheet
With wPT
Set rPT = .Range(.Range("A3"), _
.Cells(.Cells.Rows.Count, 1).End(xlUp))
End With

'loop through unique list


For Each rCell In rPT
.Range("a1").AutoFilter Field:=iCol, _
Criteria1:=rCell.Value
'create new sheet and name it with the state
Set wks = Worksheets.Add
wks.Name = rCell.Value
.AutoFilter.Range.Copy wks.Range("A1")
Next
.ShowAllData
.Select
End With
Application.DisplayAlerts = False
wPT.Delete

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

ExcelTips: The Macros Page 174


Affecting Worksheet Data

Set wks = Nothing


Set wPT = Nothing
Exit Sub

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

Selectively Importing Records


Ole ran into a problem importing information into an Excel workbook. It seems that the files he
needs to import typically have thousands and thousands of records in them. Ole doesn’t need
most of the input rows, however, and normally gets rid of them once the records are imported
into Excel. Ole is looking for a way to get rid of the unwanted records during the import process,
so that he has less work to do when the data is in his workbook.

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"

ExcelTips: The Macros Page 175


Affecting Worksheet Data

sUnwanted = "bad text"


sDelim = ","

Open sFile For Input As #1

iRow = 1
While Not EOF(1) 'Scan file line by line
iCol = 1
Line Input #1, sBuffer

' Check to see if should ignore record


bBadRecord = Instr(sBuffer, sUnwanted)

If Not bBadRecord Then


iTemp = Instr(sBuffer, sDelim)
While iTemp > 0
With Application.Cells(iRow, iCol)
.NumberFormat = "@" 'Text formatting
.Value = Left(sBuffer, iTemp-1)
End With
iCol = iCol + 1
sBuffer = Mid(sBuffer, iTemp+1, Len(sBuffer))
iTemp = Instr(sBuffer, sDelim)
Wend
If Len(sBuffer) > 0 Then
With Application.Cells(iRow, iCol)
.NumberFormat = "@" 'Text formatting
.Value = sBuffer
End With
End If
iRow = iRow + 1
End If
Wend
Close #1
End Sub

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.

ExcelTips: The Macros Page 176


Affecting Worksheet Data

Importing Huge Data Files


Excel has a limit on the number of rows you can have in a worksheet—up to 65,535 rows prior
to Excel 2007. It is very possible, however, to have a raw data file that has more than this
number of rows. If you need to import that file into Excel, then doing so can appear almost
impossible without upgrading to Excel 2007. Even in later program versions you may run into
the limits of your system if you try to import a file that has hundreds of thousands of rows. There
are a couple of things you can do, however.

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.

Public Sub LoadFile()


Dim strLine As String
Dim I As Long
Dim J As Long
Dim iLen As Integer
Dim iSh As Integer
Dim lL As Long
Dim sDelim As String
Dim MaxSize As Long

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

ExcelTips: The Macros Page 177


Affecting Worksheet Data

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.

Merging Many Workbooks


Joy ran into a problem merging quite a few workbooks together. The majority of the
workbooks—about 200 of them, all in a single folder—each contain a single worksheet, but
some contain more. The worksheets forming each of these workbooks needs to be added to a
single workbook.

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

On Error GoTo ErrHandler


Application.ScreenUpdating = False

FilesToOpen = Application.GetOpenFilename _
(FileFilter:="Microsoft Excel Files (*.xls?), *.xls?", _
MultiSelect:=True, Title:="Files to Merge")

If TypeName(FilesToOpen) = "Boolean" Then


MsgBox "No Files were selected"
GoTo ExitHandler
End If

x = 1
While x <= UBound(FilesToOpen)
Workbooks.Open FileName:=FilesToOpen(x)
Sheets().Move After:=ThisWorkbook.Sheets _
(ThisWorkbook.Sheets.Count)
x = x + 1
Wend

ExcelTips: The Macros Page 178


Affecting Worksheet Data

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.

Importing Multiple Files to a Single Workbook


Let’s say that you have a folder on your hard drive that contains thirty text files, and you want to
import all of them to an Excel workbook. You want each text file to end up on its own worksheet
in the workbook, so that you will have a total of thirty worksheets.

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

On Error GoTo ErrHandler


Application.ScreenUpdating = False

sDelimiter = "|"

FilesToOpen = Application.GetOpenFilename _
(FileFilter:="Text Files (*.txt), *.txt", _
MultiSelect:=True, Title:="Text Files to Open")

If TypeName(FilesToOpen) = "Boolean" Then


MsgBox "No Files were selected"
GoTo ExitHandler
End If

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, _

ExcelTips: The Macros Page 179


Affecting Worksheet Data

Tab:=False, Semicolon:=False, _
Comma:=False, Space:=False, _
Other:=True, OtherChar:="|"
x = x + 1

While x <= UBound(FilesToOpen)


Set wkbTemp = Workbooks.Open(FileName:=FilesToOpen(x))
With wkbAll
wkbTemp.Sheets(1).Move After:=.Sheets(.Sheets.Count)
.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:=sDelimiter
End With
x = x + 1
Wend

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

ExcelTips: The Macros Page 180


Affecting Worksheet Data

.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

Getting Rid of Extra Quote Marks in Exported Text


Files
Sometimes the export filters used by Excel can produce undesired results in the exported file. For
instance, when you export to a tab-delimited text file, some Excel filters place quote marks
around the text in a cell. For instance, assume that a particular cell contains the following text:

Create bts; sitemask = "0110"; pcmlink = 40

This, however, is how the text in the cell is exported by Excel:

"Create bts; sitemask = ""0110""; pcmlink = 40"

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

Open "c:\MyOutput.txt" For Output As #1

ExcelTips: The Macros Page 181


Affecting Worksheet Data

For Each r In Selection.Rows


sTemp = ""
For Each c In r.Cells
sTemp = sTemp & c.Text & Chr(9)
Next c

'Get rid of trailing tabs


While Right(sTemp, 1) = Chr(9)
sTemp = Left(sTemp, Len(sTemp) - 1)
Wend
Print #1, sTemp
Next r
Close #1
End Sub

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.)

Getting Rid of Alphabetic Characters


Bryan has a worksheet that has a lot of cells that have some alphabetic characters in them. He is
looking for a way to get rid of only those alphabetic characters, no matter where they appear in
the cell. For instance, if the cell contains "ABC123," Bryan wants to get rid of "ABC" and have
just "123" remaining. Similarly, "A3B2C1" should become "321" and "#45P*%" should become
"#45*%".

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

For Each rngCell In Selection


strCheckString = rngCell.Value
strClean = ""

For intChar = 1 To Len(strCheckString)


strCheckChar = Mid(strCheckString, 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

ExcelTips: The Macros Page 182


Affecting Worksheet Data

'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.

Function CleanText2(ByVal sRaw As String) As String


Dim intChar As Integer
Dim strCheckString As String
Dim strCheckChar As String
Dim intCheckChar As Integer
Dim strClean As String

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.

ExcelTips: The Macros Page 183


Affecting Worksheet Data

Adjusting Values with Formulas


There are times that you need to adjust the values stored in the cells of a worksheet. Most times,
the tools provided by Paste Special will fit the bill just fine. For instance, you can use Paste
Special to multiply or divide the values in a range of cells, as described in other issues of
ExcelTips.

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

Set Target = ActiveSheet.Range(ActiveWindow.Selection.Address)


sMod = InputBox("Formula to add?")
If sMod > "" Then
For J = 1 To Target.Cells.Count
If Target.Cells(J).HasFormula Then
sForm = Target.Cells(J).Formula
sForm = "=(" & Mid(sForm, 2, 500) & ")"
sForm = sForm & sMod
Target.Cells(J).Formula = sForm
Else
sForm = "=" & Target.Cells(J).Value & sMod
Target.Cells(J).Formula = sForm
End If
Next J
End If
End Sub

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.

ExcelTips: The Macros Page 184


Affecting Worksheet Data

Returning Zero When a Referenced Cell is Blank


If you have a formula in a worksheet, and the cell referenced by the formula is blank, then the
formula still returns a zero value. For instance, if you have the formula =A3, then the formula
returns the contents of cell A3, unless cell A3 is blank. In that case, the formula returns a value
of zero.

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.

Private Sub Worksheet_Change(ByVal Target As Excel.Range)


Dim rMonitor As Range
Dim rTarget As Range

Set rMonitor = Range("A3")


Set rTarget = Range("B7")

If Not Intersect(Target, rMonitor) Is Nothing Then


rMonitor.Copy rTarget
End If

Set rMonitor = Nothing

ExcelTips: The Macros Page 185


Affecting Worksheet Data

Set rTarget = Nothing


End Sub

Counting Commas in a Selection


At work, Mark regularly needs to count the number of commas in a range of selected cells. He
can’t find an Excel function to do this type of task, and is wondering if a macro might be able to
do the trick.

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.

Function CountComma(rng As Range)


Dim iCount As Integer
Dim rCell As Range
Dim sTemp As String

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)

ExcelTips: The Macros Page 186


Affecting Worksheet Data

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:

1. Select the range of cells in which you want to count commas.


2. Press CTRL+H to display the Replace tab of the Find and Replace dialog box.
3. In the Find What box, enter a comma.
4. In the Replace With box, enter a comma.
5. Click Replace All.

Excel does the replacement and displays a dialog box that shows how many replacements were
made.

Counting Consecutive Negative Numbers


Lori has a series of numbers, in adjacent cells, that can be either positive or negative. She would
like a way to determine the largest sequence of negative numbers in the range. Thus, if there
were seven negative numbers in a row in this sequence, she would like a formula that would
return the value 7.

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)

Then, in cell B2 enter the following:

=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.

ExcelTips: The Macros Page 187


Affecting Worksheet Data

Function MaxNegSequence(rng As Range)


' search for the largest sequence
' of negative numbers in the range

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)

Summing Digits in a Value


If you have a cell that contains a value, you may want to devise a way to add together all the
digits in the value. For instance, if a cell contains the value 554, you might want to determine the
sum of 5+5+4, which is 14.

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:

=SUMPRODUCT(--MID(A1,ROW(INDIRECT("1:" & LEN(A1))),1))

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.

ExcelTips: The Macros Page 188


Affecting Worksheet Data

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:

Function AddDigits(Number As Long) As Integer


Dim i As Integer
Dim Sum As Integer
Dim sNumber As String

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:

Function AddDigits(ByVal N As Long) As Integer


Do While N >= 1
AddDigits = AddDigits + N Mod 10
N = Int(N / 10)
Loop
End Function

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.

Summing Absolute Values


Joseph has a worksheet that contains a list of values. Some of those values are above zero and
others are below. He can use the SUM function to calculate a sum of the values, but he really
wants to calculate a sum of the absolute value of each item in the list. So, the sum of the three
values -33, 14, -5 would be 52 instead of -24.

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")

ExcelTips: The Macros Page 189


Affecting Worksheet Data

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:

Function SumAbs(Rng As Range) As Double


Result = 0
On Error GoTo Done
For Each element In Rng
Result = Result + Abs(element)
Next element
Done:
SumAbs = Result
End Function

You can use the function by entering a simple formula in your worksheet:

=SumAbs(A1:A10)

ExcelTips: The Macros Page 190


Affecting Worksheet Data

Summing Based on Formatting in Adjacent Cells


Srinivas has data in both columns A and B. He needs to sum the values in column B for which
the cell format of the corresponding cells in column A are yellow. For instance, if the format in
cell A17 is yellow, then the value in cell B17 should be included in the sum.

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:

Function SumNextYellow(ByVal r As Range)


Dim c As Range
Dim a As Double

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.

Function ColorConditionSum(cSample As Excel.Range, rng As Excel.Range)


' This Function returns sum of values in rng.Columns(2) if
' corresponding cell in rng.Columns(1) is colored with sample
' color (cSample cell)

' Arguments: cSample = cell colored by sample color


' rng = cell range to be processed (2 columns)

Dim rngCol2 As Excel.Range


Dim rngConstants As Excel.Range
Dim rngFormulas As Excel.Range
Dim lColorIndex As Long
Dim MySum As Double
Dim area As Excel.Range
Dim c As Excel.Range

ColorConditionSum = False
If Not TypeOf cSample Is Excel.Range Then Exit Function '>>>
lColorIndex = cSample.Interior.ColorIndex

MySum = 0
Set rngCol2 = Nothing

ExcelTips: The Macros Page 191


Affecting Worksheet Data

If TypeOf rng Is Excel.Range Then


If rng.Columns.Count < 2 Then Exit Function '>>>
On Error Resume Next
For Each area In rng.Areas
If rngCol2 Is Nothing Then
Set rngCol2 = area.Columns(2).SpecialCells(xlCellTypeConstants,
1)
If rngCol2 Is Nothing Then
Set rngCol2 =
area.Columns(2).SpecialCells(xlCellTypeFormulas, 1)
Else
Set rngCol2 = Application.Union( _
rngCol2, area.Columns(2).SpecialCells(xlCellTypeFormulas,
1))
End If
Else
Set rngCol2 = Application.Union( _
rngCol2, area.Columns(2).SpecialCells(xlCellTypeConstants,
1))
Set rngCol2 = Application.Union( _
rngCol2, area.Columns(2).SpecialCells(xlCellTypeFormulas, 1))
End If
Next area

For Each area In rngCol2.Areas


For Each c In area.Cells
With c.Offset(0, -1)
If .Interior.ColorIndex = lColorIndex Then
MySum = MySum + c.Value
End If
End With
Next c
Next area
End If

ColorConditionSum = MySum
End Function

You use this function in the following manner in a worksheet:

=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.

Summing Based on Part of the Information in a Cell


Kathy has a worksheet that includes information for all the parts in her warehouse. In this sheet,
part numbers are shown in column A using the format 12345 XXX, where XXX represents a
location code. This means she could have multiple entries on the worksheet for the same part
numbers, but each entry representing a different location for that part. Kathy needs a formula that
sums the values associated with each part number, regardless of its location code. Thus, she

ExcelTips: The Macros Page 192


Affecting Worksheet Data

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.

ExcelTips: The Macros Page 193


Affecting Worksheet Data

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.)

Function AddPrtQty(ByVal Parts As Range, PartsQty As Range, _


FindPart As Variant) As Long
Dim Pos As Integer
Dim Pos2 As Integer
Dim i As Long
Dim tmp As String
Dim tmpSum As Long
Dim PC As Long

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), " ")

If Pos2 > Pos And Len(Parts(i)) > Pos + 1 Then


tmp = CStr(Trim(Left(Parts(i), Pos2 - 1)))
ElseIf Pos > 0 And Len(Parts(i)) > 0 Then
tmp = CStr(Trim(Left(Parts(i), Pos - 1)))
End If

If CStr(Trim(tmp)) = CStr(Trim(FindPart)) Then


tmpSum = tmpSum + PartStock(i)
End If
Next 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")

Summing Only Visible Values


Kirk is using the SUM function in many of his worksheets to (naturally) determine the sum of a
range of values. The problem he is running into, however, is that the range he is summing
contains some hidden rows, and he doesn’t want those values—the hidden ones—included in the
sum.

ExcelTips: The Macros Page 194


Affecting Worksheet Data

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:

Function Sum_Visible(Cells_To_Sum As Object)


Dim vTotal As Variant

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)

Displaying a Count of Zeros on the Status Bar


Jeremy's company is often interested in how many cells contain the value zero. He wonders if
there is a way to customize the status bar to automatically display the COUNTIF formula. He
knows he can see the results of functions such as AVERAGE, COUNT, SUM and others, but
can't find a way to do a more complex COUNTIF display.

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)

ExcelTips: The Macros Page 195


Affecting Worksheet Data

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.

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


zCount = Application.WorksheetFunction.CountIf(Target.Cells,0)
Application.StatusBar = "Selection has " & CStr(zCount) & " zeros"
End Sub

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.)

Inserting Dashes between Letters and Numbers


Let's say you have a worksheet with lots of product codes in column A. These codes are in the
format A4, B12, AD4, etc. Due to a change in the way your company operates, you are directed
to change all the product codes so they contain a dash between the letters and the numbers.

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:

=LEFT(A1,1) & "-" & RIGHT(A1,LEN(A1)-1)

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")),

ExcelTips: The Macros Page 196


Affecting Worksheet Data

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 part, MID(A1,ROW(INDIRECT("1:100")),1), refers to each individual character in the


string. This results in the array: "B", "R", "2", and "7". Multiplying the array by 1 (the next part
of the formula) results in each of the individual characters being converted to a number. If the
character is not a number, this conversion yields an error. In the case of the string being
converted (BR27), this results in: #VALUE, #VALUE, 2, and 7.

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:

Function DashIn(myText As String)


Dim i As Integer
Dim myCharCode As Integer
Dim myLength As Integer

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)

ExcelTips: The Macros Page 197


Affecting Worksheet Data

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

For Each c In Selection.Cells


c = Replace(c, " ", "")
Next
End Sub

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.

Generating Random Strings of Characters


Nancy is trying to get Excel to pick 50 "numbers" that each contain eight random characters. The
characters can be either digits or letters (uppercase or lowercase).

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

ExcelTips: The Macros Page 198


Affecting Worksheet Data

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

ExcelTips: The Macros Page 199


Affecting Worksheet Data

iTemp = Int((122 - 48 + 1) * Rnd + 48)


Select Case iTemp
Case 48 To 57, 65 To 90, 97 To 122
bOK = True
Case Else
bOK = False
End Select
Loop Until bOK
bOK = False
sNumber = sNumber & Chr(iTemp)
Next K
ActiveCell.Value = sNumber
ActiveCell.Offset(1, 0).Select
Next J
End Sub

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.

Generating Unique, Sequential Names


Steven is testing some software and he needs to feed into the program a bunch of "fake" names.
He would like these names to be patterned such as Nameaaa, Nameaab, Nameaac, and so on
through Namezzz. This would require creating 17,576 names (26 x 26 x 26). He wonders if there
is an easy way to generate all these names in Excel.

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.

ExcelTips: The Macros Page 200


Affecting Worksheet Data

If you don't want to use a macro for some reason, type the following formula into cell A1 of a
blank worksheet:

="Name" & CHAR((ROW()-1)/676+97)&CHAR(MOD(


(ROW()-1)/26,26)+97)&CHAR(MOD(ROW()-1,26)+97)

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.

Randomly Assigning Names to Items


Gary has two lists in a worksheet. One of them, in column A, contains a list of surplus items in
our company and the other, in column G, contains a list of names. There is nothing in columns
B:F. Gary would like to assign names, randomly, to the list of items. Each name from column G
should be assigned only once. If there are more names than items, then some names won't get
used. If there are fewer names than items, then some items won't have associated 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

ExcelTips: The Macros Page 201


Affecting Worksheet Data

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.

Pulling Apart Characters in a Long String


John has a worksheet that, in column A, has a large number of very long text strings. He needs to
individually pull the first 249 characters from each string, placing a single character in each cell
to the right of the string.

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

ExcelTips: The Macros Page 202


Affecting Worksheet Data

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

Set r = Range("A1", Range("A65536").End(xlUp))


For Each c In r
sTemp = Left(c, 249)
For z = 1 To Len(sTemp)
c.Offset(0, z) = Mid(sTemp, z, 1)
Next z
Next
End Sub

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.

Breaking Up Variable-Length Part Numbers


Marty has a worksheet with a long series of part numbers in column A. These consist of letters
and numbers, such as A123BC, AB123C, etc. Marty wants to break the data into three columns,
so that text before the numbers will be in one column, numbers in the second column, and text
after the numbers in the third.

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)

ExcelTips: The Macros Page 203


Affecting Worksheet Data

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)))

Again, this is a single formula, and it needs to be entered as an array formula


(CTRL+SHIFT+ENTER) so that it can work on each character in the original part number. It
examines the part number and determines the beginning point of the digits, and then extracts all
those digits. It returns a text string, even though that string is composed of digits. If you want it
to actually be treated as a number (which would get rid of any leading zeros, of course), then you
need to enclose the entire formula in a Value function, as shown here:

=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

For Each C In Selection


sNew = ""
i = 1

' Get first part, which is text


Do While IsNumeric(Mid(C, i, 1)) = False
sNew = sNew & Mid(C, i, 1)

i = i + 1
If i > Len(C) Then Exit Do

ExcelTips: The Macros Page 204


Affecting Worksheet Data

Loop
C.Offset(0, 1).Value = sNew

' Pull next part, which should be digits


sNew = ""
Do While IsNumeric(Mid(C, i, 1)) = True
sNew = sNew & Mid(C, i, 1)

i = i + 1
If i > Len(C) Then Exit Do
Loop
C.Offset(0, 2).Value = sNew

' Use rest of original cell


sNew = Mid(C, i, Len(C))
C.Offset(0, 3).Value = sNew
Next C
End Sub

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

For Each C In Selection


j = 1
Do While Not (IsNumeric(Mid(C.Value, j, 1))) And j <= Len(C)
j = j + 1
Loop
k = j

Do While IsNumeric(Mid(C.Value, k, 1)) And k <= Len(C)


k = k + 1
Loop

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

ExcelTips: The Macros Page 205


Affecting Worksheet Data

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)

Splitting Text to Multiple Cells


Phil has a series of cells that contain text. Some of the text is too long, so he needs to split it into
multiple cells. For instance, if a cell contains "the quick brown fox" and he needs to split the text
so that no part is longer than 12 characters, Phil can easily do it. But he wants to make sure that
the split is made at the space before the 12th character, rather than at the exact 12th character.
Phil is at a loss as to how to do this.

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.

ExcelTips: The Macros Page 206


Affecting Worksheet Data

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:

Function SplitMe(sSentence As String, iPos As Integer, Optional iLen = 12)


Dim sSegments() As String
Dim iSegments As Integer
Dim sRest As String
Dim sTemp As String
Dim iSpace As Integer
Dim J As Integer

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.

ExcelTips: The Macros Page 207


Affecting Worksheet Data

Splitting Cells by Case


Manik has a worksheet that, in column A, has text values in the format "mikeDAVIS", where the
person's first name is in lowercase and the last name is in uppercase. He would like to split the
names to two separate columns, according to the case of the text.

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,"")

This is a regular formula, not an array formula.

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:

Function GetFirstUpper(MyCell As Range) As Integer


Dim sCellValue As String
Dim i As Integer

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

ExcelTips: The Macros Page 208


Affecting Worksheet Data

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:

Function GetFirstName(MyCell As Range) As String


Dim sCellValue As String
Dim i As Integer

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.

Function GetLastName(MyCell As Range) As String


Dim sCellValue As String
Dim i As Integer

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

ExcelTips: The Macros Page 209


Affecting Worksheet Data

If i > Len(sCellValue) Then


GetLastName = sCellValue
Else
GetLastName = Mid(sCellValue, i)
End If
End Function

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:

Function GetName(MyCell As Range, sWanted As String) As String


Dim sCellValue As String
Dim i As Integer

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.

Adding Dashes between Letters


Scott wonders how he can make Excel automatically add a dash between every letter in a given
cell. As an example, if cell A1 contains "house", Scott would like to convert it to "h-o-u-s-e".

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:

=CHOOSE(LEN(A1),A1,LEFT(A1,1) & "-" & RIGHT(A1,1),


LEFT(A1,1) & "-" & MID(A1,2,1) & "-" & RIGHT(A1,1),
LEFT(A1,1) & "-" & MID(A1,2,1) & "-" & MID(A1,3,1) & "-"

ExcelTips: The Macros Page 210


Affecting Worksheet Data

& RIGHT(A1,1),LEFT(A1,1) & "-" & MID(A1,2,1) & "-"


& MID(A1,3,1) & "-" & MID(A1,4,1) & "-" & RIGHT(A1,1),
LEFT(A1,1) & "-" & MID(A1,2,1) & "-" & MID(A1,3,1)
& "-" & MID(A1,4,1) & "-" & MID(A1,5,1) & "-" & RIGHT(A1,1))

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

For Each Cell In Selection


sTemp = ""
For C = 1 To Len(Cell)
sTemp = sTemp + Mid(Cell, C, 1) + "-"
Next
Cell.Value = Left(sTemp, Len(sTemp) - 1)
Next
End Sub

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:

Function AddDashes2(Src As String) As String


Dim sTemp As String
Dim C As Integer

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:

ExcelTips: The Macros Page 211


Affecting Worksheet Data

Function AddDashes3(Src As String) As String


Dim sTemp As String
Dim C As Integer

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

Removing Dashes from ISBN Numbers


Ciaran works in a library, and often has to work with long lists of ISBN numbers in Excel. The
numbers must contain either 10 or 13 digits, may contain dashes, and may have leading zeros.
He uses text format for cells containing ISBNs in order to keep them intact as text strings. For
some purposes, the dashes in the ISBNs have to be stripped out (he uses Find and Replace for
this) and that's where the trouble starts. 0-241-95011-2 becomes 241950112 (now it's dropped to
9 digits), and worse, 978-0-00-200784-9 becomes 9.78E+12 (scientific notation). Ciaran can't
find any way of working around these two issues, no matter what he does with formatting before
or after using Find and Replace to get rid of the dashes.

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:

ExcelTips: The Macros Page 212


Affecting Worksheet Data

=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

For Each c In Selection


sISBN = Application.Substitute(c, "-", "")
c.NumberFormat = "@"
c.Value = "'" & sISBN
Next
End Sub

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.

Adding a Missing Closing Bracket


Terry has a huge list of names in an Excel worksheet. Some are just the names, but some have
words in brackets after them. Unfortunately some of the words in brackets don't have the closing
bracket and Terry has to manually add the closing bracket. He wonders if there is a way that he
can add a bracket using a wild card search and replace.

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

ExcelTips: The Macros Page 213


Affecting Worksheet Data

Const csLBrk As String = "["


Const csRBrk As String = "]"

On Error Resume Next


For Each c In Selection.Cells
If InStr(1, c.Value, csLBrk) > 0 And _
InStr(1, c.Value, csRBrk) = 0 Then
c.Value = c.Value & csRBrk
End If
Next c
End Sub

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.

Pulling Initial Letters from a String


Rajeev needs a formula that will extract the first letters of a series of words. For instance, if a cell
contains the text "Rajeev Kumar Pandey" he would like to extract, into another cell, the letters
"RKP". The number of words in series can vary from cell to cell.

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)),

ExcelTips: The Macros Page 214


Affecting Worksheet Data

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:

Function Initials(Raw As String) As String


Dim Temp As Variant
Dim J As Integer

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)

Making PROPER Skip Certain Words


Terry uses the PROPER worksheet function all the time to change the case of text in his
worksheets. He wonders if there is a way to instruct the function to ignore certain words, so that
they aren't started with a capital letter. It is not unusual for him to have to go back after using
PROPER and change words like "the" or "an" to all lowercase. If PROPER could skip changing
such words automatically, it would be a big help.

ExcelTips: The Macros Page 215


Affecting Worksheet Data

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:

=SUBSTITUTE(PROPER(A1)," The "," the ")

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:

=SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(PROPER(A1)," The ",


" the ")," An "," an ")," And "," and ")

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.

Function Title(ByVal ref As Range) As String


Dim vaArray As Variant
Dim c As String
Dim i As Integer
Dim J As Integer
Dim vaLCase As Variant
Dim str As String

' Array contains terms that should be lower case


vaLCase = Array("a", "an", "and", "in", "is", _
"of", "or", "the", "to", "with")

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

' rebuild the sentence


str = ""
For i = 1 To UBound(vaArray)
str = str & " " & vaArray(i)
Next i

Title = Trim(str)
End Function

ExcelTips: The Macros Page 216


Affecting Worksheet Data

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.

Function Split(Raw As String, Delim As String) As Variant


Dim vAry() As String
Dim sTemp As String
Dim J As Integer
Dim Indx As Integer

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

Determining "Highest Since" or "Lowest Since"


Alex routinely analyzes the latest building industry data, and needs to write articles about the
data. Frequently he needs to highlight some new piece of data, such as “industrial building
construction was the lowest since August 2007.” Alex wondered if there was a way to automate
this type of highlighting; if column A contains the month and year and column B contains the
values for those periods, Alex would like a formula in column C that indicates “this value is the
highest since April 2007” or “this value is the lowest since November 2004.”

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:

=IF(ROW(B2)=2,"",IF(B2>MAX($B$1:B1), "this value is


the highest since " & TEXT(INDEX($A$1:A1,MATCH(MAX(
$B$1:B1),$B$1:B1,0)), "mmmm yyyy"), IF(B2<MIN($B$1:B1),

ExcelTips: The Macros Page 217


Affecting Worksheet Data

"this value is the lowest since " & TEXT(INDEX($A$1:A1,


MATCH(MIN($B$1:B1),$B$1:B1,0)), "mmmm yyyy"),"")))

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

Set orig_cell = ActiveCell


orig_row = ActiveCell.Row
orig_val = orig_cell.Value

' find lowest


lowrow = 0
For rownum = orig_cell.Row - 1 To 1 Step -1
Set newcell = Cells(rownum, 2)
new_val = newcell.Value
If orig_val >= new_val Then
lowrow = rownum
Exit For
End If
Next
If lowrow = 0 Then lowrow = 1
Cells(orig_row, 3).Value = "Lowest since " & Cells(lowrow, 1)

' find highest


hirow = 0
For rownum = orig_cell.Row - 1 To 1 Step -1
Set newcell = Cells(rownum, 2)
new_val = newcell.Value
If orig_val <= new_val Then
hirow = rownum
Exit For
End If
Next
If hirow = 0 Then hirow = 1
Cells(orig_row, 4).Value = "Highest since " & Cells(hirow, 1)
End Sub

ExcelTips: The Macros Page 218


Affecting Worksheet Data

Making Changes in a Group of Workbooks


Over time, it is very easy to create and collect a huge number of Excel workbooks. Suppose that
you had a whole bunch of workbooks in which you needed to make the same change. For
instance, you might need to change the value stored in cell A10 of each of the worksheets in each
of the workbooks.

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:

dir /b > myfilelist.txt

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

Open "c:\myfiles\myfilelist.txt" For Input As #1


Do While Not EOF(1)
Input #1, sFilename ' Get workbook path and name
Workbooks.Open sFilename

With ActiveWorkbook
For Each wks In .Worksheets
' Specify the change to make
wks.Range("A1").Value = "A1 Changed"
Next

ExcelTips: The Macros Page 219


Affecting Worksheet Data

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

' Change directory path as desired


dirName = "c:\myfiles\"

MyPath = dirName & "*.xlsx"


MyFile = Dir(MyPath)
If MyFile > "" Then MyFile = dirName & MyFile

Do While MyFile <> ""


If Len(MyFile) = 0 Then Exit Do

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.

Public Sub ChangeFiles3()


Dim MyPath As String
Dim MyFile As String
Dim dirName As String

With Application.FileDialog(msoFileDialogFolderPicker)

ExcelTips: The Macros Page 220


Affecting Worksheet Data

' Optional: set folder to start in


.InitialFileName = "C:\Excel\"
.Title = "Select the folder to process"
If .Show = True Then
dirName = .SelectedItems(1)
End If
End With

MyPath = dirName & "\*.xlsx"


myFile = Dir(MyPath)
If MyFile > "" Then MyFile = dirName & MyFile

Do While MyFile <> ""


If Len(MyFile) = 0 Then Exit Do

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:

1. Create a PivotTable from your data as you normally would.


2. Place the column from which you want worksheets created into the “Report Filter”
section of the PivotTable.
3. Display the Options tab of the ribbon. (This tab is visible only when you are working on
a PivotTable.)
4. Click the down-arrow next to the Options tool, in the PivotTable group at the left end of
the ribbon.

ExcelTips: The Macros Page 221


Affecting Worksheet Data

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:

1. Create a PivotTable from your data as you normally would.


2. Place the column from which you want worksheets created into the “Pages” section of
the PivotTable.
3. On the PivotTable toolbar, click the PivotTable option at the left side of the toolbar.
Excel displays a number of options you can choose.
4. Choose Show Pages. Excel asks you to confirm that you want to show the pages.
5. Click OK.

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

Set rngTbl = Selection


ReDim Table1(1 To RCount, 1 To CCount)
ReDim Row1(1 To 1, 1 To CCount)
Table1() = rngTbl.Value

ExcelTips: The Macros Page 222


Affecting Worksheet Data

On Error GoTo Abend

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

Function SheetExists(SheetName As String) As Boolean


Dim ws As Worksheet
SheetExists = False
For Each ws In ThisWorkbook.Worksheets
If ws.Name = SheetName Then
SheetExists = True
Exit For
End If
Next ws
End Function

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.

ExcelTips: The Macros Page 223


Affecting Worksheet Data

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

sUniv = Cells(1, 1).Value

ExcelTips: The Macros Page 224


Affecting Worksheet Data

iWanted = Cells(2, 1).Value

ReDim iA(iWanted)
For j = 1 To iWanted
iA(j) = j
Next j

iRow = PutRow(iA, sUniv, 1)

Do Until DoneYet(iA, Len(sUniv))


j = WorkHere(iA, Len(sUniv))
iA(j) = iA(j) + 1
For k = j + 1 To iWanted
iA(k) = iA(k - 1) + 1
Next k
iRow = PutRow(iA, sUniv, iRow)
Loop
End Sub

Function DoneYet(iB, n) As Boolean


iMax = UBound(iB)
Temp = True
For j = iMax To 1 Step -1
If iB(j) <> j + (n - iMax) Then
Temp = False
End If
Next
DoneYet = Temp
End Function

Function WorkHere(iB, n) As Integer


iMax = UBound(iB)
j = iMax
Do Until iB(j) <> j + (n - iMax)
j = j - 1
Loop
WorkHere = j
End Function

Function PutRow(iB, sUniv, i)


iMax = UBound(iB)
sTemp = ""
For j = 1 To iMax
sTemp = sTemp & Mid(sUniv, iB(j), 1)
Next j
Cells(i, 2).Value = sTemp
PutRow = i + 1
End Function

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.

ExcelTips: The Macros Page 225


Affecting Worksheet Data

Putting an X in a Clicked Cell


Wendy has a worksheet that has quite a bit of data in it, with the main data in the range C3:P312.
She would like to have a macro that, if she clicks a cell in this data range, would put an "x" into
the cell.

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:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


Dim rInt As Range
Dim rCell As Range

Set rInt = Intersect(Target, Range("C3:P312"))


If Not rInt Is Nothing Then
For Each rCell In rInt
rCell.Value = "x"
Next
End If
Set rInt = Nothing
Set rCell = Nothing
End Sub

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:

Private Sub Worksheet_BeforeDoubleClick( _


ByVal Target As Range, Cancel As Boolean)
Dim rInt As Range
Dim rCell As Range

Set rInt = Intersect(Target, Range("C3:P312"))


If Not rInt Is Nothing Then
For Each rCell In rInt
rCell.Value = "x"
Next
End If
Set rInt = Nothing
Set rCell = Nothing
Cancel = True
End Sub

ExcelTips: The Macros Page 226


Affecting Worksheet Data

Deleting Everything Except Formulas


At the beginning of each year Richard makes a copy of the previous year's Excel workbook. To
get ready for the new year’s data, he then needs to delete everything in the copy except for
formulas. Richard wonders if there is a quick way to do this.

Actually, there is a very quick way to manually delete all the non-formula information in a
worksheet. Follow these steps:

1. Press F5. Excel displays the Go To dialog box.


2. Click Special. Excel displays the Go To Special dialog box.

The Go To Special dialog box.

3. Make sure the Constants radio button is selected.


4. Click OK. All the cells containing constants (everything except formulas) are selected.
5. Press the DEL key.

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

For Each wks In ThisWorkbook.Worksheets


On Error Resume Next
Set rng = wks.Cells.SpecialCells(xlCellTypeConstants, 23)

ExcelTips: The Macros Page 227


Affecting Worksheet Data

On Error GoTo 0
If Not rng Is Nothing Then
rng.ClearContents
End If
Set rng = Nothing
Next
Set wks = Nothing
End Sub

Getting Rid of Everything Except Numbers


Linda has a column that contains alpha and numeric characters. She needs to retain the numeric
characters and delete the alpha ones. For example, a cell may contain 10003E111 and she wants
to end up with 10003111.

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:

Function OnlyNums(sWord As String)


Dim sChar As String
Dim x As Integer

ExcelTips: The Macros Page 228


Affecting Worksheet Data

Dim sTemp As String

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

You use this function by calling it from within a worksheet cell:

=OnlyNums(A1)

The function returns a numeric value. If you want to create an even shorter macro to do the
processing, consider the following:

Function StripChar(aText As String)


Dim I As Integer

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

To use this function, use either of the following in your worksheet:

=STRIPCHAR(A1)
=VALUE(STRIPCHAR(A1))

The first returns a text string consisting of the digits, the second returns the numeric version of
that string.

Number of Terms in a Formula


Pradeep has a need to figure out the number of terms in any given formula. For instance, in the
formula =5+80*3/6 there are four terms. He would like a formula he can use to tell him the
number of terms (4) in the formula.

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:

ExcelTips: The Macros Page 229


Affecting Worksheet Data

Function TermsInFormula(TheCell As Range)


Dim sFormula As String
Dim vOps As Variant
Dim iCount As Integer
Dim J As Integer
Dim AWF As WorksheetFunction

Application.Volatile
vOps = Array("+", "-", "*", "/", "^")

Set AWF = Application.WorksheetFunction


sFormula = TheCell.Formula
iCount = 1
For J = LBound(vOps) To UBound(vOps)
iCount = iCount + Len(sFormula) _
- Len(AWF.Substitute(sFormula, vOps(J), ""))
Next

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.)

Finding the Number of Significant Digits


Brenda is interested in knowing the number of significant digits in a value. She wonders if there
is an Excel function or formula she can use that would return the number of significant digits in
the value shown in a cell.

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))

ExcelTips: The Macros Page 230


Affecting Worksheet Data

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.

Function SigFigs(rng As Range, Optional iType As Integer = 1)


'iType = 1 is Min
'iType = 2 is Max

Dim rCell As Range


Dim sText As String
Dim sText2 As String
Dim iMax As Integer
Dim iMin As Integer
Dim iDec As Integer
Dim i As Integer

Application.Volatile
Set rCell = rng.Cells(1)

'if not a number then error


If Not IsNumeric(rCell) Or IsDate(rCell) Then
SigFigs = CVErr(xlErrNum)
Exit Function
End If

sText2 = Trim(rCell.Text)
sText = ""
'find position of decimal point (it matters)
iDec = InStr(sText2, ".")

'strip out any non-numbers (including decimal point)


For i = 1 To Len(sText2)
If Mid(sText2, i, 1) >= "0" And _
Mid(sText2, i, 1) <= "9" Then _
sText = sText & Mid(sText2, i, 1)
Next

'remove any leading zeroes (they don't matter)


While Left(sText, 1) = "0"

ExcelTips: The Macros Page 231


Affecting Worksheet Data

sText = Mid(sText, 2)
Wend
iMax = Len(sText)

'strip trailing zeroes (they don't matter if no decimal point)


sText2 = sText
If iDec = 0 Then
While Right(sText2, 1) = "0"
sText2 = Left(sText2, Len(sText2) - 1)
Wend
End If
iMin = Len(sText2)

'return Min or Max


Select Case iType
Case 1
SigFigs = iMin
Case 2
SigFigs = iMax
Case Else
SigFigs = CVErr(xlErrNum)
End Select
End Function

You call this function by using the following in your worksheet:

=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.

Leaving a Cell Value Unchanged If a Condition Is


False
While using the IF function, Vineet wants to retain the old value in the cell if the condition is
false. In other words, the value in a cell where the IF function is used should change only if the
condition being tested by the IF function is true. By default, however, the IF function makes the
value 0 if the condition is False.

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

ExcelTips: The Macros Page 232


Affecting Worksheet Data

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:

Private Sub Workbook_SheetChange(ByVal Sh As Object, _


ByVal Target As Range)

' See if the change is related to our cell


If Not (Application.Intersect(Target, Range("A1")) _
Is Nothing) Then
If Range("A1") < 10 Then
Range("B1") = "under ten"
End If
End If
End Sub

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."

ExcelTips: The Macros Page 233


Affecting Cell Formatting

Affecting Cell Formatting

Adding Drop Shadows to Cells


When formatting the data in your worksheet, you may wonder if there is a way to add a drop
shadow to a single cell. There is a very simple way to do it, depending on your version of Excel.
If you are using a version of Excel prior to Excel 2007, follow these steps:

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

ExcelTips: The Macros Page 234


Affecting Cell Formatting

Sub AddDrop(rng As Range, _


Optional vShadowType = msoShadow14, _
Optional vSchemeColor = 55)

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.

Highlighting Values in a Cell


Trev has a table of sales forecasts by product that several users review and update. The forecasts
are initially set with various formulas, but the users are allowed to override the formulas by
entering a value into any cell that contains one of the formulas. If a user does this, it would be
helpful for Trev to have Excel somehow highlight that 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.

ExcelTips: The Macros Page 235


Affecting Cell Formatting

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:

Function IsFormula(Check_Cell As Range) As Boolean


Application.Volatile
IsFormula = Check_Cell.HasFormula
End Function

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))

ExcelTips: The Macros Page 236


Affecting Cell Formatting

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.

Creating a Center Across Selection Button


If you have been using Excel for twenty years or so, you know that in versions of the program up
through Excel 95 there was a toolbar button that would center the contents of a particular cell
across a number of columns. In Excel 97 this toolbar button was replaced with one that merges
cells and centers the content within the merged cells. The difference, of course, between the two
tools is that one merges prior to centering, and the other does not.

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.

Making a Cell's Contents Bold or Italic


If you are writing macros using VBA, it is not uncommon to process data and place the results of
your processing into cells in a worksheet. If desired, you can also make the results in a bold
typeface so that they stand out. You do this by setting the Bold property of the Font object for a
selection.

ExcelTips: The Macros Page 237


Affecting Cell Formatting

For instance, if you wanted to make the contents of cell A1 bold, you could use the following in
your macro:

Cells(1, 1).Font.Bold = True

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:

Cells(1, 1).Font.Italic = True

Counting Cells with Text Colors


Michala has a worksheet of survey responses that includes different text in different colors. For
instance, if survey response is "I dislike dogs," the word "dogs" may be colored red and the rest
of the text is black. A response might have multiple colors in it, for instance in the response "I
like dogs and cats," the word "dogs" may be in red and the word "cats" in blue. Michala needs a
way to highlight a range of cells and count how many cells contain text of a specific color, such
as red or blue.

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.

Function CountColorIndex(rng As Range, iColor As Integer)


Dim v As Variant
Dim rCell As Range
Dim str As String
Dim sChar As String
Dim x As Integer
Dim iCount As Integer

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

ExcelTips: The Macros Page 238


Affecting Cell Formatting

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.

Official Color Names in VBA


Excel uses a color palette consisting of 56 colors. You can see these colors if you display the
Patterns tab of the Format Cells dialog box. When creating macros in VBA, you may want to
refer to these colors, by name, using constants.

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:

ExcelTips: The Macros Page 239


Affecting Cell Formatting

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:

Selection.Interior.Color = RGB(128, 64, 255)

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.

Working with Colors in a Macro


Jerri is trying to reference rather simple colors in a macro. In older versions of Excel the color
references were understandable, but she's having problems with colors since the introduction of
themes. If she records a macro, she gets references to various properties, such as Pattern,
PatternColorIndex, TintAndShade, PatternTintAndShade, and ThemeColor. Jerri can't make
sense of these and wonders if there is a good reference available that provides the background
information she needs to work with colors in a macro.

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

ExcelTips: The Macros Page 240


Affecting Cell Formatting

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.

Showing RGB Colors in a Cell


Dennis wants to fill three cells (A1:A3) with RGB values and have another cell (C1) show the
color based on those values. He wonders if there is an easy way to do this.

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:

Private Sub Worksheet_Change(ByVal Target As Range)


If Not Intersect(Target, Range("A1:A3")) Is Nothing Then
lRed = Abs(Range("A1").Value) Mod 256
lGreen = Abs(Range("A2").Value) Mod 256
lBlue = Abs(Range("A3").Value) Mod 256

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.

ExcelTips: The Macros Page 241


Affecting Cell Formatting

Determining the RGB Value of a Color


Neil uses colors a lot in his worksheets. He knows that he can generate a color based upon a
numeric RGB value (and as explained in other ExcelTips issues). Neil would like to do the
opposite—determine an RGB value. He wonders if there is a way to return (via function or
macro) the RGB value of the color used to fill a cell.

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.

The Custom tab of the Colors dialog box.

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.

ExcelTips: The Macros Page 242


Affecting Cell Formatting

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:

Function getRGB1(rcell) As String


Dim sColor As String

sColor = Right("000000" & Hex(rcell.Interior.Color), 6)


getRGB1 = Right(sColor, 2) & Mid(sColor, 3, 2) & Left(sColor, 2)
End Function

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:

Function getRGB2(rcell) As String


Dim C As Long
Dim R As Long
Dim G As Long
Dim B As Long

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:

Function getRGB3(rcell As Range, Optional opt As Integer) As Long


Dim C As Long
Dim R As Long
Dim G As Long
Dim B As Long

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

ExcelTips: The Macros Page 243


Affecting Cell Formatting

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

Replacing Background Colors in Cells


Jozef is looking for a way to change background colors of specific cells in the worksheet he
created. For example, he would like to find all red background cells and change them to blue, or
find all yellow backgrounds and change them to blue. Jozef wonders if there is an easy way to do
this.

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.

ExcelTips: The Macros Page 244


Affecting Cell Formatting

The Replace tab of the Find and Replace dialog box.

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.

ExcelTips: The Macros Page 245


Affecting Cell Formatting

The Fill tab of the Find Format dialog box.

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.

ExcelTips: The Macros Page 246


Affecting Cell Formatting

The Replace tab of the Find and Replace dialog box.

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.

The Patterns tab of the Find Format dialog box.

5. Use the controls in the dialog box to specify the background color you want to replace.
6. Click OK.

ExcelTips: The Macros Page 247


Affecting Cell Formatting

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

Creating Superscript and Subscript Buttons


When you are editing a cell in Excel, you have access to several of the formatting toolbar buttons
that can make formatting the cell a bit easier. For instance, you can use the Bold or Italics tools
to change these two attributes for any text selected in a cell. At some point you may want to
create other toolbar buttons to handle other formatting, such as applying superscript or subscript.

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:

1. Press CTRL+F11 to display the VBA Editor.


2. In the VBA Editor, choose User Form from the Insert menu. A new, blank user form
displays, along with the form toolbox.

ExcelTips: The Macros Page 248


Affecting Cell Formatting

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:

Private Sub UserForm_Activate()


TextBox1.Text = ActiveCell.Formula
End Sub

Private Sub btnSuper_Click()


Dim intStart As Integer
Dim intLength As Integer
intLength = TextBox1.SelLength
If intLength > 0 Then
intStart = TextBox1.SelStart + 1
ActiveCell.Characters(intStart, intLength).Font.Superscript = True
End If
End Sub

Private Sub btnSub_Click()


Dim intStart As Integer
Dim intLength As Integer
intLength = TextBox1.SelLength
If intLength > 0 Then
intStart = TextBox1.SelStart + 1
ActiveCell.Characters(intStart, intLength).Font.Subscript = True
End If
End Sub
Private Sub btnExit_Click()
Unload UserForm1
End Sub

Private Sub btnNormal_Click()


Dim intStart As Integer
Dim intLength As Integer
intLength = TextBox1.SelLength
If intLength > 0 Then
intStart = TextBox1.SelStart + 1
ActiveCell.Characters(intStart, intLength).Font.Superscript = False

ExcelTips: The Macros Page 249


Affecting Cell Formatting

ActiveCell.Characters(intStart, intLength).Font.Subscript = False


End If
End Sub

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.

Automatically Copying Formatting


One of the foundational features of Excel is to allow one cell to be equal to another cell. For
instance, you could use the most simple of formulas in a cell:

=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.

Private Sub Worksheet_Change(ByVal Target As Excel.Range)


If Not Intersect(Target, Range("C7")) Is Nothing Then
Range("C7").Copy (Worksheets("Sheet1").Range("E3"))
End If
End Sub

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.)

ExcelTips: The Macros Page 250


Affecting Cell Formatting

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. Select the cells or range of which you want a picture taken.


2. Click on the Camera tool. The mouse pointer changes to a large plus sign.
3. Change to a different worksheet.
4. Click where you want the top left-hand corner of the picture to appear. The picture is
inserted as a graphic on the worksheet.

Matching Formatting when Concatenating


When using a formula to merge the contents of multiple cells into one cell, Kris is having trouble
getting Excel to preserve the formatting of the original cells. For example, assume that cells A1
and B1 contain 1 and 0.33, respectively. In cell C1, he enters the following formula:

=A1 & " : " & B1

The result in cell C1 looks like this:

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:

=TEXT(A1,"0") & " : " & TEXT(B1,"0.00")

ExcelTips: The Macros Page 251


Affecting Cell 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:

=ROUND(A1,0) & " : " & ROUND(B1,2)

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.

The Calculation tab of the Options dialog box.

3. Ensure that the Precision As Displayed check box is selected.


4. Click OK.

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.

ExcelTips: The Macros Page 252


Affecting Cell Formatting

The Advanced options of the Excel Options dialog box.

4. Ensure that the Set Precision As Displayed check box is selected.


5. Click OK.

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:

Function FmtText(rng As Range)


Application.Volatile
FmtText = rng.Cells(1).Text
End Function

To use this macro, you would use a formula like this in your worksheet:

=FmtText(A1) & " : " & FmtText(B1)

ExcelTips: The Macros Page 253


Affecting Cell Formatting

Checking All Cell Formatting in VBA


Excel has two different types of formatting that can be applied to cell. The first is explicit
formatting. This type of formatting is applied by using the toolbars or by using the Cells option
from the Format menu. The second type of formatting is conditional formatting. This type of
formatting is applied based on a set of rules that you define.

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

Replacing Cell Formats


David needs to find and change every occurrence of a specific cell format in a multi-worksheet
workbook. For example, he may need to find all cells that are formatted as Currency and change
that format to General. He wonders how to accomplish the task.

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.

ExcelTips: The Macros Page 254


Affecting Cell Formatting

The Replace tab of the Find and Replace 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.

The Number tab of the Find Format dialog box.

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.

ExcelTips: The Macros Page 255


Affecting Cell Formatting

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

For iSht = 1 To Sheets.Count


Set rng = Worksheets(iSht).UsedRange
With rng
.NumberFormat = "General"
End With
Next
End Sub

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

For iSht = 1 To Sheets.Count


For Each c In Worksheets(iSht).UsedRange.Cells
If c.NumberFormat = "$#,##0.00" Then
c.NumberFormat = "General"
End If
Next c
Next
End Sub

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.

Public Sub UpdateFormats()


Dim rFind As Range

ExcelTips: The Macros Page 256


Affecting Cell Formatting

Dim rReplace As Range


Dim rNextCell As Range
Dim sNewFormat As String
Dim sOldFormat As String
Dim ws As Worksheet

On Error Resume Next

' Determine the old format


Do
Set rFind = Application.InputBox( _
prompt:="Select a cell that uses the format " & _
"for which you want to search", _
Type:=8)

If rFind Is Nothing Then


If MsgBox("Do you want to quit?", vbYesNo) = vbYes Then
Exit Sub
ElseIf InStr(1, rFind.Address, ":", vbTextCompare) > 0 Then
MsgBox "Please select only one cell."
Set rFind = Nothing
End If
End If
Loop Until Not rFind Is Nothing
sOldFormat = rFind.NumberFormat

' Determine the new format


Do
Set rReplace = Application.InputBox( _
prompt:="Select a cell using the new format", _
Type:=8)

If rReplace Is Nothing Then


If MsgBox("Do you want to quit?", vbYesNo) = vbYes Then
Exit Sub
ElseIf InStr(1, rReplace.Address, ":", vbTextCompare) > 0 Then
MsgBox "Please select only one cell."
Set rReplace = Nothing
End If
End If
Loop Until Not rReplace Is Nothing
sNewFormat = rReplace.NumberFormat

' Do the replacing


For Each ws In ActiveWorkbook.Worksheets
For Each rNextCell In ws.UsedRange
If rNextCell.NumberFormat = sOldFormat Then
rNextCell.NumberFormat = sNewFormat
End If
Next rNextCell
Next ws
MsgBox "The selected format has been changed."
End Sub

ExcelTips: The Macros Page 257


Affecting Cell Formatting

Determining Font Formatting


Oscar has a need to determine the font and font size applied to text in a cell. For instance, if the
text in cell A1 is in 12-pt Arial, he would like a function that can be used to return "Arial" in cell
B1 and 12 in cell C1.

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.

Function FontInfo1(Rn As Range, Optional iType As Integer)


Application.Volatile
If iType = 2 Then
FontInfo1 = Rn.Font.Size
Else
FontInfo1 = Rn.Font.Name
Endif
End Function

You use the function by using a formula such as this in a cell:

=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:

Function FontInfo2(c As Range) As Variant


Application.Volatile
FontInfo2 = Array(c.Font.Name, c.Font.Size)
End Function

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).

Changing Fonts in Multiple Workbooks


Hamish is facing a daunting task: He needs to change the default fonts used in a large number of
Excel workbooks. He has over 100 workbooks, and the fonts used in those workbooks need to be

ExcelTips: The Macros Page 258


Affecting Cell Formatting

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

'Change these lines as appropriate


'These are the fontnames to find
vNamesFind = Array("Arial", "Allegro BT")
'These are the fontnames to replace
vNamesReplace = Array("Wingdings", "Times New Roman")
'This is the folder to look for xls files
sPath = "C:\foldername\"

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

ExcelTips: The Macros Page 259


Affecting Cell Formatting

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.)

Making All Occurrences Bold


Tom rightly notes that in Word you can use Find and Replace to make all occurrences of a word
bold. (Search for the word, replace it with the same word with bold formatting turned on.) He
wonders how he can do the same thing in Excel.

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.

ExcelTips: The Macros Page 260


Affecting Cell Formatting

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.

ExcelTips: The Macros Page 261


Affecting Cell Formatting

The Font tab of the Replace Format dialog box.

7. In the Font Style list, choose Bold.


8. Click OK to close the Replace Font dialog box.
9. Click Replace All to perform the replacements.

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:

ExcelTips: The Macros Page 262


Affecting Cell Formatting

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

On Error Resume Next


Set rng = ActiveSheet.UsedRange. _
SpecialCells(xlCellTypeConstants, xlTextValues)
On Error GoTo ErrHandler
If rng Is Nothing Then
MsgBox "There are no cells with text"
GoTo ExitHandler
End If

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

For Each rCell In rng


With rCell
iFind = InStr(.Value, sFind)
Do While iFind > 0
.Characters(iFind, iLen).Font.Bold = True
lCount = lCount + 1
iStart = iFind + iLen
iFind = InStr(iStart, .Value, sFind)
Loop
End With
Next

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

ExcelTips: The Macros Page 263


Affecting Cell Formatting

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.

Separating Cells Based on Text Color


Thomas has a column that contains nothing but text values, such as sentences and phrases. Some
cells show their text in red and the rest are in black. He would like a way to separate the text such
that black-text cells remain in the column and red-text cells are moved to the next column.

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.

For instance, here's how you would do the sort:

1. Select the cells that contain your text values.


2. Display the Data tab of the ribbon.
3. Click the Sort tool in the Sort & Filter group. Excel displays the Sort dialog box.
4. Using the Sort On drop-down list, choose Font Color.
5. Use the Order drop-down list to indicate which color you want to be shown first in the
sorted list. (This is where you would pick your red color. The drop-down list includes
each color detected in the range you selected in step 1.)
6. Click on OK.

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

If Selection.Columns.Count > 1 Then Exit Sub

ExcelTips: The Macros Page 264


Affecting Cell Formatting

For Each c In Selection


If c.Font.Color = vbRed Then
c.Offset(0, 1) = cell.Value
c.Offset(0, 1).Font.Color = vbRed
c.ClearContents
c.Font.Color = vbBlack
End If
Next c
End Sub

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

For Each c In Selection


If c.Font.Color = vbRed Then _
c.Cut Destination:=c.Offset(0, 1)
Next c
End Sub

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.

Shortcut Key for Format Painter


Fred loves to use the Format Painter, but doesn’t like to use the mouse to use the tool. He would
find it much easier to use if there were a shortcut key instead. Unfortunately, Excel doesn’t
provide a single shortcut key you can use for the Format Painter. You can, however, use the
following steps if you are using a version of Excel prior to Excel 2007:

1. Select the cell from which the format is to be copied.


2. Press CTRL+C. This copies the cell to the Clipboard.
3. Select the cells to receive the format.
4. Press ALT+E, S, T, ENTER. This sequence displays the Paste Special dialog box and
chooses to paste only the format.

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:

1. Select the cell from which the format is to be copied.


2. Press CTRL+C. This copies the cell to the Clipboard.

ExcelTips: The Macros Page 265


Affecting Cell Formatting

3. Select the cells to receive the format.


4. Press SHIFT+F10, S, T, ENTER. This sequence displays the Paste Special dialog box
and chooses to paste only the format.

If you are using Excel 2010 or Excel 2013 then you should follow these steps:

1. Select the cell from which the format is to be copied.


2. Press CTRL+C. This copies the cell to the Clipboard.
3. Select the cells to receive the format.
4. Press SHIFT+F10, S, R. This sequence displays the Context menu and selects the
options to paste just formatting.

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.

Selecting Cells Filled with a Particular Color


Vanita asked if there is a way to select cells containing a specific color. Accomplishing the task
is easy if you are using Excel 2003 or a later version. Just follow these steps:

1. Press CTRL+F to display the Find tab of the Find and Replace dialog box.

ExcelTips: The Macros Page 266


Affecting Cell Formatting

The Find tab of the Find What dialog box.

2. Make sure there is nothing in the Find What box.


3. Click Format. (You may need to click Options to see the Format button.) Excel displays
the Find Format dialog box.
4. Make sure the Patterns tab (Excel 2003) or the Fill tab (later versions) is displayed.

ExcelTips: The Macros Page 267


Affecting Cell Formatting

The Fill tab of the Find Format 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.

ExcelTips: The Macros Page 268


Affecting Cell Formatting

The expanded Find What dialog box.

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

'Select the color by name (8 possible)


'vbBlack, vbBlue, vbGreen, vbCyan,
'vbRed, vbMagenta, vbYellow, vbWhite
lColor = vbBlue

'If you prefer, you can use the RGB function


'to specify a color
'lColor = RGB(0, 0, 255)

Set rColored = Nothing


For Each rCell In Selection
If rCell.Interior.Color = lColor Then
If rColored Is Nothing Then
Set rColored = rCell
Else
Set rColored = Union(rColored, rCell)
End If
End If

ExcelTips: The Macros Page 269


Affecting Cell Formatting

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

Counting Colors of Cells


Besides using values and text in your worksheets, Excel allows you to use colors to either
enliven or provide meaning to your data. If you use colors in your worksheets, you may wonder
if there is a way to count the number of cells that are formatted with a particular fill color. There
is no intrinsic function in Excel to perform such a task, but you can certainly make one with a
user-defined function. The following is an example of one that will count the number of cells in a
range that are formatted with a yellow fill color:

Function CountYellow(MyRange As Range)


Dim iCount As Integer
Application.Volatile
iCount = 0
For Each cell In MyRange
If cell.Interior.ColorIndex = 6 Then
iCount = iCount + 1
End If
Next cell
CountYellow = iCount
End Function

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.

ExcelTips: The Macros Page 270


Affecting Cell Formatting

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.

Counting Empty Colored Cells


Cindy has a fully formatted worksheet that uses color in many cells. Some of the cells have
values in them; many do not. She needs a way to count any colored cells that are empty and
wonders if there is a quick way to do this.

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.

ExcelTips: The Macros Page 271


Affecting Cell Formatting

The Go To Special dialog box.

4. Select the Blanks radio button.


5. Click OK. Now all the blank cells in the worksheet are selected.
6. Press CTRL+F. Excel displays the Find tab of the Find and Replace dialog box.
7. Click the Options button to expand the dialog box.

The Find tab of the Find and Replace dialog box.

8. Click the Format button. Excel displays the Find Format dialog box.
9. Make sure the Fill tab is displayed.

ExcelTips: The Macros Page 272


Affecting Cell Formatting

The Fill tab of the Find Format dialog box.

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

ExcelTips: The Macros Page 273


Affecting Cell Formatting

ActiveSheet.Range("a1").CurrentRegion.SpecialCells(xlCellTypeBlanks).Select

For Each c In Selection


With c.Interior
If .Pattern <> xlNone Then
If .ColorIndex <> xlNone Then
If IsEmpty(c) Then
ColorCount(.ColorIndex) = _
ColorCount(.ColorIndex) + 1
End If
End If
End If
End With
Next c

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

For Each c In Selection


If c.Interior.Pattern <> xlNone Then
If c.Interior.ColorIndex <> xlNone Then
If IsEmpty(c) Then x = x + 1
End If
End If
Next c
MsgBox "Number of colored blank cells: " & x
End Sub

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.)

ExcelTips: The Macros Page 274


Affecting Cell Formatting

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:

1. Select the cell that you want to flash on and off.


2. Make sure the Home tab is displayed on the ribbon.
3. In the Styles group, click Cell Styles. Excel displays a drop-down selections of pre-
defined styles.
4. Choose New Cell Style. Excel displays the Style dialog box.

The Style dialog box in Excel 2007.

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:

1. Select the cell that you want to flash on and off.

ExcelTips: The Macros Page 275


Affecting Cell Formatting

2. Choose Style from the Format menu. Excel displays the Style dialog box.

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:

Dim NextTime As Date

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.

ExcelTips: The Macros Page 276


Affecting Cell Formatting

Using a Custom Format to Add Dashes


Justin has part numbers that he routinely uses in a worksheet, such as 660501C016971. He
would like to apply a custom format to the cell and have the part number automatically displayed
with dashes at the proper location, such as 6605-01-C01-6971.

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

Private Sub DoDashes(What As String)


Dim c As Range

ExcelTips: The Macros Page 277


Affecting Cell Formatting

Dim J As Integer

For Each c In Selection.Cells


If c.Value <> "" Then
J = InStr(c.Value, "-")
Select Case What
Case "Out"
While J > 0
c.Value = Left(c.Value, J - 1) & _
Mid(c.Value, J + 1, Len(c.Value))
J = InStr(c.Value, "-")
Wend
Case "In"
If J = 0 Then
c.Value = _
Left(c.Value, 4) & "-" & _
Mid(c.Value, 5, 2) & "-" & _
Mid(c.Value, 7, 3) & "-" & _
Right(c.Value, 4)
End If
End Select
End If
Next c
End Sub

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.

Moving Custom Formats to Number Formatting


Categories
Neil knows how to create custom formats in Excel. He has created a custom format that displays
dates exactly as he wants them displayed and he would like this custom format to show up in the
Date category (Number tab of the Format Cells dialog) rather than being left in the Custom
category. Neil wonders if there is a way to do this.

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

ExcelTips: The Macros Page 278


Affecting Cell Formatting

You can assign the macro to a shortcut key or to the Quick Access Toolbar, thereby making it
very easy to apply.

Coloring Cells with Formulas


Cells in a worksheet can contain values or they can contain formulas. At some time, you may
wish to somehow highlight all the cells in your worksheet that contain formulas by coloring
those cells. There are several ways you can approach and solve this problem. If you don't have a
need to do the highlighting that often, a manual approach may be best. Follow these steps:

1. Press either F5 or CTRL+G. Excel displays the Go To dialog box.


2. Click Special. Excel displays the Go To Special dialog box.

The Go To Special dialog box.

3. Select the Formulas radio button.


4. Click OK.

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

ExcelTips: The Macros Page 279


Affecting Cell Formatting

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:

Function CellHasFormula(c As Range) As Boolean


CellHasFormula = c.HasFormula
End Function

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:

1. Press CTRL+F3. Excel displays the Define Name dialog box.


2. In the Names field (at the top of the dialog box), enter a name such as FormulaInCell.
3. In the Refers To field (at the bottom of the dialog box), enter the following:
=GET.CELL(48,INDIRECT("rc",FALSE))

ExcelTips: The Macros Page 280


Affecting Cell Formatting

4. Click OK.

If you are using Excel 2007 or later, follow these steps instead:

1. Press CTRL+F3. Excel displays the Name Manager dialog box.


2. Click New. Excel displays the New Name dialog box.

The New Name dialog box.

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

Coloring Identical Company Names


Connie has a worksheet that has company names in each cell of column B. They are grouped
under a region heading (Northeast, West, etc.) in column A. She would like to apply conditional
formatting to the company names so that if a name appears in more than one region, it shows up
using a background or text color that makes the matching companies easy to find. This means
that if one company is formatted as red, no other company should appear as red (it should appear

ExcelTips: The Macros Page 281


Affecting Cell Formatting

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:

1. Select the cells that contain your company names.


2. Display the Home tab of the ribbon and click the Conditional Formatting tool in the
Styles group.
3. Select New Rule. Excel displays the New Formatting Rule dialog box.
4. Click the Format Only Unique or Duplicate Values option at the top of the dialog box.
5. Make sure that Duplicate is selected in the drop-down list in the bottom half of the
dialog box.

The New Formatting Rule dialog box.

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.

ExcelTips: The Macros Page 282


Affecting Cell Formatting

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:

=AND(ISNUMBER(FIND("ABC Company",B1)),COUNTIF($B$1:$B$99,"ABC Company")>1)

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

ExcelTips: The Macros Page 283


Affecting Cell Formatting

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:

1. Select cell A1.


2. If you are using Excel 2007 or later, display the Formulas tab of the ribbon and click the
Define Name tool. Excel displays the New Name dialog box.
3. If you are using older versions of Excel click Insert | Name | Define. Excel displays the
Define Name dialog box.
4. Use a name such as “mycolor” (without the quote marks).
5. In the Refers To box, enter the following, as a single line:

ExcelTips: The Macros Page 284


Affecting Cell Formatting

=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.

ExcelTips: The Macros Page 285


Affecting Cell Formatting

One way you can do this is to use a very simple macro that does nothing but return a color index
value:

Function GetFillColor(Rng As Range) As Long


GetFillColor = Rng.Interior.ColorIndex
End Function

Now, in your worksheet, you can use the following:

=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:

=IF(GetFillColor(B5)=14,"Go", IF(GetFillColor(B5)=3,"Stop", "Neither"))

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

Conditionally Highlighting Cells Containing


Formulas
You probably already know that you can select all the cells containing formulas in a worksheet
by pressing F5 and choosing Special | Formulas. If you need to keep a constant eye on where
formulas are located, then repeatedly doing the selecting can get tedious. A better solution is to
use the conditional formatting capabilities of Excel to highlight cells with formulas.

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:

Function HasFormula(rCell As Range) As Boolean


Application.Volatile
HasFormula = rCell.HasFormula
End Function

ExcelTips: The Macros Page 286


Affecting Cell Formatting

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.”

The Conditional Formatting dialog box.

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.

The Format Cells dialog box.

ExcelTips: The Macros Page 287


Affecting Cell Formatting

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.

The New Formatting Rule dialog box.

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.

ExcelTips: The Macros Page 288


Affecting Cell Formatting

Conditional Formats that Distinguish Blanks and


Zeroes
Let’s say that you routinely import information from another program into Excel. The
information contains numeric values, but it can also contain blanks. You might want to use a
conditional format on the imported information to highlight any zero values. The problem is, if
you just add a conditional format that highlights the cells to see if they are zero, then the
condition will also highlight any cells that are blank, since they contain a “zero” value, as well.

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.

ExcelTips: The Macros Page 289


Affecting Cell Formatting

The New Formatting Rule dialog box.

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.

ExcelTips: The Macros Page 290


Affecting Cell Formatting

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).

The Conditional Formatting dialog box after setting one condition.

5. Click Add. Excel adds a second condition to the dialog box.


6. Using the first drop-down list for Condition 2, choose Cell Value Is.
7. Using the second drop-down list for Condition 2, choose Equal To
8. In the value box for Condition 2, enter 0.

The Conditional Formatting dialog box after setting two conditions.

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.

ExcelTips: The Macros Page 291


Affecting Cell Formatting

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.

The Format Cells dialog box.

ExcelTips: The Macros Page 292


Affecting Cell Formatting

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

For i = 1 To Cells(TotalRows, ColNum).End(xlUp).Row


Cells(i, ColNum).Interior.ColorIndex = xlAutomatic
If IsNumeric(Cells(i, ColNum).Value) Then
If Cells(i, ColNum).Value = 0 Then
Cells(i, ColNum).Interior.ColorIndex = 3
End If

ExcelTips: The Macros Page 293


Affecting Cell Formatting

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.

Changing Font Face and Size Conditionally


Robin asks if there is a way to use Excel’s conditional formatting capabilities to change the font
use in a cell or to change the font size in a cell. The short answer is no, that can’t be done—at
least not with conditional formatting. (The controls that allow you to specify font name and size
are grayed-out in the formatting dialog box used with conditional formatting.)

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

For Each rCell In Selection.Cells


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
End Sub

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:

Private Sub Worksheet_Calculate()


Dim rng As Range
Dim rCell As Range

Set rng = Range("A1:A10")

For Each rCell In rng


If Len(rCell.Text) > 2 Or _
Val(rCell.Value) > 10 Then
rCell.Font.Name = "Arial"
rCell.Font.Size = 16
Else

ExcelTips: The Macros Page 294


Affecting Cell Formatting

rCell.Font.Name = "Times New Roman"


rCell.Font.Size = 12
End If
Next
End Sub

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:

Private Sub Worksheet_Change(ByVal Target As Range)


Dim rCell As Range

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.

ExcelTips: The Macros Page 295


Affecting Cell Formatting

Diagonal Borders in a Conditional Format


Parin likes using the diagonal border on some cells to show the value as “crossed out.” She
would like to use diagonal borders in a conditional format, however. When she tries, she can set
other types of borders, but not a diagonal border—it is not selectable in the conditional format.
She wonders if there is a way to use diagonal borders with conditional formats.

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:

Private Sub Worksheet_Change(ByVal Target As Range)


Dim c As Variant
Dim addr As String

Set Target = Range("C12:C20")

If Intersect(Target, ActiveCell) Is Nothing Then Exit Sub


For Each c In Target
If c = 0 And Len(c) <> 0 Then
addr = c.Address
With Range(addr).Borders(xlDiagonalUp)
.LineStyle = xlContinuous
End With
ElseIf c > 0 And Len(c) > 0 Then
addr = ActiveCell.Address
With Range(addr).Borders(xlDiagonalUp)
.LineStyle = xlNone
End With
End If
Next
End Sub

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.

ExcelTips: The Macros Page 296


Affecting Cell Formatting

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.

Protecting Conditional Formatting


Larry wrote about a problem he was encountering with protecting a worksheet he developed. He
has cells that contain both formulas and conditional formatting. He can protect both of them in a
worksheet, but if someone selects a cell and copies it to another worksheet, the conditional
formatting is visible.

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.

Private Sub Worksheet_Deactivate()


Application.CutCopyMode = False
End Sub

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.

Detecting Errors in Conditional Formatting


Formulas
Allan uses a lot of conditional formatting, nearly always using formulas to specify the conditions
for the formatting. Recently he discovered, by chance, that he had a #REF! error in one of his
conditional format formulas. As far as Allan could figure, this was the result of deleting the row
of a cell referred to in the formula. The impact is that the conditional formatting won't work for

ExcelTips: The Macros Page 297


Affecting Cell Formatting

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.

Removing Conditional Formats, but Not the Effects


Charlie wondered if there is a way to “make permanent” the effects of conditional formatting at
any given time. For instance, if a conditional format specifies that a particular cell be bold red
type, then Charlie wanted a way to remove the conditional format and make the cell bold and
red.

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

Set rWhole = Selection

ExcelTips: The Macros Page 298


Affecting Cell Formatting

For Each rCell In rWhole


rCell.Select
ndx = ActiveCondition(rCell)
If ndx <> 0 Then
'Change the Font info
Set FCFont = rCell.FormatConditions(ndx).Font
With rCell.Font
.Bold = NewFC(.Bold, FCFont.Bold)
.Italic = NewFC(.Italic, FCFont.Italic)
.Underline = NewFC(.Underline, FCFont.Underline)
.Strikethrough = NewFC(.Strikethrough, _
FCFont.Strikethrough)
.ColorIndex = NewFC(.ColorIndex, FCFont.ColorIndex)
End With
'Change the Border Info for each of the 4 types
For x = 0 To 3
Set FCBorder = rCell.FormatConditions(ndx).Borders(iBorders(x))
With rCell.Borders(iBorders(x))
.LineStyle = NewFC(.LineStyle, FCBorder.LineStyle)
.Weight = NewFC(.Weight, FCBorder.Weight)
.ColorIndex = NewFC(.ColorIndex, FCBorder.ColorIndex)
End With
Next x
'Change the interior info
Set FCInt = rCell.FormatConditions(ndx).Interior
With rCell.Interior
.ColorIndex = NewFC(.ColorIndex, FCInt.ColorIndex)
.Pattern = NewFC(.Pattern, FCInt.Pattern)
End With
'Delete FC
rCell.FormatConditions.Delete
End If
Next
rWhole.Select
Application.ScreenUpdating = True
MsgBox ("The Formatting based on the Conditions" & vbCrLf & _
"in the range " & rWhole.Address & vbCrLf & _
"has been made standard for those cells" & vbCrLf & _
"and the Conditional Formatting has been removed")
End Sub

Function NewFC(vCurrent As Variant, vNew As Variant)


If IsNull(vNew) Then
NewFC = vCurrent
Else
NewFC = vNew
End If
End Function

Function ActiveCondition(rng As Range) As Integer


'Chip Pearson http://www.cpearson.com/excel/CFColors.htm
Dim ndx As Long
Dim FC As FormatCondition

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

ExcelTips: The Macros Page 299


Affecting Cell Formatting

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.

ExcelTips: The Macros Page 300


Affecting Cell Formatting

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.

ExcelTips: The Macros Page 301


Affecting Rows and Columns

Affecting Rows and Columns

Removing Duplicate Rows


When you are working with a large data table, it is not uncommon for the table to contain what is
essentially duplicate information. To process the information in the table, you may want to
remove any of the rows you consider duplicate, thereby paring down the amount of information
you need to process.

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

'Start wiping out duplicates


For J = ThisRow To (ThatRow - 1)
If Cells(J, ThisCol) > "" Then
For K = (J + 1) To ThatRow
If Cells(J, ThisCol) = Cells(K, ThisCol) Then
Cells(K, ThisCol) = ""
End If
Next K
End If
Next J

'Remove rows with empty key cells


For J = ThatRow To ThisRow Step -1
If Cells(J, ThisCol) = "" Then
Range(Cells(J, ThisCol), _
Cells(J, RightCol)).Delete xlShiftUp

ExcelTips: The Macros Page 302


Affecting Rows and Columns

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.

Conditionally Deleting Rows


When you are working with data tables containing information that you received from another
person, you may want to prune the amount of data in the table by deleting rows if a particular
condition is met. There are several ways you can approach such a task.

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

strToDelete = InputBox("Value to Trigger Delete?", "Delete Rows")


Set rngSrc = ActiveSheet.Range(ActiveWindow.Selection.Address)

NumRows = rngSrc.Rows.Count
ThisRow = rngSrc.Row
ThatRow = ThisRow + NumRows - 1
ThisCol = rngSrc.Column

For J = ThatRow To ThisRow Step -1


If Cells(J, ThisCol) = strToDelete Then
Rows(J).Select
Selection.Delete Shift:=xlUp
DeletedRows = DeletedRows + 1

ExcelTips: The Macros Page 303


Affecting Rows and Columns

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

Deleting Every X Rows


When you import data from an outside source, you may run into a need to delete extraneous data
from a worksheet. For instance, you may have a need to remove every second line from the data,
or every fifth line. Doing this by hand can be tedious and prone to error. Fortunately, you can
create a macro to help eliminate both the tedium and the errors.

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

iStep = 2 'Delete every 2nd row


Application.ScreenUpdating = False
iStart = 1
iCount = Selection.Rows.Count
'Find ending row to start deleting
For J = iStart To iCount Step iStep
iEnd = J
Next

Do While iEnd >= iStart


Selection.Rows(iEnd).Delete
iEnd = iEnd – iStep
Loop
Application.ScreenUpdating = True

ExcelTips: The Macros Page 304


Affecting Rows and Columns

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.)

Hiding Rows Based on a Cell Value


Excel provides conditional formatting which allows you to change the color and other attributes
of a cell based on the content of the cell. There is no way, unfortunately, to easily hide rows
based on the value of a particular cell in a row. You can, however, achieve the same effect by
using a macro to analyze the cell and adjust row height accordingly. The following macro will
examine a particular cell in the first 100 rows of a worksheet, and then hide the row if the value
in the cell is less than 5.

Sub HideRows()
BeginRow = 1
EndRow = 100
ChkCol = 3

For RowCnt = BeginRow To EndRow


If Cells(RowCnt, ChkCol).Value < 5 Then
Cells(RowCnt, ChkCol).EntireRow.Hidden = True
End If
Next RowCnt
End Sub

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

For RowCnt = BeginRow To EndRow


If Cells(RowCnt, ChkCol).Value < 5 Then
Cells(RowCnt, ChkCol).EntireRow.Hidden = True
Else
Cells(RowCnt, ChkCol).EntireRow.Hidden = False
End If
Next RowCnt
End Sub

ExcelTips: The Macros Page 305


Affecting Rows and Columns

Hiding Rows Based on Two Values


Mike, as an accountant, has a need to hide rows in a worksheet based on the values in two cells
in the row. His data tables have three columns, and if a row contains a zero in columns two and
three, then the row should be hidden. If either column two or three is blank or contains some
other value, then the row should not be hidden.

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.

ExcelTips: The Macros Page 306


Affecting Rows and Columns

Detecting Hidden Rows


Jesse has a large worksheet that may contain hidden rows. He wonders if there is a way to find
out if there are hidden rows in the worksheet other than by looking down the many rows to see
what's missing. If he unhides all the hidden rows, he still won't be able to tell what, if any, rows
may have been hidden.

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

Set rng = Range("A1:A1000")


sTemp = ""
For Each r In rng.Rows
If r.EntireRow.Hidden Then
sTemp = sTemp & "Row " & Mid(r.Address, 4) & vbCrLf
End If
Next r

If sTemp > "" Then


sTemp = "The following rows are hidden:" & vbCrLf & _
vbCrLf & sTemp
MsgBox sTemp
Else
MsgBox "There are no hidden rows"
End If
End Sub

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.

ExcelTips: The Macros Page 307


Affecting Rows and Columns

Skipping Hidden Rows in a Macro


When using a worksheet, it is not uncommon to hide rows that contain data you don’t want
displayed at the current time. If you have written a macro that processes the data in the
worksheet, you may have wondered how to skip over and not process the rows that you have
marked as 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

If Selection.Columns.Count > 1 Then


MsgBox "Only select the cells you want numbered"
Exit Sub
End If

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.

Easily Adding Blank Rows


There may be a time when you are working with a table and you want to insert a blank row
between each existing row in the table. There are several easy ways to do this. If you don’t want
to use a macro, you can follow these steps:

1. Insert a blank column anywhere in the list or table.

ExcelTips: The Macros Page 308


Affecting Rows and Columns

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.

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.

ExcelTips: The Macros Page 309


Affecting Rows and Columns

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

If TypeName(Selection) <> "Range" Then Exit Sub


Set MySelection = Selection
Application.ScreenUpdating = False
For J = MySelection.Rows.Count To 1 Step –1
MySelection.Rows(J).EntireRow.Insert
Next J
Application.ScreenUpdating = True
End Sub

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.

Inserting and Copying Rows


As you are editing worksheets, you may notice that some of your work is done based on work
you have done before. For instance, you may have a row of data that you entered in a previous
Excel session. In this session, you need to copy that row of data and use it as the basis for your
new data, but with a few changes.

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:

ExcelTips: The Macros Page 310


Affecting Rows and Columns

Sub InsertCopyRow2()
ActiveCell.Offset(1, 0).EntireRow.Insert
ActiveCell.EntireRow.Copy ActiveCell.Offset(1, 0).EntireRow
End Sub

Moving and Selecting Rows


James asked if there is a keyboard shortcut to move down a row and select the entire row. In
Excel there is no way to do this with a single keystroke, but there is a way to do it using two
keystrokes. All you need to do is press the DOWN ARROW, immediately followed by pressing
SHIFT+SPACE BAR.

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

ExcelTips: The Macros Page 311


Affecting Rows and Columns

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

Setting Row Height in a Macro


It is not unusual to use macros to process data and format output in an workbook. If you use
macros to do this type of work, you may be interested in changing the height of a row using a
macro. If so, you should pay attention to the RowHeight property. This property, when applied to
a Row object, indicates the height of the row in points.

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):

For Each r In ActiveWindow.RangeSelection.Rows


r.RowHeight = 36
Next r

Automatic Row Height for Wrapped Text


Jordan formatted some cells in his worksheet to wrap text within them. Even though the text in
the cells wraps, Excel won’t automatically adjust the row height to show all the wrapped text.
Jordan wonders if there is a way to “reset” the row so that Excel will adjust its height based on
the text being wrapped within the cells.

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.

ExcelTips: The Macros Page 312


Affecting Rows and Columns

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.

Adjusting Row Height for a Number of Worksheets


Ron knows how to adjust the height of a group of adjacent rows. What he doesn't know (and
needs to) is how to make row heights the same across several worksheets in the same workbook.

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.

ExcelTips: The Macros Page 313


Affecting Rows and Columns

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.

Splitting Information into Rows


James has some data in a worksheet that is contained in a series of rows. One of the columns in
the data includes cells that have multiple lines per cell. (The data in the cell was separated into
lines by pressing ALT+ENTER between items.) James would like to split this data into multiple
rows. For instance, if there were three lines of data in a single cell in the row, then the data in
that cell should be split out into three rows.

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

ExcelTips: The Macros Page 314


Affecting Rows and Columns

Dim lNumRows As Long

iColumn = 4

Set wksSource = ActiveSheet


Set wksNew = Worksheets.Add

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

On Error GoTo ErrRoutine

ExcelTips: The Macros Page 315


Affecting Rows and Columns

Application.ScreenUpdating = False

'Set Column to split


iSplitCol = 4

iCols = Selection.Columns.Count
lRows = Selection.Rows.Count

iColOffset = Selection.Column - 1
lRowOffset = Selection.Row - 1
lRowNew = lRowOffset

Set wksSource = ActiveSheet


Set wksNew = Worksheets.Add

Set AWF = Application.WorksheetFunction


With wksSource
For lRow = (lRowOffset + 1) To (lRowOffset + lRows)
sTemp = .Cells(lRow, iSplitCol)
If Right(sTemp, 1) <> vbLf Then
sTemp = sTemp & vbLf
End If
iCount = (Len(sTemp) - _
Len(AWF.Substitute(sTemp, vbLf, "")))

For iTargetRows = 1 To iCount


lRowNew = lRowNew + 1
For i = (iColOffset + 1) To (iColOffset + iCols)
If i <> iSplitCol Then
wksNew.Cells(lRowNew, i) _
= .Cells(lRow, i)
Else
iEnd = InStr(sTemp, vbLf)
wksNew.Cells(lRowNew, i) _
= Left(sTemp, iEnd - 1)
sTemp = Mid(sTemp, iEnd + 1)
End If
Next i
Next iTargetRows
Next lRow
End With

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.

ExcelTips: The Macros Page 316


Affecting Rows and Columns

Shading Rows for Ease in Reading Output


Those who have been around computers long enough may remember the days of green-bar
paper. That's not to slight any places that may still be using green-bar; its just that the hey-day of
such paper seems to be past. The need for such paper, particularly when dealing with numeric
printouts, is still present however.

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

iStep = 5 'Shade every 5th row


Application.ScreenUpdating = False
iStart = 1
iEnd = Selection.Rows.Count

For J = iStart To iEnd Step iStep


With Selection.Rows(J).Interior
.ColorIndex = 15
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
End With
Next J
Application.ScreenUpdating = True
End Sub

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.

Summing Every Fourth Cell in a Row


Kevin needs to create a formula that sums every fourth cell in a row. He knows he can use a
formula such as =A6+E6+I6+M6, etc., but this becomes cumbersome if there are a lot of
columns in the worksheet.

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

ExcelTips: The Macros Page 317


Affecting Rows and Columns

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:

ExcelTips: The Macros Page 318


Affecting Rows and Columns

=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:

Function SumEveryFourth(MyRange As Range)


Dim x As Integer
SumEveryFourth = 0
For x = 1 To MyRange.Cells.Count
If (x Mod 4) = 1 Then
SumEveryFourth = SumEveryFourth + MyRange.Cells(x).Value
End If
Next x
End Function

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)

Condensing Sequential Values to a Single Row


Rusty has a list of ZIP Codes in a column of a worksheet. He would like a way to "compress" the
codes so that sequential ranges of values are on a single row. So, for instance, instead of 35013,
35014, and 35015 taking up three rows, they would appear on a single row as 35013-35015.

ExcelTips: The Macros Page 319


Affecting Rows and Columns

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))

Then, in cell C2, enter the following long formula:

=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

Set rng = Selection


sStart = rng.Cells(1)
sEnd = sStart
y = 1
For x = 1 To rng.Count - 1
If rng.Cells(x + 1) - _
rng.Cells(x) > 1 Then 'End
ReDim Preserve sNewArray(1 To y)
If sStart = sEnd Then
sNewArray(y) = sStart
Else
sNewArray(y) = sStart & "-" & sEnd
End If
sStart = rng.Cells(x + 1)
y = y + 1
End If
sEnd = rng.Cells(x + 1)
ReDim Preserve sNewArray(1 To y)
If sStart = sEnd Then
sNewArray(y) = sStart
Else
sNewArray(y) = sStart & "-" & sEnd
End If

ExcelTips: The Macros Page 320


Affecting Rows and Columns

Next
rng.ClearContents
For x = 1 To y
rng.Cells(x) = "'" & sNewArray(x)
Next
Set rng = Nothing
Set rCell = Nothing
End Sub

Hiding a Huge Number of Rows


Julia would like to hide all the rows in a worksheet except rows 1-30. She is looking for the
fastest, easiest way to do this.

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:

1. Select cell A31.


2. Press SHIFT+CTRL+DOWN ARROW. All the cells from A31 through the last used cell in
column A are selected.
3. Press SHIFT+CTRL+DOWN ARROW again. The selection is extended through the very last
cell in column A.
4. Hide your rows as you normally would. (Either right-click and hide that way or use the
menus/ribbon.)

If you don't have data in all the cells of column A, then the following variation is probably the
fastest method:

1. Select cell A31.


2. Press SHIFT+CTRL+END. All the cells from A31 through the last used cell in the data
table selected.
3. Press SHIFT+CTRL+DOWN ARROW. The selection is extended through the very last row in
the worksheet.
4. 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.)

Here's another quick method that can be used:

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.)

ExcelTips: The Macros Page 321


Affecting Rows and Columns

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.

Getting Rid of Empty Rows after Importing


There are numerous times when Tom has a worksheet imported from another program into Excel
so he can work with the data. The importing works fine, but the import process adds lots (dozens
and sometimes hundreds) of extra rows that have no data in them. After the import Tom has to
manually delete those extra rows so he can use the rest of the data. Tom wonders if there is a
way to easily get rid of these empty rows.

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.

Another approach is to follow these steps:

1. Select an entire column.


2. Press F5. Excel displays the Go To dialog box.
3. Click Special. Excel displays the Go To Special dialog box.

ExcelTips: The Macros Page 322


Affecting Rows and Columns

The Go To Special dialog box.

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.

Additional information on this topic can be found on these pages:

ExcelTips: The Macros Page 323


Affecting Rows and Columns

http://www.cpearson.com/Excel/deleting.htm#DeleteBlankRows
http://dmcritchie.mvps.org/excel/lastcell.htm

Floating Information in a Frozen Row


Bev has a worksheet with two header rows that are frozen and a column that is frozen. She can
then scroll across the page adding data week by week. Names and subtotals are fixed on the left,
week dates across the top. Above all this, in the first frozen row, Bev has a nice fancy title
describing the workbook. She’s looking for a way that she can have her title (the one in the first
row) "float" so that when she scrolls across the page the title does not disappear off the edge of
the visible worksheet.

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:

Private Sub Worksheet_SelectionChange(ByVal Target As Excel.Range)


Me.Shapes("TitleTextBox").Left = ActiveWindow.VisibleRange.Left
End Sub

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

ExcelTips: The Macros Page 324


Affecting Rows and Columns

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:

Private Sub Workbook_Open()


UpdateTB
End Sub

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.

There is an additional "downside" to either macro-based technique besides any sluggishness


introduced by the code running: every time the code runs it clears the "undo stack." This means
that you won’t be able to “undo” changes that you make to the workbook if you need to.

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

ExcelTips: The Macros Page 325


Affecting Rows and Columns

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

Alphabetic Column Designation


You can easily determine the numeric column of cell by using the COLUMN function. All you
need to do is put a formula like this in a cell, and the result is a value where A=1, B=2, etc.:

=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:

ExcelTips: The Macros Page 326


Affecting Rows and Columns

=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:

Function AlphaCol() As String


Dim J As Integer
Dim K As Integer
Dim iDiv As Integer
Dim sTemp As String

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()

A text string is returned, consisting of the column designation.

Unhiding a Single Column


I regularly hide and unhide columns in my worksheets. If I have an entire range of columns
hidden, I find it a bother to unhide a single column out of all those hidden. For instance, if I hide
columns C:M, and I want to unhide column F, then I need to unhide the entire range and then
rehide C:E and G:M. (There are other ways I could accomplish the same task, but none of them
are particularly endearing.)

However, I find that a single column can be unhidden very easily using a macro. Consider the
following:

ExcelTips: The Macros Page 327


Affecting Rows and Columns

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

MsgBox "Column " & UCase(Col) & " is now visible.", _


vbOKOnly, "Unhide Specified Column"
Set rng = Nothing
On Error GoTo 0
End Sub

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.

Setting Column Width in a Macro


It is not unusual to use macros to process data and format output in an workbook. If you use
macros to do this type of work, you may be interested in changing the width of a column using a
macro. If so, you should pay attention to the ColumnWidth property. This property, when
applied to a Column object, indicates the width of the column in characters, based on the current
font settings.

For instance, the following code snippet steps through the columns in a selection and sets the
width of each column to 10 characters:

For Each c In ActiveWindow.RangeSelection.Columns


c.ColumnWidth = 10
Next c

ExcelTips: The Macros Page 328


Affecting Rows and Columns

Deleting Duplicate Columns


Dror has a worksheet that contains quite a bit of data. It is possible that the data in one column
will be exactly the same as the data in another column, so he wonders if there is an easy way to
delete any duplicate columns within the worksheet.

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)

(Remember that an array formula is entered by using SHIFT+CTRL+ENTER.) The formula


compares all the values in the first 100 rows of columns A and B. If they are all the same, then
the formula returns TRUE. If any of the cells don’t match, then the formula returns FALSE. If
the result is TRUE you could then delete one of the columns because they are the same.

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

On Error Resume Next


Set rngData = ActiveSheet.UsedRange
If rngData Is Nothing Then Exit Sub

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

ExcelTips: The Macros Page 329


Affecting Rows and Columns

End Sub

Function AreEqualArr(arr1, arr2) As Boolean


Dim i As Long, n As Long
AreEqualArr = False
For n = LBound(arr1) To UBound(arr1)
If arr1(n, 1) <> arr2(n, 1) Then
Exit Function
End If
Next n
AreEqualArr = True
End Function

Deleting Blank Columns


When importing information from an external source, it is possible that the data will contain
blank columns—columns with nothing in them. If you import a lot of data, then deleting these
columns can be a bother. There are a couple of ways you can approach how to delete these
columns.

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.

The Sort Options dialog box.

4. Choose the Sort Left to Right radio button.


5. Click on OK to dismiss the Sort Options dialog box.
6. Using the first Sort By drop-down list, specify the row by which you want to sort.

ExcelTips: The Macros Page 330


Affecting Rows and Columns

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 & " "

ExcelTips: The Macros Page 331


Affecting Rows and Columns

Cells(ThisRow, ThisCol).Value = ""


Next K
MyText = Trim(MyText)
Cells(ThisRow, FirstCol).Value = MyText
Next J
End Sub

Combining Multiple Rows in a Column


Bonnie described a common problem that occurs when importing a file into Excel. The file being
imported is a scanned text file, and the import goes just fine, with one small glitch: in one
column where there was wrapped text in the original document, the text now occupies several
rows in the worksheet. Bonnie is looking for a way to combine those rows back into a single cell
in that column.

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

If Selection.Cells.Count > 1 Then


For J = 2 To Selection.Cells.Count
Selection.Cells(1).Value = _
Selection.Cells(1).Value & " " & _
Selection.Cells(J).Value
Selection.Cells(J).Clear
Next J
End If
End Sub

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.

ExcelTips: The Macros Page 332


Affecting Rows and Columns

Changing Shading when a Column Value Changes


Doug has a data table that includes a column of part numbers. This data is sorted by the part
numbers column. The part numbers are not unique; for instance, some part numbers appear three
times in the table and others appear five times. Doug would like to format the table so that the
rows of the table have a "green bar" effect.

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

ExcelTips: The Macros Page 333


Affecting Rows and Columns

Dim LastRow As Long


Dim Clr As Integer
Dim R As Long

LastRow = ActiveSheet.Range("A" & Rows.Count).End(xlUp).Row

' Enter desired color codes here


' (24 is Lavender, 35 is Light Green)
RwColor = Array(24, 35)

Clr = 0 ' Used to toggle between the two colors

For R = 2 To LastRow
ThisOrder = Cells(R, 1).Value
PrvOrder = Cells(R - 1, 1).Value
If ThisOrder <> PrvOrder Then Clr = 1 - Clr

' Select only the columns that are used


Range("A" & R & ":M" & R).Select
Selection.Interior.ColorIndex = RwColor(Clr)
Next R
End Sub

Selecting Columns in VBA when Cells are Merged


Say you have a blank worksheet and the range A1:F1 has the "merge and center" format applied
to it. If you select column B by clicking the column heading, Excel dutifully selects column B
and makes cell B2 the active cell. This behavior was modified in either Excel 2000 or Excel
2002; in previous versions of Excel you get the merged cell (A1:F1) included in the selection.

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

ExcelTips: The Macros Page 334


Affecting Rows and Columns

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.

ExcelTips: The Macros Page 335


Affecting Rows and Columns

Hiding Columns Based on a Cell Value


Excel’s great conditional formatting capabilities allow you to change the formatting of cells
based on the content of a cell. There is no way, unfortunately, to easily hide entire columns of
data based on the value of a particular cell.

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.

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


If Range("B4").Value = 0 Then
Columns("H").EntireColumn.Hidden = True
Else
Columns("H").EntireColumn.Hidden = False
End If
End Sub

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

ExcelTips: The Macros Page 336


Affecting Rows and Columns

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.

ExcelTips: The Macros Page 337


Filtering Data and Working with Filtered Data

Filtering Data and Working with Filtered


Data

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:

ExcelTips: The Macros Page 338


Filtering Data and Working with Filtered Data

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.

Here are the steps for earlier versions of Excel:

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.

Quickly Identifying Applied AutoFilters


Jim wants a way to quickly tell what filtering criteria have been applied in an AutoFilter. He has
a hard time telling even which columns have filtering applied (the slight change in drop-down
arrow color from black to blue is hardly noticeable), so some other method of telling where a
filter is applied would be nice.

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:

ExcelTips: The Macros Page 339


Filtering Data and Working with Filtered Data

Function DispCriteria(Rng As Range) As String


Dim Filter As String

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

Filtering for Comments


Robert has a worksheet that has comments included in various places. He wonders if it is
possible to filter the rows in a data table so that only those rows that include comments in a
particular column will be displayed.

ExcelTips: The Macros Page 340


Filtering Data and Working with Filtered Data

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:

1. Make a copy of the column that contains comments to be filtered.


2. Select the duplicate column.
3. Press F5 to display the Go To tab of the Find and Replace dialog box.
4. Click Special. Excel displays the Go To Special dialog box.
5. Click the Comments radio button and then press ENTER. Only those cells containing
comments are selected.
6. Type any number, character, or phrase not already present in the column.
7. Press CTRL+ENTER. All the selected cells (those with comments) should now contain
what you typed in step 6.
8. Use AutoFilter to display only those rows that contain whatever you typed in step 6.

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:

Function CellHasComment(c As Range)


Application.Volatile True
CellHasComment = Not c.Comment Is Nothing
End Function

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.

Clearing Only Filtering Settings


On many of his worksheets Roy uses sorting and filtering extensively. On the Data tab of the
ribbon, in the Sort & Filter group, there is a Clear tool. This tool clears all filtering and sorting
settings. Roy routinely needs to clear the filtering settings, but he would like his sorting settings
to remain unaffected by the clearing. He wonders if there is a way to either change how this tool
behaves or to clear only the filtering settings in a single action.

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:

ExcelTips: The Macros Page 341


Filtering Data and Working with Filtered Data

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.

ExcelTips: The Macros Page 342


Filtering Data and Working with Filtered Data

Changing AutoFilter Drop-Down Arrow Colors


Gus asked if there was a way to change the color of the drop-down arrows that appear at the top
of each column when AutoFilter is turned on. When a filter is not applied to a column, the drop-
down arrow is black; when a filter is applied, the drop-down arrow is navy blue. Gus wanted to
change the colors because there isn’t enough contrast between black and navy blue on his
monitor.

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:

Private Sub Workbook_SheetCalculate(ByVal Sh As Object)


If Sh.AutoFilterMode Then ColorDisplayFilter
End Sub

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)

ExcelTips: The Macros Page 343


Filtering Data and Working with Filtered Data

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.

Easy Filtering Specifications for a PivotTable


David has many PivotTables that are filtered by year. In his data there is a separate field for year
(values 2008, 2009, 2010, etc., derived via the Year function). He excludes older years in most
cases and reports on more recent years. When a new year arrives, it is a tedious process for
David to edit every PivotTable and select the most recent year. He wonders if there is a way to
specify the selection values for a PivotTable filter so he doesn't need to go through the tedious
editing.

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

iYear = 2012 ' Change to desired year


For Each sht In Worksheets
For Each pvt In sht.PivotTables
pvt.PivotFields("Year").ClearAllFilters
pvt.PivotFields("Year").CurrentPage = iYear
Next pvt
Next sht
End Sub

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.

ExcelTips: The Macros Page 344


Filtering Data and Working with Filtered Data

Macro Fails after AutoFilter


In testing new macros that you develop, you may find that the macro fails when it is run and the
information in a worksheet is filtered using the AutoFilter. (Beginning with Excel 2007 the term
AutoFilter was essentially done away with; it is now simply called Filter.) This can happen
because the macro may expect that all the information in the worksheet is available, or it may try
to update information that is not visible on the screen.

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.

ExcelTips: The Macros Page 345


Working with Dates and Times

Working with Dates and Times

Modifying Default Year for Dates


When entering dates into a worksheet, Excel provides quite a bit of latitude for how you enter
them. One area where latitude is granted is when it comes to including a year. If you include a
year (such as 3/2/08), that is great. If you don’t include a year (such as 3/2), then Excel helpfully
adds the current year to what you are entering.

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:

Private Sub Worksheet_Change(ByVal Target As Range)


If Target.Cells.Count > 1 Then Exit Sub
If Target = "" Then Exit Sub
If Target.Column <> 1 Then Exit Sub

If DateValue(Target) > Date Then


Application.EnableEvents = False
Target = DateAdd("yyyy", -1, Target)
Application.EnableEvents = True
End If
End Sub

ExcelTips: The Macros Page 346


Working with Dates and Times

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.

Entering Dates without Separators


Different people enter data in different ways. When you enter information into a cell, Excel tries
to figure out what type of information you are entering. If you enter a number such as 08242008,
Excel assumes you are entering a numeric value, and treats it accordingly. What if the number
you enter actually is a date, without any separators? Can Excel understand what you are
entering?

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:

1. Select the column of dates.


2. Make sure there is nothing in the column just to the right of the dates.
3. Choose Text to Columns from the Data menu. (In Excel 2007 or later, select Text to
Columns from the Data tab of the ribbon.) Excel displays the Convert Text to Columns
Wizard.

ExcelTips: The Macros Page 347


Working with Dates and Times

The Convert Text to Columns Wizard.

4. Select the Fixed Width option, then click on Next.


5. Click on Next again.
6. In the Column Data Format area, choose Date.
7. Select the range in the Destination box, then in the worksheet click the cell just to the
right of the first value you selected in step 1.
8. Click on Finish.

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.

ExcelTips: The Macros Page 348


Working with Dates and Times

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:

Function DateTime(dblDateTime As Double, _


Optional bAmerican As Boolean = True)
'Converts Date and time "number" without
'delimiters into an excel serialdate (which
'can then be formatted with the Excel
'date/time formats)

'If optional argument is TRUE (or missing),


'function assumes value is of form:
' [m]mddyyyy.hhmm (leading "0" not required)

'If optional argument is FALSE, function


'assumes value is of form:
' [d]dmmyyyy.hhmm (leading "0" not required)

Dim iYear As Integer


Dim iMonth As Integer
Dim iDay As Integer
Dim iHour As Integer
Dim iMin As Integer

iYear = Int((dblDateTime / 10000 - _


Int(dblDateTime / 10000)) * 10000)
iDay = Int((dblDateTime / 1000000 - _
Int(dblDateTime / 1000000)) * 100)
iMonth = Int((dblDateTime / 1000000))
iHour = Int((dblDateTime - Int(dblDateTime)) * 100)
iMin = Int((dblDateTime * 100 - _
Int(dblDateTime * 100)) * 100 + 0.5)

If bAmerican Then
DateTime = DateSerial(iYear, iMonth, iDay)
Else
DateTime = DateSerial(iYear, iDay, iMonth)
End If

DateTime = DateTime + (iHour + iMin / 60) / 24


End Function

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.)

ExcelTips: The Macros Page 349


Working with Dates and Times

Entering or Importing Times without Colons


When you enter a time into a cell, Excel keys on the presence of the colon between the hour and
minute portions of the time. Because of the position of the colon on the keyboard, however,
entering a colon for each time value that you enter can slow you down—particularly if you have
quite a few time values to enter.

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:

Private Sub Workbook_SheetChange(ByVal Sh As Object, _


ByVal Target As Excel.Range)
Dim TimeStr As String

On Error GoTo EndMacro


If Application.Intersect(Target, Range("C7:D15")) Is Nothing Then
Exit Sub
End If
If Target.Cells.Count > 1 Then
Exit Sub
End If
If Target.Value = "" Then
Exit Sub
End If

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) & ":" & _

ExcelTips: The Macros Page 350


Working with Dates and Times

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

Parsing Non-Standard Date Formats


Bill is faced with the challenge of importing data into Excel that was originally created in other
applications. The problem is that the data contains lots of dates, but they are in a format that
Excel doesn’t understand. For instance, the dates may be in the format 01.15.11 or 1.15.2011,
neither of which is treated as a date by Excel. Bill wants to know how to convert the non-
standard dates to a date format that Excel understands.

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:

1. Insert three blank columns to the right of the date column.

ExcelTips: The Macros Page 351


Working with Dates and Times

2. Select the cells containing the non-standard dates.


3. Using the Text to Columns Wizard (Data | Text to Columns or, in Excel 2007 and later
versions, choose Text to Columns from the Data tab of the ribbon), choose delimited
data and use a period as the delimiter. After the wizard is done, you end up with three
columns containing the month, day, and year.
4. In the remaining blank column, enter a formula such as the following:

=DATE(C1,A1,B1)

5. Copy the formula down to other cells next to the dates.


6. Select the cells containing the formulas you just created, then press CTRL+C.
7. Use Paste Special to convert the formulas to results. (Select the Values option when
using Paste Special.)
8. Delete the three columns that contain the separated dates, and keep the column that
contains the final dates.

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.

Public Function Convert_Date(A As String) As Date


Dim K As Long
Dim K1 As Long
Dim K2 As Long

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

Converting an Unsupported Date Format


It is not uncommon to load information from other programs into Excel. For instance, you may
have data generated by another program, and you want to analyze that data in Excel. When you
import data into Excel, it does a fairly good job of assigning the proper data types to information,
and it can even parse and convert some data.

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.

ExcelTips: The Macros Page 352


Working with Dates and Times

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.

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.

ExcelTips: The Macros Page 353


Working with Dates and Times

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

For Each c In Selection.Cells


c = DateValue(Mid(c, 5, 6) & ", " _
& Mid(c, 21, 4)) + TimeValue(Mid(c, 12, 8))
c.NumberFormat = "dd MMMM yyyy h:mm:ss"
Next c
End Sub

The macro converts the text string to an acceptable date/time (using DateValue) and then formats
the cell to display the value property.

Deciphering a Coded Date


Luis receives information in which dates are coded such that years, months, and days are
replaced with a single character each. For each field, the numbers 1 to 9 are used and after that
letters, from a=10, b=11, ... v=31. For example, the date code 'bc2' means b=11 (the year 2011),
c=12 (the month), and day=2. Luis wonders if a function can be devised to replace the coded
date with a common date format, such as dd/mm/yyyy.

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),

ExcelTips: The Macros Page 354


Working with Dates and Times

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:

Function DecodeDate(datecode As String)


Const X = "123456789abcdefghijklmnopqrstuvwxyz"
Dim D As Integer
Dim M As Integer
Dim Y As Integer

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.

Limiting Entry of Prior Dates


If you use the data validation capabilities of Excel, you can limit what goes into a cell, based on
the contents of another cell. For instance, you can easily limit what goes into cell A2 based on a
date that is in cell A1. Follow these steps:

1. Select cell A2.

ExcelTips: The Macros Page 355


Working with Dates and Times

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.

The Settings tab of the Data Validation dialog box.

4. Using the Allow drop-down list, choose Date.


5. Using the Data drop-down list, choose Greater Than or Equal To.
6. In the Start Date box, enter =A1. This tells Excel that the date must be greater than or
equal to whatever date is in cell A1.
7. Click OK.

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.

ExcelTips: The Macros Page 356


Working with Dates and Times

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:

Private Sub Worksheet_Change(ByVal Target As Range)


On Error Resume Next
If Target = Range("A1") Then Date_Validation
End Sub

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

ExcelTips: The Macros Page 357


Working with Dates and Times

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.

Adjusting Date Values by Keypress


Robert notes that in a Quicken date field, if he presses the plus or minus key the date increments
or decrements by one day. He would like to create this same sort of effect in Excel.

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"

ExcelTips: The Macros Page 358


Working with Dates and Times

End Sub

Sub End_OnKey()
Application.OnKey "^u"
Application.OnKey "^d"
End Sub

Hiding Columns Not within a Date Range


Jason has a worksheet that contains several columns, each of which represents a fiscal week for
his company. These fiscal weeks begin with column G, with column H being the second fiscal
week, column I being the third week, and so forth. Jason would like to create a macro that, when
run, would look at today's date, calculate the fiscal week based on that date, and then hide any of
the fiscal-week columns that are not within a specified range.

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.

The following macro implements the approach discussed so far.

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

BeginYear = Cells(1, 1).Value


FirstWeekCol = 7 'fiscal weeks begin with Col 7 (G)

'Calculate Column of the current fiscal week


CurrWkCol = ((Date - BeginYear) \ 7) + FirstWeekCol - 1
'Calculate column of the first week to show
FirstShowWkCol = CurrWkCol - 14
If FirstShowWkCol < FirstWeekCol Then
FirstShowWkCol = FirstWeekCol
End If

Application.ScreenUpdating = False

'Unhide all columns


Columns("G:IV").Hidden = False 'Unhide all week Columns

'Hide week column before the rolling quarter

ExcelTips: The Macros Page 359


Working with Dates and Times

For J = FirstWeekCol To FirstShowWkCol


Columns(J).Hidden = True
Next J

'Hide week column after current week


For J = CurrWkCol + 1 To 256
Columns(J).Hidden = True
Next intCol

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.

Getting Excel Dates into Outlook's Calendar


Kelly has a worksheet in which she has a table of dates used as closing dates for getting
advertisements into the local Yellow Pages. She wants to import these dates into Outlook
Calendar with a 72 hour reminder, but some of the dates keep changing to numbers. Kelly
wonders how she can get the Excel dates into Outlook like she needs.

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

ExcelTips: The Macros Page 360


Working with Dates and Times

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")

' Start at row 2


r = 2

Do Until Trim(Cells(r, 1).Value) = ""


' Create the AppointmentItem
Set myApt = myOutlook.createitem(1)
' Set the appointment properties
myApt.Subject = Cells(r, 1).Value
myApt.Location = Cells(r, 2).Value
myApt.Start = Cells(r, 3).Value
myApt.Duration = Cells(r, 4).Value
' If Busy Status is not specified, default to 2 (Busy)
If Trim(Cells(r, 5).Value) = "" Then
myApt.BusyStatus = 2
Else
myApt.BusyStatus = Cells(r, 5).Value
End If
If Cells(r, 6).Value > 0 Then
myApt.ReminderSet = True
myApt.ReminderMinutesBeforeStart = Cells(r, 6).Value
Else
myApt.ReminderSet = False
End If
myApt.Body = Cells(r, 7).Value
myApt.Save
r = r + 1
Loop
End Sub

The macro continues to loop through the rows until the Subject column is empty.

Adding Ordinal Notation to Dates


When developing a workbook, you may have a need to place suffixes such as "st, nd, rd, or th" at
the end of dates, as in "9th March." Unfortunately, there is no way to do this using the built-in
date formats you can apply to individual cells. You can create custom formats for each of the
four suffix types, if desired, but they would have to be applied individually based on the contents
of the cell itself.

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")

ExcelTips: The Macros Page 361


Working with Dates and Times

&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:

Function OrdinalDate(myDate As Date)


Dim dDate As Integer
Dim dText As String
Dim mDate As Integer
Dim mmmText As String

dDate = Day(myDate)
mDate = Month(myDate)

Select Case dDate


Case 1: dText = "st"
Case 2: dText = "nd"
Case 3: dText = "rd"
Case 21: dText = "st"
Case 22: dText = "nd"
Case 23: dText = "rd"
Case 31: dText = "st"
Case Else: dText = "th"
End Select

Select Case mDate


Case 1: mmmText = " January"
Case 2: mmmText = " February"
Case 3: mmmText = " March"
Case 4: mmmText = " April"
Case 5: mmmText = " May"
Case 6: mmmText = " June"
Case 7: mmmText = " July"
Case 8: mmmText = " August"
Case 9: mmmText = " September"
Case 10: mmmText = " October"
Case 11: mmmText = " November"
Case 12: mmmText = " December"
End Select

OrdinalDate = dDate & dText & mmmText


End Function

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)

ExcelTips: The Macros Page 362


Working with Dates and Times

Tombstone Date Math


Robert loves to work on genealogy. Sometimes when he finds an older cemetery, instead of the
birth and death dates being visible on a tombstone, just one date is visible and an age. For
example, "born: Jan 18, 1801, died 81 yrs, 11 mths, 17 days" or "age: 93 yrs, 8 mths, 22 days,
died March 18, 1901." Robert is wondering if there is any way to calculate the missing date to
the day.

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).

Then, create this macro:

Function FindDate(Start As Date, iYrs As Integer, _


iMths As Integer, iDays As Integer)

Application.Volatile
Dim D As Date

D = DateAdd("yyyy", iYrs, Start)


D = DateAdd("m", iMths, D)
D = DateAdd("d", iDays, D)

FindDate = Format(D, "m/d/yyyy")


End Function

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.

ExcelTips: The Macros Page 363


Working with Dates and Times

Finding the Date Associated with a Negative Value


Stuart has a series of readings in a worksheet. In the first column he has dates associated with the
readings and in the second column he has the actual readings. Stuart would like to have a
formula that will return the first date at which a reading became negative. In other words, the
formula should look for the first value that is negative in the second column and then return the
date associated with that value. There can be multiple negative values in the second column, but
he needs only the date associated with the first negative value.

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

For Each c In rngdata


If c < 0 Then
GetFirstNegative = c.Offset(0, -1)
Exit Function
Else

ExcelTips: The Macros Page 364


Working with Dates and Times

GetFirstNegative = "All Data is Positive"


End If
Next
End Function

In your worksheet, you would use this user-defined function in this manner:

=GetFirstNegative(B1:B42)

Automatically Advancing by a Month


Jim has a need to advance the date in a particular cell by one month at midnight on the 14/15 of
each month (00:00:00 on the 15th), and he wondered how it could be done.

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.

ExcelTips: The Macros Page 365


Working with Dates and Times

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:

Private Sub Workbook_Open()


If Day(Now) = 14 Then
Application.OnTime ("23:59:59"), "IncreaseMonth"
End If
End Sub

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.

First, consider the following formula.

=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

ExcelTips: The Macros Page 366


Working with Dates and Times

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:

=SUM(IF(WEEKDAY(DATE(C2, D2, ROW(INDIRECT("1:" &


DAY(DATE(C2, D2+1, 0))))))=E2, 1, 0))

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.

Function MonthWeekDays(dDate As Date, iWeekDay As Integer)


Dim dLoop As Date

If iWeekDay < 1 Or iWeekDay > 7 Then


MonthWeekDays = CVErr(xlErrNum)
Exit Function
End If
MonthWeekDays = 0
dLoop = DateSerial(Year(dDate), Month(dDate), 1)
Do While Month(dLoop) = Month(dDate)
If WeekDay(dLoop) = iWeekDay Then _
MonthWeekDays = MonthWeekDays + 1
dLoop = dLoop + 1
Loop
End Function

You use the function by entering the following in a cell:

=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.

ExcelTips: The Macros Page 367


Working with Dates and Times

Pulling All Fridays


When developing a worksheet to track business information, you may have a need to determine
all the Fridays in a range of dates. The best way to do this depends on the data in your worksheet
and the way in which you want results displayed.

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:

1. Select the list of dates.


2. Choose Conditional Formatting from the Format menu. Excel displays the Conditional
Formatting dialog box.

The Conditional Formatting dialog box.

3. Use the Condition drop-down to choose Formula Is.


4. In the formula area, to the right of the drop-down list used in step 3, enter the following
formula, replacing A2 with the address of the active cell selected in step 1:

=WEEKDAY(A2)=6

5. Click Format to display the Format Cells dialog box.


6. Set the formatting options to highlight the Fridays as desired.
7. Click OK to dismiss the Format Cells dialog box. The formatting you specified in step 6
should now appear in the preview area for the condition.
8. Click OK.

ExcelTips: The Macros Page 368


Working with Dates and Times

If you are using Excel 2007 or a later version, the steps to define the conditional format are
different:

1. Select the list of dates.


2. Make sure the Home tab of the ribbon is displayed.
3. Click the Conditional Formatting tool. Excel displays a series of choices.
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 Use a Formula to
Determine Which Cells to Format.

The New Formatting Rule dialog box.

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:

ExcelTips: The Macros Page 369


Working with Dates and Times

=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

Set dat = ActiveSheet.Range("A2:A24")


rw = 2
For Each c In dat
If Weekday(c) = vbFriday Then
Cells(rw, 3).Value = Format(c)
rw = rw + 1
End If
Next
End Sub

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

ExcelTips: The Macros Page 370


Working with Dates and Times

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:

Function PullFridays3(dStartDate As Date, _


dEndDate As Date, _
iIndex As Integer)
Dim iMaxDays As Integer
Dim dFirstday As Date

Application.Volatile
If dStartDate > dEndDate Then
PullFridays3 = CVErr(xlErrNum)
Exit Function
End If

dFirstday = vbFriday - Weekday(dStartDate) + dStartDate


If dFirstday < dStartDate Then dFirstday = dFirstday + 7
iMaxDays = Int((dEndDate - dFirstday) / 7) + 1

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.

The Last Business Day


When developing a worksheet, you may have a need to know the last business day of a given
month. Assuming that your business days run Monday through Friday, the following formula
will return the desired date:

=DATE(YEAR(A1),MONTH(A1)+1,0)-(MAX(0,WEEKDAY
(DATE(YEAR(A1),MONTH(A1)+1,0),2)-5))

ExcelTips: The Macros Page 371


Working with Dates and Times

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.

Function LastWorkDay(lRawDate As Long, _


Optional rHolidayList As Range, _
Optional bFriday As Boolean = False) As Long

LastWorkDay = DateSerial(Year(lRawDate), _
Month(lRawDate) + 1, 0) - 0
If bFriday Then
LastWorkDay = MakeItFriday(LastWorkDay)
Else
LastWorkDay = NoWeekends(LastWorkDay)
End If

If Not rHolidayList Is Nothing Then


Do Until myMatch(LastWorkDay, rHolidayList) = 0
LastWorkDay = LastWorkDay - 1
If bFriday Then
LastWorkDay = MakeItFriday(LastWorkDay)
Else
LastWorkDay = NoWeekends(LastWorkDay)
End If
Loop
End If
End Function

Private Function myMatch(vValue, rng As Range) As Long


myMatch = 0
On Error Resume Next
myMatch = Application.WorksheetFunction _
.Match(vValue, rng, 0)
On Error GoTo 0
End Function

Private Function NoWeekends(lLastDay As Long) As Long


NoWeekends = lLastDay
If Weekday(lLastDay) = vbSunday Then _
NoWeekends = NoWeekends - 2

ExcelTips: The Macros Page 372


Working with Dates and Times

If Weekday(lLastDay) = vbSaturday Then _


NoWeekends = NoWeekends - 1
End Function

Private Function MakeItFriday(lLastDay As Long) As Long


MakeItFriday = lLastDay
While Weekday(MakeItFriday) <> vbFriday
MakeItFriday = MakeItFriday - 1
Wend
End Function

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:

=LastWorkDay(A1, HolidayList, TRUE)

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.

Expiration Date for Excel Programs


Excel provides a robust development environment of which many people take full advantage. In
fact, many people have written entire application programs using VBA with Excel as the
framework.

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")

ExcelTips: The Macros Page 373


Working with Dates and Times

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.

Inserting the Current Time with Seconds


As you have learned in other ExcelTips, you can use CTRL+: (that’s a colon) to enter the current
time into a cell. The resulting cell value is equal to the hours and minutes of the current time. In
other words, the seconds will always be zero.

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.

Entering Large Time Values


If you format a cell for elapsed time (using a custom display format of [h]:mm:ss), then Excel
allows you to enter hours, minutes and seconds into that cell. For instance, you could simply
enter 129:14:30 to signify 129 hours, 14 minutes, and 30 seconds. You run into a problem,
however, if you try to enter very large time values into the cell. When you try to enter time
values in excess of 10000 hours, as in 12721:52:45, then Excel won’t parse the entry as a time,
but treats it as text.

ExcelTips: The Macros Page 374


Working with Dates and Times

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:

Public Function RealBigTime(hr As Double, _


min As Double, sec As Double) As Double
Dim hr1 As Double
Dim min1 As Double
Dim sec1 As Double

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.

ExcelTips: The Macros Page 375


Working with Dates and Times

Checking for Time Input


Excel provides a number of functions that return True or False depending on the content of a
cell. For instance, ISBLANK returns True if a cell is empty, ISERR returns True if a cell
contains an error value, and ISTEXT returns True if a cell contains text. You may wonder if it is
possible to determine if a cell contains a time.

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:

=IF(TIMEVALUE(TEXT(B2,"hh:mm:ss"))=B2, "Time Entry", "Not a Time Entry")

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:

Function IsTime(rng As Range) As Boolean


Dim sValue As String
sValue = rng.Cells(1).Text
On Error Resume Next
IsTime = IsDate(TimeValue(sValue))
On Error GoTo 0
End Function

To use the function, use the following formula in a cell:

=IsTime(B2)

ExcelTips: The Macros Page 376


Working with Dates and Times

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.

Automatically Converting to GMT


GMT is an acronym for Greenwich Meridian Time, which is a reference time for the world; it is
the time in Greenwich, England, and is sometimes referred to as “Zulu time.” (Zulu is the
phonetic name for zero, and the zero refers to the longitude of Greenwich, England.)

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.

ExcelTips: The Macros Page 377


Working with Dates and Times

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

Public Declare Function SystemTimeToFileTime Lib _


"kernel32" (lpSystemTime As SYSTEMTIME, _
lpFileTime As FILETIME) As Long

Public Declare Function LocalFileTimeToFileTime Lib _


"kernel32" (lpLocalFileTime As FILETIME, _
lpFileTime As FILETIME) As Long

Public Declare Function FileTimeToSystemTime Lib _


"kernel32" (lpFileTime As FILETIME, lpSystemTime _
As SYSTEMTIME) As Long

Public Type FILETIME


dwLowDateTime As Long
dwHighDateTime As Long
End Type

Public Type SYSTEMTIME


wYear As Integer
wMonth As Integer
wDayOfWeek As Integer
wDay As Integer
wHour As Integer
wMinute As Integer
wSecond As Integer
wMilliseconds As Integer
End Type

Public Function LocalTimeToUTC(dteTime As Date) As Date


Dim dteLocalFileTime As FILETIME
Dim dteFileTime As FILETIME
Dim dteLocalSystemTime As SYSTEMTIME
Dim dteSystemTime As SYSTEMTIME

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)

LocalTimeToUTC = CDate(dteSystemTime.wMonth & "/" & _


dteSystemTime.wDay & "/" & _
dteSystemTime.wYear & " " & _
dteSystemTime.wHour & ":" & _
dteSystemTime.wMinute & ":" & _
dteSystemTime.wSecond)

ExcelTips: The Macros Page 378


Working with Dates and Times

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.

Using Excel for Timing


You may want to use Excel to record the elapsed time for different events. There are two ways
that this can be approached: either native, within Excel, or with a macro.

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:

Public Sub TimeIt()


Dim vStartTime As Date

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.

ExcelTips: The Macros Page 379


Working with Dates and Times

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:

Function Time2Num(Raw) As Long


Dim FirstColon As Integer
Dim SecondColon As Integer
Dim ThirdColon As Integer
Dim NumHours As Integer

ExcelTips: The Macros Page 380


Working with Dates and Times

Dim NumMinutes As Integer


Dim NumSeconds As Integer
Dim NumFrames As Integer
Dim T2D As Long

Application.Volatile

FirstColon = InStr(Raw, ":")


SecondColon = InStr(FirstColon + 1, Raw, ":")
ThirdColon = InStr(SecondColon + 1, Raw, ":")

NumHours = Val(Mid(Raw, 1, FirstColon - 1))


NumMinutes = Val(Mid(Raw, FirstColon + 1, SecondColon - 1))
NumSeconds = Val(Mid(Raw, SecondColon + 1, ThirdColon - 1))
NumFrames = Val(Mid(Raw, ThirdColon + 1, Len(Raw)))

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:

Function Num2Time(Raw) As String


Dim NumHours As Integer
Dim NumMinutes As Integer
Dim NumSeconds As Integer
Dim NumFrames As Integer
Dim RemainingTime As Long

Application.Volatile

NumHours = Raw \ (CLng(30 * 60) * 60)


RemainingTime = Raw Mod (CLng(30 * 60) * 60)

NumMinutes = RemainingTime \ (60 * 30)


RemainingTime = RemainingTime Mod (60 * 30)

NumSeconds = RemainingTime \ 30
RemainingTime = RemainingTime Mod 30

NumFrames = RemainingTime

Num2Time = Format(NumHours, "00") & ":" & _


Format(NumMinutes, "00") & ":" & _
Format(NumSeconds, "00") & ":" & _
Format(NumFrames, "00")
End Function

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:

ExcelTips: The Macros Page 381


Working with Dates and Times

=Num2Time(Time2Num(A4)-Time2Num(A5))

The result is 00:12:57:17.

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

Recording a Data Entry Time


Vinay uses an Excel worksheet for data entry. Information is entered in column A and Vinay
would like to have a way to automatically add a time into column B, adjacent to the value
entered in column A, that indicates when the value was entered.

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:

Private Sub Worksheet_Change(ByVal Target As Excel.Range)


Dim rCell As Range
Dim rChange As Range

On Error GoTo ErrHandler


Set rChange = Intersect(Target, Range("A:A"))

ExcelTips: The Macros Page 382


Working with Dates and Times

If Not rChange Is Nothing Then


Application.EnableEvents = False
For Each rCell In rChange
If rCell > "" Then
With rCell.Offset(0, 1)
.Value = Now
.NumberFormat = "hh:mm:ss"
End With
Else
rCell.Offset(0, 1).Clear
End If
Next
End If

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.

Converting Numeric Values to Times


Sam has a lot of worksheets that contain times. The problem is that the times are in the format
“1300” instead of the format “13:00.” Thus, Excel sees them as regular numeric values instead of
recognizing them as times. Sam wants them to be converted to actual time values.

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.

A similar formulaic approach can be taken using the TIMEVALUE function:

ExcelTips: The Macros Page 383


Working with Dates and Times

=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

For Each rCell In Selection


If IsNumeric(rCell.Value) And Len(rCell.Value) > 0 Then
iHours = rCell.Value \ 100
iMins = rCell.Value Mod 100
rCell.Value = (iHours + iMins / 60) / 24
rCell.NumberFormat = "h:mm AM/PM"
End If
Next
End Sub

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.

ExcelTips: The Macros Page 384


Working with Dates and Times

Is Daylight Savings Time in Effect?


PJ wondered if there is a way, in a macro, to determine whether the system time on a machine is
daylight savings time or not. The answer is that you can find it out, but it is not a trivial task.

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.

Public Sub DSTorST()


Dim myWksht As Worksheet
Dim sTime As Date, nTime As Date
Dim sST As String, sDST As String, answer As String
Dim absDif As Variant
Dim myT As Integer
Dim sURL As String

Set myWksht = ActiveSheet


sURL = "http://nist.time.gov/timezone.cgi?Eastern/d/-5"
myT = 5

With myWksht.QueryTables.Add("URL;";sURL, myWksht.Range("A1"))


.Refresh
End With

On Error Resume Next


Application.ScreenUpdating = False
sST = "Your computer system displays Standard Time"
sDST = "Your computer system displays Daylight Savings Time"
sTime = Now()
sTime = TimeValue(sTime)
nTime = Range("B3").Value
absDif = Abs(sTime - nTime)
If (absDif > (myT * 0.000694444) And _
WorksheetFunction.IsNumber(WorksheetFunction.Find _
("Not Daylight", D11)) = True) Or (absDif <= _
(myT * 0.000694444) And WorksheetFunction.IsNumber _
(WorksheetFunction.Find("Not Daylight", D11)) = False) _

ExcelTips: The Macros Page 385


Working with Dates and Times

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.

ExcelTips: The Macros Page 386


Working with Worksheets

Working with Worksheets

Creating Worksheets with a Macro


Excel lets you create new worksheets in a number of different ways. What if you want to create a
new worksheet and name it all in one step? The easiest way to do this is with a macro. The
following is an example of a macro that will ask for a name, and then create a worksheet and
give that worksheet the name provided.

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

'Remember where we started


'Not needed if you don't want to return
'to where you started but want to stay
'on the New Sheet

CurrentSheetName = ActiveSheet.Name

'Add New Sheet


Sheets.Add

'Make sure the name is valid


On Error Resume Next

'Get the new name


ActiveSheet.Name = InputBox("Name for new worksheet?")

ExcelTips: The Macros Page 387


Working with Worksheets

'Keep asking for name if name is invalid


Do Until Err.Number = 0
Err.Clear
ActiveSheet.Name = InputBox("Try Again!" _
& vbCrLf & "Invalid Name or Name Already Exists" _
& vbCrLf & "Please name the New Sheet")
Loop
On Error GoTo 0

'Go back to where you started


'Not needed if you don't want to return
'to where you started but want to stay
'on the New Sheet
Sheets(CurrentSheetName).Select
End Sub

Creating and Naming a Worksheet Using a Macro


Jeff would like to create a copy of his "master" worksheet, prompt for a name of the new
worksheet, and move it to the end of the worksheet tabs, all from within a macro. He tried to
record a macro to do this, but it didn’t work.

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

ExcelTips: The Macros Page 388


Working with Worksheets

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.

Jumping to a Specific Worksheet


If you have a huge number of worksheets in a workbook, you may be looking for a way to jump
to a specific sheet rather easily. There are a number of ways you can approach this task, and their
applicability to your situation depends on how many worksheets you actually have in the
workbook.

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:

1. Press F5. Excel displays the Go To dialog box.


2. In the Reference box, enter Sheet83!A1. (Replace “Sheet83” with the name of the
worksheet you want to jump to.)
3. Click OK.

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

ExcelTips: The Macros Page 389


Working with Worksheets

Worksheets(Val(sSheet)).Activate
Else
Worksheets(sSheet).Activate
End If
End Sub

Shortcut to Move between Two Worksheets


You can easily move between worksheets in a workbook by using CTRL+PG UP and CTRL+PG
DOWN. What if you want to use a shortcut to move between two specific, non-neighboring
worksheets, such as Sheet1 and Sheet4? In this case, it is best to use a macro to do the jumping
around.

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.

ExcelTips: The Macros Page 390


Working with Worksheets

Displaying the First Worksheet in a Macro


Terri has a macro that runs automatically when a worksheet is opened. One thing it does is to
display the first worksheet in the workbook, which is usually called "Consolidated." This works
great, unless the first worksheet doesn't have this name. Then Terri must remember to change the
macro to specify a different worksheet name. She wonders if there is a way to write her macro so
that the first worksheet is always displayed, regardless of its name?

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.)

ExcelTips: The Macros Page 391


Working with Worksheets

Opening a Workbook to a Specific Worksheet


Marcus wonders if it is possible to somehow configure a workbook so that it opens on the same
worksheet tab each time it is opened, rather than on the worksheet tab that was displayed when
the workbook was last saved. The short answer is that you can do this—provided you use a
macro. (There is no way to do it without a macro.)

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.

Telling which Worksheets are Selected


When writing a macro that does some type of processing on different worksheets, you may need
to figure out which worksheets were selected by a user before the macro was run. The way you
do this is to use the SelectedSheets property. (Well, it is technically a property, but it acts in
many ways like a collection.) The following simple macro displays the names of each worksheet
that is currently selected:

Sub ShowSheets()
Dim aSheet As Variant

For Each aSheet In ActiveWindow.SelectedSheets


MsgBox aSheet.Name
Next aSheet
End Sub

Once you understand how to get the worksheet names, they can be put into an array or used in
any other way deemed necessary.

ExcelTips: The Macros Page 392


Working with Worksheets

Determining a Worksheet's Number


Lawrence needs a way to determine the number of a worksheet even if the worksheet has been
renamed. For instance, if a worksheet is named Sheet11 it is easy enough to figure out that it is
sheet 11. If he renames the sheet to January, Lawrence still needs a way to know this is sheet 11.

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:

Function SheetNumber1(shtname As String)


Dim sht As Worksheet

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:

Function SheetNumber2(shtname As String)


Dim sht As Worksheet
Dim sTemp As String

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

ExcelTips: The Macros Page 393


Working with Worksheets

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.

Deriving the Worksheet Name


There may be instances when you are developing a worksheet and you need to reference the
name that you have assigned to the worksheet. (This is the name that appears on the worksheet
tab, at the bottom of the Excel window.) Unfortunately, Excel does not include any intrinsic
functions to do this. You can create such a function, however, by starting with the use of the
CELL worksheet 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.

Function SheetStuff(numWanted As Byte) As String


Select Case numWanted
Case 1
SheetStuff = ActiveSheet.Name
Case 2

ExcelTips: The Macros Page 394


Working with Worksheets

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.

Getting the Name of the Worksheet Into a Cell


Kevin has a workbook containing 36 worksheets. He needs a way, in each of the worksheets, to
have the worksheet name (from the worksheet's tab) in a cell of that worksheet. He has created a
user-defined function that returns the worksheet name, but it returns the same name on all 36
worksheets—the name of whatever worksheet is displayed when the user-defined function is
executed. He wonders if there is a macro, in user-defined function (UDF) form, that he can use
that will always return the name of the sheet on which the function is used. In other words, in his
36-worksheet workbook, it should return 36 different results, depending on the worksheet in
which it is used.

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:

ExcelTips: The Macros Page 395


Working with Worksheets

Function TabName1() As String


Application.Volatile
TabName1 = ActiveSheet.Name
End 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:

Function TabName2() As String


Application.Volatile
TabName2 = Application.Caller.Parent.Name
End Function

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:

Function TabName3(cell As Range)


TabName3 = cell.Worksheet.Name
End Function

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.)

Retrieving Worksheet Names


If you have a very large number of worksheets in a workbook, you might want to retrieve the
names of those worksheets and put then on their own worksheet. For instance, you may want
them in one place so you can use them in a table of contents or in some other fashion. The

ExcelTips: The Macros Page 396


Working with Worksheets

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

Dynamic Worksheet Tab Names


You probably already know that you can change the name of a worksheet tab by double-clicking
on the tab and providing a new name. What if you want to do it dynamically, however? What if
you want to have the value in cell A1 automatically appear as the tab name?

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:

Private Sub Worksheet_SelectionChange(ByVal Target As Excel.Range)


Set Target = Range("A1")
If Target = "" Then Exit Sub
On Error GoTo Badname
ActiveSheet.Name = Left(Target, 31)
Exit Sub
Badname:
MsgBox "Please revise the entry in A1." & Chr(13) _
& "It appears to contain one or more " & Chr(13) _
& "illegal characters." & Chr(13)
Range("A1").Activate
End Sub

To set up this macro, follow these steps:

1. Open a new workbook that has only one worksheet in it.

ExcelTips: The Macros Page 397


Working with Worksheets

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:

Private Sub Worksheet_SelectionChange(ByVal Target As Excel.Range)


Set Target = Range("A1")
If Target = "" Then Exit Sub
On Error GoTo Badname
ActiveSheet.Name = Format(Target, "mmm-dd-yy")
Exit Sub
Badname:
MsgBox "Please revise the entry in A1." & Chr(13) _
& "It appears to contain one or more " & Chr(13) _
& "illegal characters." & Chr(13)
Range("A1").Activate
End Sub

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.

Ordering Worksheets Based on a Cell Value


Other issues of ExcelTips have provided ways that you can sort the worksheets in your workbook
based on the worksheet name. What if you want to sort the worksheets based on a value in a
given cell of each worksheet, however? For instance, you may have a series of worksheets that
share the same general layout, and you want the worksheets ordered based on the value in cell
H7 of each worksheet.

ExcelTips: The Macros Page 398


Working with Worksheets

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.

Copying Worksheets in a Macro


When organizing data in workbooks, it is not uncommon to copy worksheets from one workbook
to another. Indeed, the Move or Copy Sheet command (visible when you right-click on a
worksheet tab) is one that I use quite often, and I’d be willing to bet that others use it just as
often.

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

ExcelTips: The Macros Page 399


Working with Worksheets

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

sCopyName = "My New Workbook.xlsm"

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.

Creating a Copy without Formulas


John knows how to create a copy of a worksheet, but he needs to create a copy that uses only
static values, not values based on formulas. He wonders if there is a quick way to make a copy
(perhaps with a macro) that maintains all formatting and column widths, but has all formulas
replaced with their results. For the work John does this would be very helpful in sending out
worksheets to individuals outside his organization.

ExcelTips: The Macros Page 400


Working with Worksheets

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.

The Move or Copy dialog box.

3. Check the Create a Copy check box.


4. Using the To Book pull-down list, choose New Book.
5. Click OK. Excel copies the worksheet to a new workbook.
6. Make sure the newly created workbook is the one displayed.
7. Select all the cells in the worksheet by pressing CTRL+A.
8. Copy all the cells to the Clipboard by pressing CTRL+C.
9. Display the Home tab of the ribbon.
10. Click the down-arrow under the Paste tool. Excel displays some different ways you can
paste information.
11. Choose the Values option; it looks like an icon that has 123 on it.

If you are using an earlier version of Excel, follow these steps:

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.

ExcelTips: The Macros Page 401


Working with Worksheets

4. Click OK. Excel copies the worksheet to a new workbook.


5. Make sure the newly created workbook is the one displayed.
6. Select all the cells in the worksheet by pressing CTRL+A.
7. Copy all the cells to the Clipboard by pressing CTRL+C.
8. Chose Paste Special from the Edit menu. Excel displays the Paste Special dialog box.

The Paste Special dialog box.

9. Click the Values radio button.


10. Click OK.

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.

ExcelTips: The Macros Page 402


Working with Worksheets

Relative Worksheet References


Suppose you have a workbook with three worksheets, Sheet1, Sheet2 and Sheet3. In column A1
of worksheet Sheet2 you have the formula =Sheet1!A1. When you copy that formula from
Sheet2 to cell A1 of Sheet3, the formula still references Sheet1. How can that be, though? Why
doesn't Excel adjust the sheet reference, like it does the cell references?

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:

Function PrevSheet(rCell As Range)


Application.Volatile
Dim i As Integer
i = rCell.Cells(1).Parent.Index
PrevSheet = Sheets(i - 1).Range(rCell.Address)
End Function

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.

ExcelTips: The Macros Page 403


Working with Worksheets

Sheets for Days


When you are starting a new workbook, it is very common to name each worksheet after a
different day of the month. If you do this quite a bit, you know it can be tiresome to rename each
worksheet, in turn, to exactly what you need.

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

If J <= Sheets.Count Then


If Left(Sheets(J).Name, 5) = "Sheet" Then
Sheets(J).Name = sDay
Else
Sheets.Add.Move after:=Sheets(Sheets.Count)
ActiveSheet.Name = sDay
End If
Else
Sheets.Add.Move after:=Sheets(Sheets.Count)
ActiveSheet.Name = sDay
End If
End If
Next J

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

ExcelTips: The Macros Page 404


Working with Worksheets

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.

Referencing Worksheet Tabs


Myrna asked if there was a way to use the information in a worksheet tab within a cell. In
particular, she named her tabs using dates, and wants to use those dates within the worksheet
itself.

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:

Function SheetName() As Date


Dim sTab As String
Application.Volatile
sTab = ActiveSheet.Name
sTab = Trim(Right(sTab, 8))
SheetName = CDate(sTab)
End Function

To use this function in your worksheet, you simply enter the following in a cell:

=SheetName()

ExcelTips: The Macros Page 405


Working with Worksheets

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.

Referencing a Worksheet Name


Jon wonders if there is a function equivalent to =ROW() or =COLUMN() for worksheets. He
needs to reference (for example) the fourth sheet in a workbook, but he can't be sure of the
worksheet’s name.

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.

Function TabName(snum As Long) As String


Application.Volatile
If snum > 0 And snum <= Sheets.Count Then
TabName = Sheets(snum).Name
End If
End Function

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.

ExcelTips: The Macros Page 406


Working with Worksheets

Locking Worksheet Names


If you are developing workbooks for others to use, you may want your worksheets to retain
whatever names you give them. Excel normally allows users to change worksheet names, as
desired. If you don’t want them to change, the only way to prevent it is to lock the workbook.
You can take these steps if you are using Excel 2007 or a later version:

1. Display the Review tab of the ribbon.


2. Click Protect Workbook in the Changes group on the ribbon. Excel displays the Protect
Structure and Windows dialog box.

The Protect Structure and Windows dialog box.

3. Make sure that the Structure check box is selected.


4. Enter a password in the Password box.
5. Click on OK. Excel displays the Confirm Password dialog box, prompting you to
reenter the password.
6. Reenter the password and click on OK.

If you are using a version of Excel prior to Excel 2007, follow these steps:

1. Choose Protection from the Tools menu. Excel displays a submenu.


2. Choose the Protect Workbook option from the submenu. Excel displays the Protect
Workbook dialog box.

ExcelTips: The Macros Page 407


Working with Worksheets

The Protect Workbook dialog box.

3. Make sure that the Structure check box is selected.


4. Enter a password in the Password box.
5. Click on OK. Excel displays the Confirm Password dialog box, prompting you to
reenter the password.
6. Reenter the password and click on OK.

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:

ActiveWorkbook.Protect Password:="MyPassword", Structure:=True

All you need to do is provide password you want to use in place of the “MyPassword” example.

Freezing Worksheet Tabs


Jonathan has a workbook that contains over fifty worksheets, one of which is named “Main” and
is positioned as the first tab in the workbook. He is constantly having to revert back to the
“Main” worksheet. In order to display the worksheet he must either click back a tab at a time or
scroll all the way to the left of the tabs (by clicking on the control at the far left of the tabs) and
then select the “Main” tab. This last method is the easiest, but still is time consuming. Jonathan
wonders if there is a way, much like freezing a pane, to freeze a worksheet tab. He would like the
“Main” tab to always be visible, and the tabs to its right to scroll.

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

ExcelTips: The Macros Page 408


Working with Worksheets

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.

Private Sub Workbook_SheetActivate(ByVal Sh As Object)


Dim sc As Long ' count of sheets
Dim NewPos As Long ' index of serlected sheet

Application.EnableEvents = False
Application.ScreenUpdating = False

If ActiveSheet.Index <> 1 Then


sc = Sheets.Count
NewPos = ActiveSheet.Index
For i = 2 To NewPos - 1
Sheets(2).Move After:=Sheets(sc)
Next i
Sheets(1).Activate
Sheets(2).Activate
End If

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.

ExcelTips: The Macros Page 409


Working with Worksheets

Running Macros on Hidden Worksheets


Macros are often used to process information within a workbook. Your macro can access any
cells in the workbook, unless the worksheet containing the cell is hidden. When you hide a
worksheet, it is even hidden from normal macro operations.

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:

Sheets("My Hidden Sheet").Visible = True

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:

Sheets("My Hidden Sheet").Visible = False

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.

Spell-Checking in a Protected Worksheet


Craig has a protected Excel worksheet in which he would like to spell-check a specific cell. The
problem, of course, is that the spell-checker cannot be run on a protected worksheet. So, the
process of doing the desired spell-checking is to unprotect the worksheet, do the check, and then
again protect the worksheet.

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

ExcelTips: The Macros Page 410


Working with Worksheets

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.)

Preventing Someone from Recreating a Protected


Worksheet
Jack creates worksheets that he forwards to others in his group so they can input information,
and then return the worksheet to him. He protects the worksheets, but has gotten burned a few
times by users who have used copy and paste to recreate the worksheet in its entirety. Checking
everything to make sure the returned worksheets are the originals is very time consuming, so
Jack is looking for a way to remove the ability to copy and paste the worksheets.

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:

Private Sub Worksheet_Deactivate()


If ActiveSheet.ProtectContents = True Then
Application.CutCopyMode = False
End If
End Sub

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:

ExcelTips: The Macros Page 411


Working with Worksheets

• 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.

Fixing Macro Button Behavior in Protected


Worksheets
Robin asked a question concerning an oddity in Excel. She creates a worksheet that contains a
button drawn using the Forms toolbar, and assigns a macro to the button. (There is no Forms
toolbar in Excel 2007 and later versions; instead you would display the Developer tab and use
the Insert tool to add the button using a Form control.) The button works fine until the worksheet
is protected, then the entire top section of the worksheet acts as a button. (The hand icon appears
no matter where you move the mouse, and when you click the macro executes—you don't have
to click on the button itself.)

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

ExcelTips: The Macros Page 412


Working with Worksheets

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.

Visually Showing a Protection Status


Todd has developed a workbook used by others. To prevent data from being ruined, he’s
protected the worksheet as well as the workbook. The problem is, Todd sometimes forgets to
protect the worksheet and workbook after making changes. He is wondering if there is a way to
create a visual indicator that shows whether the worksheet/workbook is currently protected or
unprotected.

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:

Function WksProtected(rng As Range) As String


Application.Volatile
If rng.Parent.ProtectContents Then
WksProtected = "Protected”
Else
WksProtected = "Not Protected”
End If
End Function

Function WkbProtected(rng As Range) As String


Application.Volatile
If rng.Parent.Parent.ProtectStructure Then
WkbProtected = "Protected”

ExcelTips: The Macros Page 413


Working with Worksheets

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.

Forcing a Worksheet to be Protected Again


Barry can lock a worksheet so that only those to whom he gives the password can edit it. If the
person enters the password, makes edits, and then saves the workbook containing the worksheet,
that worksheet is then unprotected. Barry wonders if there is a way that, when saving the
worksheet, Excel can remind the user to once again protect the worksheet using the same
password originally used.

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:

Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)


Sheets("ABC").Protect ("XYZ")
End Sub

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:

Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)


If MsgBox("Reprotect Sheet ABC?", vbYesNoCancel) = vbYes Then
Sheets("ABC").Protect ("XYZ")
End If
End Sub

ExcelTips: The Macros Page 414


Working with Worksheets

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.)

The Allow Users to Edit Ranges dialog box.

2. Click the New button. Excel displays the New Range dialog box.

ExcelTips: The Macros Page 415


Working with Worksheets

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.

Unhiding Multiple Worksheets


When you choose to hide worksheets in a workbook, Excel allows you to hide multiple sheets
with one action: all you need to do is select the sheets before actually doing the hiding. Unhiding
worksheets is a different story, however. Excel only allows you to unhide one at a time. If you
have many worksheets you want to unhide, this can be very tedious.

ExcelTips: The Macros Page 416


Working with Worksheets

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

For Each wsSheet In ActiveWorkbook.Worksheets


wsSheet.Visible = xlSheetVisible
Next wsSheet
End Sub

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

For Each wsSheet In ActiveWorkbook.Worksheets


If wsSheet.Visible = xlSheetHidden Then
sSheetName = wsSheet.Name
sMessage = "Unhide the following sheet?" _
& vbNewLine & sSheetName
Msgres = MsgBox(sMessage, vbYesNo)
If Msgres = vbYes Then wsSheet.Visible = xlSheetVisible
End If
Next wsSheet
End Sub

Detecting Types of Sheets in VBA


If you are writing macros that process different worksheets in a workbook, you may have a need
to figure out what type of worksheets there are in the workbook, before doing any processing.
This can be especially critical because some VBA commands only work on certain types of
worksheets.

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:

ExcelTips: The Macros Page 417


Working with Worksheets

• xlWorksheet. This is a regular worksheet.


• xlChart. This is a chart.
• xlExcel4MacroSheet. This is a macro sheet, as used in Excel 4.0.
• xlExcel4IntlMacroSheet. This is an international macro sheet, as used in Excel 4.0.

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

ExcelTips: The Macros Page 418


Working with Worksheets

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.

Selecting All Visible Worksheets in a Macro


In Excel, selecting all the visible worksheets is as easy as right-clicking on any sheet tab and
choosing Select All Sheets. However, accomplishing the same task with VBA code is more
difficult.

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

ExcelTips: The Macros Page 419


Working with Worksheets

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.

Deleting Worksheets in a Macro


Most Excel commands are available for use within your macros, provided you know the proper
commands to accomplish the task at hand. You can use the following macro command to delete
the active worksheet:

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

ExcelTips: The Macros Page 420


Working with Worksheets

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.)

Condensing Multiple Worksheets Into One


If you get workbooks that have identically structured data on each worksheet, you may be
interested in a way to combine the multiple worksheets into a single, large worksheet.

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.

ExcelTips: The Macros Page 421


Working with Worksheets

The Consolidate dialog box.

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

On Error Resume Next


Sheets(1).Select
Worksheets.Add ' add a sheet in first place
Sheets(1).Name = "Combined"

' copy headings


Sheets(2).Activate
Range("A1").EntireRow.Select
Selection.Copy Destination:=Sheets(1).Range("A1")

ExcelTips: The Macros Page 422


Working with Worksheets

' work through sheets


For J = 2 To Sheets.Count ' from sheet 2 to last sheet
Sheets(J).Activate ' make the sheet active
Range("A1").Select
Selection.CurrentRegion.Select ' select all cells in this sheets

' select all lines except title


Selection.Offset(1, 0).Resize(Selection.Rows.Count - 1).Select

' copy cells selected in the new sheet on last line


Selection.Copy Destination:=Sheets(1).Range("A65536").End(xlUp)(2)
Next
End Sub

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.

Combining Worksheets from Many Workbooks


David has several workbooks that have several worksheets in each of them. He would like to
combine a certain worksheet (just one) out of each of these workbooks into a new workbook. He
knows how to do this manually using Move or Copy Sheet, but he would like a way to do it more
automatically, particularly since there may be many workbooks that he needs to "combine" in
this way.

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:

ExcelTips: The Macros Page 423


Working with Worksheets

Sub CopySheets2()
Dim wkb As Workbook
Dim sWksName As String

For Each wkb In Workbooks


If wkb.Name <> ThisWorkbook.Name Then
wkb.Worksheets(2).Copy _
Before:=ThisWorkbook.Sheets(1)
End If
Next
Set wkb = Nothing
End Sub

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.

ExcelTips: The Macros Page 424


Working with Worksheets

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

Counting the Times a Worksheet is Used


You may want a way to keep track of how many times a particular worksheet is used. There are
many ways you can accomplish this. One simple way is to just store the count in the worksheet
itself. Right-click a worksheet tab, then choose View Code from the Context menu. Excel
displays the Visual Basic Editor, where you should paste the following code:

Private Sub Worksheet_Activate()


Range("A1").Select 'customize Range
ActiveCell = ActiveCell + 1
Range("B1") = "times opened" 'customize Range
End Sub

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:

Function IncrementEventCounter(sName As String, sht As Object)


On Error Resume Next
If sht.Names(sName) Is Nothing Then _
ThisWorkbook.Names.Add "'" & sht.Name & "'!" & sName, "1", False
On Error GoTo 0
With ThisWorkbook.Names("'" & sht.Name & "'!" & sName)
.RefersTo = Val(Mid(.Value, 2)) + 1
End With
End Function

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:

Private Sub Workbook_SheetActivate(ByVal sh As Object)


IncrementEventCounter "Activated", sh
End Sub

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:

ExcelTips: The Macros Page 425


Working with Worksheets

Private Sub Workbook_SheetChange(ByVal sh As Object, _


ByVal Target As Excel.Range)
IncrementEventCounter "Changed", sh
End Sub

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.

Generating Unique Numbers for Worksheets


Sometimes you may need Excel to generate a unique number for your worksheets. For instance,
you could be using Excel to create forms such as invoices, statements, or tracking sheets, and
you need unique numbers for each form (I’ll call this a ticket number). This, of course, implies
that Excel needs to remember the number from one session to the next.

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:

Private Sub Workbook_NewSheet(ByVal Sh As Object)


Dim lTicket As Long
lTicket = CLng(Time * 24 * 60 * 60)
Sh.Range("A1") = lTicket
End Sub

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:

Private Sub Workbook_NewSheet(ByVal Sh As Object)


Dim sTemp As String
sTemp = Format(Date, "yymmdd") & Format(Time, "hhmmss")
Sh.Range("A1") = sTemp
End Sub

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.

ExcelTips: The Macros Page 426


Working with Worksheets

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. Display the Formulas tab of the ribbon.


2. Click on the Define Name tool in the Defined Names group. Excel displays the New
Name dialog box.

The New Name dialog box.

3. In the Name box, enter a name such as MaxNum.


4. In the Refers To area at the bottom of the dialog box, enter an equal sign followed by
the value you want used for the next ticket number.
5. Click on OK. The new name is stored in the workbook.

The steps in older versions of Excel are slightly different:

1. Choose Name from the Insert menu, then choose Define. Excel displays the Define
Name dialog box.

ExcelTips: The Macros Page 427


Working with Worksheets

The Define Name dialog box.

2. In the Name box, enter a name such as MaxNum.


3. In the Refers To area at the bottom of the dialog box, enter an equal sign followed by
the value you want used for the next ticket number.
4. Click on OK. The new name is stored in the workbook.

Now, add the following macro to the ThisWorksheet object in the VBA Editor:

Private Sub Workbook_NewSheet(ByVal Sh As Object)


Dim iMax As Integer
iMax = Mid(ThisWorkbook.Names("MaxNum"), 2)
Sh.Range("A1") = iMax
iMax = iMax + 1
ThisWorkbook.Names("MaxNum").RefersTo = "=" & iMax
End Sub

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.

Disabling Moving between Worksheets


Excel provides a variety of ways that you can move from one worksheet to another in a
workbook. If you want to disable moving between worksheets, you’ve got a difficult task in front
of you because of the variety of methods you need to do something about.

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:

Private Sub Workbook_Activate()

ExcelTips: The Macros Page 428


Working with Worksheets

Application.OnKey "^{PgDn}", ""


Application.OnKey "^{PgUp}", ""
End Sub

Private Sub Workbook_Deactivate()


Application.OnKey "^{PgDn}"
Application.OnKey "^{PgUp}"
End Sub

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:

Private Sub Workbook_SheetDeactivate(ByVal mySheet As Object)


Application.EnableEvents = False
mySheet.Activate
Application.EnableEvents = True
End Sub

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.

Random Width and Height Changes


Gary has a workbook set up with twelve worksheets, one for every month. He has framed the
information in these worksheets so it just fits on his screen. At some point he opens his
workbook to find that the June column widths have expanded so that they don't all fit on his
screen. It is only the June columns that expand, but all worksheets will have their row heights
expand from 12.75 to 13.50. If Gary resizes everything back so it once more fits his screen,
everything is fine for a week or so, and then the resizing happens again.

ExcelTips: The Macros Page 429


Working with Worksheets

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:

Private Sub Workbook_Open()


Dim wSheet As Worksheet

For Each wSheet In Worksheets


' Change to the columns you need
Columns("A:M").Select
' Change to the width you need
Selection.ColumnWidth = 12

' Change to the rows you need


Rows("1:15").Select
' Change to the height you need
Selection.RowHeight = 13
Next wSheet
End Sub

Identifying the Last Cell Changed in a Worksheet


John wonders if there is a way in VBA to identify the last cell that was changed by a user. He
doesn’t want to know if the cell was changed by a macro, but specifically by a user.

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:

Private Sub Worksheet_Change(ByVal Target As Range)


Application.StatusBar = Target.Address
End Sub

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:

Dim sAddr As String

Private Sub Worksheet_Change(ByVal Target As Range)


sAddr = Target.Address(False, False)

ExcelTips: The Macros Page 430


Working with Worksheets

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.

Transferring Data between Worksheets Using a


Macro
Leonard is writing a macro to transfer data from one worksheet to another. Both worksheets are
in the same workbook. The data he wants to transfer is on the first worksheet and uses a named
range: “SourceData”. It consists of a single row of data. Leonard wants to, within the macro,
transfer this data from the first worksheet to the first empty row on the second worksheet, but
he's not quite sure how to go about this.

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:

Set rngSource = Worksheets("Sheet1").Range("SourceData")

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:

ExcelTips: The Macros Page 431


Working with Worksheets

Sub CopySource()
Dim rngSource As Range
Dim rngTarget As Range
Dim iRow As Integer

Set rngSource = Worksheets("Sheet1").Range("SourceData")


iRow = Worksheets("Sheet2").Cells(Rows.Count,1).End(xlUp).Row + 1
Set rngTarget = Worksheets("Sheet2").Range("A" & iRow)
rngSource.Copy Destination:=rngTarget
End Sub

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.

Viewing Same Cells on Different Worksheets


When using multiple worksheets, Chris wonders if there is a way to 'lock' the scrolling through
all worksheets. For instance, if he scrolls down and across on Sheet1 until rows 100 to 140 and
columns G to P are in view, then when he switches to Sheet2 (or any other worksheet) he would
like the same rows and columns to be shown on those worksheets.

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.

Dim grngSelection As Range


Dim gintScrollColumn As Integer
Dim glngScrollRow As Long

Private Sub Workbook_SheetActivate(ByVal Sh As Object)


If TypeName(ActiveSheet) = "Worksheet" Then
On Error Resume Next
With ActiveWindow
Sh.Range(grngSelection.Address).Select
.ScrollColumn = gintScrollColumn
.ScrollRow = glngScrollRow
End With
End If
End Sub

Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)


Dim oSheet As Object
If TypeName(Sh) = "Worksheet" Then
Set oSheet = ActiveSheet
Application.EnableEvents = False
Sh.Activate
With ActiveWindow
gintScrollColumn = .ScrollColumn
glngScrollRow = .ScrollRow
Set grngSelection = .RangeSelection

ExcelTips: The Macros Page 432


Working with Worksheets

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

Public Sub WindowLock()


If Not WindowSyncOn Then
WindowScrollRow = ActiveWindow.VisibleRange.Row
WindowScrollCol = ActiveWindow.VisibleRange.Column
Application.StatusBar = "WindowSync: ON"
Else
Application.StatusBar = ""
End If
WindowSyncOn = Not WindowSyncOn
End Sub

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:

Private Sub Workbook_SheetActivate(ByVal Sh As Object)


If WindowSyncOn Then
If Not ActiveWindow Is Nothing Then
ActiveWindow.ScrollRow = WindowScrollRow
ActiveWindow.ScrollColumn = WindowScrollCol
End If
End If
End Sub

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.

ExcelTips: The Macros Page 433


Working with Worksheets

Testing for an Empty Worksheet


Michael has a macro that prints a number of worksheets. Occasionally one or two of the
worksheets to be printed may contain no data. He is looking for a technique to test whether a
worksheet contains data, and then only print those worksheets.

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.

ExcelTips: The Macros Page 434


Working with Worksheets

Turning Off Display of Zeros for All Worksheets


King knows how to turn off the display of zeros in Excel for individual worksheets. He wants to
turn it off by default so that every worksheet he opens, zeros are not displayed. If he wants zeros,
he knows he can turn on the display of zeros.

One way to get this to happen is to set up your own default workbook. Follow these steps:

1. Open a new Excel workbook.


2. Set up the workbook the way you want it to appear, by default. (Make sure you turn off
the display of zero values in the Options dialog box.)
3. Choose Save As from the File menu, or press F12. Excel displays the Save As dialog
box.
4. In the Save As Type pull-down list at the bottom of the dialog box, select Template
(*.xltx).
5. The file name you use should be Book.xltx.
6. Save your newly created template in the XLStart folder. (Do not save it in the default
template folder.)

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.

Copying Worksheet Code Automatically


Tim correctly notes that a user can right-click on a worksheet tab, then select View Code to open
a VBA code sheet for the worksheet. He has code written that automatically manipulates cells,

ExcelTips: The Macros Page 435


Working with Worksheets

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:

Private Sub Workbook_NewSheet(ByVal Sh As Object)


Dim tmpName As String

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.

Finding the Size of Individual Worksheets


Maarten wonders if there is a way to find out the size of each worksheet in a workbook. He has a
workbook with almost 100 worksheets and he wants to reduce the size of the workbook file.
However, he doesn’t know which worksheets are the biggest ones in size.

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.

ExcelTips: The Macros Page 436


Working with Worksheets

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

sReport = "Size Report"


sWBName = "Erase Me.xls"
sFullFile = ThisWorkbook.Path & _
Application.PathSeparator & sWBName

' Add new worksheet to record sizes


On Error Resume Next
Set wks = Worksheets(sReport)
If wks Is Nothing Then
With ThisWorkbook.Worksheets.Add(Before:=Worksheets(1))
.Name = sReport
.Range("A1").Value = "Worksheet Name"
.Range("B1").Value = "Approximate Size"
End With
End If
On Error GoTo 0
With ThisWorkbook.Worksheets(sReport)
.Select
.Range("A1").CurrentRegion.Offset(1, 0).ClearContents
Set c = .Range("A2")
End With

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)

ExcelTips: The Macros Page 437


Working with Worksheets

Set c = c.Offset(1, 0)
Kill sFullFile
End If
Next wks
Application.ScreenUpdating = True
End Sub

ExcelTips: The Macros Page 438


Interacting with the Excel Environment

Interacting with the Excel Environment

Switching Windows in a Macro


Andrew needs, in his macro, to display a dialog box that allows a user to switch windows. The
files Andrew wants to display are maintained and listed by Excel, depending on your version:

• 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.

ExcelTips: The Macros Page 439


Interacting with the Excel Environment

Setting Program Window Size in a Macro


Christopher needs, within a macro, to set the size of the Excel program window. He knows how
to set the size of a worksheet within the program window, but that isn't what he needs. He
wonders how he can set the overall size of the program window, plus make sure that he doesn't
set it larger than the user's actual screen size.

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

ExcelTips: The Macros Page 440


Interacting with the Excel Environment

Dim iStartX As Integer


Dim iStartY As Integer
Dim iDesiredWidth As Integer
Dim iDesiredHeight As Integer

iStartX = 50 ' Distance from left


iStartY = 25 ' Distance from top
iDesiredWidth = 1000
iDesiredHeight = 500

With Application
.WindowState = xlMaximized
iMaxWidth = Application.Width
iMaxHeight = Application.Height

' Adjust for starting point


iMaxWidth = iMaxWidth - iStartX
iMaxHeight = iMaxHeight - iStartY
If iDesiredWidth > iMaxWidth Then
iDesiredWidth = iMaxWidth
End If
If iDesiredHeight > iMaxHeight Then
iDesiredHeight = iMaxHeight
End If

.WindowState = xlNormal
.Top = iStartY
.Left = iStartX
.Width = iDesiredWidth
.Height = iDesiredHeight
End With
End Sub

Turning Off Screen Updating


Many people write their own macros to manipulate the information in a workbook. Many times
the macro may do quite a bit with the data, such as selecting different cells, replacing values or
formulas, and taking other types of actions. This means that the Excel screen can look like it has
"gone crazy" while the macro is running.

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.

ExcelTips: The Macros Page 441


Interacting with the Excel Environment

Disabling Shift Key Use when Opening a Workbook


The VBA capabilities of Excel are quite astounding. VBA is a full-blown programming
language, which means you can do most anything with it. Some folks have even written entire
applications in VBA; applications that build upon the Excel environment to accomplish a
specific task.

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.

Opening a Workbook but Disabling Macros


Bob is processing information in a workbook by using a macro. He would like for the macro to
open a second workbook that has an AutoClose macro in it, but he doesn't want it to run when
the second workbook is closed. He is looking for a way to open the second workbook, under the
control of the macro in the first workbook, without enabling the macros in the second workbook.

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:

ExcelTips: The Macros Page 442


Interacting with the Excel Environment

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:

Dim AutoCloseDisabled as Boolean


Sub DisableAutoClose()
AutoCloseDisabled=True
End Sub

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

If Not AutoCloseDisabled then

'body of AutoClose 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.

ExcelTips: The Macros Page 443


Interacting with the Excel Environment

Forcing Editing to Be Done in a Cell


Rodolfo knows that he can configure Excel to allow editing in both the Formula bar and the cell
itself, but he wants to configure it so that editing can be done only in the cell, not in the Formula
bar.

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.

The View tab of the Options dialog box.

3. Clear the Formula Bar check box.


4. Click on OK.

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.)

ExcelTips: The Macros Page 444


Interacting with the Excel Environment

2. Click Advanced at the left side of the dialog box.

The Advanced options in the Excel Options dialog box.

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.)

Private Sub Worksheet_Activate()


Application.DisplayFormulaBar = False
End Sub

Private Sub Worksheet_Deactivate()


Application.DisplayFormulaBar = True
End Sub

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).

ExcelTips: The Macros Page 445


Interacting with the Excel Environment

Synchronous Scrolling with More than Two


Windows
David has a large worksheet that he needs to view in three different windows and have them
scroll all at the same time. He knows how to use "view side by side" and turn on synchronous
scrolling (these functions are available only in Excel 2007 and later versions), but it seems to
only work for two windows. David wonders if there is a way to do it for three.

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

If TypeName(ActiveSheet) <> "Worksheet" Then Exit Sub

Dim shUser As Worksheet


Dim sht As Worksheet
Dim lTopRow As Long
Dim lLeftCol As Long
Dim sAddr As String

Application.ScreenUpdating = False

' Note the current sheet


Set shUser = ActiveSheet

' take information from current sheet


With ActiveWindow
lTopRow = .ScrollRow
lLeftCol = .ScrollColumn
sAddr = .RangeSelection.Address
End With

' loop through worksheets


For Each sht In ActiveWorkbook.Worksheets
If sht.Visible Then 'skip hidden sheets
sht.Activate
Range(sAddr).Select
ActiveWindow.ScrollRow = lTopRow
ActiveWindow.ScrollColumn = lLeftCol
End If
Next sht

shUser.Activate
Application.ScreenUpdating = True
End Sub

ExcelTips: The Macros Page 446


Interacting with the Excel Environment

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.

Using Go To to Jump to a Chart Sheet


When you want to jump to a specific worksheet in a workbook, you can use the Go To feature of
Excel to make the jump painless, in the following manner:

1. Press F5. Excel displays the Go To dialog box.

The Go To Dialog box.

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

ExcelTips: The Macros Page 447


Interacting with the Excel Environment

All you need to do is substitute the proper name of the chart sheet in place of “MyChart.”

Displaying a Set Column Range


Let’s say that you want to display a specific number of columns on a worksheet in the available
window space. You can manually figure out the necessary width of each column and do the
adjustments, or you can write a macro that will figure out, proportionally, how the width of each
column should be adjusted to get the desired results.

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:

Private Sub Worksheet_Activate()


Range("A1:L1").Select
ActiveWindow.Zoom = True
Range("A1").Select
End Sub

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.

Selecting Visible Cells in a Macro


Karthi notes that he often needs to use Go To Special to select just the visible cells in a selection.
This makes him wonder if there is a way that such cells can be selected in a macro.

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

ExcelTips: The Macros Page 448


Interacting with the Excel Environment

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

Stepping Through a Non-Contiguous Range of Cells


Stephe needs to develop a macro that will perform an operation based on the cells that a user
selects before running the macro. She knows how to do this if the user selects a range of cells,
but she doesn't know how to step through the cells in a selection if the user selects a non-
contiguous range of cells.

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

For Each c In Selection


' do something here
MsgBox c.Address & vbTab & c.Value
Next c

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

For Each a In Selection.Areas


'Now each a refers to a contiguous range
'Do something here with areas, if desired
For Each c In a.Cells
'Now each c refers to a cell in the area
'Do something here
MsgBox c.Address & vbTab & c.Value
Next c
Next a

ExcelTips: The Macros Page 449


Interacting with the Excel Environment

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

For Each c In Range("MyNamedRange")


' do something here
MsgBox c.Address & vbTab & c.Value
Next c

Hiding Excel in VBA


Many macros are written to perform a specific, limited task. Other macros are written as part of a
larger, overall application designed to be used start-to-finish by a user. For instance, I have seen
accounting packages written completely in Excel VBA. The functions of the accounting package
are written in VBA, of course. The user of the accounting package never uses “regular Excel,”
but instead utilizes menus, dialog boxes, and choices presented exclusively by the VBA
application.

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.

Preserving the Undo List


As you are working in Excel, there is a bit of a “safety net” in place in regards to changes you
make. Most people know that if they mess things up, they can quickly press CTRL+Z or click the
Undo tool at the upper-left of the program window. If you don’t like what you just did, you can
easily undo it and get back to the way things were before.

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

ExcelTips: The Macros Page 450


Interacting with the Excel Environment

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:

Application.OnUndo "Primary Macro", "UndoPrimary"

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.

Clearing the Undo Stack


In Word, you can use the UndoClear method with the ActiveDocument object in order to clear
the Undo stack. If you are programming macros in Excel’s flavor of VBA, you may wonder if
there is a similar method for use in Excel.

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

ExcelTips: The Macros Page 451


Interacting with the Excel Environment

Calculating Only the Active Workbook


Linda asked if there is a way to calculate only the active workbook. When a recalc is performed
by Excel, it recalculates all her open workbooks, and if they are very large workbooks it can
sometimes take over fifteen minutes to recalc. If she is able to limit what is recalculated, then the
process will obviously run faster.

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.

ExcelTips: The Macros Page 452


Interacting with the Excel Environment

The Formulas options of the Excel Options dialog box.

3. In the Calculation Options section of the dialog box, make sure the Manual radio button
is selected.
4. Click on OK.

Follow these steps in older versions of Excel:

1. Choose Options from the Tools menu. Excel displays the Options dialog box.
2. Make sure the Calculation tab is displayed.

ExcelTips: The Macros Page 453


Interacting with the Excel Environment

The Calculation tab of the Options dialog box.

3. Select the Manual check box.


4. Click OK.

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:

ExcelTips: The Macros Page 454


Interacting with the Excel Environment

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)

Select Case iAnsure


Case 1 'Range Only
Selection.Calculate
Case 2 'Worksheet Only
ActiveSheet.Calculate
Case 3 'Workbook Only
For Each wks In ActiveWorkbook.Worksheets
wks.Calculate
Next
Case 4 'All Open Workbooks
Application.CalculateFull
End
End Select
End Sub

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.

Setting the Calculation Default


Durward wrote concerning a problem he was having with calculation settings in his Excel. He
indicated that according to all he had read, the calculation setting for Excel (Manual or
Automatic) defaults to Automatic. Yet somehow, when Durward opens a new workbook, the
calculation setting is set to Manual. This happens on his work system as well as his home
system. He wonders if there a way to reset the calculation setting back to Automatic.

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.

ExcelTips: The Macros Page 455


Interacting with the Excel Environment

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:

Private Sub Workbook_Open()


Application.Calculation = xlCalculationAutomatic
End Sub

Counting All Characters


When you work with worksheets—particularly those from other people—you may be looking for
a way count the number of characters in a workbook. The following macro is very handy in that
regard. It counts the number of characters in an entire workbook, including any characters in any
text boxes inserted in the various worksheets.

Sub CountCharacters()
Dim wks As Worksheet
Dim rng As Range
Dim rCell As Range
Dim shp As Shape

Dim bPossibleError As Boolean


Dim bSkipMe As Boolean

Dim lTotal As Long


Dim lTotal2 As Long
Dim lConstants As Long
Dim lFormulas As Long
Dim lFormulaValues As Long
Dim lTxtBox As Long
Dim sMsg As String

On Error GoTo ErrHandler


Application.ScreenUpdating = False

lTotal = 0
lTotal2 = 0
lConstants = 0
lFormulas = 0
lFormulaValues = 0
lTxtBox = 0
bPossibleError = False
bSkipMe = False
sMsg = ""

For Each wks In ActiveWorkbook.Worksheets


' Count characters in text boxes

ExcelTips: The Macros Page 456


Interacting with the Excel Environment

For Each shp In wks.Shapes


If TypeName(shp) <> "GroupObject" Then
lTxtBox = lTxtBox + shp.TextFrame.Characters.Count
End If
Next shp

' Count characters in cells containing constants


bPossibleError = True
Set rng = wks.UsedRange.SpecialCells(xlCellTypeConstants)
If bSkipMe Then
bSkipMe = False
Else
For Each rCell In rng
lConstants = lConstants + Len(rCell.Value)
Next rCell
End If

' Count characters in cells containing formulas


bPossibleError = True
Set rng = wks.UsedRange.SpecialCells(xlCellTypeFormulas)
If bSkipMe Then
bSkipMe = False
Else
For Each rCell In rng
lFormulaValues = lFormulaValues + Len(rCell.Value)
lFormulas = lFormulas + Len(rCell.Formula)
Next rCell
End If
Next wks

sMsg = Format(lTxtBox, "#,##0") & _


" Characters in text boxes" & vbCrLf
sMsg = sMsg & Format(lConstants, "#,##0") & _
" Characters in constants" & vbCrLf & vbCrLf

lTotal = lTxtBox + lConstants

sMsg = sMsg & Format(lTotal, "#,##0") & _


" Total characters (as constants)" & vbCrLf & vbCrLf

sMsg = sMsg & Format(lFormulaValues, "#,##0") & _


" Characters in formulas (as values)" & vbCrLf
sMsg = sMsg & Format(lFormulas, "#,##0") & _
" Characters in formulas (as formulas)" & vbCrLf & vbCrLf

lTotal2 = lTotal + lFormulas


lTotal = lTotal + lFormulaValues

sMsg = sMsg & Format(lTotal, "#,##0") & _


" Total characters (with formulas as values)" & vbCrLf
sMsg = sMsg & Format(lTotal2, "#,##0") & _
" Total characters (with formulas as formulas)"

MsgBox Prompt:=sMsg, Title:="Character count"

ExitHandler:
Application.ScreenUpdating = True
Exit Sub

ErrHandler:
If bPossibleError And Err.Number = 1004 Then
bPossibleError = False
bSkipMe = True

ExcelTips: The Macros Page 457


Interacting with the Excel Environment

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.

Unhiding or Listing All Objects


Mike had a problem where he knew that there were objects hidden within his workbook and he
wanted to find them all. It seems he wrote a macro that hid some objects, but then did not unhide
them.

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

'Set types for different objects


objType(1) = "Autoshape"
objType(2) = "Callout"
objType(3) = "Chart"
objType(4) = "Comment"
objType(7) = "EmbeddedOLEObject"
objType(8) = "FormControl"
objType(5) = "Freeform"
objType(6) = "Group"
objType(9) = "Line"
objType(10) = "LinkedOLEObject"
objType(11) = "LinkedPicture"
objType(12) = "OLEControlObject"
objType(13) = "Picture"
objType(14) = "Placeholder"
objType(15) = "TextEffect"
objType(17) = "TextBox"

objList = ""

'Get the number of objects


objCount = ActiveSheet.Shapes.Count

ExcelTips: The Macros Page 458


Interacting with the Excel Environment

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

ExcelTips: The Macros Page 459


Interacting with the Excel Environment

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

Using Named Ranges in a Macro


Bruce has a named range (Account) defined in a workbook and he wonders how to access and
use that named range from within a macro. There are several ways you can access the range,
using either the Range object or the Names collection.

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:

Set rng = Worksheets("Sheet1").Range("Account")

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:

Set rng = Workbooks("Book1.xls").Names("Account").RefersToRange

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.

ExcelTips: The Macros Page 460


Interacting with the Excel Environment

Deleting All Names but a Few


Do you routinely work with worksheets that contain dozens (or hundreds) of named cells, and
most of those names are unnecessary? Cleaning up the names is a huge task, but getting rid of
the ones you don’t need can make your workbook smaller and more efficient. The problem is,
how do you get rid of a lot of unnecessary names all at once? You can certainly delete them one
at a time, but such a process quickly gets tiresome.

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

'Add Names to keep here


vKeep = Array("Name1", "Name2")

Set AWF = Application.WorksheetFunction


For Each nm In ActiveWorkbook.Names
x = 0
On Error Resume Next
x = AWF.Match(nm.Name, vKeep, 0)
On Error GoTo 0
If x = 0 Then
nm.Delete
End If
Next
Set AWF = Nothing
End Sub

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:

ExcelTips: The Macros Page 461


Interacting with the Excel Environment

http://www.jkp-ads.com/officemarketplacenm-en.asp

Default Cell Movement when Deleting


When you select a number of cells (not entire rows or columns) and then choose to delete those
cells, there are two directions that remaining cells can move: to the left or up. If the selected cells
include fewer rows than columns, then Excel offers to move the remaining cells to the left. In all
other situations (the number of rows is greater than or equal to the number of columns), then
Excel offers to move the remaining cells up, by default.

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.

Forcing Manual Calculation For a Workbook


Excel workbooks can become quite complex. In fact, it is possible to create workbooks that can
take hours to calculate. The only problem with this, of course, is that when you open a
workbook, it automatically recalculates if you have Excel configured to do that. This means that
just opening a workbook can, in some instances, take hours.

ExcelTips: The Macros Page 462


Interacting with the Excel Environment

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:

Private Sub Workbook_Open()


Application.Calculation = xlManual
Application.CalculateBeforeSave = False
End Sub

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:

Private Sub Workbook_BeforeClose(Cancel As Boolean)


Application.Calculation = xlAutomatic
Application.CalculateBeforeSave = True
End Sub

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.

Pulling Formulas from a Worksheet


Excel allows you to display the formulas in a worksheet simply by making sure the Formulas
check box is selected on the View tab of the Options dialog box. This can be a handy way to
print a worksheet that shows the formulas used to create the worksheet.

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:

ExcelTips: The Macros Page 463


Interacting with the Excel Environment

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.

The Advanced options of the Excel Options dialog box.

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.

ExcelTips: The Macros Page 464


Interacting with the Excel Environment

The Paste Special dialog box in Word.

12. Choose the Unformatted Text option.


13. Click on OK.

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.

ExcelTips: The Macros Page 465


Interacting with the Excel Environment

The View tab of the Options dialog box.

3. Ensure that the Formulas check box is selected.


4. Click on OK. Excel should now be displaying formulas.
5. Select the cells whose formulas you want to copy to Word.
6. Press CTRL+C to copy the cells to the Clipboard.
7. Switch to Word.
8. Position the insertion point where you want the information added.
9. Choose Paste Special from the Edit menu. Word displays the Paste Special dialog box.

ExcelTips: The Macros Page 466


Interacting with the Excel Environment

The Paste Special dialog box in Word.

10. Choose the Unformatted Text option.


11. Click on OK.

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

Wrd.Selection.TypeText Text:="List of the Formulas of Sheet """ _


& ActiveSheet.Name & """ in Workbook """ _
& ActiveWorkbook.Name & """."
Wrd.Selection.TypeText Text:=vbCrLf & vbCrLf

'Change the following line to pick the number of columns


For SCol = 1 To 5
'Change the following line to pick the number of rows
For SRow = 1 To 10
If Cells(SRow, SCol).HasFormula Then
CellAddr = Chr(64 + SCol) & Trim(Str(SRow)) & vbTab
CellTxt = ActiveSheet.Cells(SRow, SCol).Formula
Wrd.Selection.TypeText Text:=CellAddr & CellTxt
Wrd.Selection.TypeText Text:=vbCrLf
End If
Next SRow
Wrd.Selection.TypeText Text:=vbCrLf

ExcelTips: The Macros Page 467


Interacting with the Excel Environment

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.

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.

ExcelTips: The Macros Page 468


Interacting with the Excel Environment

Saving Non-Existent Changes


You probably had this happen to you: You open a workbook, look around at some of the
worksheets, and then close the workbook. As part of closing, Excel asks you if you want to save
your changes, yet you didn't make any changes—you only looked around. What gives?

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:

Private Sub Workbook_BeforeClose(Cancel As Boolean)


ActiveWorkbook.Saved = True
End Sub

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.

A variation on this approach—one that is less unforgiving of forgotten changes—is to actually


make the macro part of the Workbook_Open procedure for the ThisWorkbook object:

Private Sub Workbook_Open()


ActiveWorkbook.Saved = True
End Sub

ExcelTips: The Macros Page 469


Interacting with the Excel Environment

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.

Counting Precedents and Dependents


Because Excel allows you to create formulas that refer to other cells, it stands to reason that cells
can be dependent on each other. In fact, Excel has two technical terms that are used to define the
relationship between cells: precedents and dependents.

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

On Error GoTo err


For Each ws In Worksheets
ws.Select
lDep = 0
lPre = 0
lDep = Range("a1:iv65536").Dependents.Count
lPre = Range("a1:iv65536").Precedents.Count
MsgBox "Worksheet: " & ActiveSheet.Name & vbCr & _
"Dependents: " & lDep & vbCr & _
"Precedents: " & lPre
Next ws
Exit Sub
err:
Resume Next
End Sub

When you run this macro, it steps through each worksheet in your workbook and displays the
number of dependents and precedents in each.

ExcelTips: The Macros Page 470


Interacting with the Excel Environment

Accessing Dependent and Precedent Information


David rightly notes that Excel provides auditing tools (Trace Dependents and Trace Precedents)
that are a very helpful way of keeping track of what is happening in large worksheets. However,
the actual interface just lists out the cells in a small area, and David cannot easily copy out this
list of cells to analyze and manipulate it. When he uses Trace Dependents on an important cell in
a large worksheet, the small dialog box can contain several hundred references. David wonders if
there is a relatively easy way of getting this information into a more usable format, like a blank
worksheet or another workbook.

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 Resume Next


Set rDep = ActiveCell.Dependents
If rDep Is Nothing Then
MsgBox ActiveCell.Address(False, False) & _
" has no dependents"
Exit Sub
End If

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."

ExcelTips: The Macros Page 471


Interacting with the Excel Environment

Discovering Dependent Workbooks


Beth wonders if there is a way that she can determine if there are other workbooks dependent on
the workbook she has open. She know how to find the precedent links to her open workbook but
not the dependent ones. Beth is in a new job and she doesn't want to risk editing a workbook
without knowing what other files she may be impacting.

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

'Change the string here to look


'for a different link / file name
sLink = "[FileA.xlsm]"
curFile = ThisWorkbook.Name

ExcelTips: The Macros Page 472


Interacting with the Excel Environment

'Change the string here to look


'in a different folder
myFldr = "C:\Users\User\mySub\"

'Look for both xlsx and xlsm extensions


iFile = Dir(myFldr & "*.xls?", vbNormal)
i = 1
'Loop through all of the files in the folder
Do While iFile <> ""
If iFile <> curFile Then
Workbooks.Open Filename:=myFldr & iFile
Set fLink = Cells.Find(What:=sLink, _
After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False)
If UCase(TypeName(fLink)) <> UCase("Nothing") Then
Windows(curFile).Activate
'Record names of dependent files
'in your open workbook
Worksheets(1).Range("D" & (i)).Value = _
ActiveWorkbook.Name
i = i + 1
End If
Workbooks(iFile).Close False
End If
iFile = Dir
Loop
End Sub

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.

Turning Off AutoFill for a Workbook


The AutoFill feature of Excel can be a great timesaver when entering information. However, you
may want to disable the feature for a particular workbook; perhaps you don’t want the feature to
be used by anyone using your workbook. Doing so is easy if you create a macro that can
recognize when you are working with that particular workbook.

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.

ExcelTips: The Macros Page 473


Interacting with the Excel Environment

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

Private Sub Workbook_Deactivate()


Application.CellDragAndDrop = True
End Sub

6. Close the VBA editor.


7. Save your workbook.

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.

Noting the Workbook Creation Date


When you are developing a worksheet, you may need to keep track of certain information about
your workbook. For instance, you might want to place the creation date of a workbook into a
cell. While Excel does provide some worksheet functions for dates (such as NOW or TODAY),
it does not provide a worksheet function to access the workbook creation date.

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:

Function CreateDate() As String


Dim Temp As String
On Error GoTo NoFile
Temp = CreateObject("scripting.filesystemobject"). _

ExcelTips: The Macros Page 474


Interacting with the Excel Environment

GetFile(ActiveWorkbook.Name).dateCreated

CreateDate = Left(Temp, InStr(Temp, " ") - 1)


GoTo Done
NoFile:
CreateDate = "Not Saved"
Done:
End Function

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.

Displaying the "Last Modified" Date


If you look at the properties stored with a workbook, you will notice that Excel maintains quite a
bit of information concerning the file. One of the items is a date and time that is simply noted as
"Modified." Many people refer to this as the "last modified" date, but it really reflects the last
time the workbook was saved.

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

On Error Resume Next

sLMD = ActiveWorkbook.BuiltinDocumentProperties("Last Save Time")


If Err = 440 Then
Err = 0
sLMD = "Not Set"
End If
sLMD = Left(sLMD, 8)
ActiveSheet.PageSetup.LeftHeader = "Last Saved: " & sLMD
End Sub

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.

ExcelTips: The Macros Page 475


Interacting with the Excel Environment

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.

Noting When a Workbook was Changed


In an environment where multiple people work on the same workbook, you may want a way to
keep track of when people last changed a workbook. There are a couple of ways you can
approach this task. One is to simply figure out when a workbook was last saved. This approach
works well if you assume that any changes to the workbook are always changed. (Unsaved
changes, of course, are not really a lasting change at all.) The following macro returns the date
that a workbook was saved and stores that date in cell A1:

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

ExcelTips: The Macros Page 476


Interacting with the Excel Environment

keep a history log for a workbook. Follow these steps if you are using Excel 2007 or a later
version:

1. Display the Review tab of the ribbon.


2. In the Changes group click the Share Workbook tool. Excel displays the Share
Workbook dialog box.
3. Make sure the Editing tab is displayed.

The Editing tab of the Share Workbook dialog box.

4. Select the Allow Changes check box.


5. Display the Advanced tab.

ExcelTips: The Macros Page 477


Interacting with the Excel Environment

The Advanced tab of the Share Workbook dialog box.

6. Make sure the Keep Change History radio button is selected.


7. Using the other controls in the dialog box, select the tracking options you want used
with the workbook.
8. Click on OK.
9. In the Changes group click the Track Changes tool, then choose Highlight Changes
from the submenu. Excel displays the Highlight Changes dialog box.

ExcelTips: The Macros Page 478


Interacting with the Excel Environment

The Highlight Changes dialog box.

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.

Date Last Edited


As part of setting up a worksheet, you may want the header or footer to contain the date that the
workbook was last edited. Excel doesn’t maintain this information, but it does allow you to

ExcelTips: The Macros Page 479


Interacting with the Excel Environment

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:

Private Sub Workbook_SheetChange(ByVal _


Sh As Object, ByVal Target As Excel.Range)
ActiveSheet.PageSetup.CenterFooter = _
"Worksheet Last Changed: " & _
Format(Now, "mmmm d, yyyy hh:mm")
End Sub

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:

Private Sub Workbook_BeforeSave(ByVal _


SaveAsUI As Boolean, Cancel As Boolean)
Dim sht
For Each sht In Sheets
sht.PageSetup.CenterFooter = _
"Workbook Last Saved: " & _
Format(Now, "mmmm d, yyyy hh:mm")
Next
End Sub

This macro steps through each worksheet in the workbook and changes every center footer to
have the date that the workbook was saved.

Finding the Path to the Desktop


Donald is writing a macro in which he needs to reference a user's desktop. However, the path to
the desktop necessarily varies from system to system and user to user. He wonders what coding
he can use to determine the path to the desktop regardless of system.

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:

Function GetDesktop() As String


Dim oWSHShell As Object

ExcelTips: The Macros Page 480


Interacting with the Excel Environment

Set oWSHShell = CreateObject("WScript.Shell")


GetDesktop = oWSHShell.SpecialFolders("Desktop")
Set oWSHShell = Nothing
End Function

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:

sPath = Environ("USERPROFILE") & "\Desktop"

Grabbing a User's Name from Excel


Mark has a worksheet where he wants to record the name of a user, but rather than asking the
user to fill in a form, I want to automatically grab their username from Excel.

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.

Inserting the User's Name in a Cell


Sunlim noted that when Office is installed, the user specifies their name. This name can be
accessed in some Office programs, such as in Word. Sunlim wonders how he can access the
user's name in Excel and place that name in a 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:

ExcelTips: The Macros Page 481


Interacting with the Excel Environment

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:

Function UserNames() As String


Dim Users As Variant
Dim sMsg As String
Dim iIndex As Integer

Users = ActiveWorkbook.UserStatus

For iIndex = 1 To UBound(Users, 1)


sMsg = Users(iIndex, 1) & vbLf
Next iIndex
'remove final line feed
sMsg = Left(sMsg, Len(sMsg) - 1)

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:

Private Declare Function GetUserName Lib "advapi32.dll" _


Alias "GetUserNameA" (ByVal lpBuffer As String, nSize _
As Long) As Long

Function UserName2() As String


Dim strBuff As String * 100
Dim lngBuffLen As Long

lngBuffLen = 100
GetUserName strBuff, lngBuffLen
UserName2 = Left(strBuff, lngBuffLen - 1)
End Function

ExcelTips: The Macros Page 482


Interacting with the Excel Environment

Grabbing the MRU List


Excel allows you to maintain a list of the most-recently used (MRU) files. This list can record
the names of up to the last nine workbooks opened and edited within Excel. You can see the
MRU list by clicking the Office button (Excel 2007) or displaying the File tab of the ribbon
(Excel 2010 and Excel 2013). In older versions of Excel you can see it at the bottom of the File
menu.

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

Seeing All Open Workbook Names


Excel allows you to open quite a few workbooks at the same time, depending on the amount of
memory you have available on your system. For some people it is not unusual to have ten,
fifteen, twenty, or more workbooks open, all at once.

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:

ExcelTips: The Macros Page 483


Interacting with the Excel Environment

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

Private Sub UserForm_Activate()


Dim wkb As Workbook

For Each wkb In Workbooks


If Windows(wkb.Name).Visible Then _
ListBox1.AddItem wkb.Name
Next
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.

ExcelTips: The Macros Page 484


Interacting with the Excel Environment

Testing if a Workbook is Open


Brian has a complex workbook that opens other supporting workbooks very briefly and then
closes them, saving changes. He has an office with three people that use his macros, and on
occasion they get a conflict where two people are trying to get a purchase order number at the
same time. This causes the PO workbook to open in 'read only' mode for the second occurrence,
which is confusing to users. Brian wonders if there is a line of VBA code that will stop the 'open
workbook' command if the target workbook is already open by a different user. That way he
could catch potential problems before they occur.

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:

Set wkBook1 = Workbooks.Open("c:\MyBigBook.xlsx")

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.

Function FileIsLocked(strFileName As String) As Boolean


FileIsLocked = False
On Error Resume Next
Open strFileName For Binary Access Read Write Lock Read Write As #1
Close #1

' If an error occurs, the document is currently open


If Err.Number <> 0 Then
FileIsLocked = True
Err.Clear
End If
End Function

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.

ExcelTips: The Macros Page 485


Interacting with the Excel Environment

Getting Rid of "Copy of"


Anna Lea has a read-only workbook that she uses as a template for a daily report that she creates.
The file name is quite long, and ends in 20507xx. When she double-clicks on the workbook, it
opens and shows that it is read-only. She makes her changes, and then uses Save As. Since Excel
recognizes that the file is read-only, it suggests a new file name that consists of the old one with
the words “Copy of” as a prefix. Anna wants to get rid of the “Copy of” so that all she has to do
is change the “xx” portion of the file name to create the day’s report.

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.

Private Sub Workbook_BeforeSave(ByVal SaveAsUI _


As Boolean, Cancel As Boolean)
Dim sTemp As String
Dim sCheck As String
sCheck = "xx.xls"

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.

ExcelTips: The Macros Page 486


Interacting with the Excel Environment

Pulling Cell Names into VBA


If you have used Excel for any length of time, you undoubtedly know that you can define names
in your worksheets that refer to various cells and ranges of cells. You can even define names that
refer to constants and to formulas. (The naming abilities of Excel are really quite handy.)

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

ReDim NamesList(1 To NumNames)

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

ExcelTips: The Macros Page 487


Interacting with the Excel Environment

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.

Looking Up Names when Key Values are Identical


Jim described a situation where he has a list of employee names and their salaries. He wants to
determine who the five highest-paid employees are. He uses the LARGE function to identify the
five largest salaries, and then tries to use VLOOKUP to return the names belonging to those
salaries. This works fine unless there are duplicates in the top five salaries (people get paid the
same salary). If there are, then VLOOKUP only returns the name of the first employee at that
salary.

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:

1. Select any cell in your data table.


2. Choose Data | Filter | AutoFilter or, if you are using Excel 2007 or later, display the
Data tab of the ribbon and click the Filter tool. Excel adds drop-down arrows at the
right of each column header in the table.
3. Use the drop-down list at the top of the salaries column to choose Top 10. (In Excel
2007 and later versions, use the drop-down list to choose Number Filters and then Top
10.) Excel displays the Top 10 AutoFilter dialog box.

The Top 10 AutoFilter dialog box.

4. Adjust the center control from 10 to 5.


5. Click on OK. Excel displays the top five salaries in the list.

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:

ExcelTips: The Macros Page 488


Interacting with the Excel Environment

=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:

Function VLIndex(vValue, rngAll As Range, _


iCol As Integer, lIndex As Long)
Dim x As Long
Dim lCount As Long
Dim vArray() As Variant
Dim rng As Range
On Error GoTo errhandler

Set rng = Intersect(rngAll, rngAll.Columns(1))


ReDim vArray(1 To rng.Rows.Count)
lCount = 0
For x = 1 To rng.Rows.Count
If rng.Cells(x).Value = vValue Then
lCount = lCount + 1
vArray(lCount) = rng.Cells(x).Offset(0, iCol).Value
End If
Next x

ReDim Preserve vArray(1 To lCount)


If lCount = 0 Then
VLIndex = CVErr(xlErrNA)
ElseIf lIndex > lCount Then
VLIndex = CVErr(xlErrNum)
Else

ExcelTips: The Macros Page 489


Interacting with the Excel Environment

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.

Determining if Caps Lock is On


When you are creating a macro, you may have a need to know if the CAPS LOCK key is engaged.
(For instance, you may want to warn a user to turn the CAPS LOCK key off.) If you have this
need, the following VBA macro code determines the status of the CAPS LOCK key, and warns you
accordingly:

If Selection.Information(wdCapsLock) Then
Print "The Caps Lock key is on"
Else
Print "The Caps Lock key is off"
End If

ExcelTips: The Macros Page 490


Interacting with the Excel Environment

Disabling Page Layout View


Chris notes that Excel supports both Normal and Page Layout views. He wonders if there is a
way to disable Page Layout view so the user of a workbook cannot select it.

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.

The Excel Options dialog box.

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.

ExcelTips: The Macros Page 491


Interacting with the Excel Environment

4. Click once on the Workbook Views option.


5. Click the Remove button.
6. Click OK.

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:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


ActiveWindow.View = xlNormalView
End Sub

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.

Disabling a Function Key


Jean asked if there is a way to disable the F1 key in Excel. It seems that she frequently presses
F1 when she means to press F2, and doing so is bothersome.

The only way to disable a key such as this is to create a macro. The following macro will do the
trick quite nicely:

Private Sub Workbook_Open()


Application.OnKey "{F1}", ""
End Sub

Private Sub Workbook_BeforeClose(CANCEL As Boolean)


Application.OnKey "{F1}"
End Sub

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.

ExcelTips: The Macros Page 492


Interacting with the Excel Environment

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.)

Disabling the F1 Key


The F1 key is used to summon forth Excel's help system. It can either start the actual Help
system interface, or it can display your favorite Office Assistant, depending on how you have
your system configured.

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:

Application.OnKey "{F1}", ""

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:

Application.OnKey "{F1}", "MyProcedure"

Disabling Excel's Help System


Mark is a high school teacher and he wants to disable Excel's Help system during student tests.
He wonders if there is an easy way to do this.

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()

ExcelTips: The Macros Page 493


Interacting with the Excel Environment

EnableControl 984, False ' help


EnableControl 1004, False ' Office Assistant
Application.OnKey "{F1}", ""
End Sub

Sub EnableHelp()
EnableControl 984, True ' help
EnableControl 1004, True ' Office Assistant
Application.OnKey "{F1}"
End Sub

Sub EnableControl(Id As Integer, Enabled As Boolean)


Dim CB As CommandBar
Dim C As CommandBarControl
For Each CB In Application.CommandBars
Set C = CB.FindControl(Id:=Id, recursive:=True)
If Not C Is Nothing Then C.Enabled = Enabled
Next
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.

ExcelTips: The Macros Page 494


Interacting with the Excel Environment

Retrieving Drive Statistics


If you are creating a full-blown application using Excel, you may want to know a bit about the
environment in which your application is running. For instance, you might want to know how
many drives are attached to the system, what their drive letters are, and how much space they
have free.

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

On Error Resume Next


i = 2
For Each Drv In fs.drives
If Drv.IsReady Then
Letter = Drv.DriveLetter
Total = Drv.TotalSize
Free = Drv.FreeSpace

FreePercent = Free / Total


TotalPercent = 1 - FreePercent

Cells(i, 1).Value = Letter


Cells(i, 2).Value = FreePercent
Cells(i, 3).Value = TotalPercent
i = i + 1
End If
Next
End Sub

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.)

ExcelTips: The Macros Page 495


Interacting with the Excel Environment

Full Path Names in Excel


When you open a workbook in Excel, the workbook name is displayed in the title bar. At times,
it would be nice to display more than a simple workbook name in the title bar. Many people
could profit by a way to display a full path name along with the workbook name in the title bar.
Unfortunately, Excel does not provide a way to do this easily.

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.

Automatically Closing a Workbook


If you work in a security-conscious environment, you may always be looking for ways to
increase the security of your system, even without any effort on your part. One way you can
increase security is to add some macro coding to your workbook that results in it being closed if
you don't make any changes to a worksheet within any ten-minute period. For instance, if you
step a way from your computer, then the workbook you were using is automatically closed after
10 minutes.

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.

The second macro (Worksheet_Change) is triggered whenever you change something in a


workbook. When this occurs, the same run_time subroutine is used to again specify that
close_wb should be run ten minutes in the future.

Private Sub Workbook_Open()


close_time = Now + TimeValue("00:10:00")
run_time
End Sub

Private Sub Worksheet_Change(ByVal Target As Range)


If close_time Then
Application.OnTime _
EarliestTime:=close_time, _

ExcelTips: The Macros Page 496


Interacting with the Excel Environment

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.

Finding Unknown Links


It can be frustrating to open an Excel file and be continually asked if you want to update linked
information, particularly if you are not sure what information is linked. If you want to get rid of
links in a workbook, there are several things to try.

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.

ExcelTips: The Macros Page 497


Interacting with the Excel Environment

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.

' When finished, the focus is returned the first sheet

Dim c As Range
Dim sht As Worksheet

On Error Resume Next

For Each sht In ActiveWorkbook.Worksheets


sht.Activate
Selection.SpecialCells(xlCellTypeFormulas, 23).Select
For Each c In Selection
c.ShowPrecedents
Next c
Next sht

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.

ExcelTips: The Macros Page 498


Interacting with the Excel Environment

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

Getting Rid of Workbook Links


Paula has a workbook that is linked to other workbooks. These are workbook links, not
hyperlinks. She is looking for a way to break all of these types of links.

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:

ExcelTips: The Macros Page 499


Interacting with the Excel Environment

http://www.oaltd.co.uk/mvp/MVPPage.asp

FindLink was written by Bill Manville and Name Manager by Jan Karel Pieterse, both Excel
MVPs.

Replacing Links with Values


John has a large number of workbooks that have links in them and they are getting very large. He
wonders if there is any way for Excel to convert the links to the data grabbed from those links so
he can archive the old workbooks.

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:

1. Open the workbook you want to affect.


2. Display the Edit Links dialog box. (In Excel 2007 and later versions display the Data
tab of the ribbon and click the Edit Links tool in the Connections group. In older
versions of Excel choose the Links option from the Edit menu.)

The Edit Links dialog box.

3. Select the links in the dialog box.


4. Click Break Links and acknowledge that you really want to break the selected links.
5. Click OK.

The result is that all the links are done away with, but the values last retrieved through the links
remain in the workbook.

ExcelTips: The Macros Page 500


Interacting with the Excel Environment

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:

1. Select the cells that contain links.


2. Press CTRL+C.
3. Display the Paste Special dialog box. (In Excel 2007 or later display the Home tab of
the ribbon and click the down-arrow under the Paste tool and choose Paste Special. In
older versions of Excel choose Paste Special from the Edit menu.)

The Paste Special dialog box.

4. Click the Values radio button.


5. Click OK.

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

ExcelTips: The Macros Page 501


Interacting with the Excel Environment

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

Task Pane Doesn't Appear Properly


Brian was perplexed by the fact that when he starts Excel 2002, the Task pane momentarily
appears and is then overlaid by the standard blank workbook. This happens in spite of the fact
that he has the Task pane set to show at start-up, and the same selection is chosen at the bottom
of the Task pane itself.

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:

Private Sub Workbook_Open()


On Error Resume Next
Application.CommandBars("Task Pane").Visible = True
End Sub

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.

Get out of Excel and follow these steps:

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.)

ExcelTips: The Macros Page 502


Interacting with the Excel Environment

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.

Finding the Last-Used Cell in a Macro


If you are working in a worksheet, you know that you can press CTRL+END to jump to the last-
cell in the worksheet. What the shortcut does is to choose the cell that represents the intersection
of the last column containing data and the last row containing data. Thus, if the last column in
which you have data is column F, and the last row in which you have data is row 27, then
CTRL+END will select cell F27.

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

ExcelTips: The Macros Page 503


Interacting with the Excel Environment

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.

Jumping to the Real Last Cell


Diane wrote about a problem she was having with a file imported into Excel. The file, created by
a non-Excel program, contains 50,000 records, but only the first 87 records contain any data.
When the file is imported, pressing CTRL+END moves to cell J50000 instead of cell J87. Diane
was wondering how to make Excel jump to the end of the real data—J87.

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.

ExcelTips: The Macros Page 504


Interacting with the Excel Environment

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.

Determining if Calculation is Necessary


Excel allows you to control when it recalculates a worksheet. Normally, Excel recalculates
anytime you change something in a cell. If you are working with very large worksheets that have
lots of formulas in them, you may want to turn off the automatic recalculation feature. You can
turn off automatic recalculation using the tools in the Calculation group on the Formulas tab of
the ribbon.

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:

If Not ActiveWorkbook.Saved Then


ActiveSheet.Calculate
End If

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:

If Not ActiveWorkbook.Saved Then


ActiveSheet.Calculate
ActiveWorkbook.Save

ExcelTips: The Macros Page 505


Interacting with the Excel Environment

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.

Iterating Circular References


Circular references occur when a formula refers—either directly or indirectly—to the cell in
which the formula is stored. For instance, if B3 contains the formula =B2+B3, then B3 contains a
circular reference.

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.

ExcelTips: The Macros Page 506


Interacting with the Excel Environment

The Calculation tab of the Options dialog box.

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.

ExcelTips: The Macros Page 507


Interacting with the Excel Environment

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:

Private Sub Workbook_Open()


Application.Iteration = True
End Sub

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


Joel noted that when he closes a workbook that has thousands of formulas in it he is getting this
message lately: "A formula in this worksheet contains one or more invalid references." Joel
wonders how he can know which of the seven worksheets in this workbook is being referred to.
How can I find the errant formula? I do not observe any problems in the display of information
on my reports.

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

Dim iSh As Integer

ExcelTips: The Macros Page 508


Interacting with the Excel Environment

Dim sShName As String


Dim sht As Worksheet
Dim c, sChar As String
Dim rng As Range
Dim i As Integer, j As Integer
Dim wks As Worksheet
Dim sChr As String, addr As String
Dim sFormula As String, scVal As String
Dim lNewRow As Long
Dim vHeaders

vHeaders = Array("Sheet Name", "Cell", "Cell Value", "Formula")


'check if 'Summary' worksheet is in workbook
'and if so, delete it
With Application
.ScreenUpdating = False
.DisplayAlerts = False
.Calculation = xlCalculationManual
End With

For i = 1 To Worksheets.Count
If Worksheets(i).Name = "Summary" Then
Worksheets(i).Delete
End If
Next i

iSh = Worksheets.Count

'create a new summary sheet


Sheets.Add After:=Sheets(iSh)
Sheets(Sheets.Count).Name = "Summary"
With Sheets("Summary")
Range("A1:D1") = vHeaders
End With
lNewRow = 2

' this will not work if the sheet is protected,


' assume that sheet should not be changed; so ignore it
On Error Resume Next

For i = 1 To iSh
sShName = Worksheets(i).Name
Application.Goto Sheets(sShName).Cells(1, 1)
Set rng = Cells.SpecialCells(xlCellTypeFormulas, 23)

For Each c In rng


addr = c.Address
sFormula = c.Formula
scVal = c.Text

For j = 1 To Len(c.Formula)
sChr = Mid(c.Formula, j, 1)

If sChr = "[" Or sChr = "!" Or _


IsError(c) Then
'write values to summary sheet
With Sheets("Summary")
.Cells(lNewRow, 1) = sShName
.Cells(lNewRow, 2) = addr
.Cells(lNewRow, 3) = scVal
.Cells(lNewRow, 4) = "'" & sFormula
End With
lNewRow = lNewRow + 1

ExcelTips: The Macros Page 509


Interacting with the Excel Environment

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/

Determining How Many Windows are Open


It is sometimes helpful for your macro to know how many Excel workbook windows are open at
any given time. For instance, you might want your macro to only run if there is a single window
open, or you might even require there to be two windows open. Either way, you need to check
how many there are.

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

After executing the line, X is equal to the number of open windows.

Saving Common Formulas


Sometimes, creating just the formula you want can be a victory all in itself. Once created,
formulas become valuable, and you may need to use them over and over again in different

ExcelTips: The Macros Page 510


Interacting with the Excel Environment

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.

Another possible solution is to assign names to your formulas.

1. Enter your formula as you normally would.


2. Select the cell containing the formula and press F2. This places Excel in edit mode.
3. Hold down the SHIFT key as you use the cursor control keys to select the entire
formula, including the equal sign at its very beginning.
4. Press CTRL+C. The formula is now on the Clipboard.
5. Press ESC. You should now be out of edit mode, and the cell with the formula is still
selected.
6. Choose Name from the Insert menu, then choose Define. Excel displays the Define
Name dialog box.

The Define Name dialog box.

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.

ExcelTips: The Macros Page 511


Interacting with the Excel Environment

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:

1. Enter your formula as you normally would.


2. Select the cell containing the formula and press F2. This places Excel in edit mode.
3. Hold down the SHIFT key as you use the cursor control keys to select the entire
formula, including the equal sign at its very beginning.
4. Press CTRL+C. The formula is now on the Clipboard.
5. Press ESC. You should now be out of edit mode, and the cell with the formula is still
selected.
6. Make sure the Formulas tab of the ribbon is selected.
7. In the Defined Names area, click the Define Name option. Excel displays the New
Name dialog box.

The New Name dialog box.

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

ExcelTips: The Macros Page 512


Interacting with the Excel Environment

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:

1. Start Excel with a new workbook.


2. Choose Macro from the Tools menu, then choose Record New Macro. Excel displays
the Record Macro dialog box.

The Record Macro dialog box.

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:

1. Start Excel with a new workbook.


2. Display the Developer tab of the ribbon.
3. Click Record Macro. Excel displays the Record Macro dialog box.
4. Using the Store Macro In drop-down list, choose Personal Macro Workbook.
5. Click OK. The Macro Recorder is now running.
6. Select any cell in the worksheet. (It doesn’t matter which one you choose.)
7. Click Stop Recording on the ribbon’s Developer tab.

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

ExcelTips: The Macros Page 513


Interacting with the Excel Environment

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()

' Macro1 Macro


'

'
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.

Turning Off Track Changes without Unsharing


Excel allows you to track changes made to a workbook, as described in other issues of ExcelTips.
When you turn on change tracking, Excel requires that you share the workbook. After all, change
tracking is meant to be used in an environment where multiple users access and change the same
workbook.

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

ExcelTips: The Macros Page 514


Interacting with the Excel Environment

Changes | Highlight Changes in earlier verions), Excel automatically shares the


workbook and turns on change tracking.
• If you start with a shared workbook and choose Review | Changes | Track Changes |
Highlight Changes (Excel 2007 or later) or Tools | Track Changes | Highlight Changes
earlier verions), the Track Changes While Editing check box is selected. This may make
you think that just because the workbook is shared, that track changes is also turned on;
it isn’t. (You can verify this because changes are not marked in the workbook.) If you
click OK in the dialog box, then it will be enabled. If you click Cancel, then it won’t be
enabled. If you clear the Track Changes While Editing check box and click OK, then
Excel unshares the workbook, as well.
• If you start with a workbook that has track changes turned on and choose Review |
Changes | Track Changes | Highlight Changes (Excel 2007 or later) or Tools | Track
Changes | Highlight Changes (earlier verions), the Track Changes While Editing check
box is selected, as it should be. Clearing the check box and clicking OK causes Excel to
unshare the workbook.
• If you start with a workbook that has track changes turned on and choose Review |
Changes | Share Workbook (Excel 2007 or later) or Tools | Share Workbook (earlier
versions), then the Allow Changes by More than One User At the Same Time check box
is selected. (Remember—if track changes is on, then the workbook is automatically
shared.) If you clear the check box and click OK, then sharing is turned off and the
track changes feature is turned off.

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)

ExcelTips: The Macros Page 515


Interacting with the Excel Environment

Next
sMsg = sMsg & vbCrLf & vbCrLf & "Proceed?"
iAnswer = MsgBox(sMsg, vbYesNo)
End If

If iAnswer = vbYes Then


.ExclusiveAccess
.SaveAs Filename:=sFile, AccessMode:=xlShared
End If
End If
End With
End Sub

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.

Checking if a Workbook is Already Open


Macros are often used to slice, dice, and otherwise process information contained in workbooks.
This presumes, of course, that the workbook that contains the information is actually open. If it is
not, then your macro will obviously need to include code to actually open the needed workbook.

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:

Function AlreadyOpen(sFname As String) As Boolean


Dim wkb As Workbook
On Error Resume Next
Set wkb = Workbooks(sFname)
AlreadyOpen = Not wkb Is Nothing
Set wkb = Nothing
End Function

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

ExcelTips: The Macros Page 516


Interacting with the Excel Environment

Else
Workbooks.Open sPath & sFilename
End If

Saving All Open Workbooks


If you work with lots of workbooks open at the same time, you know that it can be a pain to go
through and save each of the open workbooks, in turn. Wouldn't it be great to have a single
command that allowed you to save all the open workbooks, without the need to do it manually?

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.

Saving a Workbook in a Macro


If you want to save a workbook under control of your macro, you can use the Save method. This
is the same as choosing the Save command from the File menu or from the ribbon options, so it
will display the Save As dialog box if the document you are saving has not been previously
saved. The syntax is as follows:

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”

ExcelTips: The Macros Page 517


Interacting with the Excel Environment

where filename is the full name (including a path) that you want used for the file.

Finding the Size of a Workbook


Mike wonders if there is a worksheet function that will show him the size, in bytes, of a
workbook without using a macro.

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. Display the File tab of the ribbon.


2. Make sure the Info option is selected at the left side of the dialog box.
3. Click the Properties link near the right side of the dialog box and then click Advanced
Properties. Excel displays the Properties dialog box for your workbook.
4. Make sure the General tab is displayed.

ExcelTips: The Macros Page 518


Interacting with the Excel Environment

The General tab of the Properties dialog box.

If you are using Excel 2007, follow these steps instead:

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.

If you are using an older version of Excel, follow these steps:

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:

ExcelTips: The Macros Page 519


Interacting with the Excel Environment

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()

The file size is displayed in bytes.

Finding Workbooks Containing Macros


Richard's company, like many others, uses Excel quite a bit. In fact, they have thousands and
thousands of Excel workbooks that they have collected over the years. Richard needs a way to
find out which of those workbooks have VBA macros in them, without the need to open and
inspect each workbook individually. He wonders if there is an easy way to do this.

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

'specify directory to use - must end in "\"


sPath = "C:\MyData\Excel Data\"

sFile = Dir(sPath)
Do While sFile <> ""
If InStr(sFile, ".xls") > 0 Then
Workbooks.Open (sPath & sFile)
If Workbooks(sFile).HasVBProject Then

ExcelTips: The Macros Page 520


Interacting with the Excel Environment

sFoundFiles = sFoundFiles & sFile & vbCrLf


End If
Workbooks(sFile).Close (False)
End If
sFile = Dir ' Get next filename
Loop
If Len(sFoundFiles) = 0 Then
MsgBox "No workbooks found that contain macros"
Else
sFoundFiles = "The following workbooks contain macros:" & _
vbCrLf & vbCrLf & sFoundFiles
MsgBox sFoundFiles
End If
End Sub

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.

Saving Changes when Closing


If you modify an Excel workbook, and then close the file, you are asked if you want to save your
changes. This is a good feature that helps ensure you don’t mistakenly throw away some of your
work.

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.

Closing a Read-Only Workbook


Gary has a read-only workbook that multiple users can access. They can modify cells but not
save their work. On exiting the workbook, Gary wants Excel to just close without informing the
user that it is read-only and giving them the option of saving a copy.

ExcelTips: The Macros Page 521


Interacting with the Excel Environment

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.

Here’s what the macro should look like, at its simplest:

Private Sub Workbook_BeforeClose(Cancel As Boolean)


If ThisWorkbook.ReadOnly Then
ThisWorkbook.Saved = True
End If
End Sub

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.

Forcing a Workbook to Close after Inactivity


Dave wonders if he can force a workbook to close after a certain amount of time, provided it is
not currently being used. In his office people open workbooks that are on the server and then
forget that they are open. When that occurs, nobody else can edit them, so he would like to force
workbooks to close if left unattended for 60 minutes.

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.

ExcelTips: The Macros Page 522


Interacting with the Excel Environment

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:

Dim DownTime As Date

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:

Private Sub Workbook_Open()


Call SetTimer
End Sub

Private Sub Workbook_BeforeClose(Cancel As Boolean)


Call StopTimer
End Sub

Private Sub Workbook_SheetCalculate(ByVal Sh As Object)


Call StopTimer
Call SetTimer
End Sub

Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, _

ExcelTips: The Macros Page 523


Interacting with the Excel Environment

ByVal Target As Excel.Range)


Call StopTimer
Call SetTimer
End Sub

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.

Using a Single Password for Multiple Workbooks


Bill has a set of multiple workbooks that he frequently uses as a suite. To prevent prying eyes
from the contents of these workbooks, he has set a password for each of them. Bill uses a menu
workbook as a gateway to the other workbooks in the suite. The workbook contains hyperlinks to
the individual workbooks, providing him with quick access to the workbooks in his suite.

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

ExcelTips: The Macros Page 524


Interacting with the Excel Environment

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:

Private Sub Worksheet_BeforeDoubleClick _


(ByVal Target As Excel.Range, Cancel As Boolean)
Dim sPW As String
Dim sFile As String

sPW = "password" 'Change to your password


sFile = Target.Value
If sFile <> "" Then
If Dir(sFile) <> "" Then
Workbooks.Open _
FileName:=sFile, _
password:=sPW
Cancel = True
End If
End If
End Sub

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.

Finding Other Instances of Excel in a Macro


If you run a VBA program from within a particular instance of Excel, you can create other
instances of Excel, open and modify workbooks in the newly created instances, and then close
those instances. However, you may wonder how you can determine, within a macro, if other
instances of Excel are already running, and, if so, take control of those instances.

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:

Public Declare Function GetDesktopWindow Lib "user32" () As Long


Public Declare Function FindWindowEx Lib "user32" Alias _
"FindWindowExA" (ByVal hWnd1 As Long, ByVal
hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long

Function ExcelInstances() As Long


Dim hWndDesk As Long

ExcelTips: The Macros Page 525


Interacting with the Excel Environment

Dim hWndXL As Long

'Get a handle to the desktop


hWndDesk = GetDesktopWindow

Do
'Get the next Excel window
hWndXL = FindWindowEx(GetDesktopWindow, hWndXL, _
"XLMAIN", vbNullString)

'If we got one, increment the count


If hWndXL > 0 Then
ExcelInstances = ExcelInstances + 1
End If

'Loop until we've found them all


Loop Until hWndXL = 0
End Function

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:

Dim xlApp As Excel.Application


Set xlApp = GetObject(, "Excel.Application")

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:

Dim xlApp As Excel.Application


Set xlApp = GetObject("ExampleBook").Application

Who Has the File Open?


Joe asked if there is a way to determine, in a macro, who has a particular workbook open. He
thought about using the WriteReservedBy property, but it doesn’t seem to have the information
he needs.

ExcelTips: The Macros Page 526


Interacting with the Excel Environment

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

Macros Run Slower in Newer Excel?


Dave has a Windows XP system that still has an old version of Excel 97 on it. That system has a
macro that is used to process information in a worksheet. It is able to process 500 rows of
information in about 12 seconds. On a newer machine, running Excel 2010 under Windows 7,
the same macro operating on the same data takes approximately 3 to 4 minutes to run. Both
machines have the same amount of memory (4 Gb) and comparable processors. Dave wonders
why the macro runs so much slower on the newer version of Excel.

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

ExcelTips: The Macros Page 527


Interacting with the Excel Environment

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.)

ExcelTips: The Macros Page 528


The Ribbon, Menus, and Toolbars

The Ribbon, Menus, and Toolbars

Displaying Excel's Developer Tab


The user interface in modern versions of Excel—first introduced in Excel 2007—can be
confusing, especially to those who have long used the interface of previous versions of the
program. The new interface uses a "ribbon" type of interface, visible at the top of the screen. The
ribbon is made up of different tabs, each of which contains a collection of related tools. The tabs
that are visible often depend on what you are doing in Excel at the moment.

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.

ExcelTips: The Macros Page 529


The Ribbon, Menus, and Toolbars

The Popular options of the Excel Options dialog box.

3. Ensure the Show Developer Tab in Ribbon check box is selected.


4. Click OK.

If you are using Excel 2010 or Excel 2013 the steps are different:

1. Display the File tab of the ribbon.


2. Click Options. Excel displays the Excel Options dialog box.
3. Click Customize Ribbon at the left side of the dialog box.
4. Using the Customize the Ribbon drop-down list (right side of the dialog box, near the
top), choose Main Tabs.

ExcelTips: The Macros Page 530


The Ribbon, Menus, and Toolbars

The options for customizing the ribbon.

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.

The developer tab should now be visible on the ribbon.

Tools on Developer Tab are Unavailable


Micky noted that when she opens a particular workbook in Excel and displays the Developer tab
on the ribbon, some of the tools are disabled (Macros, Visual Basic Editor, Record Macro). She
wonders why they are disabled and how she can make them available.

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

ExcelTips: The Macros Page 531


The Ribbon, Menus, and Toolbars

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.

Adding a Macro to a Toolbar


Excel is a very flexible program. You can configure it to work just about any way you can
imagine. For instance, you can create a macro, and then add it to a toolbar. To do this, follow
these steps if you are using a version of Excel before Excel 2007:

1. Choose Customize from the Tools menu. Excel displays the Customize dialog box.
2. Make sure the Toolbars tab is selected.

The Toolbars tab of the Customize dialog box.

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.

ExcelTips: The Macros Page 532


The Ribbon, Menus, and Toolbars

4. Click on the Commands tab.

The Commands tab of the Customize dialog box.

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.

Adding a Macro to the Quick Access Toolbar


People generally create macros because they intend to use them. (Makes sense, right?) If you
have a macro that you need to use quite often, you may want to add it to the Quick Access
Toolbar where it can be invoked easily. Just follow these steps if you are using Excel 2007:

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.

ExcelTips: The Macros Page 533


The Ribbon, Menus, and Toolbars

The Customize area of the Excel Options dialog box.

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:

1. Click the File tab on the ribbon.


2. Click Options. Excel displays the Excel Options dialog box.
3. At the left side of the dialog box click Quick Access Toolbar.

ExcelTips: The Macros Page 534


The Ribbon, Menus, and Toolbars

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.

Controlling Display of Toolbar Buttons


Subscriber Jody Perrell is in the process of developing custom toolbar buttons and assigning
macros to the buttons. She wants to have the buttons be enabled whenever at least one worksheet
is visible, but is grasping for the proper code to handle such a situation.

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

ExcelTips: The Macros Page 535


The Ribbon, Menus, and Toolbars

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.

Changing the Shortcut Menu


The shortcut menus that you see when you right-click on different objects can be very helpful. In
Word, it is relatively easy to change the shortcut menus. In Excel, however, changing the
menus—while possible—is nowhere near as easy.

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

ExcelTips: The Macros Page 536


The Ribbon, Menus, and Toolbars

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.

Adding Items to a Context Menu


When you right-click on a cell, Excel provides you with a feature-rich Context menu that allows
you to do any number of things. You may want to add some features to that Context menu,
particularly if they are features you use often.

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()

ExcelTips: The Macros Page 537


The Ribbon, Menus, and Toolbars

On Error Resume Next


CommandBars("cell").Controls("My Procedure").Delete
End Sub

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.

Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, _


Cancel As Boolean)
Dim cmdNew As CommandBarButton

For Each icbc In Application.CommandBars("cell").Controls


If icbc.Tag = "brccm" Then icbc.Delete
Next icbc

If Not Application.Intersect(Target, Range("c10:e25")) _


Is Nothing Then
Set cmdNew = CommandBars("cell").Controls.Add
With cmdNew
.Caption = "My Procedure"
.OnAction = "MyProcedure"
.BeginGroup = True
.Tag = "brccm"
End With
End If
End Sub

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.

Removing Items from a Context Menu


Don likes to use the Context menus that appear when he right-clicks on a cell. Once the Context
menu is visible, he can press a shortcut key of whichever command he wants to perform. (The
shortcut key is the underlined letter for each option on the Context menu.) The problem is that
the Context menu has two items that use the same shortcut key: Copy and Create List. Since he

ExcelTips: The Macros Page 538


The Ribbon, Menus, and Toolbars

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

Problem with Missing Context Menu Option


Rita has encountered a situation at her workplace that has her scratching her head. They have an
employee who can right-click on any cell and the Format Cells option is not available on the
resulting Context menu. Rita is wondering what is necessary to get the Context menu option
back.

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

ExcelTips: The Macros Page 539


The Ribbon, Menus, and Toolbars

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.

ExcelTips: The Macros Page 540


Working with Comments

Working with Comments

Copying Comments to Cells


Hector has a large worksheet containing approximately 600 rows and 70 columns. Spread
throughout these 43,000 cells are about 200 cells with comments. Hector wants to extract the
comments and place them into cells to the right of the main body of the data table. If a comment,
for instance, is attached to cell C43, then he’d like the text from that comment to end up in cell
CC43 and the original comment to be deleted.

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

' Horizontal displacement


Const iColOffset As Integer = 78

' extract comments from selected range


If TypeName(Selection) = "Range" Then
Set rData = Intersect(Selection, ActiveSheet.UsedRange)
For Each rCell In rData.Cells
On Error Resume Next
sComment = rCell.Comment.Text
If Len(sComment) > 0 Then
rCell.Offset(, iColOffset).Value = sComment
rCell.Comment.Delete
End If
sComment = ""
On Error GoTo 0
Next
End If
End Sub

ExcelTips: The Macros Page 541


Working with Comments

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.

Inserting Workbook Comments Into a Cell


Jim would like to insert the text found in the Comments portion of a workbook's properties into a
cell. This isn't a comment attached to a cell, but the contents of the Comments field in the
workbook properties.

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.

Function putComments() As String


putComments=ActiveWorkbook.BuiltinDocumentProperties("comments")
End Function

In order to use this user-defined function, simply use the following in a cell:

=mySheetComment()

Moving Comment Background Pictures to Cells


Francois has a bunch of comments in a worksheet, and each comment contains a picture as a
background. He would like to remove those background pictures from the comments and place
them, instead, as graphics in the cells just to the right of where the comments are located.

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:

ExcelTips: The Macros Page 542


Working with Comments

Sub CommentPictures()
Dim cmt As Comment
Dim rCell As Range
Dim bVisible As Boolean

For Each cmt In ActiveSheet.Comments


With cmt
bVisible = .Visible
.Visible = True
Set rCell = .Parent.Offset(0, 1)
.Shape.CopyPicture _
Appearance:=xlScreen, Format:=xlPicture
rCell.PasteSpecial
Selection.ShapeRange.LockAspectRatio = msoFalse
Selection.Width = rCell.Width
Selection.Height = rCell.Height
.Visible = bVisible
.Shape.Fill.OneColorGradient msoGradientFromCenter, 1, 1
End With
Next cmt
End Sub

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.

Setting the Default Font Size for Comment Balloons


When you insert a comment into a worksheet cell, Excel places the user’s name on the first line
of the comment balloon in bold type. The insertion point is then at the beginning of the next line,
ready for your to type. What you type is inserted, by default, in 8-point or 9-point Tahoma
(depending on your version of Excel).

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:

1. Right-click anywhere on the desktop. Windows displays a Context menu.


2. Choose Properties. Windows displays the Display Properties dialog box.

ExcelTips: The Macros Page 543


Working with Comments

3. Make sure the Appearance tab is selected.

The Appearance tab of the Display Properties dialog box.

4. In some versions of Windows you should click the Advanced button. Windows displays
the Advanced Appearance dialog box.

ExcelTips: The Macros Page 544


Working with Comments

The Advanced Appearance dialog box.

5. Using the Item drop-down list, choose ToolTip.


6. Using the other controls in the dialog box, change the font specifications as desired.
7. Click OK, as necessary, to close all the open dialog boxes.

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

For Each cmt In ActiveSheet.Comments


With cmt.Shape.TextFrame.Characters.Font
.Name = "Arial"
.Size = 12
End With
Next cmt
End Sub

ExcelTips: The Macros Page 545


Working with Comments

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.

Copying Comments when Filtering


Vinod frequently uses Excel's advanced filtering tools to copy filtered data from one location to
another. In some instances he would like to copy not only the cell contents but also the cell
comments of the cells that meet the filtering criteria. He’s not looking for a way to filter based on
comments, but only to copy comments along with the cell contents when using the advanced
filtering capabilities of Excel.

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:

1. Press F5 to display the Go To dialog box.


2. Click Special to display the Go To Special dialog box.

The Go To Special dialog box.

3. Make sure the Visible Cells Only option is selected.

ExcelTips: The Macros Page 546


Working with Comments

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

' goto to another worksheet


Sheets("Sheet1").Select
' and paste the copied data
With Range("A1")
.PasteSpecial xlPasteColumnWidths
.PasteSpecial xlPasteAll
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.

Adding a Comment to Multiple Cells


Keith notes that adding a comment to a cell is easy. He wonders if there is a way to insert the
same comment into multiple cells at the same time.

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:

1. Insert your comment in the first cell, as you normally would.


2. Select the commented cell and press CTRL+C. This copies the cell to the Clipboard.
3. Select the range of cells that you want to have the same comment.
4. Display the Paste Special dialog box. (In Excel 2007 or later display the Home tab of
the ribbon, click the down-arrow under the Paste tool, and select Paste Special. In
earlier versions of Excel select Paste Special from the Edit menu.)

ExcelTips: The Macros Page 547


Working with Comments

The Paste Special dialog box.

5. Click the Comments radio button.


6. Click OK.

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

ExcelTips: The Macros Page 548


Working with Comments

End Sub

Linking Comments to Multiple Cells


When you insert a comment into a worksheet, that comment is associated with a single cell.
There may be times when you want to have a single comment associated with two or more cells.
Unfortunately, Excel doesn’t provide this capability—there is a strict one-to-one correspondence
between comments and cells.

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:

Private Sub Worksheet_SelectionChange(ByVal Target As Excel.Range)


Shapes("Text Box 1").Visible = False
Shapes("Line 1").Visible = False
Shapes("Line 2").Visible = False

If Target.Address = "$C$15" Then


Shapes("Text Box 1").Visible = True
Shapes("Line 1").Visible = True
End If
If Target.Address = "$F$7" Then
Shapes("Text Box 1").Visible = True
Shapes("Line 2").Visible = True
End If
End Sub

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.

Editing a Comment Close to Its Cell


Peggy has several cells in a worksheet that have comments associated with them. When she
right-clicks on one of these cells, she can choose Edit Comment from the resulting Context menu
in order to edit the comment. If the comment was one she created in a previous session with
Excel, it is not unusual for the comment to open up elsewhere in the worksheet, sometimes

ExcelTips: The Macros Page 549


Working with Comments

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.

Finding and Replacing Text in Comments


Gerry has a workbook containing 22 worksheets. Each worksheet has about 20 comments. Some
of the comments make reference to a company division. He would like to do a mass search and
replace of the comments to find each reference (for example, "ABC Division") and replace it
with something else (for example, "XYZ subsidiary").

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

ExcelTips: The Macros Page 550


Working with Comments

Dim wks As Worksheet


Dim sFind As String
Dim sReplace As String
Dim sCmt As String

sFind = "ABC Division"


sReplace = "XYZ subidiary"

For Each wks In ActiveWorkbook.Worksheets


For Each cmt In wks.Comments
sCmt = cmt.Text
If InStr(sCmt, sFind) <> 0 Then
sCmt = Application.WorksheetFunction. _
Substitute(sCmt, sFind, sReplace)
cmt.Text Text:=sCmt
End If
Next
Next
Set wks = Nothing
Set cmt = Nothing
End Sub

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.

Placing Formula Answers in a Comment


Bob asked if it is possible to write a formula and get the answer in a comment, instead of in a
cell. The short answer is that no, you can’t do it with a formula. You can, however, do it with a
macro. For instance, the following macro adds the contents of two cells (A1 and B1) and then
sticks the result in a comment attached to cell C1:

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

ExcelTips: The Macros Page 551


Working with Comments

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:

Private Sub Worksheet_Change(ByVal Target As Range)


Dim sResult As String

If Union(Target, Range("C11")).Address = Target.Address Then


Application.EnableEvents = False
Application.ScreenUpdating = False
sResult = Target.Value
Target.ClearContents

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.

Private Sub Worksheet_Change(ByVal Target As Range)


If Target.Cells.Count > 1 Then Exit Sub
If Target.Column <> 6 Then Exit Sub

Dim x As String
Application.EnableEvents = False
If Target.HasFormula Then
x = Evaluate(Target.Formula)

ExcelTips: The Macros Page 552


Working with Comments

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.

Counting Comments in a Worksheet


Sanjib would like to get a count of all the comments in a worksheet. Unfortunately, Excel
doesn’t include a function that allows you to access this information. You can, however, get the
value manually by using this process:

1. Press F5. Excel displays the Go To dialog box.


2. Click Special. Excel displays the Go To Special dialog box.

The Go To Special dialog box.

ExcelTips: The Macros Page 553


Working with Comments

3. Make sure the Comments radio button is selected.


4. Click OK. Excel selects all the cells that contain comments.
5. Right-click the status bar and choose Count. The status bar now displays the number of
selected cells—the number of comments in the worksheet.

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.

Function CountComments(rCell As Range)


Application.Volatile
CountComments = rCell.Parent.Comments.Count
End 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.

ExcelTips: The Macros Page 554


Working with Headers and Footers

Working with Headers and Footers

Dynamic Headers and Footers


If you have a large worksheet, you may want to print it out in “parts” and automatically vary the
information contained in the header or footer of each part. There is no intrinsic way to do this in
Excel; the best approach is a macro to do the following:

1. Set the print area based on a named range.


2. Set the header or footer based on another named range.
3. Print the print area.
4. Repeat steps 1 through 3 for each desired print area.

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

'Change the dimension of the arrays to equal the number


' of printing areas you have
Dim Region(4) As String
Dim Head(4) As String

'Fill this array with the names of the ranges to be printed


Region(1) = "North"
Region(2) = "South"
Region(3) = "East"
Region(4) = "West"

'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

ExcelTips: The Macros Page 555


Working with Headers and Footers

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.

Full Path Names in Headers or Footers


Excel allows you to insert many different items in the header or footer of your spreadsheet (as
you can see in other ExcelTips). Unfortunately, one of the items you cannot easily add is the full
path name of your spreadsheet file. You can, however, add the path name to the header or footer
by using a macro, such as the following one:

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:

1. Select the worksheet whose header or footer you want to change.


2. Choose Page Setup from the File menu. Excel displays the Page Setup dialog box. (To
display the Page Setup dialog box in Excel 2007 or later versions, display the Page

ExcelTips: The Macros Page 556


Working with Headers and Footers

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.

The Header 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.

ExcelTips: The Macros Page 557


Working with Headers and Footers

Adding a File Path and Filename


Margo wants to insert a file path and filename in an Excel worksheet. She wants to be able to
insert the information in either a cell or into the header/footer. This is rather easy to do in Excel.

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.

Putting a path and filename into a header or footer is easy:

1. Select the worksheet whose header or footer you want to change.


2. Display the Page Setup dialog box. (In Excel 2007 or later versions display the Page
Layout tab of the ribbon and click the small icon at the bottom-right of the Page Setup
group. In earlier versions of Excel choose File | Page Setup.)
3. Click on either the Custom Header or Custom Footer buttons, as desired. Excel displays
either the Header or Footer dialog box.

ExcelTips: The Macros Page 558


Working with Headers and Footers

The Header 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]

6. Click on OK two times to close both dialog boxes.

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.

ExcelTips: The Macros Page 559


Working with Headers and Footers

Extracting File Names from a Path


Barry has a worksheet in which a column contains many file paths. He would like a way to
extract just the filename (the part to the right of the final backslash) from each path. He wonders
if there is a quick way to do this without using Text to Columns feature.

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 = "\"

ExcelTips: The Macros Page 560


Working with Headers and Footers

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.

Function GetFileName2(File_Path) As String


Dim Parts

Parts = Split(File_Path, Application.PathSeparator)


GetFileName2 = Parts(UBound(Parts))
End 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)

First and Last Names in a Page Header


David is administering an election for a professional society and the roster of eligible voters is a
worksheet. There are approximately 1,200 eligible voters, so the printout is over twenty pages
long. David has a footer with the page number—which is helpful—but it would be great if he
could have, in the header of each page, the first name on the page and the last name on the page.

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

ExcelTips: The Macros Page 561


Working with Headers and Footers

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

For iPage = 1 To iPages


iHP = ((iPage - 1) Mod iHorPgs)
iHPNext = iHP + 1
If iHP = 0 Then
If sPrtArea = "" Then
lRow = 1
Else
lRow = .Range(sPrtArea).Cells(1).Row
End If
Else
lRow = .HPageBreaks(iHP).Location.Row
End If
If iHPNext > .HPageBreaks.Count Then
lRowLast = .Cells(lRow, iColLast).End(xlDown).Row
Else
lRowLast = .HPageBreaks(iHPNext).Location.Row - 1
End If
.PageSetup.LeftHeader = .Cells(lRow, iCol).Value & _
" - " & .Cells(lRowLast, iColLast)
.PrintOut From:=iPage, To:=iPage, preview:=True
Next
End With
End Sub

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.

Multiple Line Headers and Footers


If you use a macro code to set your header or footer, you can designate more than one line for
them. Thus, you could have a two- or three-line header or footer, if desired. You do this by

ExcelTips: The Macros Page 562


Working with Headers and Footers

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:

ActiveSheet.PageSetup.CenterHeader = "&C&BAmalgamated Widgets, Inc.&B" & Chr(13)


& "&D"

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:

ActiveSheet.PageSetup.LeftHeader = "&C&BAmalgamated Widgets, Inc.&B" & Chr(13) &


"&D"

Putting a Different Date in a Header


Adding the current date to the header of a worksheet is easy—Excel provides a dialog box where
you can specify the placement of the date and use the ‘&[date]’ coding to actually insert the date.
But what if you want to insert yesterday’s date or tomorrow’s date into the header?

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:

Private Sub Workbook_BeforePrint(Cancel As Boolean)


ActiveSheet.PageSetup.CenterHeader = _
Format(Date - 1, "mmmm d, yyyy")
End Sub

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”.

Inserting the Saved Date in a Header or Footer


There may be times when you want your header or footer to contain the date of the last time that
your workbook was saved. Normally, this is not information you can set in Excel. However, you
can use the following macro to force the information into the proper place.

ExcelTips: The Macros Page 563


Working with Headers and Footers

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.

Last Saved Date in a Footer


Lori wants the right side of the footer for her worksheet to include the date the workbook was
last saved. Every time she tries to create a formula to do this, Excel displays an error message
that states the "string is too long" and that she needs to delete some characters. She's not sure she
understands why this is happening or how she can get the date she wants in the footer.

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:

Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, _


Cancel As Boolean)

ExcelTips: The Macros Page 564


Working with Headers and Footers

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:

Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, _


Cancel As Boolean)
Dim sht As Worksheet

For Each sht In Sheets


sht.PageSetup.RightFooter = _
"Last Saved: " & Format(Date, "mmmm d, yyyy")
Next
End Sub

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.

Private Sub Workbook_Open()


Dim sTemp As String
Dim sht As Worksheet

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

ExcelTips: The Macros Page 565


Working with Headers and Footers

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.

Specifying Date Formats in Headers


Normally, the dates used by Excel in headers and footers (with the [DATE] code) are based on
the regional settings controlled by Windows. Thus, if your local settings show the date in a
specific format in Windows itself, that is the same format that Excel will use in headers and
footers.

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.

ExcelTips: The Macros Page 566


Working with Headers and Footers

Selective Headers and Footers


One of the long-time complaints about Excel is that it doesn't have a very robust method of
creating and managing headers and footers. Consider the following scenario: You want to print
your worksheet, but only have page numbers beginning on the second page.

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

'save current header


hlft = ActiveSheet.PageSetup.LeftHeader
hctr = ActiveSheet.PageSetup.CenterHeader
hrgt = ActiveSheet.PageSetup.RightHeader

'save current footer


flft = ActiveSheet.PageSetup.LeftFooter
fctr = ActiveSheet.PageSetup.CenterFooter
frgt = ActiveSheet.PageSetup.RightFooter

'remove header and footer


With ActiveSheet.PageSetup
.CenterHeader = ""
.RightHeader = ""
.LeftHeader = ""
.CenterFooter = ""
.RightFooter = ""
.LeftFooter = ""
End With

ExcelTips: The Macros Page 567


Working with Headers and Footers

'print page one


ActiveSheet.PrintOut 1, 1

'restore header and footer


With ActiveSheet.PageSetup
.LeftHeader = hlft
.CenterHeader = hctr
.RightHeader = hrgt
.LeftFooter = flft
.CenterFooter = fctr
.RightFooter = frgt
End With

'print the rest of the pages


ActiveSheet.PrintOut 2
End Sub

Switching Headers in a Frozen Row


Tim’s got some worksheets that have two separate sections (let's call them upper and lower
sections) that have slightly different column headers. He’s frozen the top row to keep the upper
section headers visible while scrolling down, but after scrolling past a certain point Tim ends up
looking at lower-section data with upper-section headers still at the top. He'd like to know if
there is a way to switch that frozen header row to show the lower section headers when he scrolls
down to the point that only lower-section data is showing.

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.

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


Dim iBottomData As Integer

iBottomData = 40

If ActiveCell.Row < iBottomData Then


Cells(1, 1).Value = "Last Name"
Cells(1, 2).Value = "First Name"
Cells(1, 3).Value = "Address"
Cells(1, 4).Value = "Balance"
Else
Cells(1, 1).Value = "Account"
Cells(1, 2).Value = "Sales Rep"
Cells(1, 3).Value = "Status"
Cells(1, 4).Value = ""

ExcelTips: The Macros Page 568


Working with Headers and Footers

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.

Private Sub Worksheet_BeforeRightClick(ByVal _


Target As Range, Cancel As Boolean)
Application.ScreenUpdating = False
ActiveWindow.FreezePanes = False
ActiveWindow.Split = False
Application.EnableEvents = False
Application.Goto Cells(1, 1), scroll:=True
With ActiveWindow
.SplitColumn = 0
.SplitRow = 1
End With
ActiveWindow.FreezePanes = True
Application.Goto Cells(Target.Row - 1, _
Target.Column), scroll:=True
Application.EnableEvents = True
On Error Resume Next 'MUST reenable events
Application.EnableEvents = False
ActiveCell.Offset(-1, 1 - Target.Column).Select
' so the right click menu doesn't popup
' only if this is the second header row.
Cancel = True
Application.EnableEvents = True
End Sub

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


Application.ScreenUpdating = False
If Cells(Target.Row, 1).Value <> "title2" Then Exit Sub
ActiveWindow.FreezePanes = False
ActiveWindow.Split = False
Application.Goto Cells(Target.Row, 1), scroll:=True
With ActiveWindow
.SplitColumn = 0
.SplitRow = 1
End With
ActiveWindow.FreezePanes = True
On Error Resume Next 'MUST reenable events
Application.EnableEvents = False
ActiveCell.Offset(1, 1 - Target.Column).Select
Application.EnableEvents = True
End Sub

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.)

ExcelTips: The Macros Page 569


Working with Headers and Footers

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

Putting Cell Contents in Footers


You may find it helpful to sometime place the contents of a cell into the footer of a worksheet,
and to have the footer updated every time the contents of the cell changed. The easiest way to do
this is with a macro. The following is an example of a macro that will place the contents of cell
A1 into the left side of the footer:

Private Sub Worksheet_SelectionChange(ByVal Target As Excel.Range)


ActiveSheet.PageSetup.LeftFooter = Range("A1").Text
End Sub

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.

ExcelTips: The Macros Page 570


Working with Headers and Footers

Copying Headers and Footers


Excel allows you to specify headers and footers for your worksheets. You may want to copy
these headers and footers from one worksheet to another. Doing so within a workbook is
relatively easy, but doing so from one workbook to another can be more daunting.

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.

ExcelTips: The Macros Page 571


Working with Headers and Footers

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.

ExcelTips: The Macros Page 572


Working with Headers and Footers

The Header/Footer tab of the Page Setup dialog box.

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

Dim strHeadLeft As String


Dim strHeadCenter As String

ExcelTips: The Macros Page 573


Working with Headers and Footers

Dim strHeadRight As String


Dim strFootLeft As String
Dim strFootCenter As String
Dim strFootRight As String
Dim bGotHeaders As Boolean

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

ExcelTips: The Macros Page 574


Working with Headers and Footers

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.

Header and Footer Background Color


Leonid asked if there was a way to set a background color for the header or footer of a page. The
simple answer is that there isn’t such a capability in Excel. There are a couple of ways around
the issue, however. For instance, if you are using Excel 2002 (or a later version) you can add a
graphic to a header or footer. With the right graphic, you can make it appear that the header and
footer contain color when, in fact, it is the graphic that contains the color.

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

LHeader = "Top Left"


CHeader = "Top Center"
LFooter = "Bottom Left"
CFooter = "Bottom Center"
PageSize = 46

ExcelTips: The Macros Page 575


Working with Headers and Footers

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

Cells(CRow, 1).Value = LHeader


Cells(CRow, 4).Value = CHeader
Range(Cells(CRow, 1), _
Cells(CRow, 8)).Interior.ColorIndex = 34
Range(Cells(CRow + 1, 1), _
Cells(CRow + 1, 8)).Interior.ColorIndex = xlNone
CRow = CRow + 2
ElseIf CRow Mod PageSize = PageSize - 1 Then
Rows(CRow).Select
Selection.Insert Shift:=xlDown
Selection.Insert Shift:=xlDown
CBottom = CBottom + 2

Cells(CRow + 1, 1).Value = LFooter


Cells(CRow + 1, 4).Value = CFooter
Range(Cells(CRow + 1, 1), _
Cells(CRow + 1, 8)).Interior.ColorIndex = 34
CRow = CRow + 2
Else
CRow = CRow + 1
End If
Loop

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

ExcelTips: The Macros Page 576


Working with Headers and Footers

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.

Using a Different Footer on Secondary Pages


When creating a worksheet that will eventually be printed, you may want to use a different footer
on the first page of your document than you do on the subsequent pages. There is no way to do
this directly in Excel. There are a couple of workarounds you could use, however.

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

' Define first-page footers


sP1Left = "First page left"

ExcelTips: The Macros Page 577


Working with Headers and Footers

sP1Center = "First page center"


sP1Right = "First page right"

' Define second-page footers


sP2Left = "Second page left"
sP2Center = "Second page center"
sP2Right = "Second page right"

' Set up and print first page


With ActiveSheet.PageSetup
.LeftFooter = sP1Left
.CenterFooter = sP1Center
.RightFooter = sP1Right
End With
ActiveSheet.PrintOut 1, 1

' Set up and print other pages


With ActiveSheet.PageSetup
.LeftFooter = sP2Left
.CenterFooter = sP2Center
.RightFooter = sP2Right
End With
ActiveSheet.PrintOut 2
End Sub

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.

Leading Zeros in Page Numbers


Jackie asked if there was a way to include leading zeroes in the page numbers placed in a footer.
She needs her page numbers to always be five characters long, with leading zeroes, as necessary,
to fill out the length. Thus, page numbers would be 00001, 00002, etc.

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

ExcelTips: The Macros Page 578


Working with Headers and Footers

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.

Changing Page Number Format


When you print a worksheet, you can have Excel include a variety of items in the header or
footer of the printout. One of the things you can include is the page number of the page being
printed. This page number is pretty mundane—it is the Arabic value of the page being printed, as
in 1, 2, 3, etc.

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

' Fill page letter array


' "A", "B", "C", ...,"AA", "AB", etc.
For J = 0 To 26
For K = 1 To 26
If J > 0 Then
sArr((J * 26) + K) = Chr(J + 64) & Chr(K + 64)
Else
sArr(K) = Chr(K + 64)
End If
Next K
Next J

' Get count of pages in active sheet


iPages = ExecuteExcel4Macro("Get.Document(50)")

' Print worksheet, page by page


With ActiveSheet
For J = 1 To iPages
' Set page letter
.PageSetup.CenterFooter = sArr(J)
' Print page(J)
.PrintOut From:=J, To:=J
Next J
End With
End Sub

ExcelTips: The Macros Page 579


Working with Headers and Footers

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.)

Roman Numerals for Page Numbers


Excel includes a worksheet function (ROMAN) that allows you to convert Arabic numbers into
Roman numerals. You may wonder if there is any such function that allows you to print Roman
numerals as page numbers on a printout.

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

' Get count of pages in active sheet


iPages = ExecuteExcel4Macro("Get.Document(50)")

' Print worksheet, page by page


With ActiveSheet
For J = 1 To iPages
' Set page letter
.PageSetup.CenterFooter = _
Application.WorksheetFunction.Roman(J)
' Print page J
.PrintOut From:=J, To:=J
Next J
End With
End Sub

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.

ExcelTips: The Macros Page 580


Working with Headers and Footers

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)

Changing Section Headers


When working with large worksheets, it is not unusual to add subtotals so that you can group
information in the worksheet in some logical manner. (The Subtotal tool is on the Data tab of the
ribbon or, in versions of Excel before Excel 2007, available from the Data menu.) When adding
subtotals, you can specify that Excel start each group on a brand new page. This is very handy
for all types of reporting in Excel.

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)

If iCopies = "" Then Exit Sub

Set cFirst = Range("A1") ' initialization start cell


For r = 2 To rowLast ' from first row to last row
If ActiveSheet.Rows(r).PageBreak = xlPageBreakManual Then
Set cLast = Cells(r - 1, colLast)
Set rngSection = Range(cFirst, cLast)

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

ExcelTips: The Macros Page 581


Working with Headers and Footers

rngSection.PrintOut _
Copies:=iCopies, Collate:=True

Set cFirst = Cells(r, 1)


End If
Next r

' Last Section ++++++++++++++++++++++++++++


Set rngSection = Range(cFirst, c)

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.

Find and Replace in Headers


One of the very useful tools provided in Excel is Find and Replace, which allows you to locate
and change information stored in cells. One place that Find and Replace won't work, however, is
with information stored in headers or footers for your worksheets.

ExcelTips: The Macros Page 582


Working with Headers and Footers

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"

sWhat = InputBox("Replace what", csTITLE)


If Len(sWhat) = 0 Then Exit Sub
sReplacment = InputBox("With what", csTITLE)

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.

ExcelTips: The Macros Page 583


Working with Graphics and Charts

Working with Graphics and Charts

Assigning Macros to Graphics


You already know that Excel allows you to add graphics to your worksheets, such as pictures.
You may not know, however, that you can assign macros to these graphics. With a macro
associated with a graphic, the macro is executed whenever someone clicks on the graphic.

To assign a macro to a graphic, follow these steps:

1. Right-click on the graphic. Excel displays a Context menu.


2. Select the Assign Macro option from the Context menu. Excel displays the Assign
Macro dialog box.

The Assign Macro dialog box.

3. Select the macro you want associated with the graphic.


4. Click on OK.

ExcelTips: The Macros Page 584


Working with Graphics and Charts

Save your workbook and test out your assignment by clicking on the graphic. The macro should
run, as desired.

Pasting a Graphic to Multiple Worksheets


Marty has a series of workbooks, some with as many as 50 worksheets. He needs to paste a
graphic (a company logo) into the same spot on each worksheet. He tried to do this by selecting
all the worksheets and then doing the pasting, but that didn't seem to work on multiple
worksheets like regular editing does.

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"

For Each shtSheet In Worksheets


shtSheet.Activate
Range("A1").Select
ActiveSheet.Pictures.Insert (strPath)
Next shtSheet
Set shtSheet = Nothing
End Sub

ExcelTips: The Macros Page 585


Working with Graphics and Charts

You can, of course, modify the path to the graphic file and the cell at which the file is pasted into
the worksheets.

Positioning a Graphic in a Macro


Felix is writing a macro to add a graphic to a worksheet. He needs to position the graphic relative
to the top-left corner of a particular cell. He wonders how he can place the graphic, within the
macro code, so it is just to the right and beneath the upper-left corner of a given cell.

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:

Dim rCell As Range


Set rCell = Range("A2")
With ActiveSheet.Shapes("Picture 1")
.Top = rCell.Top + 5
.Left = rCell.Left + 3
End With

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.

Copying Pictures with a Macro


Lowell developed a macro to copy select cells’ data to a specific location on another worksheet.
Some of the source cells contain pictures, and he would like those pictures copied, as well.
Lowell wonders how he can get the macro to recognize if a picture is at the source cell and then
copy the pictures to the new worksheet along with the data.

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.

ExcelTips: The Macros Page 586


Working with Graphics and Charts

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.

Pop-Up Comments for Graphics


Shane knows how to add comments to cells so that when you hover the mouse over the cell you
can see the comment. He would like to do the same thing with graphics—have a comment or
pop-up box appear when a person hovers the mouse over a graphic placed in a worksheet. While
Shane could adjust cell size to match the graphic and then attach the comment to the cell, the size
of the graphics he is using really don’t make that practical. He wonders if there is a way to have
pop-up comments appear when someone moves the mouse over a graphic in a worksheet.

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:

1. Insert the graphic in your worksheet and size as desired.


2. Select the graphic (click on it once).
3. Press CTRL+K. Excel displays the Insert Hyperlink dialog box.

The Insert Hyperlink dialog box.

4. Click the Place In This Document button.

ExcelTips: The Macros Page 587


Working with Graphics and Charts

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 Set Hyperlink ScreenTip dialog box.

7. Enter the text you want displayed.


8. Click on OK to dismiss the Hyperlink ScreenTip dialog box.
9. Click on the OK button to dismiss the Insert Hyperlink 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.

ScreenTip for an Image


Eddie has added a small graphic image to a worksheet and tied a macro to the image. When the
image is clicked, the macro is executed. Eddie wonders if it is possible to add a label or comment
to the image so that when a user hovers the mouse pointer over the image, the label/comment
appears and tells the user what the macro does.

ExcelTips: The Macros Page 588


Working with Graphics and Charts

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:

Private Sub CommandButton1_Click()


'Call your regular macro here
Hide_Shape
End Sub

Private Sub CommandButton1_MouseMove( _


ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
Display_and_Hide_Shape
End Sub

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

ExcelTips: The Macros Page 589


Working with Graphics and Charts

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.

Sizing Text Boxes and Cells the Same


You already know that Excel allows you to create text boxes within your worksheets. You may
have a need, at some point, to create a text box that is exactly the same size as a particular cell. If
you only have one or two such text boxes to create, the easiest way is to follow these steps if you
are using Excel 2007 or a later version:

1. Display the Insert tab of the ribbon.


2. Click on the Text Box tool.
3. Hold down the ALT key as you click and drag to create your text box.

If you are using an older version of Excel, follow these steps:

1. Display the Drawing toolbar.


2. Click on the Text Box tool.
3. Hold down the ALT key as you click and drag to create your text box.

ExcelTips: The Macros Page 590


Working with Graphics and Charts

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.

Resizing a Text Box in a Macro


Rob has a text box, in a worksheet, that contains text copied from Word. He wants to know how
he can resize the text box using a macro, so that it covers a specific range of cells.

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

ExcelTips: The Macros Page 591


Working with Graphics and Charts

Dim sBR As String


Dim rng As Range

' Change top-left and bottom-right addresses as desired


sTL = "A1"
sBR = "M40"

' Ensure a text box is selected


If TypeName(Selection) <> "TextBox" Then
MsgBox "Text box not selected"
Exit Sub
End If

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

' Ensure a text box is selected


If TypeName(Selection) <> "TextBox" Then
MsgBox "Text box not selected"
Exit Sub
End If

' Get the range to cover


Set l_rRangeToCover = _
ActiveSheet.Range(Names("RangeToCover").RefersToRange.Value)

' Get its lower right cell


Set l_rLowerRight = _
l_rRangeToCover.Cells( _
l_rRangeToCover.Rows.Count, _
l_rRangeToCover.Columns.Count)

' Resize the text box


With Selection
.Left = l_rRangeToCover.Left
.Top = l_rRangeToCover.Top
.Width = l_rLowerRight.Left + l_rLowerRight.Width - .Left
.Height = l_rLowerRight.Top + l_rLowerRight.Height - .Top
End With
End Sub

ExcelTips: The Macros Page 592


Working with Graphics and Charts

Finding Text in Text Boxes


Walter has a worksheet that has a number of text boxes in it. He would like to search through
those text boxes to find some specific text, but Find and Replace doesn’t seem capable of finding
text in text boxes. He wonders if there is a way to search through text boxes.

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

sFind = InputBox("Search for?")


If Trim(sFind) = "" Then
MsgBox "Nothing entered"
Exit Sub
End If
Set rStart = ActiveCell
For Each shp In ActiveSheet.Shapes
sTemp = shp.TextFrame2.TextRange.Characters.Text
If InStr(LCase(sTemp), LCase(sFind)) <> 0 Then
shp.Select
Response = MsgBox( _
prompt:=shp.Name & vbCrLf & _
sTemp & vbCrLf & vbCrLf & _
"Do you want to continue?", _
Buttons:=vbYesNo, Title:="Continue?")
If Response <> vbYes Then
Set rStart = Nothing
Exit Sub
End If
End If
Next
MsgBox "No more found"
rStart.Select
Set rStart = Nothing
End Sub

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()

ExcelTips: The Macros Page 593


Working with Graphics and Charts

Dim rStart As Range


Dim shp As Shape
Dim sFind As String
Dim sTemp As String
Dim Response

sFind = InputBox("Search for?")


If Trim(sFind) = "" Then
MsgBox "Nothing entered"
Exit Sub
End If
Set rStart = ActiveCell
For Each shp In ActiveSheet.Shapes
sTemp = shp.TextFrame.Characters.Text
If InStr(LCase(sTemp), LCase(sFind)) <> 0 Then
shp.Select
Response = MsgBox( _
prompt:=shp.Name & vbCrLf & _
sTemp & vbCrLf & vbCrLf & _
"Do you want to continue?", _
Buttons:=vbYesNo, Title:="Continue?")
If Response <> vbYes Then
Set rStart = Nothing
Exit Sub
End If
End If
Next
MsgBox "No more found"
rStart.Select
Set rStart = Nothing
End Sub

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

sFind = InputBox("Search for?")


If Trim(sFind) = "" Then
MsgBox "Nothing entered"
Exit Sub
End If
sFind = LCase(sFind)
For Each shp In ActiveSheet.Shapes
sTemp = LCase(shp.TextFrame2.TextRange.Characters.Text)
iPos = InStr(sTemp, sFind)
If iPos > 0 Then
With shp.TextFrame2.TextRange.Characters(Start:=iPos, _

ExcelTips: The Macros Page 594


Working with Graphics and Charts

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

For Each shp In ActiveSheet.Shapes


With shp.TextFrame2.TextRange.Characters.Font
.UnderlineStyle = msoNoUnderline
.Bold = False
End With
Next
End Sub

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

sFind = InputBox("Search for?")


If Trim(sFind) = "" Then
MsgBox "Nothing entered"
Exit Sub
End If
sFind = LCase(sFind)
For Each shp In ActiveSheet.Shapes
sTemp = LCase(shp.TextFrame.Characters.Text)
iPos = InStr(sTemp, sFind)
If iPos > 0 Then
With shp.TextFrame.Characters(Start:=iPos, _
Length:=Len(sFind)).Font
.ColorIndex = 3
.Bold = True
End With
End If
Next
MsgBox "Finished"
End Sub

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()

ExcelTips: The Macros Page 595


Working with Graphics and Charts

Dim shp As Shape

For Each shp In ActiveSheet.Shapes


With shp.TextFrame.Characters.Font
.ColorIndex = 0
.Bold = False
End With
Next
End Sub

Placing Textbox Text Into a Worksheet


Excel allows you to place all sorts of graphic objects on your worksheet. One type of graphic
object actually contains text—a textbox. If you have quite a few textboxes in a worksheet, you
may be wondering if there is a way to extract the text from each textbox and place it in the
worksheet itself.

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

For Each shp In ActiveSheet.Shapes


With shp
If Left(.Name, 8) = "Text Box" Then
sLoc = .TopLeftCell.Address
Do Until Range(sLoc) = ""
sLoc = Range(sLoc).Offset(1, 0).Address
Loop
Range(sLoc) =.TextFrame.Characters.Text
.Delete
End If
End With
Next
End Sub

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:

If shp.Type = msoTextBox Then

ExcelTips: The Macros Page 596


Working with Graphics and Charts

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()

ExcelTips: The Macros Page 597


Working with Graphics and Charts

ThisWorkbook.Sheets(1).Shapes("FlowObj1").Copy
ActiveSheet.Paste
End Sub

9. Assign each of the macros to the corresponding toolbar button.


10. In the Workbook module of the template, add the following procedures:

Private Sub Workbook_Open()


Application.CommandBars("MyShapes").Visible = True
End Sub

Private Sub Workbook_BeforeClose(Cancel As Boolean)


Application.CommandBars("MyShapes").Delete
End Sub

11. Save the template as an Excel add-in.


12. Restart Excel and active your new add-in.

Shifting Objects Off a Sheet


Mudit ran into problems when trying to delete rows and columns in a worksheet. He sometimes
gets the message “Cannot shift object off this sheet.” Other people report getting the message
when they try to insert rows or column.

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()

ExcelTips: The Macros Page 598


Working with Graphics and Charts

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

Removing Pictures for a Worksheet in VBA


Rob wrote about a problem he was having removing pictures from a worksheet. He has macros
that add a picture (a signature) as a shape, but when he later tries to delete the picture, he cannot
find it in the Shapes collection.

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

sTemp = "You selected this type of object: " & TypeName(Selection)


sTemp = sTemp & vbCrLf
sTemp = sTemp & "It's name is " & Selection.Name
MsgBox sTemp
End Sub

ExcelTips: The Macros Page 599


Working with Graphics and Charts

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.

Snapshots of Excel Worksheets for PowerPoint


Rebecca Birch has some clients who require that all reports be presented to them in PowerPoint.
This presents a huge task, since all of the source data for those reports is available only in Excel.
Rebecca was looking about for ideas to make the burden of converting from one to the other just
a bit easier—perhaps by taking “snapshots” of worksheet data and placing it into PowerPoint
slides.

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

ExcelTips: The Macros Page 600


Working with Graphics and Charts

Dim sTargetHeight As Single


Dim sScaleHeight As Single
Dim sScaleWidth As Single
Dim iIndex As Integer

'Change these as desired


sTargetTop = 30
sTargetLeft = 60
sTargetWidth = 600
sTargetHeight = 450
sTemplatePPt = "C:\Program Files\Microsoft Office\Templates\Blank
Presentation.pot"

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.

Creating Charts in VBA


Excel is very handy at creating charts from data in a worksheet. What if you want to create a
chart directly from VBA, without using any data in a worksheet? You can do this by “fooling”

ExcelTips: The Macros Page 601


Working with Graphics and Charts

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

'Set the dummy data range for the chart


ActiveChart.SetSourceData Sheets("Sheet1").Range("a1:d4"), _
PlotBy:=xlColumns

'Manually set the values for the data series


ActiveChart.SeriesCollection(1).Formula = _
"=SERIES(""First Data"",{""a"",""b"",""c"",""d""},{2,3,4,5},1)"
ActiveChart.SeriesCollection(2).Formula = _
"=SERIES(""Second Data"",{""a"",""b"",""c"",""d""},{6,7,8,9},2)"
ActiveChart.SeriesCollection(3).Formula = _
"=SERIES(""Third Data"",{""a"",""b"",""c"",""d""},{10,11,12,13},3)"
End Sub

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.

Automatically Creating Charts for Individual Rows


in a Data Table
David has a worksheet that he uses to track sales by company over a number of months. The
company names are in column A and up to fifteen months of sales are in columns B:P. David
would like to create a chart that could be dynamically changed to show the sales for a single
company from the worksheet.

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 |

ExcelTips: The Macros Page 602


Working with Graphics and Charts

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:

1. Create a new worksheet and name it something like “ChartData”.


2. Copy the column headers from the MyData worksheet to the second row on the
ChartData sheet. (In other words, copy MyData!A1:P1 to ChartData!A2:P2. This leaves
the first row of the ChartData sheet temporarily empty.)
3. With the MyData worksheet visible, display the Developer tab of the ribbon.
4. Using the Insert tool in the Controls group, draw a Combo Box control somewhere on
the MyData worksheet. (Make sure you use the Form Controls combo box, not the
ActiveX Controls combo box.)
5. Display the Format Control dialog box for the newly created Combo Box. (Right-click
the Combo Box and choose Format Control.)
6. Using the controls in the dialog box, specify the Input Range as
MyData!$A$2:$A$151, specify the Cell Link as ChartData!$A$1, and specify the
Drop Down Lines as 25 (or whatever figure you want).

ExcelTips: The Macros Page 603


Working with Graphics and Charts

The Format Control dialog box.

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

ExcelTips: The Macros Page 604


Working with Graphics and Charts

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:

1. Create a new worksheet and name it something like “ChartData”.


2. Copy the column headers from the MyData worksheet to the second row on the
ChartData sheet. (In other words, copy MyData!A1:P1 to ChartData!A2:P2. This leaves
the first row of the ChartData sheet temporarily empty.)
3. With the MyData worksheet displayed, choose View | Toolbars | Forms. The Forms
toolbar should be displayed.
4. Using the Forms toolbar, draw a Combo Box control somewhere on the MyData
worksheet.
5. Perform steps 5 through 13, as listed earlier.

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.)

ExcelTips: The Macros Page 605


Working with Graphics and Charts

Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Excel.Range, Cancel As


Boolean)
ActiveWorkbook.Names.Add Name:="ChartXRange", _
RefersToR1C1:="=OFFSET(MyData!R1C1," & _
ActiveCell.Row - 1 & ",1,1,15)"
ActiveWorkbook.Names.Add Name:="ChartTitle", _
RefersToR1C1:="=OFFSET(MyData!R1C1," & _
ActiveCell.Row - 1 & ",0,1,1)"
Sheets("ChartSheet").Activate
Cancel = True
End Sub

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"

'Unprotect all Chart sheets


For Each cht In ActiveWorkbook.Charts
Sheets(cht.Name).Unprotect password:=PW
Next

'Unlock all Chart objects on each worksheet


For Each wks In ActiveWorkbook.Worksheets
wks.Unprotect password:=PW
For Each chtObj In wks.ChartObjects
wks.DrawingObjects(chtObj.Name).Locked = False
Next
wks.Protect password:=PW
Next
End Sub

ExcelTips: The Macros Page 606


Working with Graphics and Charts

Positive and Negative Colors in a Chart


Merril asked if there was a way to create a line chart so that when a line represented a negative
value, the color of the line would change at the point when it went negative. For instance, in a
particular data series, as long as the line represented positive values, it would be blue, but when
the line represented negative values, it would change to red.

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

Set MyChart = ActiveSheet.ChartObjects(1).Chart


Set chtSeries = MyChart.SeriesCollection(SeriesNum)
Set R = GetChartRange(MyChart, 1, "Values")

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)

If LastPtColor = CurrPtColor Then


LineColor = LastPtColor
Else
If Abs(R.Cells(i - 1).Value) > Abs(R.Cells(i).Value) Then
LineColor = LastPtColor
Else
LineColor = CurrPtColor
End If
End If
chtSeries.Points(i).Border.ColorIndex = LineColor
Next i
End Sub

Function GetChartRange(Ch As Chart, Ser As Integer, _


ValXorY As String) As Range
Dim SeriesFormula As String
Dim ListSep As String * 1
Dim Pos As Integer
Dim LSeps() As Integer
Dim Txt As String
Dim i As Integer

Set GetChartRange = Nothing

ExcelTips: The Macros Page 607


Working with Graphics and Charts

On Error Resume Next


SeriesFormula = Ch.SeriesCollection(Ser).Formula
ListSep = ","
For i = 1 To Len(SeriesFormula)
If Mid$(SeriesFormula, i, 1) = ListSep Then
Pos = Pos + 1
ReDim Preserve LSeps(Pos)
LSeps(Pos) = i
End If
Next i

If UCase(ValXorY) = "XVALUES" Then


Txt = Mid$(SeriesFormula, LSeps(1) + 1, LSeps(2) - LSeps(1) - 1)

Set GetChartRange = Range(Txt)


End If

If UCase(ValXorY) = "VALUES" Then


Txt = Mid$(SeriesFormula, LSeps(2) + 1, LSeps(3) - LSeps(2) - 1)

Set GetChartRange = Range(Txt)


End If
End Function

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

Labeling X-Y Scatter Plots


Martin has a worksheet containing 50 rows of data, each row describing a single object. Column
A contains the name of the object, column B contains its X coordinate, and column C contains its
Y coordinate. When he creates an X-Y scatter chart (column B against column C) the result, as
desired, is a graph showing an array of points showing the location of the objects. However,
Martin can't seem to label the data points with their individual names (from column A). When he
tries to label the data points the only available options are to label each point with its X value, Y
value, or Series Name. Martin wonders if there is a way he can easily use Column A to label the
plotted data points.

ExcelTips: The Macros Page 608


Working with Graphics and Charts

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

Set Cht = ActiveSheet.ChartObjects(1).Chart


On Error Resume Next
Cht.SeriesCollection(1).ApplyDataLabels _
Type:=xlDataLabelsShowValue, _
AutoText:=True, _
LegendKey:=False

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

ExcelTips: The Macros Page 609


Working with Graphics and Charts

Exporting Black and White Charts


Excel allows you to create charts in full color. When you get ready to print the chart, you can
instruct Excel to print it in “black and white” (via the settings in the Chart tab of the Page Setup
dialog box). You may wonder if there is a way to export this “black and white” version of the
chart, so that you can work with it in a different program.

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

'Set colors for BW series


iColors(1, 0) = 1 'Black
iColors(2, 0) = 56 'Gray-80%
iColors(3, 0) = 16 'Gray-50%
iColors(4, 0) = 48 'Gray-40%
iColors(5, 0) = 15 'Gray-25%

'Set colors for Color series


iColors(1, 1) = 55 'Indigo
iColors(2, 1) = 7 'pink

ExcelTips: The Macros Page 610


Working with Graphics and Charts

iColors(3, 1) = 6 'yellow
iColors(4, 1) = 8 'Turquoise
iColors(5, 1) = 13 'Violet

'Toggle Color/BW change 0 to 1 or 1 to 0


bColored = 1 - bColored

Set cht = ActiveChart

'check that a chart is selected


If cht Is Nothing Then
MsgBox ("Select a chart")
Exit Sub
End If

Set chtSC = cht.SeriesCollection

'Check for MIN of number of series or


'colors and only do the minimum
iSeriesCount = Application.WorksheetFunction.Min _
(UBound(iColors), chtSC.Count)

For x = 1 To iSeriesCount
'Define the color
iColor = iColors(x, bColored)

'Set the LINE color


chtSC(x).Border.ColorIndex = iColor

'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.

Specifying the Size of Chart Objects


When you create a Excel chart, as an object to be placed in a worksheet, the chart object is
automatically sized to some pre-determined size that Excel selects. You may not want the chart
object to be whatever size Excel determines; you may want your chart objects to always be a
standard size, so they always appear the same relative to your worksheets.

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

ExcelTips: The Macros Page 611


Working with Graphics and Charts

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

Specifying Chart Sizes


Megan has a monthly report that she creates in Excel. She has most of the report automated,
except for one annoying problem that she must handle manually. The report includes four pie
charts used to illustrate some values from the report. Each pie chart comes out a bit different in
size, and the manual task is that Megan needs to make them all the same size. She wants each of
them to be 5 centimeters square, and would love a way to remove the manual drudgery of
formatting them each month.

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

ExcelTips: The Macros Page 612


Working with Graphics and Charts

cht.Chart.ChartArea.AutoScaleFont = False
cht.Height = Application.CentimetersToPoints(5)
cht.Width = Application.CentimetersToPoints(5)
Next cht
End Sub

Changing Elements in Lots of Charts at One Time


Chris has a workbook that contains a lot of small, identical charts. He wants to change some of
the attributes of elements in each chart—such as color or font size—all at one time.

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

For Each cht In ActiveWorkbook.Charts


With cht.Axes(xlValue).TickLabels.Font
.Size = 20
.Color = vbRed
End With
Next

For Each sht In ActiveWorkbook.Sheets


For Each ChtObj In sht.ChartObjects
With ChtObj.Chart.Axes(xlValue).TickLabels.Font
.Size = 20
.Color = vbRed
End With
Next
Next
End Sub

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

ExcelTips: The Macros Page 613


Working with Graphics and Charts

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

'The pairs of line code indicate desired changes


ActiveChart.ChartArea.Select
Selection.Interior.ColorIndex = NewChartAreaColor
Next J

For J = 1 To ActiveWorkbook.Sheets.Count
For K = 1 To Sheets(J).ChartObjects.Count
Sheets(J).Select
Sheets(J).ChartObjects(K).Activate

'The pairs of line code indicate desired changes


ActiveChart.ChartArea.Select
Selection.Interior.ColorIndex = NewChartAreaColor
Next K
Next J
End Sub

Converting Charts to GIF Files


Conrad has an Excel chart that he would like to share with others. He's determined that the best
way for him to do this is to share it as a GIF image file. He isn't sure how to create a GIF file
from an Excel chart, however.

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:

1. Start a graphics program such as Paint.


2. Switch back to Excel and display your chart as you normally would.
3. Right-click the chart and choose Copy from the resulting Context menu. This copies the
chart to the Clipboard.
4. Switch to the graphics program.
5. Press CTRL+V to paste the chart into the graphics program.
6. Make any changes to the image that you want.
7. Save the image as a GIF file.

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,

ExcelTips: The Macros Page 614


Working with Graphics and Charts

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/

Creating a Photo Catalog from a Folder of Photos


Glenn is making a catalog of all his digital photos in Excel. All the photos (about 5000 of them)
are in a single folder. He would like to insert the photos to the right of the photo's description,
then put a hyperlink to all the photos to enlarge the thumbnail to a larger photo. Right now Glenn
is doing this one by one and it is driving him crazy, so he is looking for ways to speed the
process up.

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.)

ExcelTips: The Macros Page 615


Working with Graphics and Charts

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)

ExcelTips: The Macros Page 616


Working with Graphics and Charts

Do While xPhoto <> ""


i = i + 1
Range("B" & i).Select
ActiveSheet.Pictures.Insert(sLocT & xPhoto).Select
With Selection.ShapeRange
.LockAspectRatio = msoTrue
.Height = 54#
.PictureFormat.Brightness = 0.5
.PictureFormat.Contrast = 0.5
.PictureFormat.ColorType = msoPictureAutomatic
End With
Range("C" & i).Select
ActiveSheet.Hyperlinks.Add Anchor:=Selection, _
Address:= sLocP & xPhoto, TextToDisplay:=xPhoto
xPhoto = Dir
Loop

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

ExcelTips: The Macros Page 617


Working with Graphics and Charts

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.

Determining Mouse Cursor Coordinates On a


Graphic
Gerald notes that when moving the mouse over a picture on a worksheet the mouse pointer is a
cross. He wants to know how he can determine, using VBA, the coordinates of the cross when
the mouse is clicked.

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:

Private Sub Image1_MouseDown(ByVal Button As Integer, _


ByVal Shift As Integer, ByVal X As Single, _
ByVal Y As Single)
MsgBox X & ", " & Y
End Sub

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:

Private Sub Image1_MouseMove(ByVal Button As Integer, _


ByVal Shift As Integer, ByVal X As Single, _
ByVal Y As Single)
Application.StatusBar = Round(X, 0) & "," & Round(Y, 0)
End Sub

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.

ExcelTips: The Macros Page 618


Working with the Outside World

Working with the Outside World

Getting User Input in a Dialog Box


If you need to get input from a user under control of a macro, one method you can use is to
employ the InputBox function. This function displays a dialog box and allows the user to type a
response. The result is a string, returned to your macro, which you can then process and use.

The syntax for the InputBox function is as follows:

sResponse = InputBox(sPrompt, sTitle, sDefault)

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:

Dim sUserName as String


Dim sPrompt as String
Dim sTitle as String
Dim sDefault as String

sPrompt = "Please check your name and make any corrections"


sTitle = "Name Entry"
sDefault = "John Doe"
sUserName = InputBox(sPrompt, sTitle, sDefault)

Using InputBox to Get Data


If you are developing a simple custom application in Excel, you may want to use the InputBox
function to retrieve information from the user, and then place that information in a particular
place in a worksheet. This can be easily done in the following manner:

UserValue = InputBox("Value to use?")

ExcelTips: The Macros Page 619


Working with the Outside World

Cells(1, 1).Value = UserValue

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:

UserValue = InputBox("Value to use?")


Range("B3").Value = UserValue

This example deposits the value of UserValue into cell B3.

Hiding Entries in an InputBox


Andrew is writing a macro and he wants to give users the opportunity to enter their password
prior to permitting them to use certain functions provided by the macro. He is using the InputBox
function, and wonders if there is a way to “strike out” whatever someone enters, so the password
is kept private as it is typed. (This is done in many programs, where whatever is typed is replaced
on-screen with asterisks or some other character.)

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.

Forcing Input to Uppercase


If you are developing a worksheet for others to use, you may want them to always enter
information in uppercase. Excel provides a worksheet function that allows you to convert
information to uppercase, but it doesn’t apply as people are actually entering information. For

ExcelTips: The Macros Page 620


Working with the Outside World

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:

Private Sub Worksheet_Change(ByVal Target As Range)


With Target
If Not .HasFormula Then
.Value = UCase(.Value)
End If
End With
End Sub

For the macro to work, however, it must be entered in a specific place. Follow these steps to
place the macro:

1. Display the VBA Editor by pressing ALT+F11.

The VBA Editor.

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.)

ExcelTips: The Macros Page 621


Working with the Outside World

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:

Private Sub Worksheet_Change(ByVal Target As Range)


If Not (Application.Intersect(Target, Range("A1:B10")) Is Nothing) Then
With Target
If Not .HasFormula Then
.Value = UCase(.Value)
End If
End With
End If
End Sub

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.

Pausing Macros for User Input


For those who have been around spreadsheet programs for quite some time, you may remember
the old {?}~ command that was available in Lotus 1-2-3. This command allows you to pause the
macro while the user enters data in the spreadsheet.

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.

ExcelTips: The Macros Page 622


Working with the Outside World

Using Message Boxes


When you create macros in VBA, you can easily incorporate the use of message boxes. These
are typically used to convey information to the user and to get some rudimentary input. You
include message boxes by using the MsgBox command. The following portion of a macro
creates a very simple message box:

MsgBox "The Macro is Finished"

A very simple message box.

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:

MsgBox "Can't run this macro on this text", 48

ExcelTips: The Macros Page 623


Working with the Outside World

Adding a symbol to a message box.

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:

Type Button Types


1 OK, Cancel
2 Abort, Retry, Ignore
3 Yes, No, Cancel
4 Yes, No
5 Retry, Cancel

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.

Conditionally Displaying a Message Box


You may have a need to display a message box whenever specific information is placed in a
specific cell by the user. Fortunately, using the Change event for a worksheet can help you to
figure out when something has been placed in a cell.

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:

Private Sub Worksheet_Change(ByVal Target As Range)

ExcelTips: The Macros Page 624


Working with the Outside World

If Target.Address = "$C$3" Then


MsgBox "Changed It!"
End If
End Sub

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.

Specifying Location for a Message Box


Leonard wonders if, when displaying a message box in a macro, there is a way to force the box
to appear at a specific location on the screen.

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:

sName = InputBox(Prompt:="Enter your name", XPos:=2880, YPos:=1440)

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.

Progression Indicator in a Macro


Macros are often created to process data, and processing data can often take a long time. Because
of this, some users may think that their computer has stopped responding, even though the macro
is busy chunking away at its appointed task.

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:

sStatus = "Processing Input File - Please Be Patient"


Application.StatusBar = sStatus

ExcelTips: The Macros Page 625


Working with the Outside World

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/

Hiding an Excel 2007 Progress Indicator


Sally is calling Excel from an external program and, in the process, using
Application.Visible=False to hide Excel so the user can't see it. This works great in Excel 2000
but in Excel 2007, when opening a large file, Sally’s users see a popup box showing the progress
bar and giving the user a 'Cancel' option. She wonders how she can hide the popup box so that
the user never sees such a notification.

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.

Using the Status Bar


Typically, one of the first things you do when you create a macro is use a command that turns off
updating the screen display. This is done because the macro will run faster when it does not have
to update the screen. When this is done, one of the most important things you can do is provide
feedback to the user so they don’t think their system has gone out to lunch.

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:

ExcelTips: The Macros Page 626


Working with the Outside World

Application.StatusBar = "Updating past months..."

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.

ExcelTips: The Macros Page 627


Working with the Outside World

Getting a File Name


If you are writing a VBA macro in Excel, you may have a need to allow the user to specify a file
they want from the disk. Fortunately, you can access the standard Open dialog box from within
VBA and use it to return just a file name. The following example subroutine shows how this is
done:

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.)

Determining If a File Exists


As you are programming your macros, you may have a need to determine if a particular file
exists on disk. For instance, the purpose of your macro may be to open and read from a text file.
Before doing so, you will want to check to see if the file exists, in order to avoid an error.

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).

Function FileThere(FileName As String) As Boolean


FileThere = (Dir(FileName) > "")
End Function

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.

You can use the function similar to the following:

If FileThere("c:\myfile.txt") Then
'

ExcelTips: The Macros Page 628


Working with the Outside World

' Do stuff here


'
Else
MsgBox "File Not There!"
End If

Checking for the Existence of a File


John has a column of invoice numbers in a worksheet. He has a directory on the network where
staff save a PDF of the actual invoice and name it using the same invoice number that is in the
worksheet. Each invoice number in the worksheet should have a correspondingly named PDF in
the directory on the network. John is looking for a way, within Excel, to check and verify that a
PDF really does exist for each invoice number.

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:

Function FileExists1(sPath As String)


FileExists = Dir(sPath) <> ""
End 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:

=FileExists1("c:\your\path\here\" & A1 & ".pdf")

Of course, you could instead specify the path in the user-defined function:

Function FileExists2(sFile As String)


sPath = "c:\your\path\here\" & sFile & ".pdf"
FileExists = Dir(sPath) <> ""
End 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")

ExcelTips: The Macros Page 629


Working with the Outside World

Determining the Length of a Text File


Several other ExcelTips have discussed opening, reading, writing, appending, and closing text
files. Another VBA function associated with sequential text files is the LOF function. If used on
an open file, it returns the length of the file, in bytes. In other words, you can determine the
number of characters in the file. This can come in handy if you are processing a text file
character by character. You can determine the length of the file and then read that many
characters before you finish processing the file. The following code fragment is an example of
how the LOF function is used:

Open "MyFile.Dat" for Input as #1


FileLen = LOF(1)

Getting Input from a Text File


True to its BASIC roots, VBA allows you to do file input on sequential files. This means you can
open and read a sequential text file, loading the information from the file into string variables.
The steps are simple. You only have to open the file, get the input, and then close the file. The
following code is a common example of reading from a sequential file:

Dim Raw As String


Dim NumValues As Integer, J As Integer
Dim UserVals() As String

Open "MyFile.Dat" For Input As #1


Line Input #1, Raw
NumValues = Val(Raw)
ReDim UserVals(NumValues)

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.

Saving Information in a Text File


There may be times when you want a macro to save information to a text file. This is very easy
to do. All you need is to open the file for output, and then start sending information to the file.
The following code fragment writes a text file using this method.

Open "MyFile.Dat" For Output As #1


Print #1, NumValues
For J = 1 to NumValues

ExcelTips: The Macros Page 630


Working with the Outside World

Print #1, UserVals(J)


Next J
Close #1

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:

Open "MyFile.Dat" For Append As #1


For J = 1 to NewValues
Print #1, UserVals(OrigVals + J)
Next J
Close #1

When the file is opened for Append mode, any new information is added to the end of the file,
without disturbing the existing contents.

Using Seek In a Macro


Several other tips in other issues of ExcelTips discuss opening, reading, writing, appending, and
closing text files from within a macro. Another command associated with sequential text files is
the Seek command. If used on an open file, Seek positions the internal file pointer at a specific
character number in the file. The following code fragment is an example of how it is used:

Open "DOSTEXT.DAT" for Input as #1


iFileLen = LOF(1)
Seek 1, iFileLen / 2

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.

ExcelTips: The Macros Page 631


Working with the Outside World

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:

Name OldFile As NewFile

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"

Faster Text File Conversions


Pat wondered how to change the default column data type from “general” to “text” for all
columns of a comma-delimited text file. Changing the format of each column, especially when
there are many of them, can be tedious at best.

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:

1. Start to import your comma-delimited text file as you normally would.

ExcelTips: The Macros Page 632


Working with the Outside World

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.

ExcelTips: The Macros Page 633


Working with the Outside World

Aligning Cells when Importing from CSV


Marinos works with CSV files a lot. In his case, the CSV files are created by a custom
application and he found that he can even include formulae in them. So if a line of the CSV file
contains ",,,Total:,=SUM(D5:D13),,,," the formula is evaluated and all is fine. One thing
Marinos wants to do, however, is indicate in the CSV file how individual cells should be justified
after they are imported into Excel. He seems to remember that in Lotus 123 he could use a prefix
character to indicate the alignment of the cell (' for left, ^ for middle, and " for right); he figures
the same capability would be great in Excel.

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

For Each rCell In ActiveSheet.UsedRange


With rCell
Select Case Left(.Value, 1)
Case "<"
.Value = Mid(.Value, 2)
.HorizontalAlignment = xlHAlignLeft
Case "^"
.Value = Mid(.Value, 2)
.HorizontalAlignment = xlHAlignCenter
Case ">"
.Value = Mid(.Value, 2)
.HorizontalAlignment = xlHAlignCenter
End Select
End With
Next
Set rCell = Nothing
End Sub

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.

Specifying a Delimiter when Saving a CSV File in a


Macro
When creating a CSV file using the menus or ribbon tools to export a worksheet, Arkadiusz
noted that he can specify that he wants to use a semicolon (;) as a field delimiter. However, if he
saves a CSV file using a macro (FileFormat:=xlCSV or xlCSVWindows), then he cannot specify
a semicolon as a delimiter.

ExcelTips: The Macros Page 634


Working with the Outside World

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

ActiveWorkbook.SaveAs Filename:=FName & ".txt", _


FileFormat:=xlPrinter
End Sub

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/

ExcelTips: The Macros Page 635


Working with the Outside World

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.

Determining the Current Directory


If you are programming macros in VBA, it is often helpful to know the directory that Windows
feels is the current one. You can find out which directory is current by using the following
syntax:

MyDir = CurDir

When this line is executed, MyDir (a string) will be equal to the full path of the current directory.

ExcelTips: The Macros Page 636


Working with the Outside World

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.)

Changing the Default Drive


Oftentimes it is helpful, in a macro, to specify which drive is considered the default drive. In
other words, it may be helpful to indicate the drive on which all file operations should occur
when you don’t explicitly indicate a drive in a path name. To indicate the default drive to be used
in a macro, you use the ChDrive statement, as follows:

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.

Easily Changing the Default Drive and Directory


In other issues of ExcelTips you learned how you can use VBA to switch the current drive and
directory. In short, you can change drive and directory as follows:

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

ExcelTips: The Macros Page 637


Working with the Outside World

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.

Word Documents from Excel Macros


Eric has an Excel database of company information. He wants to use an Excel macro to copy
addresses and information from the database into different Word documents. Eric is curious as to
how he can make an Excel macro open a specific Word document into which the information
will be pasted.

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")

' put your code here for working with Word


' This is Word VBA code, not Excel code

wdDoc.Close savechanges:=False
Set wdDoc = Nothing
wdApp.Quit
Set wdApp = Nothing
End Sub

ExcelTips: The Macros Page 638


Working with the Outside World

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

Selection.Copy 'Assumes you hilighted what you want copied

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.

Using Old Lotus Macros


One thing that I missed when switching to Excel from Lotus 1-2-3 was my data-entry macros.
Despite reading several sources saying that you can’t use 1-2-3 macros in Excel, I found out to
my elation that you can. Here is an example.

1. Open a new Lotus spreadsheet, and set up a simple keystroke macro like this one:
{d}{?}{R}{?}{R}{?}{L 2}{Branch \e}

ExcelTips: The Macros Page 639


Working with the Outside World

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.

Linking to a Specific Page in a PDF File


Gary wants to link from an Excel worksheet to a specific page in a PDF file. He can get Excel to
link to the PDF but it starts on the first page of the PDF, not the page he wants. Gary believes
that Excel is ignoring the PDF command that tells it the page he wants. As an example, he can
use the formula =HYPERLINK("E:\\test\gary.pdf#5") and Excel ignores the #5 part and opens to
the first page of the PDF.

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

ExcelTips: The Macros Page 640


Working with the Outside World

The code could also be rather easily changed to a function to which you can pass the desired path
and target page.

Updating Automatically when Opening Under


Macro Control
Graham wrote about a glitch he was having with his Excel automation efforts. It seems that a
scheduled macro—which would run just fine—would open another workbook in order to update
it. He could not get the macro to open the workbook without it displaying a notice asking if the
automatic links in the workbook should be opened. The question halted the macro while it waited
for Graham’s response, and he was looking for a way for the links to be updated automatically,
without the bothersome notice.

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.

The Edit tab of the Options dialog box.

ExcelTips: The Macros Page 641


Working with the Outside World

3. Clear the Ask To Update Automatic Links check box.


4. Click on OK.

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.

The General section of the Excel Options dialog box.

4. Clear the Ask to Update Automatic Links check box.


5. Click on OK.

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:

1. Open the workbook that contains the links.


2. Choose Links from the Edit menu. Excel displays the Links dialog box.
3. Click the Startup Prompt button. Excel displays the Startup Prompt dialog box.

ExcelTips: The Macros Page 642


Working with the Outside World

The Startup Prompt dialog box.

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:

1. Open the workbook that contains the links.


2. Make sure the Data tab of the ribbon is displayed.
3. Click the Edit Links option in the Connections group. Excel displays the Edit Links
dialog box.
4. Click the Startup Prompt button. Excel displays the Startup Prompt dialog box.
5. Choose the third option, Don’t Display the Alert and Update Links.
6. Click on OK to dismiss the Startup Prompt dialog box.
7. Click on Close to dismiss the Edit Links dialog box.
8. Save your workbook.

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"

If this is the case, change the line to this:

Workbooks.Open FileName:="MyWorkbook.xlsx", UpdateLinks:=3

ExcelTips: The Macros Page 643


Working with the Outside World

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.

Displaying Messages when Automatic Data


Changes
Joydip has an Excel worksheet that is constantly and automatically updated with live commodity
market data. He wants to display a message box containing a particular message whenever the
data in a specified cell/range changes to meet some predefined criteria. Data validation won’t
work, because the validation feature isn’t triggered when data changes automatically.

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.

Private Sub Worksheet_Change(ByVal Target As Excel.Range)


Dim rng As Range
Set rng = Range("A1:C5")
If Not Intersect(Target, rng) Is Nothing Then
If Application.WorksheetFunction. _
Average(rng) = 5 Then
MsgBox "The average of " & _
rng.Address & " = 5"
End If
End If
Set rng = Nothing
End Sub

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

ExcelTips: The Macros Page 644


Working with the Outside World

range. If it did, then the average of the range is checked, and the message displayed if the result
is 5.

Preparing Data for Import into Access


If you are a database programmer you may sometimes get Excel files that you have to “clean up”
to put into Access. Two common problems are caused by Social Security Numbers and ZIP
Codes. These are best stored as text in the database, and not as numbers as they often are in
Excel. (In Excel the numbers may display properly because of cell formatting, and not because
they are stored as text.)

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

ExcelTips: The Macros Page 645


Working with the Outside World

If Len(c) <= 5 Then


c = "'" & Right("00000" & c, 5)
Else
c = "'" & Right("00000" & c, 10)
End If
Next c
Application.ScreenUpdating = True
End Sub

Getting Contact Information from Outlook


Chris uses Excel to create proposals for his customers. The contact information for those
customers is in Outlook. He would love a way to specify a customer, in Excel, and have it pull
from Outlook (and put into individual cells) standard contact information such as name,
company, address, phone, and e-mail.

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:

Function GrabContactInfo(rRng As Range, iWanted As Integer) As String


Dim olA As Outlook.Application
Dim olNS As Namespace
Dim olAB As MAPIFolder
Dim lItem As Long
Dim sNameWanted As String
Dim sRetValue As String

Set olA = New Outlook.Application


Set olNS = olA.GetNamespace("MAPI")
Set olAB = olNS.GetDefaultFolder(olFolderContacts)

Application.Volatile
sNameWanted = rRng.Value
sRetValue = "Not Found"

On Error Resume Next

For lItem = 1 To olAB.Items.Count


With olAB.Items(lItem)
If sNameWanted = .FullName Then
Select Case iWanted
Case 1
sRetValue = .CompanyName
Case 2
sRetValue = .BusinessAddress
Case 3
sRetValue = .BusinessAddressCity

ExcelTips: The Macros Page 646


Working with the Outside World

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.

ExcelTips: The Macros Page 647


Web and Online

Web and Online

Opening Sites in a Browser


Steve has a range of cells (A1:A10) that contain website addresses—for example,
www.example.com. He wonders if it is possible, within a macro, to open each of these addresses
in a browser all at once in separate browser tabs.

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

On Error Resume Next


Set MyRange = Range("A1:A10")
For Each hl In MyRange.Hyperlinks
hl.Follow
Next hl
End Sub

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

ExcelTips: The Macros Page 648


Web and Online

On Error Resume Next


Set MyRange = Range("A1:A10")
For Each cell In MyRange
sTemp = cell.Value
ThisWorkbook.FollowHyperlink _
Address:=sTemp
Next cell
End Sub

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

On Error Resume Next


Set MyRange = Range("A1:A10")
For Each cell In MyRange
sTemp = cell.Value
If InStr(sTemp, "://") = 0 Then
sTemp = "http://" & sTemp
End If
ThisWorkbook.FollowHyperlink _
Address:=sTemp
Next cell
End Sub

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.

Opening an HTML Page in a Macro


Excel is a “Web aware” program, meaning that it knows how to handle hyperlinks. You can add
a hyperlink in a document, click on that link, and Excel opens your Web browser and displays
the contents of that link in the browser. (You can also create a hyperlink to other Office
documents, including Excel workbooks.) You can even create hyperlinks to different objects on
your worksheet, such as a command button in a form.

ExcelTips: The Macros Page 649


Web and Online

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.

Specifying a Browser in a Hyperlink


Laura wants to include a hyperlink in a worksheet. However, she would like the hyperlink to
"force" the target of the URL to be displayed in a particular browser. For instance, she would like
the hyperlink to somehow specify that the target be opened in Internet Explorer.

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

ExcelTips: The Macros Page 650


Web and Online

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 showURL(browser As String, URL As String)


Dim pPath As String
Dim bPath As String

'Use this to resolve the correct program file path


'it is different on 32-bit and 64-bit systems
pPath = Environ("ProgramFiles")

If browser = "Firefox" Then


bPath = pPath & "\Mozilla Firefox\Firefox.exe"
ElseIf browser = "IE" Then
bPath = pPath & "\Internet Explorer\iexplore.exe"
Else
Exit Sub
End If

Call Shell(bPath & " " & URL, vbNormalFocus)


End Sub

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.

Converting a Range of URLs to Hyperlinks


John has a workbook that has well over a thousands URLs in it, all in column A. These are not
hyperlinks; they are straight text of individual URLs. John wants to convert the URLs to active
hyperlinks, but doing the conversion individually is extremely tedious, especially for that many
URLs.

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

ExcelTips: The Macros Page 651


Web and Online

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.

Showing Visited Hyperlinks


Jack likes his Excel hyperlinks to show that they have been visited. Unfortunately, when he
saves his workbook, they all get reset to unvisited. Jack wonders if there is some way to make
the "visited" status of his hyperlinks survive the Save operation.

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:

Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)


Dim wks As Worksheet
Dim hl As Hyperlink
Application.ScreenUpdating = False
For Each wks In ThisWorkbook.Worksheets
For Each hl In wks.Hyperlinks
If hl.Parent.Interior.ColorIndex = 37 Then
hl.Parent.Interior.ColorIndex = xlNone
hl.Parent.Style = "Followed Hyperlink"
End If
Next hl
Next wks
Application.ScreenUpdating = True
End Sub

Private Sub Workbook_SheetFollowHyperlink(ByVal Sh As Object, ByVal Target As


Hyperlink)
Target.Parent.Interior.ColorIndex = 37
End Sub

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.

ExcelTips: The Macros Page 652


Web and Online

Getting Rid of Many Hyperlinks


Do you create worksheets by pasting information that was copied from the Internet? This is not
unusual for some people, as there is quite a bit of public-domain information that can be copied
from the Internet. When you paste your information into a worksheet, you may notice that there
are quite a few hyperlinks. What if you want to get rid of those hyperlinks? Similarly, you might
inherit a worksheet from someone, and it contains a lot of hyperlinks you want deleted.

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

Getting Rid of All Hyperlinks


If you inherit worksheets from other people, you may find that some worksheets contain many,
many hyperlinks. These hyperlinks are often automatically created by Excel as you import or
enter information in the worksheet.

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!

Changing Portions of Many Hyperlinks


Kerstine has a worksheet with many, many different hyperlinks in it. She is wondering if there is
a way she can replace just a part of each link. For instance, she might like to change any instance
of http://www.mysite.com/ to c:/documents/mycopy/. If there is anything additional in the links,
then that part should remain. So, for instance, if the original link is
http://www.mysite.com/thispage.html, it would be changed to
c:/documents/mycopy/thispage.html.

ExcelTips: The Macros Page 653


Web and Online

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/"

For Eack lnkH In ActiveSheet.Hyperlinks


lnkH.Address = Replace(lnkH.Address, sOld, sNew)
lnkH.TextToDisplay = Replace(lnkH.TextToDisplay, sOld, sNew)
Next
End Sub

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.

Changing Huge Numbers of Hyperlinks


Wendy has a single Excel worksheet that contains over 1,200 hyperlinks to TIFF files. (These
are hyperlinks, not regular links.) Excel hiccupped and had to shut down, so Emily used the
AutoSaved files to recover the previously saved file. Now all the previously working hyperlinks
don't work. She had the hyperlinks to the images on a shared network drive, but the AutoSave
changed the hyperlinks to reference the C: drive. She wonders if there is an easy way to fix them
back to the shared network drive.

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()

ExcelTips: The Macros Page 654


Web and Online

Dim wks As Worksheet


Dim hl As Hyperlink
Dim sOld As String
Dim sNew As String

Set wks = ActiveSheet


sOld = "c:\"
sNew = "S:\Network\"
For Each hl In wks.Hyperlinks
hl.Address = Replace(hl.Address, sOld, sNew)
Next hl
End Sub

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

Set wks = ActiveSheet


sOld = "c:\"
sNew = "S:\Network\"
For Each hl In wks.Hyperlinks
hl.Address = Application.WorksheetFunction. _
Substitute(hl.Address, sOld, sNew)
Next hl
End Sub

Note that the only difference is the use of the Substitute worksheet function.

Converting to Hyperlinks in a Shared Workbook


Eric has a shared workbook that contains a database of some 3,500 records. Two of the cells in
each record contain an e-mail address and a URL. When a new record is added to the database,
the e-mail address and URL appear as regular text instead of as hyperlinks. To make them into
hyperlinks Eric must unshare the workbook, make the change, and then reshare the workbook.
Eric wondered if there is, perhaps, an easier way to handle this situation.

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:

ExcelTips: The Macros Page 655


Web and Online

=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

' Unshare the Workbook


If ActiveWorkbook.MultiUserEditing Then
ActiveWorkbook.ExclusiveAccess
End If

' Change address in cell to a hyperlink.


If ActiveCell = "" Then
ActiveCell.Hyperlinks.Delete
Else
For Each cell In Intersect(Selection, _
Selection.SpecialCells(xlConstants, xlTextValues))
With Worksheets(1)
.Hyperlinks.Add Anchor:=cell, _
Address:=cell.Value, _
ScreenTip:=cell.Value, _
TextToDisplay:=cell.Value
End With
Next cell
End If

' Reshare the Workbook


If Not ActiveWorkbook.MultiUserEditing Then
ActiveWorkbook.SaveAs _
Filename:=ActiveWorkbook.FullName, _
AccessMode:=xlShared
End If
End Sub

Extracting E-mail Addresses from Hyperlinks


Do you have a worksheet that has a bunch of e-mail addresses in it, as a series of hyperlinks? If
so, you may be interested in a way to pull out those addresses and put them into cells as plain
text. There are a few ways you can perform this task.

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:

ExcelTips: The Macros Page 656


Web and Online

=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:

Function ExtractEmailAddress(rCell As Range)


ExtractEmailAddress = _
Mid(rCell.Hyperlinks(1).Address, 8)
End Function

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)

Extracting URLs from Hyperlinks


Mezga has a series of cells that contain hyperlinks. These hyperlinks consist of words such as
"click here" or "more information." In other words, each hyperlink contains display text that is
different from the underlying URL that is activated when the link is clicked. Mezga would like to
know if there is a way, without using a macro, to extract the underlying URL for each of these
hyperlinks and place that URL into a different cell.

Without using macros, you can do this:

1. Right-click a hyperlink. You’ll see a Context menu appear.


2. From the Context menu, choose Edit Hyperlink. Excel displays the Edit Hyperlink
dialog box.

ExcelTips: The Macros Page 657


Web and Online

The Edit Hyperlink dialog box.

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:

Function GetURL(rng As Range) As String


On Error Resume Next
GetURL = rng.Hyperlinks(1).Address
End Function

ExcelTips: The Macros Page 658


Web and Online

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)

Extracting URLs from Hyperlinked Images


One way you can use data from the Internet in an Excel worksheet is to copy it from a Web page
and then paste it into the worksheet. For instance, you can select a table of data on a Web page,
press CTRL+C to copy it to the Clipboard, select a cell in Excel, and then press CTRL+V. Excel
does its best to parse the data and put it in the proper cells, just like it was in the original table.

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

For Each shp In ActiveSheet.Shapes


sTemp = ""
On Error Resume Next 'go to next shape if no hyperlink
sTemp = shp.Hyperlink.Address
On Error GoTo 0
If sTemp <> "" Then
shp.TopLeftCell.Value = sTemp
shp.Delete
End If
Next
End Sub

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.

ExcelTips: The Macros Page 659


Web and Online

Extracting Hyperlink Information


Cheryl has a worksheet that contains many hyperlinks. The display text for each hyperlink is
different than the target for the hyperlink. These hyperlinks are all in column A. Cheryl would
like to leave the display text in column A, move the target URL into column B, and delete the
hyperlink in column A. What she needs to end up with is the display text in column A, the URL
in column B, and no active hyperlinks in the worksheet.

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

Set rRng = ActiveSheet.Range(ActiveWindow.Selection.Address)


For Each cell In rRng
If cell.Hyperlinks.Count > 0 Then
cell.Offset(0, 1) = cell.Hyperlinks(1).Address
cell.Hyperlinks(1).Delete
End If
Next
End Sub

Get Rid of Web Stuff


Grant regularly copies information from Web pages and pastes that information into worksheets.
He ends up not only with raw data, but also with other items, such as checkboxes, pictures,
logos, etc. Grant wants an easy way to get rid of all these non-data items.

The first thing that most people try is to use Go To Special, in this manner:

1. Press F5. Excel displays the Go To dialog box.


2. Click the Special button. Excel displays the Go To Special dialog box.
3. Select the Objects option.
4. Click OK.

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.

ExcelTips: The Macros Page 660


Web and Online

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.

Sending Single Worksheets via E-mail


Jessica asked if there is a way to send one worksheet in a workbook as an e-mail attachment
without sending the entire workbook. If you only need to do this once in a while, then the easiest
way is to follow these steps:

1. Right-click the tab for the worksheet you want to e-mail.


2. From the resulting Context menu, choose Move or Copy. Excel displays the Move or
Copy dialog box.

ExcelTips: The Macros Page 661


Web and Online

The Move or Copy dialog box.

3. Using the To Book drop-down list, choose New Book.


4. Make sure the Create a Copy check box is selected.
5. Click OK.

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

' Make a copy of the active worksheet


' and save it to a temporary file
ActiveSheet.Copy
Set WB = ActiveWorkbook

FileName = WB.Worksheets(1).Name
On Error Resume Next
Kill "C:\" & FileName
On Error GoTo 0

ExcelTips: The Macros Page 662


Web and Online

WB.SaveAs FileName:="C:\" & FileName

'Create and show the Outlook mail item


Set oApp = CreateObject("Outlook.Application")
Set oMail = oApp.CreateItem(0)
With oMail
'Uncomment the line below to hard code a recipient
'.To = "[email protected]"
'Uncomment the line below to hard code a subject
'.Subject = "Subject Line"
'Uncomment the lines below to hard code a body
'.body = "Dear John" & vbCrLf & vbCrLf & _
'"Here is the file you asked for"
.Attachments.Add WB.FullName
.Display
End With

'Delete the temporary file


WB.ChangeFileAccess Mode:=xlReadOnly
Kill WB.FullName
WB.Close SaveChanges:=False

'Restore screen updating and release Outlook


Application.ScreenUpdating = True
Set oMail = Nothing
Set oApp = Nothing
End Sub

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

Suppressing the Reviewing Toolbar on E-mailed


Workbooks
If you are using Excel 2002 or Excel 2003 and you e-mail a workbook to someone, then when
they open the workbook, Excel automatically displays the Reviewing toolbar. If you receive
workbooks from other people via e-mail, and you get tired of the Reviewing toolbar always
being displayed, there is little you can do to force it to stay hidden.

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

ExcelTips: The Macros Page 663


Web and Online

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.

Retrieving Web Query Data without Interruption


Nikolas has developed a Web query to retrieve external data on a regular basis. The problem is
that he frequently receives an “Unable to open the web page...” error message when running the
Web query. This message appears when there is some interruption of the Internet connection
between Nikolas and the Web server, and he needs to click OK on the error message so that
Excel will continue.

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)

Function GetData(strStartURL As String) As String


Dim Attempt As Long
Dim Connected As Boolean
Dim ieDocNew As MSHTML.HTMLDocument

GetData = "N/A"
Attempt = 0

retry:
Attempt = Attempt + 1

'Create browser object references and open an IE window


Dim ieNew As New InternetExplorer
'Load page
With ieNew
.Visible = True 'show window
.navigate strStartURL 'open page
While Not .readyState = READYSTATE_COMPLETE
Sleep 500 'wait 1/2 sec before trying again
Wend
End With

'The page should be open in IE, time for parsing


'Create document object model references
Set ieDocNew = ieNew.Document

ExcelTips: The Macros Page 664


Web and Online

If ieDocNew.Scripts.Length = 13 _
And ieNew.LocationName = "Microsoft Excel Tips" _
Then
Connected = True
GetData = "Data successfully captured"

'This is where you do something with the data


End If

'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.”

ExcelTips: The Macros Page 665


Web and Online

Hiding a Hyperlink on a Printout


Kathy has a hyperlink in cell A1 of every worksheet in her workbook. The hyperlink, when
clicked, displays a worksheet that contains a table of contents so she can move around the
enormous workbook faster. Kathy doesn't want to print the hyperlink when she prints the
worksheets. She wonders if there is any way to format the hyperlink so it doesn't print. She notes
that hiding column A or row 1 defeats the purpose because she wants this cell visible while
working in the workbook.

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:

Private Sub Workbook_BeforePrint(Cancel As Boolean)


Dim wks As Worksheet

For Each wks In Worksheets


wks.Range("A1").NumberFormat = ";;;"
Next
End Sub

ExcelTips: The Macros Page 666


Web and Online

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:

Private Sub Workbook_SheetActivate(ByVal Sh As Object)


Sh.Range("A1").NumberFormat = "General"
End Sub

ExcelTips: The Macros Page 667


Working with the Printer

Working with the Printer

Page Numbers in VBA


Steve is looking for a way to determine, in a VBA macro, the number of pages that a worksheet
will have, when printed, and the page number on which a particular cell will print. This task is
not quite as easy as one would hope, but it can be done.

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

ExcelTips: The Macros Page 668


Working with the Printer

lRow = lRow + 1
End If

If .PageSetup.Order = xlDownThenOver Then


iPage = (iCol - 1) * (lRows + 1) + lRow
Else
iPage = (lRow - 1) * (iCols + 1) + iCol
End If
End With
MsgBox "Cell " & ActiveCell.Address & _
" is on " & vbCrLf & "Page " & _
iPage & " of " & iPages & " pages"
End Sub

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.

Custom Page Numbers on Printouts


Wendy wants to include page numbers in the header of her worksheet printout, but with a
twist—Page 21A on page 1, Page 21B on page 2, Page 21C on page 3, etc. She wonders how to
go about creating such a page numbering scheme.

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

ExcelTips: The Macros Page 669


Working with the Printer

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.

Specifying the Y Value in X of Y Page Numbering


Dawn has workbooks with multiple tabs (spreadsheets) in them. She uses "Page of Pages" (X of
Y) numbering in the page footers. She notes that she used to be able to print entire workbooks at
once and have the numbering come out correctly, with the Y value being the number of pages in
each worksheet. Since upgrading to Excel 2010, the Y value on her printouts is always the total
number of pages in the workbook, not the number of pages in each worksheet. She wonders if
there is a setting in Excel that determines how the Y value is counted (per worksheet or per
workbook).

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:

ExcelTips: The Macros Page 670


Working with the Printer

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.

Changing Paper Size for a Complete Workbook


Bob has a workbook that has about fifteen worksheets in it, and he needs to change the page
format for all the worksheet from Letter to A4. There is a quick way to make the change by
following these steps:

1. Right-click any worksheet tab. You’ll see a Context menu appear.


2. Choose Select All Sheets. All the worksheet tabs should now be selected.
3. 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 bottom-right of the Page Setup group. In
earlier versions of Excel choose Page Setup from the File menu.)
4. Make sure the Page tab is displayed.

ExcelTips: The Macros Page 671


Working with the Printer

The Page tab of the Page Setup dialog box.

5. Using the Paper Size drop-down list, choose A4.


6. Click OK.
7. Right-click any worksheet tab. You’ll again see a Context menu.
8. Choose Ungroup Sheets.

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

For Each sht In ActiveWorkbook.Sheets


sht.PageSetup.PaperSize = xlPaperA4
Next
End Sub

ExcelTips: The Macros Page 672


Working with the Printer

The macro steps through each sheet in the workbook, changing only the PaperSize property so
that the sheet will print on A4 paper.

Displaying the Print Dialog Box in a Macro


Before printing anything in Excel, it is not unusual to display the Print dialog box. This allows
you to make changes to how the print job will be handled by the printer driver.

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.

To add this capability, simply include the following macro line:

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.

Specifying Print Quantity in a Cell


Tom is trying to create some macro code that will control the quantity of copies to print, based
on the value entered in a cell. He has created an input form for his shipping personnel to use that
prints package content labels. He would like to be able to have them enter into cell B11 the
number of labels that need to be printed, and then have that number printed.

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:

Set MyRange = Range("A1:A5")


iNumCopies = Range("B11").Value
If iNumCopies < 1 Then iNumCopies = 1
MyRange.PrintOut Copies:=iNumCopies

ExcelTips: The Macros Page 673


Working with the Printer

Controlling the Printer in a Macro


Many of the printers available on the market these days have some amazing capabilities. Most of
these capabilities are accessible by using the Print dialog box and clicking on the Properties
button next to the printer name. As you are developing your own macros, you may wonder if it is
possible to access these capabilities from within the macro.

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:

Application.ActivePrinter = "HP Glossy Paper"

Working with Multiple Printers


You already know that Windows supports multiple printers. Using Excel with multiple printers
can be a bother, however, since you must display the Print dialog box (Excel 2007 and earlier) or
the Print information (Excel 2010 and Excel 2013), change the printer, and then print the
worksheet.

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.)

ExcelTips: The Macros Page 674


Working with the Printer

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.

Using Multiple Print Settings


If you have multiple areas that you print in a worksheet, you may get tired of repeatedly
specifying what area you want to print and then printing it. Such a task is well suited to being
done with a macro. The macro can take care of specifying a print area and then actually printing
the information.

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. Make sure the View tab of the ribbon is displayed.


2. In the Workbook Views group, click Custom Views. Excel displays the Custom Views
dialog box.
3. Click on Add. Excel displays the Add View dialog box.

ExcelTips: The Macros Page 675


Working with the Printer

The Add View dialog box.

4. Enter a descriptive name for the view you are defining.


5. Make sure the Print Settings check box is selected.
6. Click OK.

If you are using an earlier version, follow these steps:

1. Choose Custom Views from the View menu. Excel displays the Custom Views dialog
box.

The Custom Views dialog box.

2. Click on Add. Excel displays the Add View dialog box.


3. Enter a descriptive name for the view you are defining.
4. Make sure the Print Settings check box is selected.
5. Click OK.

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.)

ExcelTips: The Macros Page 676


Working with the Printer

Can Only Print to Default Printer


On Bob's system, Excel refuses to print to any printer other than the one set as the default for the
system. This only happens in Excel, not in Word or any other installed application. So in order to
print he has to temporarily change the default printer to the one he wants, print, and then
remember to set the printer back afterwards. Bob is wondering why he can't choose other
printers.

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:

Dim sDefault As String


sDefault = Application.ActivePrinter 'save current default printer
Application.ActivePrinter = "XYZ SuperPrinter"
' do your printing
Application.ActivePrinter = sDefault 'restore default

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.

ExcelTips: The Macros Page 677


Working with the Printer

Printing Selected Worksheets


If you have a lot of workbooks that have accumulated over the years, you may have a need to
print some of the worksheets out of each of them. For instance, you might have a folder that
contains a workbook for each of your company's divisions for the previous decade. If your
company has eight divisions, that means you have 80 workbooks in the folder. Now, if you need
to print the second-quarter and third-quarter figures (from the second and third worksheets out of
each workbook), you start to see the problem. Loading each workbook and then printing selected
sheets could take a huge amount of time.

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.

Public Sub PrintWorkbooks()


Dim sCurFile As String
Dim sPath As String

'Get the path


sPath = InputBox("Starting path?", "PrintWorkbooks")
If sPath <> "" Then
On Error Resume Next
Application.ScreenUpdating = False
If Right(sPath, 1) <> "\" Then
sPath = sPath & "\"
End If
sCurFile = Dir(sPath & "*.xls", vbNormal)
Do While Len(sCurFile) <> 0
Workbooks.Open sPath & sCurFile, , True
With Workbooks(sCurFile)
.Worksheets(2).PrintOut
.Worksheets(3).PrintOut
.Close SaveChanges:=False
End With
sCurFile = Dir
DoEvents
Loop
Application.ScreenUpdating = True
On Error GoTo 0
End If
End Sub

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.

ExcelTips: The Macros Page 678


Working with the Printer

Printing Only Non-Blank Worksheets


Clinton has a workbook containing over 200 worksheets that get populated by various people in
his company during the month. At the end of the month he needs to print these worksheets. Not
all the worksheets contain data and Clinton only wants to print the worksheets that contain data
so he doesn't waste paper. He wonders if there is, perhaps, a macro that he can use to print only
those worksheets that have a value in cell G41.

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.

Printing Limited Pages from a Range of Worksheets


Ian puts together Excel workbooks that typically contain, at minimum, 30 worksheets. Each
worksheet, if printed, requires a minimum of eight pages. Ian often updates data in each
worksheet that would appear on the first two printed pages of those worksheets. When it comes
time to print, Ian would like a way to print just the first two pages of each worksheet.

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

ExcelTips: The Macros Page 679


Working with the Printer

For Each sht In ActiveWindow.SelectedSheets


sht.PrintOut From:=1, To:=2, Preview:=True
Next
Set sht = Nothing
End Sub

Printing Odd or Even Pages


When printing longer worksheets, you may long for a way to print either odd or even pages.
Unfortunately, Excel doesn’t' include this capability. There are a couple of ways you can work
around this problem, however.

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

StartPage = InputBox("Enter starting page number")


TotalPages = Application.ExecuteExcel4Macro("GET.DOCUMENT(50)")
If StartPage > 0 And StartPage <= TotalPages Then
For Page = StartPage To TotalPages Step 2
ActiveSheet.PrintOut From:=Page, To:=Page, _
Copies:=1, Collate:=True
Next
End If
End Sub

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:

ExcelTips: The Macros Page 680


Working with the Printer

1. Change to Page Break Preview.


2. Use the mouse to select all the cells of page 1.
3. Hold down the CTRL key as you select all the cells of page 3.
4. In turn, and still holding down the CTRL key, select all the cells of the other odd-
numbered pages.
5. Define the selected cells as the print area.

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).

Printing an Entire Workbook by Default


When you choose to print in Excel, the Print dialog box (Excel 97 through 2007) or the File tab
of the ribbon (Excel 2010 and 2013) allows you to specify many things about the print job. The
Print What controls allow you to indicate whether you want to print the selected worksheets, the
selection, or the entire workbook. The Print What setting normally defaults to Active
Worksheets, but what if you want it to default so the entire workbook is printed?

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.

Automatic Selection of Portrait or Landscape


Barbara has a worksheet that she needs to print out periodically. Sometimes she needs to only
print three columns of data and other times she needs to print more. When she prints only three
columns they fit very nicely on a page printed in portrait orientation. When she prints more
columns then she needs to print in landscape orientation. Barbara wonders if there is a way she
can set up Excel so that it automatically switches from portrait to landscape based upon the
number of columns she wants to print.

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

ExcelTips: The Macros Page 681


Working with the Printer

portrait orientation. Any other number of columns and landscape orientation is used. Here is the
macro; you should add it to the ThisWorkbook module:

Private Sub Workbook_BeforePrint(Cancel As Boolean)


With ActiveSheet.PageSetup
If Range(.PrintArea).Columns.Count > 3 Then
.Orientation = xlLandscape
Else
.Orientation = xlPortrait
End If
End With
End Sub

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.

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.

ExcelTips: The Macros Page 682


Working with the Printer

If you are using an older version of Excel, follow these steps:

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.

Printing a Single Column in Multiple Columns


Sometimes the data you collect in a worksheet fits very nicely into a single column. For instance,
you may have a list of names, and they all are contained in column A of your worksheet. When
you choose to print the worksheet, it can consume quite a few pages, all of them nearly blank as
the left side of each page contains a name, and the right side contains white space.

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.

ExcelTips: The Macros Page 683


Working with the Printer

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

Set rng = Application.InputBox _


(prompt:="Select the range to convert", _
Type:=8)
iCols = InputBox("How many columns do you want?")
lRowSource = rng.Rows.Count
lRows = lRowSource / iCols
If lRows * iCols <> lRowSource Then lRows = lRows + 1

Set wks = Worksheets.Add


lRow = 1
x = 1
For iCol = 1 To iCols
Do While x <= lRows And lRow <= lRowSource
Cells(x, iCol) = rng.Cells(lRow, 1)
x = x + 1
lRow = lRow + 1
Loop
x = 1
Next
End Sub

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

Printing Multiple Worksheets on a Single Page


Workbooks can contain all sorts of data. If you have a workbook that includes a number of
worksheets, each containing only a small amount of data, you may wonder if there is a way to
print the multiple worksheets on a single sheet of paper.

ExcelTips: The Macros Page 684


Working with the Printer

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

On Error Resume Next

ExcelTips: The Macros Page 685


Working with the Printer

Set c = wsh.Cells.SpecialCells(xlCellTypeLastCell)
If Err = 0 Then
On Error GoTo 0

'Prevent empty rows


Do While Application.CountA(c.EntireRow) = 0 _
And c.EntireRow.Row > 1
Set c = c.Offset(-1, 0)
Loop

Set rngArr(i) = wsh.Range(wsh.Range("A1"), c)


End If
Next wsh

'Add temp.Worksheet
Set wshTemp = Sheets.Add(after:=Worksheets(Worksheets.Count))

On Error Resume Next


With wshTemp
For i = 1 To UBound(rngArr)
If i = 1 Then
Set c = .Range("A1")
Else
Set c = _
ActiveSheet.Cells.SpecialCells(xlCellTypeLastCell)
Set c = c.Offset(2, 0).End(xlToLeft) 'Skip one row
End If

'Copy-paste range (prevent empty range)


If Application.CountA(rngArr(i)) > 0 Then
rngArr(i).Copy c
End If
Next i
End With
On Error GoTo 0

Application.CutCopyMode = False ' prevent marquies

With ActiveSheet.PageSetup 'Fit to 1 page


.Zoom = False
.FitToPagesWide = 1
.FitToPagesTall = 1
End With

'Preview New Sheet


ActiveWindow.SelectedSheets.PrintPreview

'Print Desired Number of Copies


i = InputBox("Print how many copies?", "ExcelTips", 1)
If IsNumeric(i) Then
If i > 0 Then
ActiveSheet.PrintOut Copies:=i
End If
End If

'Delete temp.Worksheet?
If MsgBox("Delete the temporary worksheet?", _
vbYesNo, "ExcelTips") = vbYes Then
Application.DisplayAlerts = False
wshTemp.Delete
Application.DisplayAlerts = True
End If

ExcelTips: The Macros Page 686


Working with the Printer

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.

Printing a Worksheet List


In complex workbooks that contain many worksheets, it is not unusual to need a list of the
different worksheets. Once you have the list, you can print it or use it in some other fashion, such
as to create a table of contents for your workbook. The following macro, GetSheets, quickly
retrieves the names of the worksheets in the current workbook. It places them in the current
worksheet, starting at cell A1 and then working downwards.

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.

Printing Workbook Properties


When you are putting together a workbook, Excel tracks quite a bit of information that it
collectively refers to as workbook properties. You can view the different properties maintained
by displaying the Properties dialog box.

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:

ExcelTips: The Macros Page 687


Working with the Printer

Public Sub WorksheetProperties()


Dim p As DocumentProperty
Dim iRow As Integer

'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

Adjusting Comment Printouts


If you use comments in your worksheets quite a bit, you may wonder if there is a way to print the
comments, but without the name and colon that normally preface every comment. Unfortunately,
there is no built-in way to accomplish this in Excel. However, a macro can be used to quickly
pull all the comments from a worksheet and place them in their own worksheet. This worksheet
could then be printed, as it would amount to a compendium of all the comments. The macro is as
follows:

Sub Workbook_BeforePrint(Cancel As Boolean)


myCount = 0
For Each c In ActiveSheet.Comments
myCount = myCount + 1
myComment = ActiveSheet.Comments(myCount).Text
Sheets(2).Range("a1").Offset(myCount, 0).Value = _
Mid(myComment,InStr(myComment, Chr(10)))
Next
End Sub

ExcelTips: The Macros Page 688


Working with the Printer

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.

Disabling the Print Option


If you are creating workbooks to be used by others, you may be interested in disabling the print
options (menu and toolbar) whenever a specific workbook is open. The easiest way to do so is to
use the Auto_Open macro (which runs immediately upon opening the workbook that contains it)
to make the menu and toolbar printing commands no longer available. The following will do the
trick nicely:

Sub Auto_Open()
'Prevent Printing via menu
MenuBars(xlWorksheet).Menus("File").MenuItems("Print...").Delete

'Turn off Print icon wherever it may be in the toolbars


For J = 1 To Toolbars.Count
For K = 1 To Toolbars(J).ToolbarButtons.Count
If Toolbars(J).ToolbarButtons(K).Id = 2 Then
Toolbars(J).ToolbarButtons(K).Enabled = False
End If
If Toolbars(J).ToolbarButtons(K).Id = 3 Then
Toolbars(J).ToolbarButtons(K).Enabled = False
End If
Next K
Next J
End Sub

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

'Reset the buttons


For J = 1 To Toolbars.Count
For K = 1 To Toolbars(J).ToolbarButtons.Count
If Toolbars(J).ToolbarButtons(K).Id = 2 Then
Toolbars(J).ToolbarButtons(K).Enabled = True
End If
If Toolbars(J).ToolbarButtons(K).Id = 3 Then
Toolbars(J).ToolbarButtons(K).Enabled = True
End If
Next K
Next J
End Sub

ExcelTips: The Macros Page 689


Working with the Printer

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:

Private Sub Workbook_BeforePrint(Cancel As Boolean)


Cancel = True
End Sub

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.)

Automatically Printing a Range


If you are automating your office using Excel, you may wonder if there is a way to automatically
print the contents of a cell range at a given time each day. For instance, you might have a
workbook that is always open, logging input from a different program. At a particular time each
day you may want to automatically print a range that contains summary information.

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.

Private Sub Workbook_Open()


'Schedule the printing
Application.OnTime TimeValue("17:00:00"), "PrintMe"
End Sub

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:

Private Sub PrintMe()


Sheets(2).PrintOut
'Reschedule the printing
Application.OnTime TimeValue("17:00:00"), "PrintMe"
End Sub

ExcelTips: The Macros Page 690


Working with the Printer

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.

Setting Print Ranges for Multiple Worksheets


Martin asked if there is a way to set print ranges for multiple worksheets at the same time. He
has a workbook containing a number of worksheets structured exactly the same, and he wants
their respective print ranges to be exactly the same.

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

ExcelTips: The Macros Page 691


Working with the Printer

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

sPrintArea = InputBox("Enter print area range")


For Each wks In ActiveWindow.SelectedSheets
wks.PageSetup.PrintArea = sPrintArea
Next
Set wks = Nothing
End Sub

Printing All or Nothing


Karen is looking for a way to print an entire workbook, even if a user chooses to print a single
worksheet. In other words, she is looking for a way to print either the entire workbook, or
nothing at all—there should be no “in between” options.

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.

Private Sub Workbook_BeforePrint(Cancel As Boolean)


Dim sht As Variant
Dim bPreview As Boolean
Dim iResponse As Integer
On Error GoTo ErrHandler

iResponse = MsgBox(prompt:="Do you want to Print Preview?", _


Buttons:=vbYesNoCancel, Title:="Preview?")

Select Case iResponse


Case vbYes
bPreview = True
Case vbNo
bPreview = False
Case Else
GoTo ExitHandler
End Select
Application.EnableEvents = False
For Each sht In Sheets
If sht.Visible Then
sht.PrintOut Preview:=bPreview
End If

ExcelTips: The Macros Page 692


Working with the Printer

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.

ExcelTips: The Macros Page 693


Working with the Printer

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.

The following macro can be used to create the “control sheet.”

Sub CreateControlSheet()
Dim i as integer

On Error Resume Next 'Delete this sheet if it already exists


Sheets("Control Sheet").Delete
On Error GoTo 0

Sheets.Add 'Add the WhatToPrint Sheet


ActiveSheet.Name = "Control Sheet"

Range("A1").Select 'Label the columns


ActiveCell.FormulaR1C1 = "Sheet Name"

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

ExcelTips: The Macros Page 694


Working with the Printer

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

Do Until Sheets("Control Sheet").Cells(i, 1).Value = ""


If Trim(Sheets("Control Sheet").Cells(i, 2).Value <> "") Then
Sheets(Sheets("Control Sheet").Cells(i, 1).Value).Select
ActiveWindow.SelectedSheets.PrintOut Copies:=1
End If
i = i + 1
Loop
End Sub

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.

Private Sub Workbook_BeforePrint(Cancel As Boolean)


Dim vShts As Variant
Dim iResponse As Integer
Dim bPreview As Boolean

On Error GoTo ErrHandler

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

ExcelTips: The Macros Page 695


Working with the Printer

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.

Protecting Print Settings


Sharing an Excel workbook with a group also means being involved with different printers,
different PCs and different user requirements and expectations. This is nowhere more apparent
then when it comes to printing a worksheet. Different users obviously have different PCs and
may have different printers, so the printed results can vary from one user to another. In addition,
different users may change the print ranges in what is produced from a 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)

ExcelTips: The Macros Page 696


Working with the Printer

.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.

Locking the Print Area


Karolyne shares workbooks with other people. Once in a while those people will, without
knowing it, make changes to a worksheet that results in many, many pages being printed.
Karolyne is looking for a way to set a print area in such a way that it is "locked" and could not be
changed or removed.

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:

Private Sub Workbook_BeforePrint(Cancel As Boolean)

ExcelTips: The Macros Page 697


Working with the Printer

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.

Showing Filter Criteria on a Printout


Microsoft Excel includes some great tools that help you filter large data tables to include only the
information you want displayed. In effect, the filters allow you to “slice and dice” your data until
you get just what you want.

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

' add Criteria string to Header/Footer


With ActiveSheet.PageSetup
.LeftHeader = strCriteria
End With
End Sub

Function FilterCriteria() As String


Dim rngCriteria As Range, col As Range, cel As Range
Dim strCriteria As String, r As Integer, c As Integer
Const strCriteriaRange As String = "Criteria"

ExcelTips: The Macros Page 698


Working with the Printer

FilterCriteria = ""

On Error Resume Next


'Set Criteria-Range reference
Set rngCriteria = Range(strCriteriaRange)
If Err <> 0 Then Exit Function
On Error GoTo 0

' Create Criteria String


c = 0
For Each col In rngCriteria.Columns
c = c + 1 ' CriteriaRange Columns
r = 1 ' CriteriaRange Rows
For Each cel In col.Cells
If r = 1 Then
strCriteria = strCriteria & "Criteria" _
& c & " (" & cel.Value & ") = "
Else
strCriteria = strCriteria & "'" & cel.Value & "'"
If IsEmpty(cel.Offset(1, 0)) Then
'Add New row Char if not Last Criteria Column
If c < rngCriteria.Columns.Count Then
strCriteria = strCriteria & Chr(10)
End If
Exit For
End If
strCriteria = strCriteria & " <or> "
End If
r = r + 1
Next cel ' next criteria row
Next col ' next criteria column

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.

Repeating Rows on a Printout Except On the Last


Page
Nancy knows how to format a worksheet so that rows are repeated at the top of each page of a
printout. What she wants to do, however, is to have the rows repeated at the top of each printed
page except the last one.

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()

ExcelTips: The Macros Page 699


Working with the Printer

Dim lPages As Long


Dim sTemp As String

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

Printing Based on Cell Contents


Theresa wonders if there is a way to format a cell so that if the contents of the cell meet certain
criteria then a specific worksheet is automatically printed. The short answer is no, there is no
way to use formatting to achieve this goal. You can, however, use an event handler macro to do
the printing.

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.

Private Sub Worksheet_Change(ByVal Target As Range)


Dim targCell As Range
Set targCell = Worksheets(1).Range("B2")

If Not Application.Intersect(Target, targCell) Is Nothing Then


If targCell.Value = 1001 Then
Worksheets(1).PrintOut
End If
End If
End Sub

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:

Private Sub Workbook_BeforePrint(Cancel As Boolean)


Application.EnableEvents = False
Select Case Worksheets("Sheet1").Range("E1")
Case 1
Worksheets("Sheet1").PrintOut
Case 2
Worksheets("Sheet2").PrintOut

ExcelTips: The Macros Page 700


Working with the Printer

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.

ExcelTips: The Macros Page 701


Special-Purpose Macros

Special-Purpose Macros

Running a Macro when a Workbook is Opened


You can cause Excel to run a procedure automatically whenever a particular workbook is
opened. For instance, when the workbook is opened, you might want to run a procedure that asks
the users if they want to perform some task, such as saving the previous day's data to another
file.

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

strMsg = "Do you want to save yesterday's transactions?"


intBoxType = vbYesNo + vbQuestion
strTitle = "Automatic Backup Routine"
intUpdate = MsgBox(Msg, BoxType, Title)
If intUpdate = vbYes Then
strMsg = "Which filename would you like use?"
strDefault = "OLD.DAT"
strOldFile = InputBox(strMsg, strTitle, strDefault)
intStatusState = Application.DisplayStatusBar
Application.DisplayStatusBar = True
Application.StatusBar = "Updating past months..."
UpdateYesterday
Application.StatusBar = False
Application.DisplayStatusBar = intStatusState
End If
End Sub

ExcelTips: The Macros Page 702


Special-Purpose Macros

Running a Macro when a Workbook is Closed


You can cause Excel to run a macro automatically whenever a particular workbook is closed. For
instance, when the workbook is closed you might want to run a macro that asks the users if they
want to perform some task, such as saving the day’s data to another file.

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

Running a Macro when a Worksheet is Activated


It is possible to configure Excel so that a macro of your choosing is executed every time a
particular worksheet is activated. What does that mean? Simply that a macro can be run every
time you click on the tab for a worksheet and display it. All you need to do is follow these steps
if you are using Excel 2007 or a later version:

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.

ExcelTips: The Macros Page 703


Special-Purpose Macros

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.

For older versions of Excel, do the following:

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.

ExcelTips: The Macros Page 704


Special-Purpose Macros

Running a Macro when a Worksheet is Deactivated


It is possible to configure Excel so that a macro of your choosing is executed every time a
particular worksheet is deactivated. What does that mean? Simply that a macro can be run every
time you click on a worksheet tab to leave the current sheet. All you need to do is follow these
steps:

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.

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.

ExcelTips: The Macros Page 705


Special-Purpose Macros

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.

Triggering an Event when a Worksheet is


Deactivated
John needs to ensure that certain actions have taken place (information added, etc.) before a user
leaves a worksheet. He wonders if there is there some sort of macro event such as
WorksheetBeforeDeactivate.

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:

Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)


MsgBox Prompt:="You just left sheet:" & Sh.Name
End Sub

ExcelTips: The Macros Page 706


Special-Purpose Macros

If you want to trigger actions only when they leave a particular worksheet, then you can use the
Deactivate event in the WorkSheet object:

Private Sub Worksheet_Deactivate()


' sheet specific code goes here
End Sub

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:

Private Sub Worksheet_Deactivate()


' sheet specific code goes here
Sheets("Sheet1").Select
End Sub

This assumes, of course, that the name of the worksheet you want the user to remain on is
Sheet1.

Running a Macro in a Number of Workbooks


If you have quite a number of workbooks that you want to process through the use of macros,
you may be tempted to place the processing macro within each workbook (as an Auto_Open
macro), and then write some type of routine to load each workbook, in turn, and save it.

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

ExcelTips: The Macros Page 707


Special-Purpose Macros

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

ExcelTips: The Macros Page 708


Special-Purpose Macros

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.

Mouse Click Event in VBA


Supriyo asked if there is a mouse event handler in VBA. He wants a value inserted in a cell when
that cell is clicked on.

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:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


If Not Intersect(Target, Range("B5")) Is Nothing Then _
Range("B5").Value = 10
End Sub

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:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)


If Target.Address = Range("B5").Address Then _
Range("B5").Value = 10
End Sub

Saving in Two Locations


Sam asked if there was a way to save the same workbook to two separate locations. For instance,
one copy could be saved to the normal network location, and the other to a folder on the local
hard drive.

ExcelTips: The Macros Page 709


Special-Purpose Macros

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:

Private Sub Workbook_BeforeSave(ByVal _


SaveAsUI As Boolean, Cancel As Boolean)
With ThisWorkbook
.SaveCopyAs ("c:\Backups\Backup of " & .Name)
End With
End Sub

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.

Saving in Multiple Locations


You may have a need to routinely copy a workbook to multiple locations on your system. For
instance, the open workbook may need to be copied to a local hard drive and to several mapped
drives that are actually on your office network.

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

ExcelTips: The Macros Page 710


Special-Purpose Macros

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:

• The program code cannot be altered by others.


• The program code runs a bit quicker.
• The add-in is available without needing to open any particular workbook.
• The functions provided by the add-in appear to be a part of Excel.

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.

ExcelTips: The Macros Page 711


Special-Purpose Macros

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:

1. Load the workbook that is destined to become your add-in.


2. Start the Visual Basic Editor by choosing Macro from the Tools menu, then choosing
Visual Basic Editor.
3. At the very top of the Project window, select the bold entry that declares the name of
the VBA project that is open.
4. Choose the Properties option from the Tools menu. This displays the Project Properties
dialog box.
5. Make sure the Protection tab is selected.

The Protection tab of the project’s Properties dialog box.

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.

ExcelTips: The Macros Page 712


Special-Purpose Macros

10. Choose Properties from the File menu. This displays the Properties dialog box for your
workbook.
11. Make sure the Summary tab is displayed.

The Summary tab of the workbook’s Properties dialog box.

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:

ExcelTips: The Macros Page 713


Special-Purpose Macros

1. Load the workbook that is destined to become your add-in.


2. Press ALT+F11. Excel displays the Visual Basic Editor.
3. At the very top of the Project window, select the bold entry that declares the name of
the VBA project that is open.
4. Choose the Properties option from the Tools menu. This displays the Project Properties
dialog box.
5. Make sure the Protection tab is selected.

The Protection tab of the project’s Properties dialog box.

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.

ExcelTips: The Macros Page 714


Special-Purpose Macros

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:

1. Load the workbook that is destined to become your add-in.


2. Press ALT+F11. Excel displays the Visual Basic Editor.
3. At the very top of the Project window, select the bold entry that declares the name of
the VBA project that is open.
4. Choose the Properties option from the Tools menu. Excel displays the Project
Properties dialog box.
5. Make sure the Protection tab is selected.
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. Display the File tab of the ribbon.
11. Make sure the Info option is selected at the left side of the dialog box.
12. Click the Properties link near the right side of the dialog box and then click Advanced
Properties. Excel displays the Properties dialog box for your workbook.
13. Make sure the Summary tab is displayed.

ExcelTips: The Macros Page 715


Special-Purpose Macros

The Summary tab of the workbook’s Properties dialog box.

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.

Using Custom Add-Ins


After you have created your own add-in, you can use it in your system. Once the add-in has been
loaded, the functions or features in the add-in become available to any other workbook you may

ExcelTips: The Macros Page 716


Special-Purpose Macros

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.

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.

ExcelTips: The Macros Page 717


Special-Purpose Macros

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.

Automatically Loading Add-ins


Michael asked if there is a way to selectively load add-ins for specific worksheets. There is a
way to do this, but it involves the use of macros attached to the Workbook module for the
specific worksheets. Follow these general steps:

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:

ExcelTips: The Macros Page 718


Special-Purpose Macros

Private Sub Workbook_BeforeClose(Cancel As Boolean)


AddIns("Add-In Name").Installed = False
End Sub

Private Sub Workbook_Open()


AddIns("Add-In Name").Installed = True
End Sub

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.

ExcelTips: The Macros Page 719


Searching and Sorting

Searching and Sorting

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)

ExcelTips: The Macros Page 720


Searching and Sorting

If Found.Address = FirstAddress Then Exit Do


Loop
End If
Next Sht
If Found Is Nothing Then ' Nothing found
Msg = "Not found! Do you want to start a new search?"
Style = vbYesNo + vbCritical + vbDefaultButton2
Else
Msg = "Search complete. Do you want to start a new search?"
Style = vbYesNo + vbDefaultButton2
End If
Title = "Search Complete"
Response = MsgBox(Msg, Style, Title)
If Response <> vbYes Then DoIt = False
Wend
MsgBox ("Search has ended.")
End Sub

Searching for All


Jack wonders how he can do a search for a certain word or phrase and, in one step, highlight all
the cells containing it so that he can cut or copy them and paste them elsewhere.

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.

The Find tab of the Find and Replace dialog box.

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.

ExcelTips: The Macros Page 721


Searching and Sorting

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

sInpt = InputBox("Enter the search text")

If sInpt > "" Then


For Each c In u
If Instr(LCase(sInpt),LCase(c.Value)) > 0 Then
If v Is Nothing Then
Set v = Range(c.Address)
Else
Set v = Union(v, Range(c.Address))
End If
End If
Next
v.Select
Set v = Nothing
End If

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

ExcelTips: The Macros Page 722


Searching and Sorting

If Selection.Cells.Count = 1 Then
MsgBox "Select the range to be searched."
Exit Sub
End If

'Specify search string


sSrch = InputBox(Prompt:="Enter the search text")

' Set the paste address


On Error Resume Next
Set rPaste = Application.InputBox(Prompt:="Enter the upper-left " & _
"cell address for the paste range", Type:=8)
On Error GoTo 0

' Exit if canceled


If TypeName(rPaste) <> "Range" Then Exit Sub

' Upper left cell to be used


Set rPaste = rPaste.Range("A1")

'Set where paste will start and headings


Application.ScreenUpdating = False
lTopR = rPaste.Row
iLeftC = rPaste.Column
Cells(lTopR, iLeftC) = "Address"
Cells(lTopR, iLeftC + 1) = "Cell Value"
lTopR = lTopR + 1

'Start copying cell values


With Selection
Set c = .Find(What:=sSrch, LookAt:=xlPart, MatchCase:=True)
If Not c Is Nothing Then
sFirst = c.Address
Do
Cells(lTopR, iLeftC) = c.Address
Cells(lTopR, iLeftC + 1) = c.Value
Set c = .FindNext(c)
lTopR = lTopR + 1
Loop While Not c Is Nothing And c.Address <> sFirst
End If
End With

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.

ExcelTips: The Macros Page 723


Searching and Sorting

Searching through Many Workbooks


Amit has a folder that contains hundreds of Excel workbooks. He needs to search through all the
workbooks for some specific text and wonders if there is a way to search through all the
workbooks and determine the names of the workbooks that contain the desired text, along with
the cells in the workbooks that contain that text.

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

On Error GoTo ErrHandler


Application.ScreenUpdating = False

'Change as desired
strPath = "c:\MyFolder"
strSearch = "Specific text"

Set wOut = Worksheets.Add


lRow = 1
With wOut
.Cells(lRow, 1) = "Workbook"
.Cells(lRow, 2) = "Worksheet"
.Cells(lRow, 3) = "Cell"
.Cells(lRow, 4) = "Text in Cell"
Set fso = CreateObject("Scripting.FileSystemObject")
Set fld = fso.GetFolder(strPath)

strFile = Dir(strPath & "\*.xls*")


Do While strFile <> ""
Set wbk = Workbooks.Open _
(Filename:=strPath & "\" & strFile, _
UpdateLinks:=0, _
ReadOnly:=True, _
AddToMRU:=False)

For Each wks In wbk.Worksheets


Set rFound = wks.UsedRange.Find(strSearch)

ExcelTips: The Macros Page 724


Searching and Sorting

If Not rFound Is Nothing Then


strFirstAddress = rFound.Address
End If
Do
If rFound Is Nothing Then
Exit Do
Else
lRow = lRow + 1
.Cells(lRow, 1) = wbk.Name
.Cells(lRow, 2) = wks.Name
.Cells(lRow, 3) = rFound.Address
.Cells(lRow, 4) = rFound.Value
End If
Set rFound = wks.Cells.FindNext(After:=rFound)
Loop While strFirstAddress <> rFound.Address
Next

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.

ExcelTips: The Macros Page 725


Searching and Sorting

Searching a Workbook by Default


Take a moment and display the Find tab of the Find and Replace dialog box. (The easiest way to
do this is to press CTRL+F.) When the dialog box is first displayed, Excel makes certain
assumptions about what exactly you want to search. What you want to search is dictated by the
setting of the Within drop-down list. (You may need to click the Options button to see the Within
drop-down list.)

The Find tab of the Find and Replace dialog box.

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

ExcelTips: The Macros Page 726


Searching and Sorting

The Find dialog box.

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.

Searching by Columns, by Default


When you use the Find command, Excel defaults to "search by row" as the order it will use in
looking for information. Your needs may vary, however; you may have a need to search by
column most of the time. You can certainly change this setting when you start the search, but
wouldn't it be nice to change the default so that Excel starts out by searching columns?

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:

Private Sub Workbook_Open()


On Error Resume Next
Cells.Find("", , , , xlByColumns, , , False) = True

ExcelTips: The Macros Page 727


Searching and Sorting

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.

Changing Default Search Settings


When Dan displays the Find dialog box, the default settings are to search within worksheet and
to look in formulas. He would like the default to be within workbook and to look in values, so he
is wondering if there is a way to change the default.

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:

ExcelTips: The Macros Page 728


Searching and Sorting

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.

Searching for Leading Apostrophes


Richard would like to be able to search for an apostrophe (') in the leftmost position in a cell, but
Excel won't let him do it. In other words, if a cell contains '123 or 'a34tp, Richard would like to
be able to find that leading apostrophe and, optionally, replace it with something else.

Doing what Richard wants to do takes a bit of preliminary explaining. Technically, it is a


misnomer to refer to the apostrophe as a “leading character” or mentioning that it is in the
“leftmost position” of a cell. Even though you may be able to look at the Formula bar and see the
apostrophe at the beginning of the formula, that apostrophe is not really a part of the cell’s
contents; that is why you can’t use Find and Replace to find and replace it.

ExcelTips: The Macros Page 729


Searching and Sorting

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:

For Each c In Selection


If c.PrefixCharacter = "'" Then
c.Value = c.Value
End If
Next c

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:

ExcelTips: The Macros Page 730


Searching and Sorting

1. Select a blank cell and copy it to the Clipboard (use CTRL+C).


2. Select the range of cells from which you want to remove the prefix character.
3. Display the Paste Special dialog box.
4. Click the Add radio button.
5. Click OK.

After the “adding” of the blank cell to each of the target cells, the prefix character—if any—is
removed.

Searching for Line Breaks


Veronica wondered how to search for a line break (ALT+ENTER) in a cell. In Word you can
search for ^l to find line breaks, but there does not seem to be a similar way to search for line
breaks in Excel.

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.

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:

ExcelTips: The Macros Page 731


Searching and Sorting

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

Searching for a Value Using a Function


Thor wonders if there is a way to perform a lookup without having to specify a specific column
or row and having the result be the address of the cell at which the value is found. For instance,
he wants to look up a value (such as 345 or "my text") and have the function search all the cells
in all the worksheets in the workbook and return the full address of the cell in which the value
was found.

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.

Function FindAddr(vValue As Variant)


Dim wks As Worksheet
Dim rCell As Range
Dim bFound As Boolean

bFound = False
For Each wks In ActiveWorkbook.Worksheets
With wks
Set rCell = .Cells.Find _
(What:=vValue, After:=.Cells(1), _

ExcelTips: The Macros Page 732


Searching and Sorting

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.

Where Is that Text?


Jay needs to determine the cell in which a particular text value occurs. He knows he can use
Excel's Find and Replace capabilities to manually determine the address of cells containing a text
value, but he is looking for a formula to determine the address of the cells. He wonders if there is
a way to search for piece of text within a range and have Excel return the address of the cell in
which the text is found.

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.

ExcelTips: The Macros Page 733


Searching and Sorting

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:

Function FindMe(x As Range, y As String) As String


Dim r As Range
Dim sResults As String
Dim sSearch As String

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.

Using Find and Replace to Pre-Pend Characters


Mel often wants to pre-pend a character to the beginning of whatever is in a range of cells. For
instance, he may want to add a letter to the start of some text (so "123" becomes "A123" and
"xyz" becomes "Axyz") or he may want to add an apostrophe (so "123" becomes "'123" and
"xyz" becomes "'xyz"). Mel wonders if this can be done using Find and Replace.

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.)

ExcelTips: The Macros Page 734


Searching and Sorting

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"

For Each rcell In Selection


If LCase(Mid(rcell.Value, 1, ToFindLength)) = LCase(ToFind) Then
rcell.Value = ToPrepend & rcell.Value
End If
Next
End Sub

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"

' Process only text and number constants


Set rng = Selection.SpecialCells(xlCellTypeConstants, 3)

ExcelTips: The Macros Page 735


Searching and Sorting

For Each c In rng


c.Value = ToPrepend & c.Value
Next c
End Sub

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.)

Replacing Characters at the End of a Cell


Sam has a large number of addresses in a worksheet. In those addresses he needs to make sure
that all compass directions (NE, SE, NW, and SW) are all uppercase. It would be very helpful if
Sam could figure out how to change any of these lowercase (or mixed case) directions that
appear only at the end of a cell with their uppercase counterparts. He can't just search for a space
followed by "ne", as that would change Newton to NEwton, so he wonders how he can make
sure that the replacement occurs only when the letters appear at the end of a cell.

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:

=LEFT(A1,LEN(A1)-2) & UPPER(RIGHT(A1,2))

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:

=IF(LOWER(RIGHT(A1,3))=" ne", LEFT(A1,LEN(A1)-2) & "NE",


IF(LOWER(RIGHT(A1,3))=" se", LEFT(A1,LEN(A1)-2) & "SE",
IF(LOWER(RIGHT(A1,3))=" nw", LEFT(A1,LEN(A1)-2) & "NW",
IF(LOWER(RIGHT(A1,3))=" sw", LEFT(A1,LEN(A1)-2) & "SW", A1))))

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(OR(LOWER(RIGHT(A1,3))=" ne", LOWER(RIGHT(A1,3))=" se",


LOWER(RIGHT(A1,3))=" nw", LOWER(RIGHT(A1,3))=" sw"),
LEFT(A1,LEN(A1)-2) & UPPER(RIGHT(A1,2)), A1)

ExcelTips: The Macros Page 736


Searching and Sorting

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.

Finding and Replacing Error Values


Lindsay often has huge worksheets with hundreds of rows of calculated values. Inevitably there
will be scattered cells with the #N/A error that she would like to all be 0 (or some other value) so
she can use the cells in other formulas. Due to the calculations Lindsay is running, she feels an IF
formula or other such method to anticipate and remove these values from the calculation is
usually impossible, and it's very tedious to remove them by hand. Lindsay wonders if there is
any way to do the equivalent of a "find and replace" on those error values.

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:

1. Press F5. Excel displays the Go To dialog box.

ExcelTips: The Macros Page 737


Searching and Sorting

The Go To dialog box.

2. Click Special. Excel displays the Go To Special dialog box.

The Go To Special dialog box.

3. Make sure the Formulas radio button is selected.


4. The only check box that should be selected under Formulas is Errors.
5. Click OK. Excel selects all cells where the formula returned an error value.
6. Type 0 or whatever value you want.
7. Press CTRL+ENTER.

ExcelTips: The Macros Page 738


Searching and Sorting

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.

Finding and Replacing in Text Boxes


David wonders if it is possible to use Find and Replace to locate and modify text in text boxes or
in labels in charts. The short answer is that it is not possible, but there are several workarounds
you can try.

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

ExcelTips: The Macros Page 739


Searching and Sorting

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.

Superscripts in Find and Replace


Kirk needs to search for things like "yd2" and replace it with "yd2" where the "2" is
superscripted. He wonders if there is a way to do that in Excel.

ExcelTips: The Macros Page 740


Searching and Sorting

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:

1. Replace all instances of yd2 with yd$*$


2. Replace all instances of $*$ with a superscripted 2.

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.

The Replace tab of the Find and Replace dialog box.

2. In the Find What box enter yd2.


3. In the Replace With box enter yd and then hold down the ALT key as you type 0178 on
the numeric keypad.
4. Click Replace All.

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.

ExcelTips: The Macros Page 741


Searching and Sorting

Sub DoConvert()
Dim c As Range

For Each c In Selection.Cells


If c.Value = "yd2" Then
c.Characters(3, 1).Font.Superscript = True
End If
Next
End Sub

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.

Wildcards in 'Replace With' Text


Anne-Mie realizes that she can use wildcards (*?) to search in Excel, but she wonders if she can
use wildcards in the replace string. For instance, she would like to search for “ab*de” and replace
it with “aa*de”, where the asterisk represents any number of characters, or none at all.

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”.

ExcelTips: The Macros Page 742


Searching and Sorting

Here is the code that implements the use of the REGEX object to do the actual replacements.

Public Function SearchNReplace1(Pattern1 As String, _


Pattern2 As String, Replacestring As String, _
TestString As String)
Dim reg As New RegExp

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"

For Each rCell In Selection


sTemp = rCell.Value
iLenInitial = Len(sFindInitial)
iLenFinal = Len(sFindFinal)
If Left(sTemp, iLenInitial) = sFindInitial And _
Right(sTemp, iLenFinal) = sFindFinal Then
sTemp = Mid(sTemp, iLenInitial + 1)
sTemp = Left(sTemp, Len(sTemp) - iLenFinal)

ExcelTips: The Macros Page 743


Searching and Sorting

sTemp = sReplaceInitial & sTemp & sReplaceFinal


rCell.Value = sTemp
End If
Next
Set rCell = Nothing
End Sub

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.

Finding Columns of a Certain Width


Howard has a need to discover all the columns in a worksheet that are a given width. For
instance, he needs to know which columns have a width of 3.6.

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

ExcelTips: The Macros Page 744


Searching and Sorting

Dim OutStr As String ' Output String


Dim Title As String ' Msgbox Title
Dim I As Integer
Dim S As String

' Find out column width wanted


S = InputBox("Enter ColumnWidth to find ?", _
" Find ColumnWidth on " & ActiveSheet.Name)
Desired_Width = Val(S)
If Desired_Width = 0 Then Exit Sub

' Initialize Columns Found Count and Output String


ColsFound = 0
OutStr = ""

For Col = 1 To ActiveSheet.Columns.Count


If Columns(Col).ColumnWidth = Desired_Width Then
ColsFound = ColsFound + 1

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

' Construct MsgBox Title string


Title = "Width=" & Desired_Width _
& " on " & ColsFound & " column" _
& Left("s", - (ColsFound > 1)) & " "

If ColsFound = 0 Then
OutStr = "No matches found"
End If

MsgBox OutStr, vbOKOnly, Title


End Sub

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

ExcelTips: The Macros Page 745


Searching and Sorting

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

Sub BubbleSort(sToSort() As String)


Dim Lower As Integer, Upper As Integer
Dim I As Integer, J As Integer, K As Integer
Dim Temp As String

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.)

ExcelTips: The Macros Page 746


Searching and Sorting

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:

Function SecLine(x) As String


Dim B1 As Integer
Dim B2 As Integer

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

ExcelTips: The Macros Page 747


Searching and Sorting

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.

Sorting Data on Protected Worksheets


When you protect a worksheet, Excel stops users from performing a wide variety of tasks on the
data in the worksheet. One of the things that the user can no longer do is to sort data. What if you
want the user to be able to sort data, but still have the sheet protected?

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.

ExcelTips: The Macros Page 748


Searching and Sorting

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

Dim sRangeAddress As String


Dim sStartCell As String
Dim sEndCell As String
Dim rngSort As Range
Dim rng As Range

Application.ScreenUpdating = False

sStartCell = InputBox("Enter the cell address of the " & _


"top cell in the range to be sorted by color" & _
Chr(13) & "i.e. 'A1'", "Enter Cell Address")

If sStartCell > "" Then


sEndCell = Range(sStartCell).End(xlDown).Address
Range(sStartCell).EntireColumn.Insert
Set rngSort = Range(sStartCell, sEndCell)
For Each rng In rngSort
rng.Value = rng.Offset(0, 1).Interior.ColorIndex
Next
Range(sStartCell).Sort Key1:=Range(sStartCell), _
Order1:=xlAscending, Header:=xlNo, _
Orientation:=xlTopToBottom
Range(sStartCell).EntireColumn.Delete
End If

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.

ExcelTips: The Macros Page 749


Searching and Sorting

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.

Sorting by Fill Color


Chuck wrote about a need he has to sort records in a worksheet based on the fill color used in a
cell. Excel provides no intrinsic function to perform such an action, but it is possible to create a
user-defined function that will help with any sorting that needs to be done. Consider the
following macro:

Function GetFillColor(rng As Range) As Long


GetFillColor = rng.Interior.ColorIndex
End Function

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:

Function GetColor(rngIndex As Range, rngSource As Range) As Long


Dim lngColor As Long
Dim J As Integer

Application.Volatile
lngColor = rngSource.Interior.ColorIndex

GetColor = 99 'Set to default color


For J = 1 To rngIndex.Count
If rngIndex(J).Interior.ColorIndex = lngColor Then
GetColor = J
End If
Next J
End Function

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:

ExcelTips: The Macros Page 750


Searching and Sorting

=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.

Sorting Data Containing Merged Cells


Excel has long included the ability to merge adjacent cells into a larger, single cell. This ability
has been used by many worksheet designers to give their worksheets a polished, professional
look.

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"

ExcelTips: The Macros Page 751


Searching and Sorting

.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

With Range(.Cells(2, 1), .Cells(3, 1))


.Merge
.Copy
.Cells(3, 1).Resize(lRows - 2, 1). _
PasteSpecial Paste:=xlFormats
End With
End With
Application.CutCopyMode = False
Range(sAddStart).Select
Application.ScreenUpdating = True
End Sub

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.

Determining Sorting Criteria


Suppose that a co-worker gives you have a worksheet that has several hundred rows of data in 27
columns. Before you start working with the data, you might want to know if it has previously
been sorted. Knowing the information may not only remove the need to resort the data, but will
also give you an idea as to what your co-worker felt was the most important way to look at the
data.

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

ExcelTips: The Macros Page 752


Searching and Sorting

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

'Identify Current Column and Current Sheet


CColumn = i
CSheet = ActiveSheet.Name
FlagSort = ""

'Add a temporary sheet to test for sorting


Sheets.Add
ActiveSheet.Name = "TempSort"

'Copy CURRENT column to Columns A,B in Current Sheet


Sheets(CSheet).Select
Columns(CColumn).Select
Selection.Copy

Sheets("TempSort").Select
Range("A1").Select
ActiveSheet.Paste
Range("B1").Select
ActiveSheet.Paste
Application.CutCopyMode = False

'In Column C test for equality of Columns A/B


'If Sum in C1=0 then OK otherwise Col A<>Col B
Range("B2").Select
Selection.End(xlDown).Select
Bottom = ActiveCell.Row
Range(Cells(2, 3), Cells(Bottom, 3)).Select
Selection.FormulaArray = "=IF(RC[-2]=RC[-1],0,1)"
Range("C1").Select
ActiveCell.FormulaR1C1 = "=SUM(R[1]C:R[6535]C)"

'Sort Column B--Ascending - See if c1=0


Columns("B:B").Select
Selection.Sort Key1:=Range("B2"), Order1:=xlDescending, _
Header:=xlYes, OrderCustom:=1, MatchCase:=False, _
Orientation:=xlTopToBottom, DataOption1:=xlSortNormal
If Cells(1, 3).Value = 0 Then FlagSort = "Ascending"

ExcelTips: The Macros Page 753


Searching and Sorting

'Sort Column B--Descending - See if c1=0


Columns("B:B").Select
Selection.Sort Key1:=Range("B2"), Order1:=xlAscending, _
Header:=xlYes, OrderCustom:=1, MatchCase:=False, _
Orientation:=xlTopToBottom, DataOption1:=xlSortNormal
If Cells(1, 3).Value = 0 Then FlagSort = "Descending"

If FlagSort = "Ascending" Then


'Color Header on original sheet yellow
Sheets(CSheet).Cells(1, CColumn).Interior.ColorIndex = 36
End If

If FlagSort = "Descending" Then


'Color Header on original sheet orange
Sheets(CSheet).Cells(1, CColumn).Interior.ColorIndex = 44
End If

'Delete temporary sheet


Sheets("TempSort").Select
ActiveWindow.SelectedSheets.Delete
End Sub

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.

Storing Sorting Criteria


Stephanie often has to perform sorts of her data using the same criteria over and over again. For
instance, she often needs to perform a sort that uses the same three columns. She wonders if
there is a way to "store" a set of sorting criteria so she doesn't need to enter them over and over
again.

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.

ExcelTips: The Macros Page 754


Searching and Sorting

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

Automatically Sorting as You Enter Information


Pat wonders if there is a way to automatically sort every time she adds new data to a worksheet.
Pat thinks it would be great, for instance, that when she adds a new name to a list of names that
the names are automatically sorted to always be in order.

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:

Private Sub Worksheet_Change(ByVal Target As Range)


On Error Resume Next
Range("A1").Sort Key1:=Range("A2"), _
Order1:=xlAscending, Header:=xlYes, _
OrderCustom:=1, MatchCase:=False, _
Orientation:=xlTopToBottom
End Sub

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.

Private Sub Worksheet_Change(ByVal Target As Range)


On Error Resume Next
If Not Intersect(Target, Range("A:A")) Is Nothing Then
Range("A1").Sort Key1:=Range("A2"), _
Order1:=xlAscending, Header:=xlYes, _

ExcelTips: The Macros Page 755


Searching and Sorting

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.

ExcelTips: The Macros Page 756


Macro Cookbook

Macro Cookbook

AutoFilling with the Alphabet


Marlene is a teacher, and she has students who love word searches. She finds it quite time
consuming to make them, but the students seem to remember the course material much better
when she uses them. Marlene wondered if there was some way to AutoFill a range of cells with
letters of the alphabet, A through Z. That way she can use the feature to fill in the squares of the
word search with letters, before she replaces some of those letters with the actual words to be
searched.

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.

ExcelTips: The Macros Page 757


Macro Cookbook

The Custom Lists tab of the Options dialog box.

3. In the Custom Lists column, make sure NEW LIST is selected.


4. In the List Entries box, enter each letter of the alphabet, one letter per line. (Press ENTER
after each letter you type.)
5. Click Add.

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.

ExcelTips: The Macros Page 758


Macro Cookbook

The Custom Lists dialog box.

4. In the Custom Lists column, make sure NEW LIST is selected.


5. In the List Entries box, enter each letter of the alphabet, one letter per line. (Press ENTER
after each letter you type.)
6. Click Add.

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:

ExcelTips: The Macros Page 759


Macro Cookbook

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

Title = "AlphaFill Cell Selection"


Default = Selection.Address
Prompt = vbCrLf _
& "Use mouse in conjunction with " _

ExcelTips: The Macros Page 760


Macro Cookbook

& "SHIFT and CTRL keys to" & vbCrLf _


& "click and drag or type in name(s) " _
& "of cell(s) to AlphaFill" & vbCrLf & vbCrLf _
& "Currently selected cell(s): " _
& Selection.Address

On Error Resume Next


Set rangeSelected = Application.InputBox(Prompt, Title, _
Default, Type:=8)
If rangeSelected Is Nothing Then Exit Sub

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.

Numbers Spelled Out


There are times when it is beneficial, or even mandatory, to spell numbers out. For instance, you
may want to spell out “1234” as “one thousand two hundred thirty four.” The following macro,
NumberToWords, does just that. It is rather long, but it has to do a lot of checking to put together
the proper string. There are actually five macros in the set; the four besides NumberToWords are
called by NumberToWords to do the actual conversion.

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

Set rngSrc = ActiveSheet.Range(ActiveWindow.Selection.Address)


lMax = rngSrc.Cells.Count

bNCFlag = False
For lCtr = 1 To lMax
vCVal = rngSrc.Cells(lCtr).Value
sWords = ""

ExcelTips: The Macros Page 761


Macro Cookbook

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

Private Function SetOnes(ByVal lNumber As Integer) As String


Dim OnesArray(9) As String
OnesArray(1) = "One"
OnesArray(2) = "Two"
OnesArray(3) = "Three"
OnesArray(4) = "Four"
OnesArray(5) = "Five"
OnesArray(6) = "Six"
OnesArray(7) = "Seven"
OnesArray(8) = "Eight"
OnesArray(9) = "Nine"
SetOnes = OnesArray(lNumber)
End Function

Private Function SetTens(ByVal lNumber As Integer) As String


Dim TensArray(9) As String
TensArray(1) = "Ten"
TensArray(2) = "Twenty"
TensArray(3) = "Thirty"
TensArray(4) = "Fourty"
TensArray(5) = "Fifty"
TensArray(6) = "Sixty"
TensArray(7) = "Seventy"
TensArray(8) = "Eighty"
TensArray(9) = "Ninety"
Dim TeensArray(9) As String
TeensArray(1) = "Eleven"
TeensArray(2) = "Twelve"
TeensArray(3) = "Thirteen"
TeensArray(4) = "Fourteen"
TeensArray(5) = "Fifteen"
TeensArray(6) = "Sixteen"

ExcelTips: The Macros Page 762


Macro Cookbook

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

Private Function SetHundreds(ByVal lNumber As Integer) As String


Dim iTemp1 As Integer
Dim iTemp2 As Integer
Dim sTemp As String
iTemp1 = Int(lNumber / 100)
iTemp2 = lNumber Mod 100
If iTemp1 > 0 Then sTemp = SetOnes(iTemp1) + " Hundred"
If iTemp2 > 0 Then
If sTemp > "" Then sTemp = sTemp + " "
If iTemp2 < 10 Then sTemp = sTemp + SetOnes(iTemp2)
If iTemp2 > 9 Then sTemp = sTemp + SetTens(iTemp2)
End If
SetHundreds = sTemp
End Function

Private Function SetThousands(ByVal lNumber As Long) As String


Dim iTemp1 As Integer
Dim iTemp2 As Integer
Dim sTemp As String
iTemp1 = Int(lNumber / 1000)
iTemp2 = lNumber Mod 1000
If iTemp1 > 0 Then sTemp = SetHundreds(iTemp1) + " Thousand"
If iTemp2 > 0 Then
If sTemp > "" Then sTemp = sTemp + " "
sTemp = sTemp + SetHundreds(iTemp2)
End If
SetThousands = sTemp
End Function

Converting Phone Numbers


We have all seen the ads on TV: “Call 1-800-GET THIS for your set of super-sharp knives.”
You may be faced with the need to convert phone numbers from the text version (as shown on
the ads) to the numbers represented by that text. The following macro, DoPhone, will perform
the conversion magic for you:

Sub DoPhone()

ExcelTips: The Macros Page 763


Macro Cookbook

Dim rngSrc As Range


Dim lMax As Long, lCtr As Long
Dim J As Integer
Dim Phone As String, Digit As String

Set rngSrc = ActiveSheet.Range(ActiveWindow.Selection.Address)


lMax = rngSrc.Cells.Count

For lCtr = 1 To lMax


If Not rngSrc.Cells(lCtr).HasFormula Then
Phone = rngSrc.Cells(lCtr).Value
For J = 1 To Len(Phone)
Digit = Ucase(Mid(Phone, J, 1))
Select Case Digit
Case "A" To "P"
Digit = Chr((Asc(Digit) + 1) \ 3 + 28)
Case "Q"
Digit = "7" 'May want to change
Case "R" To "Y"
Digit = Chr(Asc(Digit) \ 3 + 28)
Case "Z"
Digit = "9" 'May want to change
End Select
Mid(Phone, J, 1) = Digit
Next J
rngSrc.Cells(lCtr).Value = Phone
End If
Next lCtr
End Sub

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.)

Adding Leading Zeroes to ZIP Codes


When you import ZIP Codes from a text file into an Excel workbook, it is not uncommon for
Excel to translate the values as numbers rather than as ZIP Codes. This results in leading zeroes
being dropped from the ZIP Codes, which can obviously cause problems later using the data for
its intended purpose.

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:

ExcelTips: The Macros Page 764


Macro Cookbook

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.

Shortening ZIP Codes


In the United States, ZIP Codes come in two formats: five-digit and nine-digit. (Actually, the
five-digit ZIP Code is a subset of the nine-digit ZIP Code.) If you are an Excel worksheet that
contains address information, you may want to convert nine-digit ZIP Codes to their five-digit
equivalent.

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)

3. Copy this formula into all the appropriate cells of column H.


4. Select the entire column H.
5. Press CTRL+C. Excel copies the entire column to the Clipboard.
6. Display the Paste Special dialog box. (In Excel 2007 or later display the Home tab of
the ribbon, click the down-arrow under the Paste tool, and choose Paste Special from
the resulting choices. In older versions of Excel choose Paste Special from the Edit
menu.)

ExcelTips: The Macros Page 765


Macro Cookbook

The Paste Special dialog box.

7. Make sure the Values radio button is selected.


8. Click on OK. Column H has now been transformed from formulas into the formula
results.
9. Delete column G.

If you have an empty column to the right of your ZIP Codes, you can also use Excel's Text to
Columns feature:

1. Select all the cells that contain your ZIP Codes.


2. 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.)

ExcelTips: The Macros Page 766


Macro Cookbook

The Convert Text to Columns Wizard

3. Make sure the Delimited option is selected, then click on Next.


4. Select the Other check box, place a single dash in the box just to the right of Other, then
click on Next.
5. In the Column Data Format area, select Text. (You want your ZIP Codes to be
formatted as text so that you don't lose any leading zeros.)
6. Click on Finish.

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.

ExcelTips: The Macros Page 767


Macro Cookbook

Working with Imperial Linear Distances


Peter asked if it was possible in Excel to create a custom number format that will deal with
imperial linear distances, such as inches, feet, miles, etc. The short answer is that no, this is not
possible. Excel works natively in the decimal system, and many imperial measuring systems are
based on other numeric systems (feet on base 12, for instance). While custom formatting can
change the way that numbers are displayed, it cannot perform the conversions necessary for
imperial measurements.

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:

Function N2MYFI(x) As String


Dim x1 as Long
Dim FinalAnswer As String

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.

ExcelTips: The Macros Page 768


Macro Cookbook

Calculating the Distance between Points


Mike tracks latitude and longitude values in an Excel worksheet. As these are essentially points
on a grid, Mike would like to calculate the distance between any two given latitude/longitude
points.

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:

ExcelTips: The Macros Page 769


Macro Cookbook

=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:

Function CrowFlies(dlat1, dlon1, dlat2, dlon2)


Pi = Application.Pi()
earthradius = 3443.89849 'nautical miles

lat1 = dlat1 * Pi / 180


lat2 = dlat2 * Pi / 180
lon1 = dlon1 * Pi / 180
lon2 = dlon2 * Pi / 180

cosX = Sin(lat1) * Sin(lat2) + Cos(lat1) _


* Cos(lat2) * Cos(lon1 - lon2)
CrowFlies = earthradius * Application.Acos(cosX)
End Function

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:

ExcelTips: The Macros Page 770


Macro Cookbook

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

Selecting Random Names


A common task for many people is to pick a number of random names from a large list. For
instance, you may be running a contest for your community, and you have 1,000 people that
have entered. With their names in each row of a table, you may be wondering how to select a
certain number of the names randomly.

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)

A similar formula uses the OFFSET function:

=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:

1. Enter =RAND() in cell B1.


2. Enter the following formula in cell C1:

ExcelTips: The Macros Page 771


Macro Cookbook

=RANK(B1,$B$1:$B$1000)

3. Select the range B1:C1, and fill down to row 1000.


4. Select the range B1:C1000.
5. Press CTRL+C to copy the range to the Clipboard.
6. Display the Home tab of the ribbon.
7. Click the down-arrow under the Paste tool and then select Paste Special. Excel displays
the Paste Special dialog box.

The Paste Special dialog box.

8. Make sure the Values radio button is selected.


9. Click on OK. You now have static values in B1:C1000, which means they won't change
every time the worksheet is recalculated.
10. Select a cell in column C.
11. Display the Data tab of the ribbon.
12. Click the Sort tool. Excel display the Sort dialog box.

ExcelTips: The Macros Page 772


Macro Cookbook

The Sort dialog box.

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

Set TempDO = New DataObject

iRows = Selection.Rows.Count
iCols = Selection.Columns.Count
iBegRow = Selection.Row
iBegCol = Selection.Column

ExcelTips: The Macros Page 773


Macro Cookbook

If iRows < 16 Or iCols > 1 Then


MsgBox "Too few rows or too many columns"
Else
Randomize Timer
sCells = ""
For J = 1 To 15
iWantRow = Int(Rnd() * iRows) + iBegRow
sCells = sCells & Cells(iWantRow, iBegCol) & vbCrLf
Next J
TempDO.SetText sCells
TempDO.PutInClipboard
End If
End Sub

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.

Generating Random Testing Data


When you develop worksheets that will be used by other people, you should test those
worksheets to make sure that they work as you expect. This is particularly true of worksheets
that contain complex formulas or will be used for critical purposes. The concept of testing a
worksheet means that you will need to generate some sort of data to use in testing the worksheet.

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.

ExcelTips: The Macros Page 774


Macro Cookbook

Sub RandNums()
Dim MyRange As Range
Dim lMin As Long, lMax As Long
Dim dRand As Double

' If selection is not Excel Range


If TypeName(Selection) <> "Range" Then Exit Sub

Set MyRange = Selection

' Get Min and Max value


lMin = CLng(InputBox("Minimum?"))
lMax = CLng(InputBox("Maximum?"))

Randomize
Application.ScreenUpdating = False

For Each c In MyRange.Cells


' Calculate random value, where
' Value >= Min And Value <= Max
dRand = Rnd * (lMax - lMin) + lMin

' Use the following line only if the random


' value should be an integer
' dRand = Int(dRand)

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

' If selection is not Excel Range


If TypeName(Selection) <> "Range" Then Exit Sub

Set MyRange = Selection

' Get Min and Max value


' From: 1/1/1990 (put your start Date)
dtMin = #1/1/1990#
' To: Today
dtMax = Date

Randomize
Application.ScreenUpdating = False

For Each c In MyRange.Cells


' Calculate random value, where

ExcelTips: The Macros Page 775


Macro Cookbook

' Value >= Min And Value <= Max


dtRand = Rnd * (dtMax - dtMin) + dtMin
dtRand = Int(dtRand)

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.

Zooming with the Keyboard


For most purposes, Excel allows you to issue commands and perform functions by using either
the mouse or the keyboard. Unfortunately, Excel does not provide “equal access” for all
commands. For instance, it is relatively easy to zoom in or out using the mouse, but there is no
easy way to do it using the keyboard (other than using the keyboard to traverse the menus and
select a zoom setting).

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.

ExcelTips: The Macros Page 776


Macro Cookbook

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.

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.

ExcelTips: The Macros Page 777


Macro Cookbook

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.

Always Open at 100% Zoom


If you work with workbooks first worked on by your colleagues, you may be frustrated by the
zoom factor applied to those workbooks by those others. For instance, if your colleague (Wanda)
has a huge monitor, it wouldn’t be uncommon for her to reduce the zoom factor on Excel to 75%
or even 60%. The purpose, of course, is so she isn’t overpowered by things that look very large
at the full zoom factor.

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.)

Private Sub Workbook_Open()


ActiveWindow.Zoom = 100
End Sub

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.

ExcelTips: The Macros Page 778


Macro Cookbook

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:

Declare Function GetSystemMetrics32 Lib "user32" _


Alias "GetSystemMetrics" (ByVal nIndex As Long) As Long

Public Sub ScreenRes()


Dim lResWidth As Long
Dim lResHeight As Long
Dim sRes As String

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:

Private Sub Workbook_Open()


ScreenRes
End Sub

Determining If a Number is Odd or Even


A common programming task is validating user input. Often, your macro may need to determine
if a number entered by a user is odd or even. For instance, suppose you wrote your own macro
that asked the user what worksheet number they wanted to process. If your macro had to process
odd and even worksheets differently, then you need to figure out if the number the user provided
was odd or even. The technique for this is relatively simple, as shown here:

Even = (UserNum Mod 2) - 1

After execution of this line, Even will be True (-1) if UserNum was even, or False (0) if
UserNum was odd.

ExcelTips: The Macros Page 779


Macro Cookbook

Playing with a Full Deck


How’s that for a tip title? The title refers to the fact that you may have a need to populate a range
of cells with a series of numbers in random order. For instance, you might want to populate 52
cells with the numbers 1 through 52, in random order. (This would be similar to drawing cards
from a shuffled deck, thus the tip title.)

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)

'Fill the initial array


For j = 1 To maxval
nums(j, 1) = j
nums(j, 2) = Int((Rnd * maxval) + 1)
Next j

'Sort the array based on the random numbers


For j = 1 To maxval - 1
Ptr = j
For k = j + 1 To maxval
If nums(Ptr, 2) > nums(k, 2) Then Ptr = k
Next k
If Ptr <> j Then
k = nums(Ptr, 1)
nums(Ptr, 1) = nums(j, 1)
nums(j, 1) = k
k = nums(Ptr, 2)
nums(Ptr, 2) = nums(j, 2)
nums(j, 2) = k
End If
Next j

'Fill in the cells


Ptr = 0
For j = 1 To nrows
For k = 1 To ncols
Ptr = Ptr + 1
s.Cells(j, k) = nums(Ptr, 1)
Next k
Next j
End Sub

ExcelTips: The Macros Page 780


Macro Cookbook

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.

Counting Wins and Losses


Graham has, in Excel, created a matrix of player names for his league. Cells B2:H2 contain the
names of the players, as do cells A3:A9. At each intersection in the matrix, Graham places a “W”
or “L” to indicate whether the match-up resulted in a win or loss for the player in each row. If a
player plays another person more than once, then a cell contains a “W” or “L” for each game.
Graham was wondering what formula could be used, starting in column I, to indicate the number
or wins and losses for each player.

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","")))

ExcelTips: The Macros Page 781


Macro Cookbook

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:

Function CharNums(r, chr) As Integer


Dim c As Range
Dim strX As String
Dim J As Integer

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.

Row height is pretty straightforward—it is measured in points. Column width, however, is


measured in character widths. If your Standard style is set to Courier 10, then a column width of
12 means that you can fit exactly twelve characters in a given column. For proportional fonts, the
character 0 is used to count the characters. (Yup, it’s absurd.)

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

Temp = InputBox("Height and width in inches?")


DInch = Val(Temp)

ExcelTips: The Macros Page 782


Macro Cookbook

If DInch > 0 And DInch < 2.5 Then


For Each c In ActiveWindow.RangeSelection.Columns
WPChar = c.Width / c.ColumnWidth
c.ColumnWidth = ((DInch * 72) / WPChar)
Next c
For Each r In ActiveWindow.RangeSelection.Rows
r.RowHeight = (DInch * 72)
Next r
End If
End Sub

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

Set MyRange = ActiveSheet.Range(ActiveWindow.Selection.Address)


TotalWords = 0
For CellCount = 1 To MyRange.Cells.Count
If Not MyRange.Cells(CellCount).HasFormula Then
Raw = MyRange.Cells(CellCount).Value
Raw = Trim(Raw)
If Len(Raw) > 0 Then
NumWords = 1
Else
NumWords = 0

ExcelTips: The Macros Page 783


Macro Cookbook

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.

Default Worksheet when Opening


When you open a workbook, Excel normally displays the worksheet last displayed when the
workbook was last saved. You may want a specific worksheet to always be displayed when the
workbook is opened, regardless of the worksheet displayed when the workbook was last saved.

You can control which worksheet is displayed by using this macro:

Private Sub Workbook_Open()


Worksheets("StartSheet").Activate
End Sub

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.

ExcelTips: The Macros Page 784


Macro Cookbook

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.

Protecting Individual Worksheets, by User


Excel allows you to protect individual worksheets, as you have learned in other issues of
ExcelTips. You can use this approach to protect individual worksheets independently, using
different passwords. This means that one user could make changes to one worksheet using one
password, and another could use a different password to make changes to the other worksheet.

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:

ExcelTips: The Macros Page 785


Macro Cookbook

1. Press CTRL+F11 to display the VBA Editor.


2. In the VBA Editor, choose User Form from the Insert menu. A new, blank user form
displays, along with the form toolbox.
3. Using the controls in the form toolbox, add a TextBox control where the user will enter
their user name.
4. Change the properties for the TextBox control so that its Name is txtUser.
5. Using the controls in the form toolbox, add a TextBox control where the user will enter
their password.
6. Change the properties for the TextBox control so that its Name is txtPass.
7. Just under the TextBox controls, add a CommandButton control.
8. Change the properties for the CommandButton control so its Name is btnOK and its
Caption is OK.

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:

Dim bOK2Use As Boolean

Private Sub btnOK_Click()


Dim bError As Boolean
Dim sSName As String
Dim p As DocumentProperty
Dim bSetIt As Boolean

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

ExcelTips: The Macros Page 786


Macro Cookbook

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

Private Sub UserForm_Terminate()


If Not bOK2Use Then
ActiveWorkbook.Close (False)
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.

Private Sub Workbook_BeforeClose(Cancel As Boolean)


Dim w As Worksheet
Dim bSaveIt As Boolean

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

ExcelTips: The Macros Page 787


Macro Cookbook

Next w
If bSaveIt Then
ActiveWorkbook.CustomDocumentProperties("auth").Delete
ActiveWorkbook.Save
End If
End Sub

Private Sub Workbook_Open()


UserForm1.Show
End Sub

Private Sub Workbook_SheetActivate(ByVal Sh As Object)


If Sh.Name <> "Main" Then
If Sh.Name <> ActiveWorkbook.CustomDocumentProperties("auth").Value Then
Sh.Visible = False
MsgBox "You don't have authorization to view that sheet!"
End If
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.

Another interesting macro here is the Workbook_SheetActivate procedure. This is included in


case a user tries to unhide another user's worksheet. In this case, the user's authorized sheet name
(stored in a document variable when the user was originally authorized) is compared to the sheet
being displayed. If it doesn't match, then the user isn't allowed to view the worksheet. Note, as
well, that this procedure references a worksheet called "Main". This worksheet is the third
worksheet mentioned at the beginning of this tip. This worksheet is also the one first displayed
when the workbook is opened.

Unprotecting Groups of Worksheets


Excel allows you to protect and unprotect worksheets. The purpose, of course, is to allow others
to use your workbook, but not to modify certain cells within each worksheet.

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.

A less time-consuming method of protecting each worksheet in a workbook is to use a macro to


do the actual work. The following macro will do the trick:

Sub ProtectAllSheets()
Dim ws As Worksheet
Dim sOrigSheet As String
Dim sOrigCell As String
Dim J As Integer

ExcelTips: The Macros Page 788


Macro Cookbook

Application.ScreenUpdating = False
sOrigSheet = ActiveSheet.Name
sOrigCell = ActiveCell.Address

For Each ws In Worksheets


ws.Select
ws.Protect Password:="Password"
Next ws

Application.GoTo Reference:=Worksheets("" _
& sOrigSheet & "").Range("" & sOrigCell & "")
Application.ScreenUpdating = True
End Sub

The macro to unprotect all the worksheets is only slightly different:

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

For Each ws In Worksheets


ws.Select
ws.Unprotect Password:="Password"
Next ws

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

ExcelTips: The Macros Page 789


Macro Cookbook

sPWord = InputBox("What password?", "Protect All")


If sPWord > "" Then
For Each ws In Worksheets
ws.Select
ws.Protect Password:=sPWord
Next ws
End If
Application.GoTo Reference:=Worksheets("" _
& sOrigSheet & "").Range("" & sOrigCell & "")
Application.ScreenUpdating = True
End Sub

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.

Creating Individual Workbooks


If you use Excel quite a bit, you know you may get some rather large workbooks from
colleagues. Often it is desirable to break the workbook down, so that each worksheet is in its
own workbook. While this can be done manually, the process quickly becomes tedious if you
have a lot of breaking down to do.

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:\"

For Each sht In ActiveWorkbook.Worksheets


sht.Copy
NFName = WBPath & sht.Name & ".xls"
ActiveWorkbook.SaveAs FileName:=NFName, _
FileFormat:=xlNormal, CreateBackup:=False
ActiveWindow.Close
Next
End Sub

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

ExcelTips: The Macros Page 790


Macro Cookbook

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.

Disappearing Toolbar Buttons for Macros


Gary wrote about a frustration he experienced with Excel. He had some macros, stored in his
Personal.xls workbook, that were assigned to some custom toolbar buttons. Out of the blue, the
macros disappeared, and Gary couldn’t figure out why.

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.

ExcelTips: The Macros Page 791


Macro Cookbook

Sheets for Months


When you are starting a new workbook, it is very common to name each worksheet after a
different month of the year. If you do this quite a bit, you know it can be tiresome to rename each
worksheet, in turn, to exactly what you need.

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

ExcelTips: The Macros Page 792


Macro Cookbook

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.

Macro for Month Name


Brian is looking for a macro that returns the full name of the current month, such as July,
August, etc. Before getting to the macros, it should be mentioned that depending on your needs,
you can get the desired information with one of several formulas. Perhaps the easiest formula is
the following:

=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:

1. Select the cell containing the formula.


2. Display the Format Cells dialog box. (In Excel 2007 or later display the Home tab of
the ribbon and click the small icon at the lower-right of the Number group. In older
versions of Excel choose Cells from the Format menu.)
3. Make sure the Number tab is displayed.
4. In the list of format categories, select Custom.

ExcelTips: The Macros Page 793


Macro Cookbook

The Number tab of the Format Cells dialog box.

5. In the Type box, enter mmmm.


6. Click OK.

Another formulaic approach is to use the following in a cell:

=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")

Remember that this is a single formula; it goes all in one cell.

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:

ExcelTips: The Macros Page 794


Macro Cookbook

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.

Naming Tabs for Weeks


When you are starting a new workbook, one common scenario calls for creating a year’s worth
of worksheets, one for each week of the year. In other words, a workbook could end up
containing 52 or 53 worksheets, depending on how many weeks there are in a particular year.

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

sTemp = InputBox("Date for the first worksheet:", "End of Week?")


dSDate = CDate(sTemp)

ExcelTips: The Macros Page 795


Macro Cookbook

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.

Jumping to Alphabetic Worksheets


If you have a workbook containing quite a few worksheets, you may be looking for an easier
way to jump to a specific group of worksheets, rather than use the scrolling arrows near the
worksheet tabs. For instance, you might want to enter a single letter and have Excel display the
first worksheet that begins with that letter. There is a way to do this directly within Excel.
Simply follow these steps:

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.

The Activate dialog box.

3. Press the first letter of the worksheet name you want. The first worksheet that begins
with that letter is selected.

ExcelTips: The Macros Page 796


Macro Cookbook

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

sSheet = InputBox("Enter first letter of sheet", _


"Go to sheet", Left(ActiveSheet.Name, 1))
If sSheet = "" Then Exit Sub
sSheet = UCase(Left(sSheet, 1))
iTemp = 0
For i = 1 To ThisWorkbook.Sheets.Count
sThisOne = UCase(Left(ThisWorkbook.Sheets(i).Name, 1))
If sThisOne = sSheet Then
iTemp = i
Exit For
End If
Next i
If iTemp > 0 Then
ThisWorkbook.Sheets(iTemp).Activate
End If
End Sub

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.

ExcelTips: The Macros Page 797


Macro Cookbook

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:

1. Insert a new column immediately to the left of your data table.


2. In the cells of the new column, enter the numbers 1 through however many rows there
are in your table.
3. Select the rows that make up your data table.
4. Choose Sort from the Data menu. Excel displays the Sort dialog box. (Display the Sort
dialog box in Excel 2007 or later versions by clicking the Data tab of the ribbon and
then clicking Sort in the Sort & Filter group.)

The Sort dialog box.

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

ExcelTips: The Macros Page 798


Macro Cookbook

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.Rows.Count
Do While iStart < iEnd
vTop = Selection.Rows(iStart)
vEnd = Selection.Rows(iEnd)
Selection.Rows(iEnd) = vTop
Selection.Rows(iStart) = vEnd
iStart = iStart + 1
iEnd = iEnd – 1
Loop
Application.ScreenUpdating = True
End Sub

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.

Counting Shaded Cells


Excel allows you to apply all sorts of formatting to the cells in your workbook. One of the things
you can do is to “shade” cells using a pattern or color. (You do this on the Patterns tab of the

ExcelTips: The Macros Page 799


Macro Cookbook

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:

Function FindShades(a As Range) As Integer


FindShades = 0
For Each c In a
If c.Interior.ColorIndex <> xlColorIndexNone Then
FindShades = FindShades + 1
End If
Next c
End Function

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)

Counting Unique Values


Sometimes you need to know the number of unique values in a range of cells. For instance,
suppose that an instructor was teaching the following classes:

104-120
104-101

ExcelTips: The Macros Page 800


Macro Cookbook

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:

Function CountUnique(ByVal MyRange As Range) As Integer


Dim Cell As Range
Dim J As Integer
Dim iNumCells As Integer
Dim iUVals As Integer
Dim sUCells() As String

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

ExcelTips: The Macros Page 801


Macro Cookbook

CountUnique = iUVals
End Function

Simply put an equation similar to the following in a cell:

=CountUnique(MyRange)

The value returned is the number of unique values, not counting blanks, in the range.

Counting Cells According to Case


If you are using Excel to analyze a group of cells containing text, you may want to determine the
number of cells that contain uppercase, the number that contain lowercase, and the number that
contain mixed case. There are two ways you can approach this task: Using a regular worksheet
formula, or defining your own user-defined function.

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<>""))

ExcelTips: The Macros Page 802


Macro Cookbook

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:

• Step through each cell of a range


• Determine if the cell is upper, lower, or mixed case
• Increment some counter
• Return a value

The following macro is one example of how the above can be implemented:

Function CountCase(rng As Range, sCase As String) As Long


Dim vValue
Dim lUpper As Long
Dim lMixed As Long
Dim lLower As Long
Dim rCell As Range
lUpper = 0
lLower = 0
lMixed = 0

For Each rCell In rng


If Not IsError(rCell.Value) Then
vValue = rCell.Value
If VarType(vValue) = vbString _
And Trim(vValue) <> "" Then
If vValue = UCase(vValue) Then
lUpper = lUpper + 1
ElseIf vValue = LCase(vValue) Then
lLower = lLower + 1
Else
lMixed = lMixed + 1
End If
End If
End If
Next
Select Case UCase(sCase)
Case "U"
CountCase = lUpper
Case "L"
CountCase = lLower
Case "M"
CountCase = lMixed
Case Else
CountCase = CVErr(xlErrValue)

ExcelTips: The Macros Page 803


Macro Cookbook

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.

Only Showing the Maximum of Multiple Iterations


Mike has three cells (A1:A3) that show results of calculations. He needs a way to determine the
maximum value that has ever appeared in any of these cells, and have that value stored in cell
E5. He knows how to get the maximum out of the three, but when he recalculates the worksheet,
if the values in A1:A3 are less than the maximum value in E5 (based on previous determinations
of the maximum in A1:A3), then E5 should not change. In other words, E5 should only change if
whatever is in A1:A3 is greater than what is in E5. Mike isn't sure how to perform such a
calculation.

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.)

ExcelTips: The Macros Page 804


Macro Cookbook

2. Click Formulas at the left side of the dialog box.

The formulas options in the Excel Options dialog box.

3. Make sure the Enable Iterative Calculation check box is selected.


4. Click OK.

These are the steps to follow in older versions of Excel:

1. Choose Options from the Tools menu. Excel displays the Options dialog box.
2. Display the Calculation tab.

ExcelTips: The Macros Page 805


Macro Cookbook

The Calculation tab of the Options dialog box.

3. Make sure the Iteration check box is selected.


4. Click OK.

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.)

Private Sub Worksheet_Calculate()


Dim dMax As Double
dMax = Application.WorksheetFunction.Max(Range("A1:A3"))
If dMax > Range("E5") Then
Application.EnableEvents = False
Range("E5") = dMax
Application.EnableEvents = True
End If
End Sub

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.

ExcelTips: The Macros Page 806


Macro Cookbook

Finding Differences between Lists


It is not unusual for people to keep client information in Excel worksheets. If you have a
worksheet that contains the names of all your clients, and another worksheet that contains the
names of your active clients, you may want to use Excel's capabilities to discover who your
inactive clients are.

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

Set SearchRng = Worksheets("Sheet2").Range("A:A")


Counter = 1 'First row on Sheet3 contains headings
For Each cell In Worksheets("Sheet1")
.Range("A2:A1000") _
.SpecialCells(xlCellTypeConstants)
ID = cell 'Client ID
NM = cell.Offset(0, 1) 'Client name
MatchRow = 0
On Error Resume Next
MatchRow = WorksheetFunction.Match(ID, _
SearchRng, 0)
On Error GoTo 0
If MatchRow = 0 Then
Counter = Counter + 1
Worksheets("Sheet3").Cells(Counter, 1) = ID
Worksheets("Sheet3").Cells(Counter, 2) = NM

ExcelTips: The Macros Page 807


Macro Cookbook

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.

List of Macro Shortcuts in All Open Workbooks


If you develop a lot of macros, you may want to list all those macros along with the shortcut keys
used to initiate them. Of course, coming up with the code to list the shortcut keys is the tricky
part of this problem, as such an ability is not built into Excel directly. (You can do it in Word,
but not in Excel. Go figure.)

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:

Attribute VB_Name = "Module1"


Attribute MyMacro.VB_Description = "My Description"
Attribute MyMacro.VB_ProcData.VB_Invoke_Func = "q\n14"

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

ExcelTips: The Macros Page 808


Macro Cookbook

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

For Each wkb In Workbooks()


Call ListKeys(wkb)
Next
End Sub

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.

Quickly Changing Windows


If you routinely use several open workbooks to do you work, you already know that you can
change between workbook windows in various ways, depending on your version of Excel:

• 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.

ExcelTips: The Macros Page 809


Macro Cookbook

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

Using an Exact Number of Digits


Henk asked if there is a way in Excel to display a number using six digits, independent of the
placement of the decimal point. For instance, 0.1 would be displayed as 0.10000, 200 would be
displayed as 200.000, and 25000 would be displayed as 25000.0.

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

ExcelTips: The Macros Page 810


Macro Cookbook

Dim bCommas As Boolean


Dim sFormat As String
Dim CellRange As Range
Dim TestCell As Range

bCommas = False 'Change as desired

Set CellRange = Selection


For Each TestCell In CellRange
If Abs(TestCell.Value) < 1 Then
iDecimals = 5
Else
iDecimals = 5 - Int(Log(Abs(TestCell.Value)) / Log(10#))
End If

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:

1. Select the cells you want formatted.


2. Display the Format Cells dialog box. (In Excel 2007 and later versions display the
Home tab of the ribbon and click the small icon at the lower-right corner of the Number
group. In older versions choose Cells from the Format menu.)
3. Make sure the Number tab is selected.

ExcelTips: The Macros Page 811


Macro Cookbook

The Number tab of the Format Cells dialog box.

4. In the list of format categories, choose Custom.


5. In the Type box, enter ##0.0E+0 as your format. (This provides only one number to the
right of the decimal place. If you want more, increase the number of zeros after the
decimal place.)
6. Click on OK.

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)))))

ExcelTips: The Macros Page 812


Macro Cookbook

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:

=IF(OR(A2>=1, A2<=-1),CHOOSE(INT(LOG(ABS(A2))/3)+1, "Foos", "Kilofoos",


"Megafoos", "Gigafoos", "Terafoos", "Petafoos", "Exafoos"),
IF(A2=0,"",CHOOSE(INT(-LOG(ABS(A2))/3)+1, "Millifoos", "Microfoos",
"Nanofoos", "Picofoos", "Femtofoos", "Attofoos")))

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:

Function MySciNum(BaseNum As Double) As Double


Select Case BaseNum
Case Is >= 1
While Abs(BaseNum) > 1000
BaseNum = BaseNum / 1000
Wend
Case 0
'Do nothing
Case Else
While Abs(BaseNum) < 1
BaseNum = BaseNum * 1000
Wend
End Select
MySciNum = BaseNum
End Function

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:

Function MySciPre(BaseNum As Double, Unit As String) As String


Dim OrigNum As Double
Dim Pref As Integer
Dim Temp As String

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

ExcelTips: The Macros Page 813


Macro Cookbook

Wend
End Select

Select Case Pref


Case -6
Temp = "atto" & Unit
Case -5
Temp = "femto" & Unit
Case -4
Temp = "pico" & Unit
Case -3
Temp = "nano" & Unit
Case -2
Temp = "micro" & Unit
Case -1
Temp = "milli" & Unit
Case 0
Temp = Unit
Case 1
Temp = "kilo" & Unit
Case 2
Temp = "mega" & Unit
Case 3
Temp = "giga" & Unit
Case 4
Temp = "tera" & Unit
Case 5
Temp = "peta" & Unit
Case 6
Temp = "exa" & Unit
Case Else
Temp = ""
End Select

If Len(Temp) > 0 Then


Temp = LCase(Temp)
Temp = UCase(Left(Temp, 1)) & Mid(Temp, 2)
If Abs(OrigNum) <> 1 Then Temp = Temp & "s"
End If

MySciPre = Temp
End Function

Maintaining Accuracy of Significant Digits


Ken asked if it is possible to maintain the number of significant digits on values entered into
Excel. For instance, “12.345600” and “1.230”.

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.

ExcelTips: The Macros Page 814


Macro Cookbook

(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 = "."

For Each c In Selection.Cells

ExcelTips: The Macros Page 815


Macro Cookbook

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.

Counting Groupings Below a Threshold


Ronald imports a number of signal-level measurements as a series of values into Excel. He needs
to count how many consecutive groups of values exist in this series which fall below a certain
threshold. For example, he may have the following measurements:

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))

ExcelTips: The Macros Page 816


Macro Cookbook

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.

Function CountGroups(ByVal MyRange As Range, Threshold As Single)


Dim Cell As Range
Dim bInGroup As Boolean
Dim iCount As Integer

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

ExcelTips: The Macros Page 817


Macro Cookbook

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)

Referencing External Cell Colors


Beth asked how to copy the color formatting of an external cell to a cell in the current workbook.
Unfortunately, there is no intrinsic way to do this by using the linking features of Excel. You
can, however, copy formatting from one workbook to another by using a macro.

As an example, consider the following macro code:

Workbooks.Open Filename:= "C:\mypath\myworkbook.xls"


Range("A1").Select
Selection.Copy
Windows("TargetBook.xls").Activate
Range("C7").Select
Selection.PasteSpecial Paste:=xlPasteFormats, _
Operation:=xlNone, SkipBlanks:=False, _
Transpose:=False

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.

Automatically Editing Formulas


There may be times when you need to make a change in a formula that appears in a number of
different cells. If there are quite a few cells, making the change by retyping each cell can be
tedious. The following macro, SwitchStuff, allows you to search for information in formulas and
then change it to something else. The macro will only affect formulas, not other values. (This is
the reason that such a macro can be used in preference to Excel’s native Replace function, which
can affect more than just formulas.) This macro is included as part of the ExcelTips macro
workbooks.

Sub SwitchStuff()
Dim rngSrc As Range
Dim lMax As Long, lCtr As Long

ExcelTips: The Macros Page 818


Macro Cookbook

Dim sFrom As String, sTo As String


Dim sFormula As String
Dim iStart As Integer, iFoundAt As Integer
Dim bDoIt As Boolean

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

For lCtr = 1 To lMax


If rngSrc.Cells(lCtr).HasFormula Then
sFormula = rngSrc.Cells(lCtr).Formula
iStart = 1
iFoundAt = InStr(iStart, sFormula, sFrom)
While iFoundAt > 0
sFormula = Left(sFormula, iFoundAt - 1) _
& sTo _
& Mid(sFormula, iFoundAt + Len(sFrom))
iStart = iFoundAt + Len(sTo)
iFoundAt = InStr(iStart, sFormula, sFrom)
Wend
rngSrc.Cells(lCtr) = sFormula
End If
Next lCtr
End If
End Sub

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.

Developing Reciprocal Conversion Formulas


Jeremy posed a problem that is based on two cells, A1 and C1. These cells are designed to
contain inches and millimeters, respectively. Jeremy wants a way that someone could enter a
value in A1 and it would be converted to millimeters in C1. Conversely, they could enter a value
in C1 and it would be converted to inches in A1.

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

ExcelTips: The Macros Page 819


Macro Cookbook

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:

Private Sub Worksheet_Change(ByVal Target As Range)


Application.EnableEvents = False
With ActiveSheet
If Target = .[A1] Then
.[C1].Value = .[A1].Value * 25.4
ElseIf Target = .[C1] Then
.[A1].Value = .[C1].Value / 25.4
End If
End With
Application.EnableEvents = True
End Sub

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.

Spreading Out a Table


Sometimes you may get a worksheet from someone else, and you need some room to work on
the information provided. For instance, you may find it helpful to add some blank rows between
each of the original rows in a data table. While this can be done rather easily using the Insert
menu, it can quickly become tedious—particularly if you have a large table that you want to
spread out.

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

ExcelTips: The Macros Page 820


Macro Cookbook

Dim J As Integer

iBlanks = InputBox("How many blank rows?", "Insert Rows")


ActiveCell.Offset(1, 0).Select
While ActiveCell.Value > "" And iBlanks > 0
For J = 1 To iBlanks
Selection.EntireRow.Insert
Next J
ActiveCell.Offset(iBlanks + 1, 0).Select
Wend
End Sub

DOS from Macros


Macros are a wonderful way to expand the functionality of a program such as Excel. You may,
however, want to expand that functionality even more by executing a DOS batch file from your
macro. Excel allows you to do this by using the Shell command. The general syntax for the
command is as follows:

dRetVal = Shell("myfile.bat", mode)

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:

Value Variable Nam Meaning


0 vbHide Window is hidden and has focus.
1 vbNormalFocus Window is the normal size and has focus.
2 vbMinimizedFocus Window is minimized and has focus.
3 vbMaximizedFocus Window is maximized and has focus.
4 vbNormalNoFocus Window is normal size, but doesn't have focus.
6 vbMinimizedNoFocus Window is minimized, but doesn't have focus.

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.

ExcelTips: The Macros Page 821


Macro Cookbook

Deleting Macros from within a Macro


Gene asked for a way to delete macros from within a macro. He has a large worksheet that uses
macros to put together a purchase order. As the last step, the macro deletes the original data that
is no longer necessary for the finished purchase order. This pared-down workbook is then saved
under a new name. Gene needs the macro, just prior to saving, to delete a macro that is stored in
SelectionChange event handler.

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

CopiedWB = "Copy of " & ActiveWorkbook.Name

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

Set VBCodeMod = ThisWorkbook.VBProject.VBComponents("Sheet1").CodeModule


With VBCodeMod
StartLine = .ProcStartLine("Worksheet_SelectionChange", _
vbext_pk_Proc)
HowManyLines = .ProcCountLines("Worksheet_SelectionChange", _
vbext_pk_Proc)
.DeleteLines StartLine, HowManyLines
End With
End Sub

ExcelTips: The Macros Page 822


Macro Cookbook

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

'Rest of macro goes here

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:

Private Sub Workbook_Open()


Dim VBComp As VBIDE.VBComponent
Dim VBComps As VBIDE.VBComponents

'Delete if Past Date


If Date >= #6/1/2013# Then
Set VBComps = ActiveWorkbook.VBProject.VBComponents

For Each VBComp In VBComps


Select Case VBComp.Type
Case vbext_ct_StdModule, vbext_ct_MSForm, _
vbext_ct_ClassModule
VBComps.Remove VBComp
Case Else
With VBComp.CodeModule
.DeleteLines 1, .CountOfLines
End With
End Select
Next VBComp
End If

Set VBComps = Nothing


Set VBComp = Nothing

ExcelTips: The Macros Page 823


Macro Cookbook

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.

Deleting Worksheet Code in a Macro


Jean-Louis would like to write a VBA procedure that deletes the code attached to a specific
worksheet. He knows how to delete procedures stored in modules, but not how to do it when
they are stored in the sheet.

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:

ExcelTips: The Macros Page 824


Macro Cookbook

http://www.cpearson.com/excel/vbe.aspx

Changing Macro Cell References Based on Edits


David wonders if there is any way for cell references in a macro to change when adding or
deleting rows, similar to the way a formula responds to such changes?

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.

Using the Camera in VBA


In other issues of ExcelTips you learn about using the Camera tool to capture dynamic pictures of
different parts of your worksheet. You may have been wondering how to use the same sort of
feature from within VBA. The documentation on the issue is not terribly clear, but the following
macro shows the general process:

Sub DoCamera()
Dim MyPrompt As String
Dim MyTitle As String
Dim UserRange As Range
Dim OutputRange As Range

Application.ScreenUpdating = True

'Prompt user for range to capture


MyPrompt = "Select the range you would like to capture."
MyTitle = "User Input Required"
On Error Resume Next
Set UserRange = Application.InputBox(Prompt:=MyPrompt, _
Title:=MyTitle, Default:=ActiveCell.Address, Type:=8)
If UserRange Is Nothing Then End
On Error GoTo 0

'Copy range to Clipboard as picture


UserRange.CopyPicture

'Prompt user for range to paste to


MyPrompt = "Select the range on which you would like to paste."
MyTitle = "User Input Required"
On Error Resume Next

ExcelTips: The Macros Page 825


Macro Cookbook

Set OutputRange = Application.InputBox(Prompt:=MyPrompt, _


Title:=MyTitle, Default:=ActiveCell.Address, Type:=8)
If OutputRange Is Nothing Then End
On Error GoTo 0

'Paste picture to output range


OutputRange.PasteSpecial
Selection.Formula = UserRange.Address
End Sub

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.

Conditional Page Breaks


Excel is a handy tool for keeping track of all sorts of data. Many people use it at work to create
ad-hoc reports for different departments or projects. As you work with your data, you may
wonder how you can automatically insert page breaks when the contents of a certain column
change. For instance, you might have a column that contains department names, and you may
want each department to start on a new page.

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.)

ExcelTips: The Macros Page 826


Macro Cookbook

The Subtotal dialog box.

4. Using the At Each Change In drop-down list, select Department.


5. Using the Use Function drop-down list, select Count.
6. Using the Add Subtotal To list, select the name of the column where you want your
subtotal to appear.
7. Make sure the Page Break Between Groups check box is selected.
8. Click on OK. Excel adds the subtotals and the page counts, as directed.

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

Set CellRange = Selection


For Each TestCell In CellRange
ActiveSheet.Rows(TestCell.Row).PageBreak = xlPageBreakNone
If TestCell.Value <> TestCell.Offset(-1, 0).Value Then
ActiveSheet.Rows(TestCell.Row).PageBreak = xlPageBreakManual
End If
Next TestCell
End Sub

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.

ExcelTips: The Macros Page 827


Macro Cookbook

Calculating the Interval Between Occurrences


Roger asked if there was a way to calculate the interval between occurrences of values in a list.
For instance, he has several thousand numbers in column A. Looking at the value in cell A351,
the last time that value occurred in the list was in cell A246. He would like a formula that could
be placed in cell B351 and return 105, the difference between 351 and 246.

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:

Function RowInterval(TestCell As Range, LookHere As Range) As Long


Dim varValue As Variant
Dim lngRow As Long

Application.Volatile
varValue = TestCell.Value

'Check for occurrences of the test value in the search range


If WorksheetFunction.CountIf(LookHere, varValue) > 0 Then
With LookHere
'Get the last row of the search range
lngRow = .Row + .Rows.Count - 1
'Start with the last cell in the search range and work up
Do Until .Item(lngRow, 1).Value = varValue
lngRow = lngRow - 1
Loop
End With
'Subtract the number of the row containing the found occurrence
'from the number of the row containing the test value
RowInterval = TestCell.Row - lngRow
End If
End Function

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)

ExcelTips: The Macros Page 828


Macro Cookbook

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

'Now have directory information


If sTemp <> "" Then
'Process queries using sGoodPath
Else
MsgBox "Directories not found!"
End If
End Sub

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.

Pasting without Updating References


As you are working on a worksheet, copying and moving information from one place to another,
you may wonder if there is a way to copy or move a selection without Excel changing all the

ExcelTips: The Macros Page 829


Macro Cookbook

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.

ExcelTips: The Macros Page 830


Macro Cookbook

Jumping To a Specific Page


Suppose that you have a large worksheet that requires 16 pages when printed out. You may
wonder if there is a way, when working within the worksheet, to jump to some given page, such
as page 5.

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

Set wks = ActiveSheet


iPages = ExecuteExcel4Macro("Get.Document(50)")
iVertPgs = wks.VPageBreaks.Count + 1
iHorPgs = wks.HPageBreaks.Count + 1
sPrtArea = wks.PageSetup.PrintArea

sPrompt = "Enter a page number (1 through "


sPrompt = sPrompt & Trim(Str(iPages)) & ") "
sTitle = "Enter Page Number"

iPage = InputBox(Prompt:=sPrompt, Title:=sTitle)

If wks.PageSetup.Order = xlDownThenOver Then


iVP = Int((iPage - 1) / iHorPgs)
iHP = ((iPage - 1) Mod iHorPgs)

ExcelTips: The Macros Page 831


Macro Cookbook

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

Checking for Either of Two Text Values


Chris wants to count cells that contain text value A or text value B, anywhere in the cell's text. If
the cell contains both A and B, she wants to count it, but only once. For instance, Chris has three
cells containing "apple seed", "apple tree", and "peach seed" and she wants to know the number
of cells containing either "apple" or "seed". (The proper answer that should be returned is 3.)

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)))

ExcelTips: The Macros Page 832


Macro Cookbook

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:

Function FindTwoStrings(rng As Range, s1 As String, _


s2 As String) As Integer
Application.Volatile
If TypeName(rng) <> "Range" Then Exit Function
Dim cell As Range
For Each cell In rng.Cells
If (InStr(1, UCase(cell.Value), UCase(s1), _
vbTextCompare) > 0) Or (InStr(1, UCase(cell.Value), _
UCase(s2), vbTextCompare) > 0) Then _
FindTwoStrings = FindTwoStrings + 1
Next cell
End Function

To use the function you could use this formula in a cell:

=FindTwoStrings(A1:A9,"apple","seed")

ExcelTips: The Macros Page 833


Macro Cookbook

Updating Multiple PivotTables at Once


For certain types of data analysis, PivotTables can be very handy. If you have a workbook that
contains several PivotTables, all based on the same data, you may wonder if there is a way to
update them all at once, rather than going through them individually and updating them.

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

For Each wks In Worksheets


For Each pt In wks.PivotTables
pt.RefreshTable
Next pt
Next wks
End Sub

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.)

Removing Subtotals from Many PivotTable Fields


Shairal develops PivotTables on a daily basis, using various data sources such as Excel lists,
Access tables, and OLAP data. One of the most irritating things he deals with is suppressing the
automatic subtotal function on each field, one at a time. This can be time consuming depending
on the number of fields he's used. Shairal wondered if it might be easier to use a macro to
suppress the subtotals for all the fields at once.

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

On Error Resume Next


For Each pt In ActiveSheet.PivotTables

ExcelTips: The Macros Page 834


Macro Cookbook

For Each pf In pt.PivotFields


'First, set index 1 (Automatic) to True,
'so all other values are set to False
pf.Subtotals(1) = True
pf.Subtotals(1) = False
Next pf
Next pt
End Sub

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.

Reducing File Sizes for Workbooks with PivotTables


PivotTables are great for certain types of data analysis. Since PivotTables do quite a bit of
number crunching, one of the techniques Excel uses to process them faster is to create an
“intermediate dataset” to work with. This intermediate dataset, by default, is stored with the
worksheet, so PivotTables can increase the size of your workbooks, sometimes dramatically.

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:

1. Create your PivotTable as you normally would.


2. Display the Options tab of the ribbon (Excel 2007) or the Analyze tab of the ribbon
(later versions). (These tabs are visible only when you select a cell in the PivotTable.)
3. Click the Options tool in the PivotTable group. Excel displays the PivotTable Options
dialog box.
4. Make sure the Data tab is displayed.

ExcelTips: The Macros Page 835


Macro Cookbook

The Data tab of the PivotTable Options dialog box.

5. Clear the Save Source Data with File check box.


6. Choose the Refresh on Open check box.
7. Click on OK to close the PivotTable Options dialog box.

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.

ExcelTips: The Macros Page 836


Macro Cookbook

The PivotTable Options dialog box.

3. Clear the Save Data with Table Layout check box.


4. Choose the Refresh on Open check box.
5. Click on OK to close the PivotTable Options dialog box.
6. Finish the steps in the PivotTable Wizard.

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

For Each wks In ActiveWorkbook.Worksheets


For Each PT In wks.PivotTables
PT.RefreshTable
PT.CacheIndex = 1
PT.SaveData = False
Next
Next

ExcelTips: The Macros Page 837


Macro Cookbook

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.

Using Classic PivotTable Layout as the Default


Nancy creates a lot of PivotTables and likes the old layout better than the default set up in Excel
2010. Every time she creates a PivotTable she has to go to Options | Display | Classic PivotTable
Layout. Nancy wonders if there is a way she can make the classic display the default.

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

For Each pt In ActiveSheet.PivotTables


pt.InGridDropZones = True
pt.RowAxisLayout xlTabularRow
Next pt
End Sub

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.

ExcelTips: The Macros Page 838


Macro Cookbook

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:

Private Sub Label1_Click()


If Label1.Caption = Chr(254) Then
Label1.Caption = Chr(168)
Else
Label1.Caption = Chr(254)
End If
End Sub

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.

Extracting Street Numbers from an Address


Allan has a list of several hundred names and addresses. The street addresses range from Main
Street, 123 Main Street, US RT 2, or 187 South Elm St. He would like to break out the street
number from the addresses. So the address 123 Main Street would end up with "123" in one cell
and "Main Street" in another. If there is no street number, then nothing ends up in the street
number column. The Text to Columns tool will not work and he wonders how he can do this.

ExcelTips: The Macros Page 839


Macro Cookbook

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)),"")

Since this is an array formula you need to enter it by using


CTRL+SHIFT+ENTER. The result is that the formula returns the
leading numeric portion of the address. You can then determine
the non-numeric portion by using the following array formula:

=IF(ISNUMBER(1*MID(A2,ROW($1:$1),1)) = TRUE,
RIGHT(A2,LEN(A2)-FIND(" ",A2,1)),A2)

ExcelTips: The Macros Page 840


Macro Cookbook

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

For Each cell In Selection


sStreet = cell.Value
J = InStr(sStreet, " ")
If J > 0 Then
iNum = Val(Left(sStreet, J))
If iNum > 0 Then
cell.Offset(0, 1).Value = iNum
sStreet = Trim(Mid(sStreet, J, Len(sStreet)))
End If
End If
cell.Offset(0, 2).Value = sStreet
Next
End Sub

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.)

Extracting a State and a ZIP Code


Dan has a column of cells and each cell contains three items: city, state and ZIP Code. (All three
are in a single cell, much like you see in an address line.) Some of the ZIP Codes are five digits
and some are nine. Dan needs to pull both the two-character state and the five-digit ZIP Code
into their own cells, to the right of the current data. Dan knows he can use the Text to Columns
tool, but feels that it involves a lot of work since he would need to deal with multiple-word city
names and commas. Dan can't help but think there may be a formulaic approach that would be
easier.

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)

ExcelTips: The Macros Page 841


Macro Cookbook

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:

Function GetStateZIP1(rstrAddress As String, iAction As Integer) As String


Dim arr As Variant
Dim sState As String
Dim sZIP As String

Application.Volatile
rstrAddress = Trim(rstrAddress)
If Len(rstrAddress) = 0 Then Exit Function

arr = Split(rstrAddress, " ")


With arr
If UBound(arr) < 2 Then
sState = "?"

ExcelTips: The Macros Page 842


Macro Cookbook

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:

Function GetStateZIP2(rstrAddress As String, iAction As Integer) As String


Dim arr As Variant
Dim sState As String
Dim sZIP As String
Dim J As Integer
Dim K As Integer

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

ExcelTips: The Macros Page 843


Macro Cookbook

Combinations for Members in Meetings


Bob has a worksheet that has member names down the left side and months of the year across the
top. In each cell of the grid he enters the dates on which meetings occur that were attended by the
member. Bob is looking for a way to tell at a glance who has not met with whom.

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

Set rTable = Worksheets("Sheet1").Range("A1").CurrentRegion


Set rOutput = Worksheets("Sheet2").Range("a1")
sNotMet = "X"
sMet = ""

Application.ScreenUpdating = False
With rTable
iRows = .Rows.Count
iCols = .Columns.Count

.Columns(1).Copy

ExcelTips: The Macros Page 844


Macro Cookbook

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

Set rTable = Nothing


Set rOutput = Nothing
Application.ScreenUpdating = True
End Sub

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).

Replacing Some Formulas with the Formula Results


Brian has a need to process a worksheet before it can be handed out to other people. What he
needs is to eliminate most, but not all, of the formulas in the worksheet. He wants to step through
all the cells in a selected range of cells and, if the cell contains a formula, check that formula. If
the formula contains a reference (any reference) to a different worksheet in the current
workbook, then the formula is ignored. If the formula does not contain such a reference, then the
macro needs to replace the formula with the result of the formula.

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:

ExcelTips: The Macros Page 845


Macro Cookbook

=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

On Error Resume Next

For Each c In Selection


If c.HasFormula Then
frm = c.Formula
If InStr(1, frm, "!") = 0 Then
c.Value = c.Value
End If
End If
Next c
End Sub

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

On Error Resume Next

For Each c In Selection


If c.HasFormula Then
frm = c.Formula
OtherSheet = False
If InStr(1, frm, "!") Then
OtherSheet = True
If InStr(1, frm, "[") Then
OtherSheet = False
End If
End If
If Not OtherSheet Then
c.Value = c.Value
End If
End If
Next c

ExcelTips: The Macros Page 846


Macro Cookbook

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.

Finding the Address of the Lowest Value in a Range


When writing a macro, you can find the lowest value in a range of cells by using the
WorksheetFunction method to apply the MIN worksheet function. You may need, however, to
not only find the lowest value in the range, but also the address of the first cell that contains that
value.

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:

Function FindLowestAddr(pRng As Range) As String


Application.Volatile
MinVal = pRng.Cells(1).Value
MinAddr = pRng.Cells(1).Address
For Each c in pRng
If c.Value < MinVal Then
MinVal = c.Value
MinAddr = c.Address
End If
Next c
FindLowestAddr = MinAddr
End Function

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.

Function GetAddr(rng As Range) As String


Dim dMin As Double
Dim lIndex As Long
Dim sAddress As String

Application.Volatile
With Application.WorksheetFunction
dMin = .Min(rng)
lIndex = .Match(dMin, rng, 0)
End With
GetAddr = rng.Cells(lIndex).Address

ExcelTips: The Macros Page 847


Macro Cookbook

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))

Extracting Proper Words


Vanita has a worksheet that contains different combinations of letters in each cell of column A.
He is looking for a way to extract the words from that list that are “proper,” meaning that they
are found in a spell-check dictionary.

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

ExcelTips: The Macros Page 848


Macro Cookbook

On Error Resume Next


rWords.SpecialCells(xlCellTypeBlanks). _
Delete (xlShiftUp)
On Error GoTo 0
Set rCell = Nothing
Set rWords = Nothing
Application.ScreenUpdating = True
End Sub

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.

Clearing Large Clipboard Entries


Have you ever noticed that after you’ve copied a large amount of data to the Clipboard, and then
you close Excel, you see a dialog box stating that “A large amount of data is present in the
Clipboard. Do you want to save it before quitting?” You may get tired of seeing this message. If
so, then there are a couple of things you can do.

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

Pulling Apart Cells


It's probably happened to you before: you get data for your worksheet, and one of the columns
includes names. The only problem is, the names are all bunched together. For instance, the cell
contains "Allen Wyatt," but you would rather have the first name in one column and the last
name in the neighboring column to the right. How do you pull the names apart?

ExcelTips: The Macros Page 849


Macro Cookbook

You can easily use the Text to Columns feature in Excel to pull your data apart. Just follow these
steps:

1. Select the range of cells you want to split.


2. Choose Text to Columns from the Data menu. (In Excel 2007 and later versions choose
Text to Columns from the Data tab of the ribbon.) Excel starts the Convert Text to
Columns Wizard.

The beginning of the Convert Text to Columns Wizard.

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

ExcelTips: The Macros Page 850


Macro Cookbook

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

ExcelTips: The Macros Page 851


Macro Cookbook

Dim iColCount As Integer


Dim iTargetCols As Integer
Dim J As Integer

iTargetCols = Val(InputBox("How many columns?"))


If iTargetCols > 1 Then
Set rSource = ActiveSheet.Range(ActiveWindow.Selection.Address)
If rSource.Columns.Count > 1 Then Exit Sub

iWriteRow = rSource.Row + (rSource.Cells.Count / iTargetCols)


iWriteCol = rSource.Column + iTargetCols - 1
Set rTarget = Range(Cells(rSource.Row, rSource.Column), _
Cells(iWriteRow, iWriteCol))

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.

Finding Unused Names


Richard has a workbook that he’s been using for a while, and it has quite a few names in it
(named ranges, named formulas, etc.). He wonders if there is an easy way to find names that are
not used at all, as he’d like to get rid of those names.

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

On Error Resume Next


fdMsg = ""
For Each myName In Names
If Cells.Find(What:=myName.Name, _
After:=ActiveCell, _
LookIn:=xlFormulas, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False).Activate = False Then

ExcelTips: The Macros Page 852


Macro Cookbook

fdMsg = fdMsg & myName.Name & vbCr


ActiveWorkbook.Names(myName.Name).Delete
End If
Next myName
If fdMsg = "" Then
MsgBox "No unused names found in the workbook"
Else
MsgBox "Names Deleted:" & vbCr & fdMsg
End If
End Sub

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

Getting Rid of Non-Printing Characters


Intelligently
If you work with files that originate from a non-Excel source, you can sometimes end up with
characters in your cells that Excel doesn’t know how to display properly. For instance, you may
have a comma-delimited text file generated by your company’s accounting software, and you
load the file into Excel. In some cells you may notice that there are small boxes. These represent
non-printing characters. Excel displays the small boxes so that you know the character is there,
even though it cannot be displayed or printed.

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.

ExcelTips: The Macros Page 853


Macro Cookbook

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:

Function ReplaceClean1(sText As String, Optional sSubText As String = " ")


Dim J As Integer
Dim vAddText

vAddText = Array(Chr(129), Chr(141), Chr(143), Chr(144), Chr(157))


For J = 1 To 31
sText = Replace(sText, Chr(J), sSubText)
Next
For J = 0 To UBound(vAddText)
sText = Replace(sText, vAddText(J), sSubText)
Next

ExcelTips: The Macros Page 854


Macro Cookbook

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:

Function ReplaceClean2(sText As String, Optional sSubText As String = " ")


Dim J As Integer
Dim vAddText
Dim aWF As WorksheetFunction
Set aWF = Application.WorksheetFunction

vAddText = Array(Chr(129), Chr(141), Chr(143), Chr(144), Chr(157))


For J = 1 To 31
sText = aWF.Substitute(sText, Chr(J), sSubText)
Next
For J = 0 To UBound(vAddText)
sText = aWF.Substitute(sText, vAddText(J), sSubText)
Next
ReplaceClean = sText
Set aWF = Nothing
End Function

Changing Months in a Workbook


It is not unusual to keep track of monthly information, of one sort or another, in a workbook.
You might be tracking expenses, sales, inventory movements, stock prices, or any of a thousand
other things. When you start a new month, you may make a copy of the previous month’s
workbook and then look for a way to make changes to the month name that appears in various
places in the newly created copy.

ExcelTips: The Macros Page 855


Macro Cookbook

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

sFind = InputBox("Text to find?")


sReplace = InputBox("Replace it with?")

If (sFind & sReplace) = "" Then Exit Sub

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

If sTemp <> Sheets(I).Name Then


Sheets(I).Name = sTemp
End If
Next I
End Sub

ExcelTips: The Macros Page 856


Macro Cookbook

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.

Triggering a Macro for Drop-Down List Changes


If you have Excel 97 and you create a macro that runs whenever a change occurs in your
worksheet, you may have noticed an interesting phenomenon—the macro doesn’t always run
when you think it should. It runs just fine if you enter a new formula in a cell or enter a new
value, but it doesn’t run if you use data validation and someone picks something from the
validation drop-down list.

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.)

Delimited Text-to-Columns in a Macro


One of the handiest features in Excel is the Text to Columns feature, which allows you to easily
split cell contents into individual cells according to any criteria you specify. One method of using

ExcelTips: The Macros Page 857


Macro Cookbook

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

'Set up the ranges


Set objRange1 = Range("A2:A20")
Set objRange2 = Range("A21:A35")

'Do the first parse


objRange1.TextToColumns _
Destination:=Range("A2"), _
DataType:=xlDelimited, _
Tab:=False, _
Semicolon:=False, _
Comma:=False, _

ExcelTips: The Macros Page 858


Macro Cookbook

Space:=False, _
Other:=True, _
OtherChar:="-"

'Do the second parse


objRange2.TextToColumns _
Destination:=Range("A21"), _
DataType:=xlDelimited, _
OtherChar:="x"
End Sub

Replacing and Converting in a Macro


Saskia was having a problem converting information, under the control of a macro, and still
having it be usable in Excel. When she would receive a worksheet that showed numbers
formatted with decimal points, she would need to convert the values so they used decimal
commas, consistent with how numbers are displayed in Holland. She would do a find and
replace, and everything would work fine. However, when she recorded a macro that did the find
and replace, the resulting cells were treated as text instead of as numeric values.

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.

Pulling Filenames into a Worksheet


Carol has a directory with about 1,000 files with names such as YR1905-LIC12345-
Smith,Harry-Brown,Mary. She would like to bring all of these filenames (not the files

ExcelTips: The Macros Page 859


Macro Cookbook

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:

dir /b /a-d > filelist.txt

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.

ExcelTips: The Macros Page 860


Macro Cookbook

The Text Import Wizard.

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

'specify directory to use - must end in "\"


sPath = "C:\"

iRow = 0
sFile = Dir(sPath)
Do While sFile <> ""
iRow = iRow + 1
splitFile = Split(sFile, "-")

ExcelTips: The Macros Page 861


Macro Cookbook

For iCol = 0 To UBound(splitFile)


Sheet1.Cells(iRow, iCol + 1) = splitFile(iCol)
Next iCol
sFile = Dir ' Get next filename
Loop
End Sub

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:

Function Split(Raw As String, Delim As String) As Variant


Dim vAry() As String
Dim sTemp As String
Dim J As Integer
Dim Indx As Integer

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

Protecting an Entire Folder of Workbooks


Mahesh has a number of Excel workbooks, all stored in the same folder. He wonders if it is
possible to assign a password to the entire folder so that all the workbooks are protected.

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.

ExcelTips: The Macros Page 862


Macro Cookbook

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?"

sFoundFile = Dir(sPathSpec & sFileSpec)


Do While sFoundFile <> ""
Set wBk = Workbooks.Open(sPathSpec & sFoundFile)
With wBk
Application.DisplayAlerts = False
wBk.SaveAs FileName:=.FullName, _
Password:="swordfish"
Application.DisplayAlerts = True
End With
Set wBk = Nothing
Workbooks(sFoundFile).Close False
sFoundFile = Dir
Loop
End Sub

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.

Storing a User’s Location before Running a Macro


John has a macro that does some processing on various worksheets in a workbook. He wants, at
the beginning of the macro, to save the range of cells (or the single cell) that the user has
selected. He uses ActiveCell.Address to determine this. Then, at the end of the macro, he wants
to return to the user with the same range selected that they originally had selected.

ExcelTips: The Macros Page 863


Macro Cookbook

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.

Here’s how you get all four items:

Dim rngOrigSelection As Range


Dim rngOrigCell As Range
Dim sOrigWS As String
Dim sOrigWB As String

Set rngOrigSelection = Selection


Set rngOrigCell = ActiveCell
sOrigWS = ActiveSheet.Name
sOrigWB = ActiveWorkbook.Name

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:

Dim rngOrigSelection As Range


Dim rngOrigCell As Range

Set rngOrigSelection = Selection


Set rngOrigCell = ActiveCell

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:

ExcelTips: The Macros Page 864


Macro Cookbook

ActiveWorkbook.Names.Add Name:="MyOrigPlace", RefersTo:=Selection

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.

Limiting Scroll Area


When putting together a worksheet for others to use, you may want to limit the cells that the user
can access. One esoteric way to add limits is to use the following steps:

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.

ExcelTips: The Macros Page 865


Macro Cookbook

The Properties window in the VBA Editor.

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.

Waiting for Update Completion


It is not unusual to create a macro that loads data from an external source (such as a database
query) and then processes that data. If you create such a macro, you may notice a slight
problem—Excel doesn’t wait for the data refresh to complete before it begins merrily chunking
away on the code that follows.

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:

ExcelTips: The Macros Page 866


Macro Cookbook

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.)

Conditionally Making a Sound


Ken knows how to create conditional formats in Excel. What he really wants to do, however, is
have Excel make an audible sound (a beep or whatever) if the conditions are met.

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:

Function BeepMe() As String


Beep
BeepMe = ""
End Function

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.

Private Declare Function PlaySound Lib "winmm.dll" _


Alias "PlaySoundA" (ByVal lpszName As String, _
ByVal hModule As Long, ByVal dwFlags As Long) As Long

Const SND_SYNC = &H0


Const SND_ASYNC = &H1
Const SND_FILENAME = &H20000

Function SoundMe() As String


Call PlaySound("c:\windows\media\tada.wav", _
0, SND_ASYNC Or SND_FILENAME)
SoundMe = ""
End Function

This function can be called the same as the previous example:

=IF(A12>300,SoundMe(),"")

ExcelTips: The Macros Page 867


Macro Cookbook

If you want to play a different WAV file, simply change the file specification in the SoundMe
function.

Conditionally Playing an Audio File


Tassos would like to have Excel play an audio file when the value in a certain cell exceeds a
threshold. For instance, when the value in a cell exceeds 999 he would like a particular sound file
to be played.

There is no built-in way to do this in Excel (although it would be an interesting addition to


Excel's conditional formatting features). You can, however, play a sound file by using a macro
to do a call to the Windows API.

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:

Private Declare Function PlaySound Lib "winmm.dll" _


Alias "PlaySoundA" (ByVal lpszName As String, _
ByVal hModule As Long, ByVal dwFlags As Long) As Long

Const SND_SYNC = &H0


Const SND_ASYNC = &H1
Const SND_FILENAME = &H20000

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:

Private Sub Worksheet_Change(ByVal Target As Range)


Threshold = 999
If Range("C5").Value > Threshold Then PlayWAV
End Sub

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:

ExcelTips: The Macros Page 868


Macro Cookbook

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.

ExcelTips: The Macros Page 869


I'd Like Your Feedback
Do you like ExcelTips: The Macros? Is it helpful? Is it a good value? Did you expect more (or
less) than what you got?

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

[email protected]

Drop me a line—I'm anxious to hear from you!

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: The Macros Page 870


Macro Cookbook

ExcelTips Archives
Your best source of usable Excel information!

For more ExcelTips products, visit our Web site:


http://store.tips.net/

ExcelTips: The Macros Page 871

You might also like