利用VBA实现Excel 2007强大编程功能


Walkenbach Visit Mr. Spreadsheet’s Web site at www.j-walk.com COMPUTERS/Spreadsheets $49.99 US • $59.99 CAN • £ 31.99 UK Power Tip # 1 Create powerful Excel applications Power Tip # 2 Enhance Excel with VBA macros Power Tip # 3 Write event-driven VBA code Power Tip # 4 Understand the new Excel 2007 ribbon Power Tip # 5 Expand Excel’s shortcut menus Expand Excel with VBA, and feel the power! No one is better at revealing the secrets of Excel than “Mr. Spreadsheet” himself. This power-user’s guide is packed with procedures, tips, and ideas for expanding Excel’s capabilities with Visual Basic® for Applications. Excel 2007 has a few new tricks up its sleeve, and John Walkenbach helps you make the most of them all. You’ll learn to customize Excel UserForms, develop new utilities, use VBA with charts and PivotTables, and create event-handling applications. Work with VBA subprocedures and function procedures, facilitate interactions with other applications, build user-friendly toolbars, menus, and help systems, and much more. Get ready to make Excel do your bidding. Mr. Spreadsheet’s John Walkenbach, arguably the foremost authority on Excel, has written hundreds of articles and created the award-winning Power Utility Pak. His 40-plus books include Excel 2007 Bible, Excel 2007 Formulas, and John Walkenbach’s Favorite Excel Tips & Tricks, all published by Wiley. Visit his popular Spreadsheet Page at www.j-walk.com/ss. ISBN 978-0-470-04401-8 CD-ROM INCLUDES: • Valuable sample files that illustrate examples from the text • A searchable PDF version of the book See the CD appendix for details and complete system requirements. CD-ROM INCLUDED Spine: 1.90" John Walkenbach Excel® 2007Power Programming with VBA Microsoft® Offi ce Bonus CD-ROM Included! Excel ® 2007 Power Programming with VBA Microsoft ® Offi ce Excel® 2007 Power Programming with VBA 01_044018 ffirs.qxp 2/28/07 6:31 PM Page i 01_044018 ffirs.qxp 2/28/07 6:31 PM Page ii Excel® 2007 Power Programming with VBA by John Walkenbach 01_044018 ffirs.qxp 2/28/07 6:31 PM Page iii Wiley Publishing, Inc. Excel® 2007 Power Programming with VBA Published by Wiley Publishing, Inc. 111 River Street Hoboken, NJ 07030-5774 www.wiley.com Copyright © 2007 by Wiley Publishing, Inc., Indianapolis, Indiana Published by Wiley Publishing, Inc., Indianapolis, Indiana Published simultaneously in Canada Library of Congress Control Number: 2006939606 ISBN: 978-0-470-04401-8 Manufactured in the United States of America 10 9 8 7 6 5 4 3 2 1 No part of this publication may be reproduced, stored in a retrieval system or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, scanning or otherwise, except as permitted under Sections 107 or 108 of the 1976 United States Copyright Act, without either the prior written permission of the Publisher, or authorization through payment of the appropriate per-copy fee to the Copyright Clearance Center, 222 Rosewood Drive, Danvers, MA 01923, (978) 750-8400, fax (978) 646-8600. Requests to the Publisher for permission should be addressed to the Legal Department, Wiley Publishing, Inc., 10475 Crosspoint Blvd., Indianapolis, IN 46256, (317) 572-3447, fax (317) 572-4355, or online at http://www.wiley.com/go/permissions. LIMIT OF LIABILITY/DISCLAIMER OF WARRANTY: THE PUBLISHER AND THE AUTHOR MAKE NO REPRESENTATIONS OR WARRANTIES WITH RESPECT TO THE ACCURACY OR COMPLETENESS OF THE CONTENTS OF THIS WORK AND SPECIFICALLY DISCLAIM ALL WARRANTIES, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE. NO WARRANTY MAY BE CREATED OR EXTENDED BY SALES OR PROMOTIONAL MATERIALS. THE ADVICE AND STRATEGIES CONTAINED HEREIN MAY NOT BE SUITABLE FOR EVERY SITUATION. THIS WORK IS SOLD WITH THE UNDERSTANDING THAT THE PUBLISHER IS NOT ENGAGED IN RENDERING LEGAL, ACCOUNTING, OR OTHER PROFESSIONAL SERVICES. IF PROFESSIONAL ASSISTANCE IS REQUIRED, THE SERVICES OF A COMPETENT PROFESSIONAL PERSON SHOULD BE SOUGHT. NEITHER THE PUBLISHER NOR THE AUTHOR SHALL BE LIABLE FOR DAMAGES ARISING HEREFROM. THE FACT THAT AN ORGANIZATION OR WEBSITE IS REFERRED TO IN THIS WORK AS A CITATION AND/OR A POTENTIAL SOURCE OF FURTHER INFORMATION DOES NOT MEAN THAT THE AUTHOR OR THE PUBLISHER ENDORSES THE INFORMATION THE ORGANIZATION OR WEBSITE MAY PROVIDE OR RECOMMENDATIONS IT MAY MAKE. FURTHER, READERS SHOULD BE AWARE THAT INTERNET WEBSITES LISTED IN THIS WORK MAY HAVE CHANGED OR DISAPPEARED BETWEEN WHEN THIS WORK WAS WRITTEN AND WHEN IT IS READ. FULFILLMENT OF EACH COUPON OFFER IS THE SOLE RESPONSIBILITY OF THE OFFEROR. For general information on our other products and services, please contact our Customer Care Department within the U.S. at 800-762-2974, outside the U.S. at 317-572-3993, or fax 317-572-4002. For technical support, please visit www.wiley.com/techsupport. Wiley also publishes its books in a variety of electronic formats. Some content that appears in print may not be available in electronic books. Trademarks: Wiley, the Wiley Publishing logo, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc. and/or its affiliates in the United States and other countries, and may not be used without written permission. Microsoft and Excel are registered trademarks of Microsoft Corporation in the United States and/or other countries. All other trademarks are the property of their respective owners. Wiley Publishing, Inc., is not associated with any product or vendor mentioned in this book. 01_044018 ffirs.qxp 2/28/07 6:31 PM Page iv About the Author John Walkenbach is author of about 50 spreadsheet books and lives in southern Arizona. Visit his Web site: http://j-walk.com. 01_044018 ffirs.qxp 2/28/07 6:31 PM Page v wAcquisitions, Editorial, and Media Development Senior Project Editor: Christopher Morris (Previous Edition: Linda Morris) Senior Acquisitions Editor: Bob Woerner Copy Editor: Andy Hollandbeck Technical Editor: Niek Otten Editorial Manager: Kevin Kirschner Media Development Specialists: Angela Denny, Kate Jenkins, Steven Kudirka, Kit Malone Media Development Coordinator: Laura Atkinson Media Project Supervisor: Laura Moss Media Development Manager: Laura VanWinkle Editorial Assistant: Amanda Foxworth Sr. Editorial Assistant: Cherie Case Composition Services Project Coordinator: Kristie Rees Layout and Graphics: Denny Hager, Joyce Haughey, Jennifer Mayberry, Heather Ryan Proofreader: John Greenough Indexer: Johnna VanHoose Anniversary Logo Design: Richard Pacifico Publishing and Editorial for Technology Dummies Richard Swadley, Vice President and Executive Group Publisher Andy Cummings, Vice President and Publisher Mary Bednarek, Executive Acquisitions Director Mary C. Corder, Editorial Director Publishing for Consumer Dummies Diane Graves Steele, Vice President and Publisher Joyce Pepple, Acquisitions Director Composition Services Gerry Fahey, Vice President of Production Services Debbie Stailey, Director of Composition Services 01_044018 ffirs.qxp 2/28/07 6:31 PM Page vi Preface Welcome to Excel 2007 Power Programming with VBA. If your job involves developing spreadsheets that others will use — or if you simply want to get the most out of Excel — you’ve come to the right place. Topics Covered This book focuses on Visual Basic for Applications (VBA), the programming language built into Excel (and other applications that make up Microsoft Office). More specifically, it will show you how to write programs that automate various tasks in Excel. This book covers everything from recording simple macros through creating sophisticated user-oriented applications and utilities. This book does not cover Microsoft Visual Studio Tools for Office (VSTO). VSTO is a rela- tively new technology that uses Visual Basic .NET and Microsoft Visual C#. VSTO can also be used to control Excel and other Microsoft Office applications. What You Need to Know This is not a book for beginning Excel users. If you have no experience with Excel, a better choice might be my Excel 2007 Bible, which provides comprehensive coverage of all the fea- tures of Excel. That book is meant for users of all levels. To get the most out of this book, you should be a relatively experienced Excel user. I didn’t spend much time writing basic how-to information. In fact, I assume that you know the following: • How to create workbooks, insert sheets, save files, and so on • How to navigate through a workbook • How to use the Excel 2007 Ribbon • How to enter formulas • How to use Excel’s worksheet functions • How to name cells and ranges • How to use basic Windows features, such as file management techniques and the Clipboard 02_044018 fpref.qxp 2/28/07 6:33 PM Page vii If you don’t know how to perform the preceding tasks, you could find some of this material over your head, so consider yourself warned. If you’re an experienced spreadsheet user who hasn’t used Excel 2007, Chapter 2 presents a brief overview of what this product offers. What You Need to Have To make the best use of this book, you need a copy of Excel 2007. Although most of the material also applies to Excel 2000 and later versions, I assume that you are using Excel 2007. Although Excel 2007 is radically different from its predecessors, the VBA environment has not changed at all. If you plan to develop applications that will be used in earlier ver- sions of Excel, I strongly suggest that you don’t use Excel 2007 for your development work. Most of the material in this book also applies to Excel for Macintosh. However, I did no compatibility testing with the Mac version, so you’re on your own. Any computer system that can run Windows will suffice, but you’ll be much better off with a fast machine with plenty of memory. Excel is a large program, and using it on a slower system or a system with minimal memory can be extremely frustrating. I recommend using a high-resolution video driver (1024 × 768 is adequate, and 1600 × 1200 is even better). For optimal results, try a dual-monitor system and place Excel on one screen and the Visual Basic Editor on the other. You’ll soon become spoiled. To use the examples on the companion CD, you also need a CD-ROM drive. Conventions in This Book Take a minute to skim this section and learn some of the typographic conventions used throughout this book. Excel commands Excel 2007 features a brand new “menu-less” user interface. In place of a menu system, Excel uses a context-sensitive Ribbon system. The words along the top (such as Insert, View, and so on) are known as tabs. Click a tab, and the Ribbon of icons displays the commands that are most suited to the task at hand. Each icon has a name that is (usually) displayed next to or below the icon. The icons are arranged in groups, and the group name appears below the icons. The convention I use in this book is to indicate the tab name, followed by the group name, followed by the icon name. So, for example, the command used to toggle word wrap within a cell is indicated as: Home➪Alignment➪Wrap Text Excel 2007 Power Programming with VBAviii 02_044018 fpref.qxp 2/28/07 6:33 PM Page viii The large round icon in the upper left corner of Excel 2007’s window is knows as the Office Button. When I refer to commands that use the Office Button, I abbreviate it as Office. For example, the following command displays the Excel Options dialog box: Office ➪ Excel Options VBA editor commands The VBA editor is the window in which you work with your VBA code. The VBA editor uses the traditional menu-and-toolbar interface. A command like the following means to click the Tools menu and select the References menu item: Tools➪References Keyboard conventions You need to use the keyboard to enter data. In addition, you can work with menus and dia- log boxes directly from the keyboard — a method that you might find easier if your hands are already positioned over the keys. INPUT Input that you type from the keyboard appears in boldface — for example, enter =SUM(B2: B50) into cell B51. More lengthy input usually appears on a separate line in a monospace font. For example, I might instruct you to enter the following formula: =VLOOKUP(StockNumber,PriceList,2) VBA CODE This book contains many snippets of VBA code as well as complete procedure listings. Each listing appears in a monospace font; each line of code occupies a separate line. (I copied these listings directly from the VBA module and pasted them into my word proces- sor.) To make the code easier to read, I often use one or more tabs to create indentations. Indentation is optional, but it does help to delineate statements that go together. If a line of code doesn’t fit on a single line in this book, I use the standard VBA line contin- uation sequence: At the end of a line, a space followed by an underscore character indi- cates that the line of code extends to the next line. For example, the following two lines are a single code statement: If Right(ActiveCell, 1) = “!” Then ActiveCell _ = Left(ActiveCell, Len(ActiveCell) - 1) You can enter this code either on two lines, exactly as shown, or on a single line without the underscore character. Preface ix 02_044018 fpref.qxp 2/28/07 6:33 PM Page ix FUNCTIONS, FILENAMES, AND NAMED RANGES Excel’s worksheet functions appear in uppercase font, like so: “Enter a SUM formula in cell C20.” VBA procedure names, properties, methods, and objects appear in monospace font: “Execute the GetTotals procedure.” I often use mixed upper- and lowercase to make these names easier to read. I also use the monospace font for filenames and named ranges in a worksheet — for exam- ple: Open myfile.xlsm and select the range named data. Mouse conventions If you’re reading this book, you’re well versed in mouse usage. The mouse terminology I use is all standard fare: pointing, clicking, right-clicking, dragging, and so on. What the Icons Mean Throughout the book, I use icons to call your attention to points that are particularly important: NEW I use this icon to indicate that the material discussed is new to Excel 2007. NOTE I use Note icons to tell you that something is important — perhaps a concept that could help you master the task at hand or something fundamental for understanding subse- quent material. TIP Tip icons indicate a more efficient way of doing something or a technique that might not be obvious. CD-ROM These icons indicate that an example file is on the companion CD-ROM. (See “About the Companion CD-ROM,” later in this Preface.) This CD holds many of the examples that I show in the book. CAUTION I use Caution icons when the operation that I’m describing can cause problems if you’re not careful. Excel 2007 Power Programming with VBAx 02_044018 fpref.qxp 2/28/07 6:33 PM Page x CROSS-REFERENCE I use the Cross Reference icon to refer you to other chapters that have more to say on a subject. How This Book Is Organized The chapters of this book are grouped into eight main parts. Part I: Some Essential Background In this part, I set the stage for the rest of the book. Chapter 1 presents a brief history of spreadsheets so that you can see how Excel fits into the big picture. In Chapter 2, I offer a conceptual overview of Excel 2007 — quite useful for experienced spreadsheet users who are switching to Excel. In Chapter 3, I cover the essentials of formulas, including some clever techniques that might be new to you. Chapter 4 covers the ins and outs of the vari- ous files used and generated by Excel. Part II: Excel Application Development This part consists of just two chapters. In Chapter 5, I broadly discuss the concept of a spreadsheet application. Chapter 6 goes into more detail and covers the steps typically involved in a spreadsheet application development project. Part III: Understanding Visual Basic for Applications Chapters 7 through 11 make up Part III, and these chapters include everything that you need to know to learn VBA. In this part, I introduce you to VBA, provide programming fun- damentals, and detail how to develop VBA subroutines and functions. Chapter 11 contains many useful VBA examples. Part IV: Working with UserForms The four chapters in this part cover custom dialog boxes (also known as UserForms). Chapter 12 presents some built-in alternatives to creating custom UserForms. Chapter 13 provides an introduction to UserForms and the various controls that you can use. Chapters 14 and 15 present many examples of custom dialog boxes, ranging from basic to advanced. Part V: Advanced Programming Techniques Part V covers additional techniques that are often considered advanced. The first three chapters discuss how to develop utilities and how to use VBA to work with pivot tables and charts. Chapter 19 covers event handling, which enables you to execute procedures auto- matically when certain events occur. Chapter 20 discusses various techniques that you can Preface xi 02_044018 fpref.qxp 2/28/07 6:33 PM Page xi use to interact with other applications (such as Word). Chapter 21 concludes Part V with an in-depth discussion of creating add-ins. Part VI: Developing Applications The chapters in Part VI deal with important elements of creating user-oriented applica- tions. Chapter 22 discusses how to modify the new Ribbon interface. Chapter 23 describes how to modify Excel’s shortcut menus. Chapter 24 presents several different ways to pro- vide online help for your applications. In Chapter 25, I present some basic information about developing user-oriented applications, and I describe such an application in detail. Part VII: Other Topics The six chapters in Part VII cover additional topics. Chapter 26 presents information regarding compatibility. In Chapter 27, I discuss various ways to use VBA to work with files. In Chapter 28, I explain how to use VBA to manipulate Visual Basic components such as UserForms and modules. Chapter 29 covers the topic of class modules. Chapter 30 explains how to work with color in Excel. I finish the part with a useful chapter that answers many common questions about Excel programming. Part VIII: Appendixes Four appendixes round out the book. Appendix A contains useful information about Excel resources online. Appendix B is a reference guide to all VBA’s keywords (statements and functions). I explain VBA error codes in Appendix C, and Appendix D describes the files available on the companion CD-ROM. About the Companion CD-ROM The inside back cover of this book contains a CD-ROM that holds many useful examples that I discuss in the text. When I write about computer-related material, I emphasize learn- ing by example. I know that I learn more from a well-thought-out example than from reading a dozen pages in a book. I assume that this is true for many other people. Consequently, I spent more time developing the examples on the CD-ROM than I did writing chapters. The files on the companion CD-ROM are not compressed, so you can access them directly from the CD. CROSS-REFERENCE Refer to Appendix D for a description of each file on the CD-ROM. Excel 2007 Power Programming with VBAxii 02_044018 fpref.qxp 2/28/07 6:33 PM Page xii About the Power Utility Pak Offer Toward the back of the book, you’ll find a coupon that you can redeem for a discounted copy of my popular Power Utility Pak software. PUP is an award-winning collection of use- ful Excel utilities and many new worksheet functions. I developed this package exclusively with VBA. I think you’ll find this product useful in your day-to-day work with Excel. You can also pur- chase the complete VBA source code for a nominal fee. Studying the code is an excellent way to pick up some useful programming techniques. You can take Power Utility Pak for a test drive by installing the 30-day trial version available at my Web site: http://j-walk.com/ss How to Use This Book You can use this book any way that you please. If you choose to read it from cover to cover, be my guest. But because I’m dealing with intermediate-to-advanced subject matter, the chapter order is often immaterial. I suspect that most readers will skip around, picking up useful tidbits here and there. If you’re faced with a challenging task, you might try the index first to see whether the book specifically addresses your problem. Reach Out The publisher and I want your feedback. After you’ve had a chance to use this book, please take a moment to visit the Wiley Publishing Web site to give us your comments. (Go to www. wiley.com and then click the Contact Us link.) Please be honest in your evaluation. If you thought a particular chapter didn’t tell you enough, let us know. Of course, I would prefer to receive comments like, “This is the best book I’ve ever read,” or “Thanks to this book, I was promoted and now make $112,000 a year.” I get at least a half dozen questions every day, via e-mail, from people who have read my books. I appreciate the feedback. Unfortunately, I simply don’t have the time to reply to questions. Appendix A provides a good list of sources that can answer your questions. I also invite you to visit my Web site, which contains lots of Excel-related material. Despite the massive attempts to make this book completely accurate, a few errors have probably crept into its pages. My Web site includes a list of any such errors. The URL is http://j-walk.com/ss/w Preface xiii 02_044018 fpref.qxp 2/28/07 6:33 PM Page xiii 02_044018 fpref.qxp 2/28/07 6:33 PM Page xiv Contents at a Glance Preface vii Part I: Some Essential Background Chapter 1: Excel 2007: Where It Came From 3 Chapter 2: Excel in a Nutshell 15 Chapter 3: Formula Tricks and Techniques 47 Chapter 4: Understanding Excel’s Files 73 Part II: Excel Application Development Chapter 5: What Is a Spreadsheet Application? 97 Chapter 6: Essentials of Spreadsheet Application Development 109 Part III: Understanding Visual Basic for Applications Chapter 7: Introducing Visual Basic for Applications 133 Chapter 8: VBA Programming Fundamentals 189 Chapter 9: Working with VBA Sub Procedures 237 Chapter 10: Creating Function Procedures 275 Chapter 11: VBA Programming Examples and Techniques 315 Part IV: Working with UserForms Chapter 12: Custom Dialog Box Alternatives 389 Chapter 13: Introducing UserForms 413 Chapter 14: UserForm Examples 449 Chapter 15: Advanced UserForm Techniques 487 Part V: Advanced Programming Techniques Chapter 16: Developing Excel Utilities with VBA 533 Chapter 17: Working with Pivot Tables 555 Chapter 18: Working with Charts 571 Chapter 19: Understanding Excel’s Events 629 Chapter 20: Interacting with Other Applications 669 Chapter 21: Creating and Using Add-Ins 699 Part VI: Developing Applications Chapter 22: Working with the Ribbon 727 Chapter 23: Working with Shortcut Menus 761 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xv Chapter 24: Providing Help for Your Applications 781 Chapter 25: Developing User-Oriented Applications 801 Part VII: Other Topics Chapter 26: Compatibility Issues 817 Chapter 27: Manipulating Files with VBA 829 Chapter 28: Manipulating Visual Basic Components 859 Chapter 29: Understanding Class Modules 885 Chapter 30: Working with Colors 901 Chapter 31: Frequently Asked Questions about Excel Programming 929 Part VIII: Appendixes Appendix A: Excel Resources Online 971 Appendix B: VBA Statements and Functions Reference 977 Appendix C: VBA Error Codes 989 Appendix D: What’s on the CD-ROM 993 Index 1011 Excel 2007 Power Programming with VBAxvi 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xvi Table of Contents Preface vii Part I: Some Essential Background Chapter 1: Excel 2007: Where It Came From 3 A Brief History of Spreadsheets 3 It all started with VisiCalc 4 Lotus 1-2-3 5 Quattro Pro 7 Microsoft Excel 7 Why Excel Is Great for Developers 13 Excel’s Role in Microsoft’s Strategy 14 Chapter 2: Excel in a Nutshell 15 Thinking in Terms of Objects 15 Workbooks 16 Worksheets 17 Chart sheets 18 XLM macro sheets 18 Excel 5/95 dialog sheets 20 Excel’s User Interface 20 Introducing the Ribbon 20 Shortcut menus 26 Dialog boxes 27 Keyboard shortcuts 28 Smart Tags 28 Task pane 29 Customizing the Display 30 Data Entry 30 Formulas, Functions, and Names 30 Selecting Objects 32 Formatting 33 Numeric formatting 33 Stylistic formatting 34 Protection Options 34 Protecting formulas from being overwritten 35 Protecting a workbook’s structure 36 Applying password protection to a workbook 36 Protecting VBA code with a password 36 Charts 37 Shapes and SmartArt 38 Database Access 39 Worksheet databases 39 External databases 40 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xvii Internet Features 41 Analysis Tools 41 Outlines 42 Analysis ToolPak 42 Pivot tables 42 Solver 42 XML features 42 Add-Ins 43 Macros and Programming 44 File Format 44 Excel’s Help System 44 Chapter 3: Formula Tricks and Techniques 47 About Formulas 48 Calculating Formulas 48 Cell and Range References 49 Why use references that aren’t relative? 50 About R1C1 notation 50 Referencing other sheets or workbooks 51 Using Names 53 Naming cells and ranges 53 Applying names to existing references 54 Intersecting names 55 Naming columns and rows 55 Scoping names 56 Naming constants 56 Naming formulas 57 Naming objects 59 Formula Errors 59 Array Formulas 60 An array formula example 61 An array formula calendar 62 Array formula pros and cons 63 Counting and Summing Techniques 63 Counting formula examples 64 Summing formula examples 66 Other counting tools 67 Working with Dates and Times 67 Entering dates and times 67 Using pre-1900 dates 68 Creating Megaformulas 69 Chapter 4: Understanding Excel’s Files 73 Starting Excel 73 File Types 76 Excel file formats 76 Text file formats 78 Database file formats 79 Other file formats 79 Excel 2007 Power Programming with VBAxviii 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xviii Working with Template Files 80 Viewing templates 81 Creating templates 82 Creating workbook templates 84 Inside an Excel File 84 Dissecting a file 85 Why is the file format important? 88 The QAT File 89 The XLB File 90 Add-In Files 90 Excel Settings in the Registry 91 About the Registry 91 Excel’s settings 92 Part II: Excel Application Development Chapter 5: What Is a Spreadsheet Application? 97 Spreadsheet Applications 98 The Developer and the End User 99 Who are developers? What do they do? 99 Classifying spreadsheet users 100 The audience for spreadsheet applications 101 Solving Problems with Excel 102 Basic Spreadsheet Types 103 Quick-and-dirty spreadsheets 103 For-your-eyes-only spreadsheets 104 Single-user applications 104 Spaghetti applications 104 Utility applications 105 Add-ins that contain worksheet functions 105 Single-block budgets 106 What-if models 106 Data storage and access spreadsheets 106 Database front ends 107 Turnkey applications 107 Chapter 6: Essentials of Spreadsheet Application Development 109 Determining User Needs 110 Planning an Application That Meets User Needs 111 Determining the Most Appropriate User Interface 114 Customizing the Ribbon 114 Customizing shortcut menus 116 Creating shortcut keys 116 Creating custom dialog boxes 117 Using ActiveX controls on a worksheet 118 Executing the development effort 120 Concerning Yourself with the End User 120 Testing the application 121 Making the application bulletproof 122 Table of Contents xix 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xix Making the application aesthetically appealing and intuitive 124 Creating a user Help system 125 Documenting the development effort 126 Distributing the application to the user 127 Updating the application when necessary 127 Other Development Issues 128 The user’s installed version of Excel 128 Language issues 128 System speed 129 Video modes 129 Part III: Understanding Visual Basic for Applications Chapter 7: Introducing Visual Basic for Applications 133 Some BASIC Background 134 About VBA 134 Object models 134 VBA versus XLM 135 The Basics of VBA 135 Introducing the Visual Basic Editor 139 Displaying Excel’s Developer tab 139 Activating the VBE 139 The VBE windows 140 Working with the Project Explorer 142 Adding a new VBA module 143 Removing a VBA module 143 Exporting and importing objects 143 Working with Code Windows 144 Minimizing and maximizing windows 144 Storing VBA code 145 Entering VBA code 145 Customizing the VBE Environment 152 Using the Editor tab 152 Using the Editor Format tab 155 Using the General tab 156 Using the Docking tab 157 The Macro Recorder 158 What the macro recorder actually records 158 Relative or absolute? 159 Recording options 162 Cleaning up recorded macros 163 About Objects and Collections 165 The object hierarchy 165 About collections 166 Referring to objects 167 Properties and Methods 167 Object properties 168 Object methods 169 Excel 2007 Power Programming with VBAxx 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xx The Comment Object: A Case Study 171 Viewing Help for the Comment object 171 Properties of a Comment object 171 Methods of a Comment object 172 The Comments collection 173 About the Comment property 174 Objects within a Comment object 175 Determining whether a cell has a comment 176 Adding a new Comment object 177 Some Useful Application Properties 177 Working with Range Objects 179 The Range property 179 The Cells property 182 The Offset property 184 Things to Know about Objects 185 Essential concepts to remember 185 Learning more about objects and properties 186 Chapter 8: VBA Programming Fundamentals 189 VBA Language Elements: An Overview 189 Comments 192 Variables, Data Types, and Constants 193 Defining data types 194 Declaring variables 197 Scoping variables 199 Working with constants 203 Working with strings 205 Working with dates 206 Assignment Statements 207 Arrays 209 Declaring arrays 210 Declaring multidimensional arrays 210 Declaring dynamic arrays 211 Object Variables 211 User-Defined Data Types 212 Built-in Functions 213 Manipulating Objects and Collections 217 With-End With constructs 217 For Each-Next constructs 218 Controlling Code Execution 220 GoTo statements 220 If-Then constructs 221 Select Case constructs 225 Looping blocks of instructions 228 Chapter 9: Working with VBA Sub Procedures 237 About Procedures 237 Declaring a Sub procedure 238 Scoping a procedure 239 Table of Contents xxi 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxi Executing Sub Procedures 240 Executing a procedure with the Run Sub/UserForm command 241 Executing a procedure from the Macro dialog box 241 Executing a procedure with a Ctrl+shortcut key combination 242 Executing a procedure from the Ribbon 243 Executing a procedure from a customized shortcut menu 244 Executing a procedure from another procedure 244 Executing a procedure by clicking an object 249 Executing a procedure when an event occurs 250 Executing a procedure from the Immediate window 250 Passing Arguments to Procedures 251 Error-Handling Techniques 255 Trapping errors 256 Error-handling examples 257 A Realistic Example That Uses Sub Procedures 260 The goal 260 Project requirements 260 What you know 261 The approach 261 What you need to know 262 Some preliminary recording 262 Initial setup 264 Code writing 265 Writing the Sort procedure 266 More testing 270 Fixing the problems 270 Utility availability 274 Evaluating the project 274 Chapter 10: Creating Function Procedures 275 Sub Procedures versus Function Procedures 276 Why Create Custom Functions? 276 An Introductory Function Example 277 A custom function 277 Using the function in a worksheet 278 Using the function in a VBA procedure 278 Analyzing the custom function 279 Function Procedures 281 Declaring a function 281 A function’s scope 283 Executing function procedures 283 Function Arguments 285 Function Examples 286 Functions with no argument 286 A function with one argument 289 A function with two arguments 292 A function with an array argument 293 A function with optional arguments 294 A function that returns a VBA array 296 Excel 2007 Power Programming with VBAxxii 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxii A function that returns an error value 299 A function with an indefinite number of arguments 300 Emulating Excel’s SUM function 301 Debugging Functions 305 Dealing with the Insert Function Dialog Box 306 Specifying a function category 307 Adding a function description 308 Using Add-ins to Store Custom Functions 310 Using the Windows API 311 Windows API examples 311 Determining the Windows directory 311 Detecting the Shift key 313 Learning more about API functions 314 Chapter 11: VBA Programming Examples and Techniques 315 Working with Ranges 316 Copying a range 316 Moving a range 318 Copying a variably sized range 318 Selecting or otherwise identifying various types of ranges 319 Prompting for a cell value 321 Entering a value in the next empty cell 323 Pausing a macro to get a user-selected range 324 Counting selected cells 326 Determining the type of selected range 327 Looping through a selected range efficiently 329 Deleting all empty rows 332 Duplicating rows a variable number of times 333 Determining whether a range is contained in another range 334 Determining a cell’s data type 335 Reading and writing ranges 336 A better way to write to a range 337 Transferring one-dimensional arrays 339 Transferring a range to a variant array 339 Selecting cells by value 340 Copying a noncontiguous range 342 Working with Workbooks and Sheets 344 Saving all workbooks 344 Saving and closing all workbooks 344 Hiding all but the selection 345 Synchronizing worksheets 346 VBA Techniques 347 Toggling a Boolean property 347 Determining the number of printed pages 348 Displaying the date and time 349 Getting a list of fonts 351 Sorting an array 352 Processing a series of files 354 Table of Contents xxiii 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxiii Some Useful Functions for Use in Your Code 356 The FileExists function 356 The FileNameOnly function 356 The PathExists function 357 The RangeNameExists function 357 The SheetExists function 358 The WorkbookIsOpen function 358 Retrieving a value from a closed workbook 358 Some Useful Worksheet Functions 361 Returning cell formatting information 361 A talking worksheet 363 Displaying the date when a file was saved or printed 363 Understanding object parents 364 Counting cells between two values 365 Counting visible cells in a range 366 Determining the last non-empty cell in a column or row 366 Does a string match a pattern? 368 Extracting the nth element from a string 369 A multifunctional function 371 The SheetOffset function 372 Returning the maximum value across all worksheets 373 Returning an array of nonduplicated random integers 374 Randomizing a range 375 Windows API Calls 377 Determining file associations 377 Determining disk drive information 378 Determining default printer information 379 Determining video display information 380 Adding sound to your applications 382 Reading from and writing to the Registry 384 Part IV: Working with UserForms Chapter 12: Custom Dialog Box Alternatives 389 Before You Create That UserForm . . . 390 Using an Input Box 390 The VBA InputBox function 390 The Excel InputBox method 392 The VBA MsgBox Function 394 The Excel GetOpenFilename Method 399 The Excel GetSaveAsFilename Method 403 Prompting for a Directory 403 Using a Windows API function to select a directory 404 Using the FileDialog object to select a directory 406 Displaying Excel’s Built-In Dialog Boxes 407 About the Dialogs collection 407 Executing Ribbon commands 408 Excel 2007 Power Programming with VBAxxiv 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxiv Displaying a Data Form 410 Making the data form accessible 410 Displaying a data form by using VBA 411 Chapter 13: Introducing UserForms 413 How Excel Handles Custom Dialog Boxes 414 Inserting a New UserForm 414 Adding Controls to a UserForm 415 Toolbox Controls 416 CheckBox 416 ComboBox 416 CommandButton 417 Frame 417 Image 417 Label 418 ListBox 418 MultiPage 418 OptionButton 418 RefEdit 418 ScrollBar 418 SpinButton 419 TabStrip 419 TextBox 419 ToggleButton 419 Adjusting UserForm Controls 421 Adjusting a Control’s Properties 422 Using the Properties window 422 Common properties 424 Learning more about properties 424 Accommodating keyboard users 424 Displaying and Closing UserForms 427 Displaying a UserForm 427 Closing a UserForm 428 About event handler procedures 429 Creating a UserForm: An Example 430 Creating the UserForm 430 Writing code to display the dialog box 433 Testing the dialog box 434 Adding event handler procedures 435 Validating the data 436 The finished dialog box 437 Understanding UserForm Events 437 Learning about events 437 UserForm events 438 SpinButton events 439 Pairing a SpinButton with a TextBox 441 Referencing UserForm Controls 444 Table of Contents xxv 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxv Customizing the Toolbox 445 Changing icons or tip text 445 Adding new pages 445 Customizing or combining controls 445 Adding new ActiveX controls 447 Creating UserForm Templates 447 A UserForm Checklist 448 Chapter 14: UserForm Examples 449 Creating a UserForm “Menu” 450 Using CommandButtons in a UserForm 450 Using a ListBox in a UserForm 450 Selecting Ranges from a UserForm 452 Creating a Splash Screen 454 Disabling a UserForm’s Close Button 456 Changing a UserForm’s Size 457 Zooming and Scrolling a Sheet from a UserForm 458 ListBox Techniques 460 About the ListBox control 461 Adding items to a ListBox control 461 Determining the selected item 466 Determining multiple selections in a ListBox 466 Multiple lists in a single ListBox 467 ListBox item transfer 468 Moving items in a ListBox 470 Working with multicolumn ListBox controls 472 Using a ListBox to select worksheet rows 474 Using a ListBox to activate a sheet 476 Using the MultiPage Control in a UserForm 479 Using an External Control 480 Animating a Label 482 Chapter 15: Advanced UserForm Techniques 487 A Modeless Dialog Box 488 Displaying a Progress Indicator 491 Creating a standalone progress indicator 492 Showing a progress indicator by using a MultiPage control 496 Showing a progress indicator without using a MultiPage control 499 Creating Wizards 500 Setting up the MultiPage control for the wizard 500 Adding the buttons to the wizard UserForm 501 Programming the wizard buttons 502 Programming dependencies in a wizard 503 Performing the task with the wizard 505 Emulating the MsgBox Function 506 MsgBox emulation: MyMsgBox code 507 How the MyMsgBox function works 508 Using the MyMsgBox function in the MsgBox emulation 510 A UserForm with Movable Controls 510 Excel 2007 Power Programming with VBAxxvi 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxvi A UserForm with No Title Bar 511 Simulating a Toolbar with a UserForm 513 A Resizable UserForm 515 Handling Multiple UserForm Controls with One Event Handler 520 Selecting a Color in a UserForm 523 Displaying a Chart in a UserForm 524 General steps to display a chart in a userform 525 Saving a chart as a GIF file 525 Changing the Image control Picture property 526 An Enhanced Data Form 526 About the Enhanced Data Form 527 Installing the Enhanced Data Form add-in 528 A Puzzle on a UserForm 529 Part V: Advanced Programming Techniques Chapter 16: Developing Excel Utilities with VBA 533 About Excel Utilities 533 Using VBA to Develop Utilities 534 What Makes a Good Utility? 535 Text Tools: The Anatomy of a Utility 535 Background for Text Tools 536 Project goals for Text Tools 537 The Text Tools workbook 537 How the Text Tools utility works 538 The UserForm for the Text Tools utility 538 The Module1 VBA module 540 The UserForm1 code module 542 Making the Text Tools utility efficient 544 Saving the Text Tools utility settings 545 Implementing Undo 547 Displaying the Help file 549 Adding the RibbonX code 550 Post-mortem of the project 553 Understand the Text Tools utility 554 More about Excel Utilities 554 Chapter 17: Working with Pivot Tables 555 An Introductory Pivot Table Example 555 Creating a pivot table 556 Examining the recorded code for the pivot table 558 Cleaning up the recorded pivot table code 558 Creating a More Complex Pivot Table 560 Data for a more complex pivot table 560 The code that created the pivot table 562 How the more complex pivot table works 563 Creating Multiple Pivot Tables 565 Creating a Reverse Pivot Table 568 Table of Contents xxvii 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxvii Chapter 18: Working with Charts 571 About Charts 571 Chart locations 572 The macro recorder and charts 572 The Chart object model 574 Common VBA Charting Techniques 575 Creating an embedded chart 575 Creating a chart on a chart sheet 577 Using VBA to activate a chart 578 Moving a chart 578 Using VBA to deactivate a chart 580 Determining whether a chart is activated 580 Deleting from the ChartObjects or Charts collection 581 Looping through all charts 582 Sizing and aligning ChartObjects 584 Exporting a chart 585 Exporting all graphics 586 Using VBA to Apply Chart Formatting 587 Formatting a chart 587 More chart formatting examples 589 Changing the Data Used in a Chart 592 Changing chart data based on the active cell 593 Using VBA to determine the ranges used in a chart 595 Using VBA to Display Arbitrary Data Labels on a Chart 598 Displaying a Chart in a UserForm 600 Understanding Chart Events 603 An example of using Chart events 604 Enabling events for an embedded chart 607 Example: Using Chart events with an embedded chart 608 VBA Charting Tricks 610 Printing embedded charts on a full page 610 Displaying a slide show 611 Hiding series by hiding columns 612 Creating unlinked charts 613 Displaying text with the MouseOver event 615 Animating Charts 617 Scrolling a chart 619 Creating a hypocycloid chart 621 Creating a “clock” chart 622 Creating an Interactive Chart without VBA 624 Getting the data to create an interactive chart 625 Creating the Option Button controls for an interactive chart 625 Creating the city lists for the interactive chart 625 Creating the interactive chart data range 626 Creating the interactive chart 627 Excel 2007 Power Programming with VBAxxviii 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxviii Chapter 19: Understanding Excel’s Events 629 Event Types That Excel Can Monitor 630 What You Should Know about Events 631 Understanding event sequences 631 Where to put event handler procedures 631 Disabling events 632 Entering event handler code 634 Event handler procedures that use arguments 635 Workbook-Level Events 637 The Open event 639 The Activate event 640 The SheetActivate event 640 The NewSheet event 640 The BeforeSave event 640 The Deactivate event 641 The BeforePrint event 642 The BeforeClose event 643 Worksheet Events 645 The Change event 646 Monitoring a specific range for changes 647 The SelectionChange event 651 The BeforeDoubleClick event 652 The BeforeRightClick event 653 Chart Events 654 Application Events 656 Enabling Application-level events 657 Determining when a workbook is opened 658 Monitoring Application-level events 659 UserForm Events 661 Events Not Associated with an Object 662 The OnTime event 662 The OnKey event 664 Chapter 20: Interacting with Other Applications 669 Starting an Application from Excel 670 Using the VBA Shell function 670 Using the Windows ShellExecute API function 672 Activating an Application with Excel 674 Using AppActivate 674 Activating a Microsoft Office application 674 Running Control Panel Dialog Boxes 675 Using Automation in Excel 676 Working with foreign objects using automation 677 Early versus late binding 677 A simple example of late binding 680 Controlling Word from Excel 681 Controlling Excel from another application 684 Sending Personalized E-Mail via Outlook 687 Table of Contents xxix 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxix Sending E-Mail Attachments from Excel 690 Using SendKeys 693 Working with ADO 696 Chapter 21: Creating and Using Add-Ins 699 What Is an Add-In? 699 Comparing an add-in with a standard workbook 700 Why create add-ins? 701 Understanding Excel’s Add-In Manager 702 Creating an Add-in 703 An Add-In Example 704 Setting up the workbook for the example add-in 705 Adding descriptive information for the example add-in 705 Creating an add-in 706 Installing an add-in 707 Testing the add-in 708 Distributing an add-in 708 Modifying an add-in 708 Comparing XLAM and XLSM Files 710 XLSM and XLAM file size and structure 710 XLAM file VBA collection membership 710 Visibility of XLSM and XLAM files 710 Worksheets and chart sheets in XLSM and XLAM files 711 Accessing VBA procedures in an add-in 712 Manipulating Add-Ins with VBA 714 Understanding the AddIns collection 714 AddIn object properties 716 Accessing an add-in as a workbook 719 AddIn object events 719 Optimizing the Performance of Add-ins 720 Special Problems with Add-Ins 721 Ensuring that an add-in is installed 721 Referencing other files from an add-in 723 Detecting the proper Excel version for your add-in 724 Part VI: Developing Applications Chapter 22: Working with the Ribbon 727 Ribbon Basics 727 Ribbon tabs 728 VBA and the Ribbon 731 Accessing a Ribbon control 732 Working with the Ribbon 734 Activating a tab 735 Customizing the Ribbon 736 A simple RibbonX example 736 More about the simple RibbonX example 739 Another RibbonX example 742 Ribbon controls demo 745 Excel 2007 Power Programming with VBAxxx 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxx A DynamicMenu Control Example 752 More on Ribbon customization 755 Creating an Old-Style Toolbar 756 Limitations of old-style toolbars in Excel 2007 756 Code to create a toolbar 757 Chapter 23: Working with Shortcut Menus 761 CommandBar Overview 762 CommandBar types 762 Listing shortcut menus 762 Referring to CommandBars 764 Referring to controls in a CommandBar 764 Properties of CommandBar controls 766 Displaying all shortcut menu items 766 Using VBA to Customize Shortcut Menus 768 Resetting a shortcut menu 768 Disabling a Shortcut Menu 769 Disabling shortcut menu items 770 Adding a new item to the Cell shortcut menu 770 Adding a submenu to a shortcut menu 772 Shortcut Menus and Events 775 Adding and deleting menus automatically 775 Disabling or hiding shortcut menu items 776 Creating a context-sensitive shortcut menu 777 Chapter 24: Providing Help for Your Applications 781 Help for Your Excel Applications 782 Help Systems That Use Excel Components 784 Using cell comments for help 784 Using a text box for help 786 Using a worksheet to display help text 787 Displaying help in a UserForm 788 Displaying Help in a Web Browser 792 Using HTML files 792 Using an MHTML file 793 Using the HTML Help System 794 Using the Help method to display HTML Help 797 Using an API function to display HTML help 797 Associating a Help File with Your Application 798 Associating a help topic with a VBA function 798 Chapter 25: Developing User-Oriented Applications 801 What is a User-Oriented Application? 801 the Loan Amortization Wizard 802 Using the Loan Amortization Wizard 803 The Loan Amortization Wizard workbook structure 805 How the Loan Amortization Wizard works 805 Potential enhancements for the Loan Amortization Wizard 812 Application Development Concepts 813 Table of Contents xxxi 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxxi Part VII: Other Topics Chapter 26: Compatibility Issues 817 What Is Compatibility? 817 Types of Compatibility Problems 818 Avoid Using New Features 819 But Will It Work on a Mac? 820 Creating an International Application 822 Multilanguage applications 823 VBA language considerations 824 Using local properties 824 Identifying system settings 825 Date and time settings 828 Chapter 27: Manipulating Files with VBA 829 Performing Common File Operations 830 Using VBA file-related commands 830 Using the FileSystemObject object 835 Displaying Extended File Information 838 Working with Text Files 841 Opening a text file 842 Reading a text file 842 Writing a text file 843 Getting a file number 843 Determining or setting the file position 843 Statements for reading and writing 843 Text File Manipulation Examples 845 Importing data in a text file 845 Exporting a range to a text file 845 Importing a text file to a range 847 Logging Excel usage 848 Filtering a text file 848 Exporting a range to HTML format 849 Exporting a range to an XML file 852 Zipping and Unzipping Files 855 Zipping files 855 Unzipping a File 858 Chapter 28: Manipulating Visual Basic Components 859 Introducing the IDE 859 The IDE Object Model 861 The VBProjects collection 862 Displaying All Components in a VBA Project 864 Listing All VBA Procedures in a Workbook 866 Replacing a Module with an Updated Version 867 Using VBA to Write VBA Code 869 Adding Controls to a UserForm at Design Time 872 Design-time versus runtime UserForm manipulations 872 Adding 100 CommandButtons at design time 874 Excel 2007 Power Programming with VBAxxxii 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxxii Creating UserForms Programmatically 875 A simple runtime UserForm example 876 A useful (but not so simple) dynamic UserForm example 878 Chapter 29: Understanding Class Modules 885 What is a Class Module? 885 Example: Creating a NumLock Class 886 Inserting a class module 887 Adding VBA code to the class module 888 Using the NumLockClass class 890 More about Class Modules 891 Naming the object class 892 Programming properties of objects 892 Programming methods for objects 894 Class module events 894 Example: A CSV File Class 895 Class module–level variables for the CSVFileClass 895 Property procedures for the CSVFileClass 896 Method procedures for the CSVFileClass 896 Using the CSVFileClass object 898 Chapter 30: Working with Colors 901 Specifying Colors 902 The RGB color system 902 The HSL color system 903 Converting colors 904 Understanding Grayscale 906 Converting colors to gray 907 Viewing charts as grayscale 908 Experimenting with Colors 908 Understanding Document Themes 910 About document themes 910 Understanding document theme colors 911 Displaying all theme colors 915 Working with Shape Objects 917 A shape’s background color 917 Using other fill types with a shape 919 Learning more about shapes 925 Modifying Chart Colors 925 Chapter 31: Frequently Asked Questions about Excel Programming 929 General Excel Questions 930 How do I record a macro? 930 How do I run a macro? 930 What do I do if I don’t have a Developer tab? 930 I recorded a macro and saved my workbook. When I reopened it, the macros were gone! Where did they go? 931 Before saving my workbook as an XLSM file, I converted all my VBA statements to comments so I could debug the code later. When I re-opened the workbook, all my VBA code was gone. 931 How do I hide the Ribbon so it doesn’t take up so much space? 931 Where are my old custom toolbars? 931 Table of Contents xxxiii 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxxiii Can I make my old custom toolbars float? 931 Where can I find examples of VBA code? 931 How can I hide the status bar in Excel 2007? 932 Is there a utility that will convert my Excel application into a standalone EXE file? 932 Why doesn’t Ctrl+A select all the cells in my worksheet? 932 Why is the Custom Views command is grayed out? 932 How can I add a drop-down list to a cell so the user can choose a value from the list? 932 Can I use this drop-down list method if my list is stored on a different worksheet in the workbook? 932 I use Application.Calculation to set the calculation mode to manual. However, this seems to affect all workbooks and not just the active workbook. 933 Why doesn’t the F4 function key repeat all my operations? 933 What happened to the ability to “speak” the cell contents? 933 How can I increase the number of columns in a worksheet? 933 How can I increase the number of rows in a worksheet? 933 I opened a workbook, and it has only 65,546 rows. What happened? 934 How do I get my old workbook to use the new fonts? 934 How do I get a print preview? 934 When I switch to a new document template, my worksheet no longer fits on a single page. 934 How do I get rid of the annoying dotted-line page break display in Normal view mode? 934 Can I add that Show Page Breaks option to my QAT? 934 I changed the text in a cell to use Angle Clockwise orientation (in the Home ➪ Alignment group). How do I get the orientation back to normal? There’s no Horizontal Alignment option. 935 I’m trying to apply a table style to a table, but it has no visible effect. What can I do? 935 How do I get Office 2007 to support PDF output? 935 Can I change the color of the sheet tabs? 935 Can I change the font of the sheet tabs? 935 Can I change the default font and color of cell comments? 935 Can I write VBA macros that play sounds? 936 When I open a workbook, Excel asks whether I want to update the links. I’ve searched all my formulas and cannot find any links in this workbook. Is this a bug? 936 Why does Excel crash every time I start it? 936 The Visual Basic Editor 937 Can I use the VBA macro recorder to record all my macros? 937 I turned on the macro recorder when I edited a chart, but many of the commands weren’t recorded. 937 I have some macros that are general in nature. I would like to have these available all the time. What’s the best way to do this? 937 I can’t find my Personal Macro Workbook. Where is it? 937 I locked my VBA project with a password, and I forget what it was. Is there any way to unlock it? 937 How can I write a macro to change the password of my project? 937 When I insert a new module, it always starts with an Option Explicit line. What does this mean? 938 Why does my VBA code appear in different colors? Can I change these colors? 938 Can I delete a VBA module by using VBA code? 938 I wrote a macro in Excel 2000 that adds VBA code to the VB project. When I run it in Excel 2007, I get an error message. What’s wrong? 938 How can I write a macro to change the user’s macro security setting? I want to avoid the “this workbook contains macros” message when my application is opened. 938 How does the UserInterfaceOnly option work when protecting a worksheet? 939 How can I tell whether a workbook has a macro virus? 939 Excel 2007 Power Programming with VBAxxxiv 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxxiv I’m having trouble with the concatenation operator (&) in VBA. When I try to concatenate two strings, I get an error message. 939 I can’t seem to get the VBA line continuation character (underscore) to work. 939 I distributed an Excel application to many users. On some machines, my VBA error-handling procedures don’t work. Why not? 939 Procedures 940 What’s the difference between a VBA procedure and a macro? 940 What’s a procedure? 940 What is a variant data type? 940 What’s the difference between a variant array and an array of variants? 940 What’s a type-definition character? 941 I would like to create a procedure that automatically changes the formatting of a cell based on the data that I enter. For example, if I enter a value greater than 0, the cell’s background color should be red. Is this possible? 941 The Conditional Formatting feature is useful, but I’d like to perform other types of operations when data is entered into a cell. 941 What other types of events can be monitored? 941 I tried entering an event procedure (Sub Workbook_Open), but the procedure isn’t executed when the workbook is opened. What’s wrong? 942 I can write an event procedure for a particular workbook, but can I write an event procedure that will work for any workbook that’s open? 942 I’m very familiar with creating formulas in Excel. Does VBA use the same mathematical and logical operators? 942 How can I execute a procedure that’s in a different workbook? 942 I’ve used VBA to create several custom functions. I like to use these functions in my worksheet formulas, but I find it inconvenient to precede the function name with the workbook name. Is there any way around this? 943 I would like a particular workbook to be loaded every time I start Excel. I would also like a macro in this workbook to execute automatically. Am I asking too much? 943 I have a workbook that uses a Workbook_Open procedure. Is there a way to prevent this from executing when I open the workbook? 943 Can a VBA procedure access a cell’s value in a workbook that is not open? 943 How can I prevent the “save file” prompt from being displayed when I close a workbook from VBA? 944 How can I set things up so that my macro runs once every hour? 944 How do I prevent a macro from showing in the macro list? 944 Can I save a chart as a GIF file? 944 Are variables in a VBA procedure available to other VBA procedures? What if the procedure is in a different module? Or in a different workbook? 945 Functions 945 I created a VBA function for use in worksheet formulas. However, it always returns #NAME?. What went wrong? 945 I wrote a VBA function that works perfectly when I call it from another procedure, but it doesn’t work when I use it in a worksheet formula. What’s wrong? 945 When I access a custom worksheet function with the Insert Function dialog box, it reads “No help available.” How can I get the Insert Function dialog box to display a description of my function? 946 Can I also display help for the arguments for my custom function in the Insert Function dialog box? 946 My custom worksheet function appears in the User Defined category in the Insert Function dialog box. How can I make my function appear in a different function category? 946 Table of Contents xxxv 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxxv How can I create a new function category? 947 I have a custom function that will be used in a worksheet formula. If the user enters arguments that are not appropriate, how can I make the function return a true error value (#VALUE!)? 947 How can I force a recalculation of formulas that use my custom worksheet function? 947 Can I use Excel’s built-in worksheet functions in my VBA code? 948 Is there any way to force a line break in the text of a message box? 948 Objects, Properties, Methods, and Events 948 Is there a listing of the Excel objects I can use? 948 I’m overwhelmed with all the properties and methods available. How can I find out which methods and properties are available for a particular object? 948 What’s the story with collections? Is a collection an object? What are collections? 949 When I refer to a worksheet in my VBA code, I get a “subscript out of range” error. I’m not using any subscripts. What gives? 949 How can I prevent the user from scrolling around the worksheet? 950 What’s the difference between using Select and Application.Goto? 950 What’s the difference between activating a range and selecting a range? 950 Is there a quick way to delete all values from a worksheet yet keep the formulas intact? 950 I know how to write a VBA instruction to select a range by using a cell address, but how can I write one to select a range if I know only its row and column number? 951 When I try to record the Office ➪ Exit Excel command, Excel closes down before I can see what code it generates. Is there a VBA command to quit Excel? 951 How can I turn off screen updating while a macro is running? 951 What’s the easiest way to create a range name in VBA? 952 How can I determine whether a particular cell or range has a name? 952 Can I disable the Setup and Margins buttons that are displayed in Excel’s Print Preview window? 952 I have a lengthy macro, and it would be nice to display its progress in the status bar. Can I display messages in the status bar while a macro is running? 952 I recorded a VBA macro that copies a range and pastes it to another area. The macro uses the Select method. Is there a more efficient way to copy and paste? 953 I have not been able to find a method to sort a VBA array. Does this mean that I have to copy the values to a worksheet and then use the Range.Sort method? 953 My macro works with the selected cells, but it fails if something else (like a chart) is selected. How can I make sure that a range is selected? 953 How can I determine if a chart is activated? 954 My VBA macro needs to count the number of rows selected by the user. Using Selection.Rows. Count doesn’t work when nonadjacent rows are selected. Is this a bug? 954 I use Excel to create invoices. Can I generate a unique invoice number? 954 Is there a workbook property that forces an Excel workbook to always remain visible so it won’t be hidden by another application’s window? 955 Is there a VBA instruction to select the last entry in a column or row? Normally, I can use Ctrl+Shift+↓ or Ctrl+Shift+→ to do this, but how can I do it with a macro? 955 How can I determine the last non-empty cell in a particular column? 955 VBA references can become very lengthy, especially when I need to fully qualify an object by referencing its sheet and workbook. Can I reduce the length of these references? 956 Can I declare an array if I don’t know how many elements it will have? 956 Can I let the user undo my macro? 956 Can I pause a macro so the user can enter data into a certain cell? 956 VBA has an InputBox function, but there’s also an InputBox method for the Application object. Are these the same? 957 Excel 2007 Power Programming with VBAxxxvi 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxxvi I’m trying to write a VBA instruction that creates a formula. To do so, I need to insert a quote character (“) within quoted text. How can I do that? 957 I created an array, but the first element in that array is being treated as the second element. What’s wrong? 958 I would like my VBA code to run as quickly as possible. Any suggestions? 958 UserForms 958 My macro needs to get just a few pieces of information from the user, and a UserForm seems like overkill. Are there any alternatives? 958 I have 12 CommandButtons on a UserForm. How can I assign a single macro to be executed when any of the buttons is clicked? 959 How can I display a chart in a UserForm? 959 How can I remove the “X” from the title bar of my UserForm? I don’t want the user to click that button to close the form. 959 I created a UserForm with controls that are linked to cells on the worksheet with the ControlSource property. Is this the best way to do this? 959 Can I create a control array for a UserForm? It’s possible with Visual Basic, but I can’t figure out how to do it with Excel VBA. 960 Is there any difference between hiding a UserForm and unloading a UserForm? 960 How can I make my UserForm stay open while I do other things? 960 Excel 97 gives me a compile error when I write UserForm1.Show vbModeless. How can I make the form modeless in Excel 2000 and later while allowing it to remain modal in Excel 97? 960 I need to display a progress indicator like those you see when you’re installing software while a lengthy process is being executed. How can I do this? 961 How can I use Excel’s shapes on my UserForm? 961 How can I generate a list of files and directories into my UserForm so the user can select a file from the list? 961 I need to concatenate strings and display them in a ListBox control. But when I do so, they aren’t aligned properly. How can I get them to display equal spacing between strings? 962 Is there an easy way to fill a ListBox or ComboBox control with items? 962 Can I display a built-in Excel dialog box from VBA? 962 I tried the technique described in the preceding question and received an error message. Why is that? 962 Every time I create a UserForm, I go through the steps of adding an OK button and a Cancel button. Is there a way to get these controls to appear automatically? 963 Can I create a UserForm without a title bar? 963 When I click a button on my UserForm, nothing happens. What am I doing wrong? 963 Can I create a UserForm whose size is always the same, regardless of the video display resolution? 963 Can I create a UserForm box that lets the user select a range in a worksheet by pointing? 963 Can I change the startup position of a UserForm? 963 Can I make a UserForm that’s resizable by the user? 963 Add-ins 964 Where can I get Excel add-ins? 964 How do I install an add-in? 964 When I install my add-in from Excel’s Add-Ins dialog box, it shows up without a name or description. How can I give my add-in a description? 964 I have several add-ins that I no longer use, but I can’t figure out how to remove them from the Add-Ins Available list in the Add-Ins dialog box. What’s the story? 964 How do I create an add-in? 965 I try to create an add-in, but the Save as Type drop-down box doesn’t provide Add-in as an option. 965 Should I convert all my essential workbooks to add-ins? 965 Table of Contents xxxvii 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxxvii Do I need to keep two copies of my workbook: the XLSM version and the XLAM version? 965 How do I modify an add-in after it has been created? 965 What’s the difference between an XLSM file and an XLAM file created from an XLSM file? Is the XLAM version compiled? Does it run faster? 965 How do I protect the code in my add-in from being viewed by others? 966 Are my add-ins safe? In other words, if I distribute an XLAM file, can I be assured that no one else will be able to view my code? 966 User Interface 966 How do I use VBA to add a simple button to the Ribbon? 966 What are my options for modifying the user interface to make it easy for a user to run my macros? 966 How do I add a macro to the Quick Access Toolbar? 967 I added my macro to the QAT, but clicking the icon generates an error. 967 How do I use VBA to activate a particular tab on the Ribbon? 967 How can I disable all the right-click shortcut menus? 967 Part VIII: Appendixes Appendix A: Excel Resources Online 971 The Excel Help System 971 Microsoft Technical Support 972 Support options 972 Microsoft Knowledge Base 972 Microsoft Excel home page 972 Microsoft Office home page 972 Internet Newsgroups 973 Accessing newsgroups by using a newsreader 973 Accessing newsgroups by using a Web browser 973 Searching newsgroups 974 Internet Web sites 975 The Spreadsheet Page 975 Daily Dose of Excel 976 Jon Peltier’s Excel Page 976 Pearson Software Consulting 976 Stephen Bullen’s Excel Page 976 David McRitchie’s Excel Pages 976 Mr. Excel 976 Appendix B: VBA Statements and Functions Reference 977 Invoking Excel functions in VBA instructions 981 Appendix C: VBA Error Codes 989 Appendix D: What’s on the CD-ROM 993 System Requirements 993 Using the CD 994 Files and Software on the CD 994 eBook version of Excel 2007 Power Programming with VBA 994 Sample files for Excel 2007 Power Programming with VBA 994 Troubleshooting 1009 Index 1011 Excel 2007 Power Programming with VBAxxxviii 03_044018 ftoc.qxp 2/28/07 6:33 PM Page xxxviii Some Essential Background Chapter 1 Excel 2007: Where It Came From Chapter 2 Excel in a Nutshell Chapter 3 Formula Tricks and Techniques Chapter 4 Understanding Excel’s Files IPart 04_044018 pt01.qxp 2/28/07 6:33 PM Page 1 04_044018 pt01.qxp 2/28/07 6:33 PM Page 2 Chapter Excel 2007: Where It Came From In This Chapter To fully appreciate the application development features available in Excel 2007, it’s important to understand where this product came from and how it fits into the overall scheme of things. ◆ A history of spreadsheets — where they came from, who makes them, and what differentiates them ◆ A discussion of Excel’s evolution ◆ An analysis of why Excel is a good tool for developers If you’ve worked with personal computers and spreadsheets over the past decade, this information may be old hat. If you’re a trivia buff, this chapter is a gold mine. Study this information, and you’ll be a hit at the next computer geek party that you attend. A Brief History of Spreadsheets Most of us tend to take spreadsheet software for granted. In fact, it may be hard to fathom, but there really was a time when electronic spreadsheets were not available. Back then, people relied instead on clumsy mainframes or calcu- lators and spent hours doing what now takes minutes. 1 3 05_044018 ch01.qxp 2/28/07 6:34 PM Page 3 It all started with VisiCalc The world’s first electronic spreadsheet, VisiCalc, was conjured up by Dan Bricklin and Bob Frankston back in 1978, when personal computers were pretty much unheard of in the office environment. VisiCalc was written for the Apple II computer, which was an interest- ing little machine that is something of a toy by today’s standards. (But in its day, the Apple II kept me mesmerized for days at a time.) VisiCalc essentially laid the foundation for future spreadsheets, and its row-and-column–based layout and formula syntax are still found in modern spreadsheet products. VisiCalc caught on quickly, and many forward-look- ing companies purchased the Apple II for the sole purpose of developing their budgets with VisiCalc. Consequently, VisiCalc is often credited for much of the Apple II’s initial success. In the meantime, another class of personal computers was evolving; these PCs ran the CP/M operating system. A company called Sorcim developed SuperCalc, which was a spreadsheet that also attracted a legion of followers. When the IBM PC arrived on the scene in 1981, legitimizing personal computers, VisiCorp wasted no time porting VisiCalc to this new hardware environment, and Sorcim soon fol- lowed with a PC version of SuperCalc. By current standards, both VisiCalc and SuperCalc were extremely crude. For example, text entered into a cell could not extend beyond the cell — a lengthy title had to be entered into multiple cells. Nevertheless, the ability to automate the budgeting tedium was enough to lure thousands of accountants from paper ledger sheets to floppy disks. TIP You can download a copy of the original VisiCalc from Dan Bricklin’s Web site. And yes, nearly 30 years later, this 27K program still runs on today’s PCs (see Figure 1-1). You can find it at www.bricklin.com. Figure 1-1: VisiCalc, running in a DOS window on a PC running Windows XP. Part I: Some Essential Background4 05_044018 ch01.qxp 2/28/07 6:34 PM Page 4 Lotus 1-2-3 Envious of VisiCalc’s success, a small group of computer freaks at a start-up company in Cambridge, Massachusetts, refined the spreadsheet concept. Headed by Mitch Kapor and Jonathan Sachs, the company designed a new product and launched the software industry’s first full-fledged marketing blitz. I remember seeing a large display ad for 1-2-3 in The Wall Street Journal. It was the first time that I’d ever seen software advertised in a general inter- est publication. Released in January 1983, Lotus Development Corporation’s 1-2-3 was an instant success. Despite its $495 price tag (yes, people really paid that much for software), it quickly out- sold VisiCalc, rocketing to the top of the sales charts, where it remained for many years. Lotus 1-2-3 improved on all the basics embodied in VisiCalc and SuperCalc and was also the first program to take advantage of the new and unique features found in the powerful 16-bit IBM PC AT. For example, 1-2-3 bypassed the slower DOS calls and wrote text directly to display memory, giving it a snappy and responsive feel that was unusual for the time. The online help system was a breakthrough, and the ingenious “moving bar” menu style set the standard for many years. One feature that really set 1-2-3 apart, though, was its macro capability — a powerful tool that enabled spreadsheet users to record their keystrokes to automate many procedures. When such a macro was “played back,” the original keystrokes were sent to the applica- tion. Although this was a far cry from today’s macro capability, 1-2-3 macros were defi- nitely a step in the right direction. 1-2-3 was not the first integrated package, but it was the first successful one. It combined (1) a powerful electronic spreadsheet with (2) elementary graphics and (3) some limited but handy database features. Easy as 1, 2, 3 — get it? Lotus followed up the original 1-2-3 Release 1 with Release 1A in April 1983. This product enjoyed tremendous success and put Lotus in the enviable position of virtually owning the spreadsheet market. In September 1985, Release 1A was replaced by Release 2, which was a major upgrade that was superseded by the bug-fixed Release 2.01 the following July. Release 2 introduced add-ins, which are special-purpose programs that can be attached to give an application new features and extend the application’s useful life. Release 2 also had improved memory management, more functions, 8,192 rows (four times as many as its predecessor), and added support for a math coprocessor. Release 2 also included some sig- nificant enhancements to the macro language. Not surprisingly, the success of 1-2-3 spawned many clones — work-alike products that usually offered a few additional features and sold at a much lower price. Among the more notable were Paperback Software’s VP Planner series and Mosaic Software’s Twin. Lotus eventually took legal action against Paperback Software for copyright infringement (for copying the “look and feel” of 1-2-3); the successful suit essentially put Paperback out of business. In the summer of 1989, Lotus shipped DOS and OS/2 versions of the long-delayed 1-2-3 Release 3. This product literally added a dimension to the familiar row-and-column–based spreadsheet: It extended the paradigm by adding multiple spreadsheet pages. The idea Chapter 1: Excel 2007: Where It Came From Part I 5 05_044018 ch01.qxp 2/28/07 6:34 PM Page 5 wasn’t really new, however; a relatively obscure product called Boeing Calc originated the 3-D spreadsheet concept, and SuperCalc 5 and CubeCalc also incorporated it. 1-2-3 Release 3 offered features that users wanted — features that ultimately became standard fare: multilayered worksheets, the capability to work with multiple files simulta- neously, file linking, improved graphics, and direct access to external database files. But it still lacked an important feature that users were begging for: a way to produce high-quality printed output. Release 3 began life with a reduced market potential because it required an 80286-based PC and a minimum of 1MB of RAM — fairly hefty requirements in 1989. But Lotus had an ace up its corporate sleeve. Concurrent with the shipping of Release 3, the company sur- prised nearly everyone by announcing an upgrade of Release 2.01. (The product material- ized a few months later as 1-2-3 Release 2.2.) Release 3 was not a replacement for Release 2, as most analysts had expected. Rather, Lotus made the brilliant move of splitting the spreadsheet market into two segments: those with high-end hardware and those with more mundane equipment. 1-2-3 Release 2.2 wasn’t a panacea for spreadsheet buffs, but it was a significant improve- ment. The most important Release 2.2 feature was Allways, an add-in that gave users the ability to churn out attractive reports, complete with multiple typefaces, borders, and shad- ing. In addition, users could view the results onscreen in a WYSIWYG (What You See Is What You Get) manner. Allways didn’t, however, let users issue any worksheet commands while they viewed and formatted their work in WYSIWYG mode. Despite this rather severe limitation, most 1-2-3 users were overjoyed with this new capability because they could finally produce near-typeset-quality output. In May 1990, Microsoft released Windows 3.0. As you probably know, Windows changed the way that people used personal computers. Apparently, the decision makers at Lotus weren’t convinced that Windows was a significant product, and the company was slow get- ting out of the gate with its first Windows spreadsheet, 1-2-3 for Windows, which wasn’t introduced until late 1991. Worse, this product was, in short, a dud. It didn’t really capital- ize on the Windows environment and disappointed many users. It also disappointed at least one book author. My very first book was titled PC World 1-2-3 For Windows Complete Handbook. I think it sold fewer than 1,000 copies. Serious competition from Lotus never materialized. Consequently, Excel, which had already established itself as the premier Windows spreadsheet, became the overwhelming Windows spreadsheet market leader and has never left that position. Lotus came back with 1-2-3 Release 4 for Windows in June 1993, which was a vast improvement over the original. Release 5 for Windows appeared in mid-1994. Also in mid-1994, Lotus unveiled 1-2-3 Release 4.0 for DOS. Many analysts (including myself) expected a product more compatible with the Windows product. But we were wrong; DOS Release 4.0 was simply an upgraded version of Release 3.4. Because of the widespread acceptance of Windows, that was the last DOS version of 1-2-3 to see the light of day. Over the years, spreadsheets became less important to Lotus (its flagship product turns out to be Notes). In mid-1995, IBM purchased Lotus Development Corporation. Two more versions of 1-2-3 became available, but it seems to be a case of too little, too late. Excel clearly dominates the spreadsheet market, and 1-2-3 users are an increasingly rare breed. Part I: Some Essential Background6 05_044018 ch01.qxp 2/28/07 6:34 PM Page 6 Quattro Pro The other significant player in the spreadsheet world is (or, I should say, was) Borland Interna- tional. In 1994, Novell purchased WordPerfect International and Borland’s entire spreadsheet business. In 1996, WordPerfect and Quattro Pro were both purchased by Corel Corporation. Borland started in spreadsheets in 1987 with a product called Quattro. Word has it that the internal code name was Buddha because the program was intended to “assume the Lotus position” in the market (that is, #1). Essentially a clone of 1-2-3, Quattro offered a few addi- tional features and an arguably better menu system at a much lower price. Importantly, users could opt for a 1-2-3-like menu system that let them use familiar commands and also ensured compatibility with 1-2-3 macros. In the fall of 1989, Borland began shipping Quattro Pro, which was a more powerful prod- uct that built upon the original Quattro and trumped 1-2-3 in just about every area. For example, the first Quattro Pro let you work with multiple worksheets in movable and resiz- able windows — although it did not have a graphical user interface (GUI). More trivia: Quattro Pro was based on an obscure product called Surpass, which Borland acquired. Released in late 1990, Quattro Pro Version 2.0 added 3-D graphs and a link to Borland’s Paradox database. A mere six months later — much to the chagrin of Quattro Pro book authors — Version 3.0 appeared, featuring an optional graphical user interface and a slide show feature. In the spring of 1992, Version 4 appeared with customizable SpeedBars and an innovative analytical graphics feature. Version 5, which came out in 1994, had only one significant new feature: worksheet notebooks (that is, 3-D worksheets). Like Lotus, Borland was slow to jump on the Windows bandwagon. When Quattro Pro for Windows finally shipped in the fall of 1992, however, it provided some tough competition for the other two Windows spreadsheets, Excel 4.0 and 1-2-3 Release 1.1 for Windows. Importantly, Quattro Pro for Windows had an innovative feature, known as the UI Builder, that let developers and advanced users easily create custom user interfaces. Also worth noting was a lawsuit between Lotus and Borland. Lotus won the suit, forcing Borland to remove the 1-2-3 macro compatibility and 1-2-3 menu option from Quattro Pro. This ruling was eventually overturned in late 1994, however, and Quattro Pro can now include 1-2-3 compatibility features (as if anyone really cares). Both sides spent millions of dollars on this lengthy legal fight, and when the dust cleared, no real winner emerged. Borland followed up the original Quattro Pro for Windows with Version 5, which was upgraded to Version 6 after Novell took over Borland’s spreadsheet business. As I write, the current version of Quattro Pro is Version 13, which is part of WordPerfect Office X3. There was a time when Quattro Pro seemed the ultimate solution for spreadsheet develop- ers. But then Excel 5 arrived. Microsoft Excel And now on to the good stuff. Most people don’t realize that Microsoft’s experience with spreadsheets extends back to the early ’80s. Over the years, Microsoft’s spreadsheet offerings have come a long way, from the barely adequate MultiPlan to the powerful Excel 2007. Chapter 1: Excel 2007: Where It Came From Part I 7 05_044018 ch01.qxp 2/28/07 6:34 PM Page 7 In 1982, Microsoft released its first spreadsheet, MultiPlan. Designed for computers run- ning the CP/M operating system, the product was subsequently ported to several other platforms, including Apple II, Apple III, XENIX, and MS-DOS. MultiPlan essentially ignored existing software user-interface standards. Difficult to learn and use, it never earned much of a following in the United States. Not surprisingly, Lotus 1-2-3 pretty much left MultiPlan in the dust. Excel sort of evolved from MultiPlan, first surfacing in 1985 on the Macintosh. Like all Mac applications, Excel was a graphics-based program (unlike the character-based MultiPlan). In November 1987, Microsoft released the first version of Excel for Windows (labeled Excel 2.0 to correspond with the Macintosh version). Because Windows was not in widespread use at the time, this version included a runtime version of Windows — a spe- cial version that had just enough features to run Excel and nothing else. Less than a year later, Microsoft released Excel Version 2.1. In July 1990, Microsoft released a minor upgrade (2.1d) that was compatible with Windows 3.0. Although these 2.x versions were quite rudimentary by current standards (see Figure 1-2) and didn’t have the attractive, sculpted look of later versions, they attracted a small but loyal group of supporters and provided an excellent foundation for future development. Figure 1-2: The original Excel 2.1 for Windows. This product has come a long way. (Photo courtesy of Microsoft) Part I: Some Essential Background8 05_044018 ch01.qxp 2/28/07 6:34 PM Page 8 Excel’s first macro language also appeared in Version 2.The XLM macro language con- sisted of functions that were evaluated in sequence. It was quite powerful, but very diffi- cult to learn and use. As you’ll see, the XLM macro language was replaced by Visual Basic for Applications (VBA), which is the topic of this book. Meanwhile, Microsoft developed a version of Excel (numbered 2.20) for OS/2 Presentation Manager, released in September 1989 and upgraded to Version 2.21 about 10 months later. OS/2 never quite caught on, despite continued efforts by IBM. In December 1990, Microsoft released Excel 3 for Windows, which boasted a significant improvement in both appearance and features (see Figure 1-3). The upgrade included a toolbar, drawing capabilities, a powerful optimization feature (Solver), add-in support, Object Linking and Embedding (OLE) support, 3-D charts, macro buttons, simplified file consolidation, workgroup editing, and the ability to wrap text in a cell. Excel 3 also had the capability to work with external databases (via the Q+E program). The OS/2 version upgrade appeared five months later. Figure 1-3: Excel 3 was a vast improvement over the original release. (Photo courtesy of Microsoft) Chapter 1: Excel 2007: Where It Came From Part I 9 05_044018 ch01.qxp 2/28/07 6:34 PM Page 9 Version 4, released in the spring of 1992, not only was easier to use but also had more power and sophistication for advanced users (see Figure 1-4). Excel 4 took top honors in virtually every spreadsheet product comparison published in the trade magazines. In the meantime, the relationship between Microsoft and IBM became increasingly strained; Excel 4 was never released for OS/2, and Microsoft has stopped making versions of Excel for OS/2. Excel 5 hit the streets in early 1994 and immediately earned rave reviews. Like its prede- cessor, it finished at the top of every spreadsheet comparison published in the leading trade magazines. Despite stiff competition from 1-2-3 Release 5 for Windows and Quattro Pro for Windows 5 — both were fine products that could handle just about any spreadsheet task thrown their way — Excel 5 continued to rule the roost. This version, by the way, was the first to feature VBA. Figure 1-4: Excel 4 was another significant step forward, although still far from Excel 5. (Photo courtesy of Microsoft) Part I: Some Essential Background10 05_044018 ch01.qxp 2/28/07 6:34 PM Page 10 Excel 95 (also known as Excel 7) was released concurrently with Microsoft Windows 95. (Microsoft skipped over Version 6 to make the version numbers consistent across its Office products.) On the surface, Excel 95 didn’t appear to be much different from Excel 5. Much of the core code was rewritten, however, and speed improvements were apparent in many areas. Importantly, Excel 95 used the same file format as Excel 5, which is the first time that an Excel upgrade didn’t use a new file format. This compatibility wasn’t perfect, how- ever, because Excel 95 included a few enhancements in the VBA language. Consequently, it was possible to develop an application using Excel 95 that would load but not run properly in Excel 5. In early 1997, Microsoft released Office 97, which included Excel 97. Excel 97 is also known as Excel 8. This version included dozens of general enhancements plus a completely new interface for developing VBA-based applications. In addition, the product offered a new way of developing custom dialog boxes (called UserForms rather than dialog sheets). Microsoft tried to make Excel 97 compatible with previous versions, but the compatibility was far from perfect. Many applications that were developed using Excel 5 or Excel 95 required some tweaking before they would work with Excel 97 or later versions. CROSS-REFERENCE I discuss compatibility issues in Chapter 26. Excel 2000 was released in early 1999 and was also sold as part of Office 2000. The enhancements in Excel 2000 dealt primarily with Internet capabilities, although a few sig- nificant changes were apparent in the area of programming. Excel 2002 (sometimes known as Excel XP) hit the market in mid-2001. Like its predeces- sor, it didn’t offer a raft of significant new features. Rather, it had a number of minor new features and several refinements of existing features. Perhaps the most compelling new feature was the ability to repair damaged files and save your work when Excel crashed. Excel 2003 (released in Fall 2003) was perhaps the most disappointing upgrade ever. This version had very few new features. Microsoft touted the ability to import and export eXtensible Markup Language (XML) files and map the data to specific cells in a worksheet — but very few users actually need such a feature. In addition, Microsoft intro- duced some “rights management” features that let you place restrictions on various parts of a workbook (for example, allow only certain users to view a particular worksheet). In addition, Excel 2003 had a new Help system (which now puts the Help contents in the task pane) and a new “research” feature that lets you look up a variety of information in the task pane. (Some of these require a fee-based account.) NOTE For some reason, Microsoft chose to offer two sub-versions of Excel 2003. The XML and rights management features are available only in the standalone version of Excel and in the version of Excel that’s included with the Professional version of Office 2003. Because of this, Excel developers may now need to deal with compatibility issues within a particu- lar version! Chapter 1: Excel 2007: Where It Came From Part I 11 05_044018 ch01.qxp 2/28/07 6:34 PM Page 11 Excel 2007, the focus of this book, is part of the Microsoft 2007 Office System. This upgrade is clearly the most significant ever. The user interface has been completely revamped. Menus and toolbars have been replaced by a new Ribbon UI (see Figure 1-5). Excel 2007’s grid size is 1,000 times larger than in previous versions, and the product uses a new open XML file format. Other improvements include improved tables, conditional formatting enhancements, major cosmetic enhancements for charts, and document themes. It remains to be seen how the market will react to such an extreme upgrade. Clearly, Excel 2007 is easier for beginners, but long-time users will spend a lot of time wondering where to find their old commands. So there you have it: 28 years of spreadsheet history condensed into a few pages. It has been an interesting ride, and I’ve been fortunate enough to have been involved with spread- sheets the entire time. Things have changed. Microsoft not only dominates the spreadsheet market, it virtually owns it. What little competition exists is primarily in the form of “open source” products such as OpenOffice and StarOffice. You’ll also hear about up-and-coming Web spreadsheets such as Google Spreadsheets. In reality, these are not even considered minor threats to Microsoft. In fact, Microsoft’s biggest competitor is itself. Users tend to settle on a particular version of Excel and have very little motivation to upgrade. Convinc- ing users to upgrade to the radically different Excel 2007 may be one of Microsoft’s biggest challenges yet. Figure 1-5: Excel 2007 uses a new Ribbon user interface. Part I: Some Essential Background12 05_044018 ch01.qxp 2/28/07 6:34 PM Page 12 Why Excel Is Great for Developers Excel is a highly programmable product, and it’s easily the best choice for developing spreadsheet-based applications. For developers, Excel’s key features include the following: • File structure: The multisheet orientation makes it easy to organize an application’s ele- ments and store them in a single file. For example, a single workbook file can hold any number of worksheets and chart sheets. UserForms and VBA modules are stored with a workbook but are invisible to the end user. • Visual Basic for Applications: This macro language lets you create structured programs directly in Excel. This book focuses on using VBA, which, as you’ll discover, is extremely powerful and relatively easy to learn. • Easy access to controls: Excel makes it very easy to add controls such as buttons, list boxes, and option buttons to a worksheet. Implementing these controls often requires little or no macro programming. • Custom dialog boxes: You can easily create professional-looking dialog boxes by creating UserForms. • Custom worksheet functions: With VBA, you can create custom worksheet functions to simplify formulas and calculations. • Customizable user interface: Developers have lots of control over the user interface. In previous versions, this involved creating custom menus and toolbars. In Excel 2007, it involves modifying the Ribbon. Changing the Excel 2007 interface is not as easy as it was in previous versions, but it’s still possible. • Customizable shortcut menus: Using VBA, you can customize the right-click, context- sensitive shortcut menus. • Powerful data analysis options: Excel’s PivotTable feature makes it easy to summarize large amounts of data with very little effort. • Microsoft Query: You can access important data directly from the spreadsheet envi- ronment. Data sources include standard database file formats, text files, and Web pages. • Data Access Objects (DAO) and ActiveX Data Objects (ADO): These features make it easy to work with external databases by using VBA. • Extensive protection options: Your applications can be kept confidential and protected from changes by casual users. • Ability to create “compiled” add-ins: With a single command, you can create XLA add-in files that add new features to Excel. • Support for automation: With VBA, you can control other applications that support automation. For example, your VBA macro can generate a report in Microsoft Word. Chapter 1: Excel 2007: Where It Came From Part I 13 05_044018 ch01.qxp 2/28/07 6:34 PM Page 13 • Ability to create Web pages: It’s easy to create a HyperText Markup Language (HTML) document from an Excel workbook. The HTML is very bloated, but it’s readable by Web browsers. Excel’s Role in Microsoft’s Strategy Currently, most copies of Excel are sold as part of Microsoft Office — a suite of products that includes a variety of other programs. (The exact programs that you get depend on which version of Office you buy.) Obviously, it helps if the programs can communicate well with each other. Microsoft is at the forefront of this trend. All the Office products have extremely similar user interfaces, and all support VBA. Therefore, after you hone your VBA skills in Excel, you’ll be able to put them to good use in other applications — you just need to learn the object model for the other applications. Part I: Some Essential Background14 05_044018 ch01.qxp 2/28/07 6:34 PM Page 14 Chapter Excel in a Nutshell In This Chapter In this chapter, I provide a broad overview of the major components of Excel 2007. ◆ An introduction to Excel’s object orientation ◆ A conceptual overview of Excel 2007, including a description of its major features ◆ A list of the new features in Excel 2007 ◆ Some tips and techniques that even advanced users may find helpful This chapter will prove especially useful for casual Excel users who may not have discovered all of the features available. However, even experienced Excel users still may discover a thing or two by skimming through this chapter. For more details on a particular feature, consult the Help system or do a Web search. Thinking in Terms of Objects When you are developing applications with Excel (especially when you are dab- bling with Visual Basic for Applications — VBA), it’s helpful to think in terms of objects, or Excel elements that you can manipulate manually or via a macro. Here are some examples of Excel objects: • The Excel application • An Excel workbook 2 15 06_044018 ch02.qxp 2/28/07 6:34 PM Page 15 • A worksheet in a workbook • A range or a table in a worksheet • A ListBox control on a UserForm (a custom dialog box) • A chart embedded in a worksheet • A chart series on a chart • A particular data point in a chart You may notice that an object hierarchy exists here: The Excel object contains workbook objects, which contain worksheet objects, which contain range objects. This hierarchy com- prises Excel’s object model. Excel has more than 200 classes of objects that you can control directly or by using VBA. Other Microsoft Office 2007 products have their own object models. NOTE Controlling objects is fundamental to developing applications. Throughout this book, you find out how to automate tasks by controlling Excel’s objects, and you do so by using VBA. This concept becomes clearer in subsequent chapters. Workbooks One of the most common Excel objects is a workbook. Everything that you do in Excel takes place in a workbook, which is stored in a file that, by default, has an XLSX extension. An Excel workbook can hold any number of sheets (limited only by memory). There are four types of sheets: • Worksheets • Chart sheets • XLM macro sheets (obsolete, but still supported) • Dialog sheets (obsolete, but still supported) You can open or create as many workbooks as you like (each in its own window), but at any given time, only one workbook is the active workbook. Similarly, only one sheet in a work- book is the active sheet. To activate a sheet, click its sheet tab at the bottom of the screen. To change a sheet’s name, double-click the tab and enter the new text. Right-clicking a tab brings up a shortcut menu with additional options for the sheet, including changing its tab color, hiding the sheet, and so on. You can also hide the window that contains a workbook by using the View ➪ Window ➪ Hide command. A hidden workbook window remains open, but it is not visible to the user. Part I: Some Essential Background16 06_044018 ch02.qxp 2/28/07 6:34 PM Page 16 Worksheets The most common type of sheet is a worksheet, which is what people normally think of when they think of a spreadsheet. Worksheets contain cells, and the cells store data and formulas. Every Excel 2007 worksheet has 16,384 columns and 1,048,576 rows. You can hide unneeded rows and columns to keep them out of view, but you cannot increase the number of rows or columns. NOTE Versions prior to Excel 2007 used the XLS binary format, and worksheets had only 65,536 rows and 256 columns. If you open such a file, Excel 2007 enters “compatibility mode” in order to work with the smaller worksheet grid. To convert such a file to the new format, save it as an XLSX or XLSM file. Then close the workbook and re-open it. The real value of using multiple worksheets in a workbook is not access to more cells. Rather, multiple worksheets enable you to organize your work better. Back in the old days, when a file comprised a single worksheet, developers wasted a lot of time trying to orga- nize the worksheet to hold their information efficiently. Now you can store information on any number of worksheets and still access it instantly by clicking a sheet tab. As you know, a worksheet cell can hold a constant value or the result of a formula. The value may be a number, a date, a Boolean value (True or False), or text. Every worksheet also has an invisible drawing layer, which lets you insert graphic objects, such as charts, shapes, SmartArt, UserForm controls, pictures, and other embedded objects. Chapter 2: Excel in a Nutshell Part I 17 It’s interesting to stop and think about the actual size of a worksheet. Do the arithmetic (16,384 × 1,048,576), and you’ll see that a worksheet has 17,179,869,184 cells. Remember that this is in just one worksheet. A single workbook can hold more than one worksheet. If you’re using a 1024 x 768 video mode with the default row heights and column widths, you can see 15 columns and 25 rows (or 375 cells) at a time — which is about .000002 percent of the entire worksheet. In other words, more than 45 million screens of information reside within a single worksheet. If you were to enter a single digit into each cell at the relatively rapid clip of one cell per second, it would take you about 545 years, nonstop, to fill up a worksheet. To print the results of your efforts would require more than 40 million sheets of paper — a stack more than a mile high. As you might suspect, filling an entire workbook with values is not possible. It’s not even close to being possible. You would soon run out of memory, and Excel would probably crash. How Big Is a Worksheet? 06_044018 ch02.qxp 2/28/07 6:34 PM Page 17 You have complete control over the column widths and row heights — in fact, you can even hide rows and columns (as well as entire worksheets). You can specify any font size, and you have complete control over colors. Text in a cell can be displayed vertically (or at an angle) and can even be wrapped around to occupy multiple lines. NEW In the past, Excel was limited to a palette of 56 colors. With Excel 2007, the number of colors is virtually unlimited. In addition, Excel 2007 supports document themes. A single click lets you apply a new theme to a workbook, which can give it an entirely dif- ferent look. Chart sheets A chart sheet normally holds a single chart. Many users ignore chart sheets, preferring to store charts on the worksheet’s drawing layer. Using chart sheets is optional, but they make it a bit easier to print a chart on a page by itself, and they are especially useful for presentations. Figure 2-1 shows a pie chart on a chart sheet. Figure 2-1: A pie chart on a chart sheet. XLM macro sheets An XLM macro sheet (also known as an MS Excel 4 macro sheet) is essentially a worksheet, but it has some different defaults. More specifically, an XLM macro sheet displays formulas rather than the results of formulas. In addition, the default column width is larger than in a normal worksheet. Part I: Some Essential Background18 06_044018 ch02.qxp 2/28/07 6:34 PM Page 18 Chapter 2: Excel in a Nutshell 19 Part I Here’s a quick-and-dirty overview of the new features in Excel 2007: • A new tab-and-ribbon user interface • New XML file formats • Worksheet tables • Significantly larger worksheet grid (1,048,576 rows x 16,384 columns) • Ability to use more memory • Unlimited conditional formats per cell • 100 levels of undo • Maximum formula length increased to 8,000 characters • Supports 64 levels of nesting in a formula • Formula AutoComplete • Better-looking charts • Workbook themes • Skins • Page Layout view • New conditional formatting options • Less confusing Excel Options dialog box • New collaboration features (requires SharePoint) • SmartArt and improved WordArt • Compatibility checker • Easier pivot tables • Twelve new worksheet functions, plus integration of the Analysis ToolPak functions • PDF output • Resizable formula bar • Many new templates • More control over the status bar What’s New in Excel 2007? 06_044018 ch02.qxp 2/28/07 6:34 PM Page 19 As the name suggests, an XLM macro sheet is designed to hold XLM macros. As you may know, the XLM macro system is a holdover from previous versions of Excel (version 4.0 and earlier). Excel 2007 continues to support XLM macros for compatibility reasons — although it no longer provides the option of recording an XLM macro. This book does not cover the XLM macro system; instead, it focuses on the more powerful VBA macro system. Excel 5/95 dialog sheets In Excel 5 and Excel 95, you created a custom dialog box by inserting a special dialog sheet. Excel 97 and later versions still support these dialog sheets, but a much better alternative is available: UserForms. You work with UserForms in the Visual Basic Editor (VBE). If you open a workbook that contains an Excel 5/95 dialog sheet, you can access the dialog sheet by clicking its tab. I don’t discuss Excel 5/95 dialog sheets in this book. Excel’s User Interface The user interface (UI) is the means by which an end user communicates with a computer program. Generally speaking, a UI includes elements such as menus, toolbars, dialog boxes, keystroke combinations, and so on. This section discusses the main components of Excel 2007’s UI: • The Ribbon • Shortcut menus • Dialog boxes • Keyboard shortcuts • Smart Tags • Task pane Introducing the Ribbon If you’ve used Excel 2007 for more than a minute, you know that it has an entirely new UI. Menus and toolbars are gone, replaced with a brand new “tab and Ribbon” UI. Click a tab along the top (that is, a word such as Home, Insert, Page Layout), and the Ribbon displays the commands for that tab. Office 2007 is the first software in history to use this new interface, so the jury is still out regarding how it will be accepted. The appearance of the commands on the Ribbon varies, depending on the width of the Excel window. When the window is too narrow to display everything, the commands adapt, and may seem to be missing. But the commands are still available. Figure 2-2 shows the Home tab of the Ribbon with all controls fully visible. Figure 2-3 shows the Ribbon when Part I: Some Essential Background20 06_044018 ch02.qxp 2/28/07 6:34 PM Page 20 Excel’s window is narrower. Notice that some of the descriptive text is gone, but the icons remain. Figure 2-4 shows the extreme case, in which the window is very narrow. Some of the groups display a single icon. However, if you click the icon, all of the group commands are available to you. Figure 2-2: The Home tab of the Ribbon. Figure 2-3: The Home tab when Excel’s window is narrower. Figure 2-4: The Home tab when Excel’s window is very narrow. TIP If you would like to hide the Ribbon to increase your worksheet view, just double-click any of the tabs. The Ribbon goes away, and you’ll be able to see about five additional rows of your worksheet. When you need to use the Ribbon again, just click any tab, and it comes back. You can also press Ctrl+F1 to toggle the Ribbon display on and off. CONTEXTUAL TABS In addition to the standard tabs, Excel 2007 includes “contextual tabs.” Whenever an object (such as a chart, a table, a picture, or SmartArt) is selected, tools for working with that specific object are made available in the Ribbon. Figure 2-5 shows the contextual tabs that appear when a pivot table is selected. In this case, Excel displays two contextual tabs: Options and Design. Notice that the contextual tabs contain a description (PivotTable Tools) in Excel’s title bar. When contextual tabs are displayed, you can, of course, continue to use all of the other tabs. Chapter 2: Excel in a Nutshell Part I 21 06_044018 ch02.qxp 2/28/07 6:34 PM Page 21 Figure 2-5: When you select an object, contextual tabs contain tools for working with that object. TYPES OF COMMANDS ON THE RIBBON For the most part, the commands in the Ribbon work just as you would expect them to. You’ll encounter several different styles of commands on the Ribbon, as described next: • Simple buttons: Click the button, and it does its thing. An example of a simple button is the Increase Font Size button in the Font group of the Home tab. Some buttons perform the action immediately; others display a dialog box so you can enter additional information. Button controls may or may not be accompanied by text. • Toggle buttons: A toggle button is clickable and also conveys some type of information by displaying two different colors. An example is the Bold button in the Font group of the Home tab. If the active cell is not bold, the Bold button displays in its normal color. But if the active cell is already bold, the Bold button displays a different background color. If you click this button, it toggles the Bold attribute for the selection. • Simple drop-downs: If the Ribbon command has a small downward-pointing arrow, then the command is a drop-down. Click it, and additional commands appear below it. An example of a simple drop-down is the Merge and Center command in the Alignment group of the Home Tab. When you click this control, you see four options related to merging and centering information. Part I: Some Essential Background22 06_044018 ch02.qxp 2/28/07 6:34 PM Page 22 • Split buttons: A split button control combines a one-click button (on the top) with a drop- down (on the bottom). If you click the button part, the command is executed. If you click the drop-down part, you choose from a list of related commands. You can identify a split button because it displays in two colors when you hover the mouse over it. An example of a split button is the Paste command in the Clipboard group of the Home tab. Clicking the top part of this control pastes the information from the Clipboard. If you click the bottom part of the control, you get a list of paste-related commands. See Figure 2-6. • Check boxes: A check box control turns something on or off. An example is the Gridlines control in the Show/Hide group of the View tab. When the Gridlines check box is checked, the sheet displays gridlines. When the control is not checked, the sheet grid- lines are not displayed. • Spinners: An example of a spinner control is in the Scale to Fit group of the Page Layout tab. Click the top part of the spinner to increase the value; click the bottom part of the spinner to decrease the value. Figure 2-6: The Paste command is a split button control. CROSS-REFERENCE Refer to Chapter 22 for information about customizing Excel’s Ribbon. Some of the Ribbon groups contain a small icon on the right side, known as a dialog launcher. For example, if you examine the Home ➪ Alignment group, you’ll see this icon (refer to Figure 2-7). Click it, and it displays the Format Cells dialog box, with the Number tab preselected. This dialog box provides options that aren’t available in the Ribbon. Chapter 2: Excel in a Nutshell Part I 23 06_044018 ch02.qxp 2/28/07 6:34 PM Page 23 Figure 2-7: This small dialog launcher icon displays a dialog box that has additional options. THE QUICK ACCESS TOOLBAR In previous versions of Excel, end users were free to customize their menus and toolbars. Things have changed in Excel 2007. Although the Ribbon can be customized, it’s a task best left for a knowledgeable developer. In Excel 2007, the only end-user customization option is the Quick Access Toolbar (QAT). Normally, the QAT is displayed on the left side of the title bar. Alternatively, you can display the QAT below the Ribbon by right-clicking the QAT and selecting Place Quick Access Toolbar Below Ribbon. By default, the QAT contains these tools: Save, Undo, and Redo. You can, of course, cus- tomize the QAT by adding other commands that you use often. To add a command from the Ribbon to your QAT, right-click the command and choose Add To Quick Access Toolbar. Excel has commands that aren’t available in the Ribbon. In most cases, the only way to access these commands is to add them to your QAT. Figure 2-8 shows the Customization section of the Excel Options dialog box. This is your one-stop shop for QAT customization. A quick way to display this dialog box is to right-click the QAT and choose Customize Quick Access Toolbar. ACCESSING THE RIBBON BY USING YOUR KEYBOARD At first glance, you may think that the Ribbon is completely mouse-centric. After all, none of the commands has the traditional underlined letter to indicate the Alt+keystrokes. But, in fact, the Ribbon is very keyboard friendly. The trick is to press the Alt key to display the pop-up “keytips.” Each Ribbon control has a letter (or series of letters) that you type to issue the command. TIP It’s not necessary to hold down the Alt key as you type the keytip letters. Part I: Some Essential Background24 06_044018 ch02.qxp 2/28/07 6:34 PM Page 24 Figure 2-8: Add new icons to your QAT by using the Customization section of the Excel Options dialog box. Figure 2-9 shows how the Home tab looks after I press the Alt key to display the keytips. If you press one of the keytips, the screen then displays more keytips. For example, to use the keyboard to align the cell contents to the left, press Alt, followed by H (for Home) and then AL (for Align Left). If you’re a keyboard fan (like me), it will just take a few times before you memorize the keystrokes required for common commands. Figure 2-9: Pressing Alt displays the keytips. After you press Alt, you can also use the left and right arrow keys to scroll through the tabs. When you reach the proper tab, press the down-arrow key to enter the Ribbon. Then use the left- and right-arrow keys to scroll through the Ribbon commands. When you reach the command you need, press Enter to execute it. This method isn’t as efficient as using the keytips, but it is a quick way to take a quick look at the choices on the Ribbon. Chapter 2: Excel in a Nutshell Part I 25 06_044018 ch02.qxp 2/28/07 6:34 PM Page 25 Shortcut menus The only menus that remain in Excel 2007 are shortcut menus. These menus appear when you right-click after selecting one or more objects. The shortcut menus are context- sensitive. In other words, the menu that appears depends on the location of the mouse pointer when you right-click. You can right-click just about anything — a cell, a row or column border, a workbook title bar, a toolbar, and so on. Right-clicking some objects displays a mini-toolbar above the shortcut menu. This toolbar provides quick access to commonly used formatting commands. Figure 2-10 shows the mini-toolbar when a cell is selected. Figure 2-10: Right-clicking some objects displays a mini-toolbar. Although you cannot customize the Ribbon by using VBA, you can use VBA to customize any of the shortcut menus. CROSS-REFERENCE Refer to Chapter 23 for more information about customizing shortcut menus. Part I: Some Essential Background26 06_044018 ch02.qxp 2/28/07 6:34 PM Page 26 Dialog boxes Some of the Ribbon commands display a dialog box. In many cases, these dialog boxes con- tain additional controls that aren’t available in the Ribbon. You’ll find two general classes of dialog boxes in Excel: • Modal dialog boxes: When a modal dialog box is displayed, it must be closed in order to execute the commands. An example is the Format Cells dialog box. None of the options you specify are executed until you click OK. Use the Cancel button to close the dialog box without making any changes. • Modeless dialog boxes: These are “stay on top” dialog boxes. For example, if you’re work- ing with a chart using the Format dialog box, changes that you make are reflected immediately in the chart. Modeless dialog boxes usually have a Close button rather than an OK button and a Cancel button. Many of Excel’s dialog boxes use a notebook tab metaphor, which makes a single dialog box function as several different dialog boxes. In older dialog boxes, the tabs are usually along the top. But in newer dialog boxes (such as the one shown in Figure 2-11), the tabs are along the left side. Figure 2-11: Tabbed dialog boxes make many options accessible without overwhelming the user. Developers can create custom dialog boxes by using the UserForm feature. As you’ll see, it’s possible to create a wide variety of dialog boxes, including tabbed dialog boxes. Chapter 2: Excel in a Nutshell Part I 27 06_044018 ch02.qxp 2/28/07 6:34 PM Page 27 CROSS-REFERENCE Refer to Part IV for information about creating and working with UserForms. Keyboard shortcuts Excel has many useful keyboard shortcuts. For example, you can press Ctrl+D to copy a cell to selected cells below it. If you’re a newcomer to Excel — or you just want to improve your efficiency — I urge you to check out the Help system (access the Accessibility main topic, and go from there). Learning these shortcuts is key to becoming proficient in Excel. The Help file has tables that summarize useful keyboard commands and shortcuts. And, as I noted previously, you can access the Ribbon commands by using the keyboard. Smart Tags A Smart Tag is a small icon that appears automatically in your worksheet after you perform certain actions. Clicking a Smart Tag reveals several options. For example, if you copy and paste a range of cells, Excel generates a Smart Tag that appears below the pasted range (see Figure 2-12) and provides you with several options regarding the formatting of the pasted data. If you don’t like these Smart Tags, you can turn them off in the Excel Options dialog box. Choose Office ➪ Excel Options and click the Advanced tab. Use the controls in the section labeled Cut, Copy And Paste. Figure 2-12: This Smart Tag appears when you paste a copied range. Part I: Some Essential Background28 06_044018 ch02.qxp 2/28/07 6:34 PM Page 28 Task pane Excel 2002 introduced a new UI element known as the task pane. This is a multipurpose user interface element that is normally docked on the right side of Excel’s window (but you can drag it anywhere). The task pane is used for a variety of purposes, including displaying the Office Clipboard, displaying a pivot table field list, inserting clip art, providing research assistance, and mapping eXtensible Markup Language (XML) data. Figure 2-13 shows the Clip Art task pane. Figure 2-13: Locating clip art is one of several uses for the task pane. Chapter 2: Excel in a Nutshell Part I 29 Nothing. Most of Excel 2007’s updated object model is accessible in your VBA code, but the VB Editor is exactly the same as it was in Excel 2003. What’s New in the Visual Basic Editor? 06_044018 ch02.qxp 2/28/07 6:34 PM Page 29 Customizing the Display Excel offers a great deal of flexibility regarding what is displayed onscreen (status bar, for- mula bar, toolbars, and so on). These commands are located in the View tab. In fact, Excel makes it possible to develop an application that doesn’t even look like a spreadsheet. For example, by choosing View ➪ Workbook Views ➪ Full Screen, you can get rid of everything except the title bar, thereby maximizing the amount of information visible. To exit full-screen mode, right-click any cell and choose Close Full Screen from the short- cut menu. NEW Excel 2007 places a zoom control in the right side of the status bar, making it easier than ever to zoom in or out. In addition, you can right-click the status bar and specify the type of information you’d like to see. Data Entry Data entry in Excel is quite straightforward. Excel interprets each cell entry as one of the following: • A numeric value (including date and time values) • Text • A Boolean value (True or False) • A formula Formulas always begin with an equal sign (=). Excel accommodates habitual 1-2-3 users, however, and accepts an each-at symbol (@), a plus sign (+), or a minus sign (–) as the first character in a formula. Excel automatically adjusts the entry after you press Enter. Formulas, Functions, and Names Formulas are what make a spreadsheet a spreadsheet. Excel has some advanced formula- related features that are worth knowing. They enable you to write array formulas, use an intersection operator, include links, and create megaformulas (my term for a lengthy and incomprehensible — but very efficient — formula). Part I: Some Essential Background30 06_044018 ch02.qxp 2/28/07 6:34 PM Page 30 CROSS-REFERENCE Chapter 3 covers formulas and presents lots of tricks and tips. Excel also has some useful auditing capabilities that help you identify errors or track the logic in an unfamiliar spreadsheet. To access these features, use the commands in the Formulas ➪ Formula Auditing group. You may find the Formulas ➪ Formula Auditing ➪ Error Checking command useful. It will scan your worksheet and identify possibly erroneous formulas. In Figure 2-14, Excel identi- fies a possibly inconsistent formula and provides some options. Figure 2-14: Excel can monitor your formulas for possible errors. Worksheet functions enable you to perform calculations or operations that would otherwise be impossible. Excel provides a huge number of built-in functions. The easiest way to locate the function that you need is to use the Insert Function dialog box, as shown in Figure 2-15. Access this dialog box by clicking the Insert Function button on the formula bar (or by pressing Shift+F3). After you select a function, Excel displays its Function Arguments dialog box, which assists with specifying the function’s arguments. NEW In Excel 2007, the Analysis ToolPak functions are now built-in. In other words, you can use these function even if the Analysis ToolPak add-in is not installed. Chapter 2: Excel in a Nutshell Part I 31 06_044018 ch02.qxp 2/28/07 6:34 PM Page 31 Figure 2-15: The Insert Function dialog box is the best way to insert a function into a formula. CROSS-REFERENCE Excel also lets you create your own worksheet functions by using VBA. For details about this powerful feature, see Chapter 10. A name is an identifier that enables you to refer to a cell, range, value, formula, or graphic object. Formulas that use names are much easier to read than formulas that use cell refer- ences, and it’s much easier to create formulas that use named references. CROSS-REFERENCE I discuss names in Chapter 3. As you can see there, Excel handles names in some unique ways. Selecting Objects Selecting objects in Excel conforms to standard Windows practices. You can select a range of cells by clicking and dragging (it’s more efficient to learn the keyboard shortcuts, how- ever). Clicking an object that has been placed on the drawing layer selects the object. To select multiple objects or noncontiguous cells, press Ctrl while you select the objects or cells. Part I: Some Essential Background32 06_044018 ch02.qxp 2/28/07 6:34 PM Page 32 NOTE Clicking a chart selects a specific object within the chart. To select the chart object itself, press Ctrl while you click the chart. If an object has a macro assigned to it, you’ll find that clicking the object executes the macro. To actually select such an object, right-click it and press Esc to hide the shortcut menu. Or press Ctrl while you click the object. Formatting Excel provides two types of formatting: numeric formatting and stylistic formatting. Numeric formatting Numeric formatting refers to how a number appears in the cell. In addition to choosing from an extensive list of predefined formats, you can create your own formats (see Figure 2-16). The procedure is thoroughly explained in the Help system. Figure 2-16: Excel’s numeric formatting options are very flexible. Excel applies some numeric formatting automatically, based on the entry. For example, if you precede a number with a currency symbol (a dollar sign in the U.S.), Excel applies Currency number formatting. A new feature in Excel 2007 enables you to apply number for- matting conditionally, using the conditional formatting feature. Chapter 2: Excel in a Nutshell Part I 33 06_044018 ch02.qxp 2/28/07 6:34 PM Page 33 Stylistic formatting Stylistic formatting refers to the formatting that you apply to make your work look good. Many Ribbon buttons offer direct access to common formatting options, but you’ll want to access the object’s Format dialog box for the full range of formatting options. The easiest way to get to the correct dialog box and format an object is to select the object and press Ctrl+1. You could also right-click the object and choose Format xxx (where xxx is the selected object) from the shortcut menu. Either of these actions brings up a tabbed dialog box that holds all the formatting options for the selected object. Excel’s conditional formatting feature is particularly useful. This feature, accessed by choosing Home ➪ Styles ➪ Conditional Formatting, allows you to specify formatting that will be applied only if certain conditions are met. For example, you can make cells that exceed a specified value appear in a different color. NEW Excel 2007 has several new conditional formatting options, including data bars, color scales, and icon sets. Figure 2-17 shows the new data bars conditional formatting option that displays a histogram directly in the cells. Figure 2-17: The data bars option is one of the new conditional formatting features in Excel 2007. Protection Options Excel offers a number of different protection options. For example, you can protect formu- las from being overwritten or modified, protect a workbook’s structure, password-protect a workbook, and protect your VBA code. Part I: Some Essential Background34 06_044018 ch02.qxp 2/28/07 6:34 PM Page 34 Protecting formulas from being overwritten In many cases, you might want to protect your formulas from being overwritten or modi- fied. To do so, perform the following steps: 1. Select the cells that may be overwritten. 2. Right-click and choose Format Cells from the shortcut menu. 3. In the Format Cells dialog box, click the Protection tab. 4. In the Protection tab, clear the Locked check box. 5. Click OK to close the Format Cells dialog box. 6. Select Review ➪ Changes ➪ Protect Sheet to display the Protect Sheet dialog box, as shown in Figure 2-18. Figure 2-18: The Protect Sheet dialog box. 7. In the Protect Sheet dialog box, select the options that correspond to the actions to allow, specify a password if desired, and then click OK. NOTE By default, all cells are locked. The locked status of a cell has no effect, however, unless you have a protected worksheet. You can also hide your formulas so they won’t appear in Excel’s formula bar when the cell is activated. To do so, select the formula cells and make sure that the Hidden check box is marked in the Protection tab of the Format Cells dialog box. Chapter 2: Excel in a Nutshell Part I 35 06_044018 ch02.qxp 2/28/07 6:34 PM Page 35 Protecting a workbook’s structure When you protect a workbook’s structure, you can’t add or delete sheets. Choose the Review ➪ Changes ➪ Protect Workbook command to display the Protect Workbook dialog box, as shown in Figure 2-19. Make sure that you enable the Structure check box. If you also mark the Windows check box, the window can’t be moved or resized. Figure 2-19: The Protect Workbook dialog box. Applying password protection to a workbook In some cases, you might want to limit access to a workbook to only those who know the password. To save a workbook file with a password, choose Office ➪ Prepare ➪ Encrypt Document.. Then, in the Encrypt Document dialog box (see Figure 2-20), specify a password and click OK. Then save your workbook. Figure 2-20: Use the Encrypt Document dialog box to save a workbook with a password. Protecting VBA code with a password If your workbook contains VBA code, you may wish to use a password to prevent others from viewing or modifying your macros. To apply a password to the VBA code in a work- book, activate the VBE (Alt+F11) and select your project in the Projects window. Then choose Tools ➪ xxxx Properties (where xxxx corresponds to your Project name). This dis- plays the Project Properties dialog box. Part I: Some Essential Background36 06_044018 ch02.qxp 2/28/07 6:34 PM Page 36 In the Project Properties dialog box, click the Protection tab (see Figure 2-21). Enable the Lock Project for Viewing check box and enter a password (twice). Click OK and then save your file. When the file is closed and then reopened, a password will be required to view or modify the code. Figure 2-21: Protecting a VBA project with the Project Properties dialog box. CAUTION It’s important to keep in mind that Excel is not really a secure application. The protection features, even when used with a password, are intended to prevent casual users from accessing various components of your workbook. Anyone who really wants to defeat your protection can probably do so by using readily available password-cracking utilities (or by knowing a few “secrets”). Charts Excel is perhaps the most commonly used application in the world for creating charts. As I mention earlier in this chapter, you can store charts on a chart sheet or float them on a worksheet. NEW Excel 2007 still hasn’t introduced any new chart types, but charts are easier to create, and they most definitely look much better. Figure 2-22 shows an Excel 2007 chart that uses some of the new formatting options. Chapter 2: Excel in a Nutshell Part I 37 06_044018 ch02.qxp 2/28/07 6:35 PM Page 37 Figure 2-22: Excel 2007 charts have improved in the looks department. You can also create pivot charts. A pivot chart is linked to a pivot table, and you can view various graphical summaries of your data by using the same techniques used in a pivot table. Excel offers extensive chart customization options, and over the years, quite a few chart- making tricks have made the rounds. These tricks enable you to create charts that you might think are impossible. Shapes and SmartArt As I mention earlier in this chapter, each worksheet has an invisible drawing layer that holds charts, pictures, controls (such as buttons and list boxes), and shapes. Excel enables you to easily draw a wide variety of geometric shapes directly on your work- sheet. To access the Shape gallery, choose Insert ➪ Illustrations ➪ Shapes. The shapes are highly customizable, and you can even add text. You can also group objects into a single object, which is easier to size or position. NEW A new feature in Office 2007 is SmartArt, which you use to create a wide variety of cus- tomizable diagrams. Figure 2-23 shows an example of a SmartArt diagram. Part I: Some Essential Background38 06_044018 ch02.qxp 2/28/07 6:35 PM Page 38 Figure 2-23: A SmartArt diagram. Database Access Over the years, most spreadsheets have enabled users to work with simple flat database tables. Excel has some slick tools. Databases fall into two categories: • Worksheet databases: The entire database is stored in a worksheet, limiting the size of the database. • External databases: The data is stored in one or more files and is accessed as needed. Worksheet databases Generally, a rectangular range of data that contains column headers can be considered a worksheet database. Chapter 2: Excel in a Nutshell Part I 39 06_044018 ch02.qxp 2/28/07 6:35 PM Page 39 NEW Excel 2007 enables you to specifically designate a range as a table. Select any cell in your rectangular range of data and choose Insert ➪ Tables ➪ Table. Using a table offers many advantages: an automatic summary row at the bottom, easy filtering and sorting, auto-fill formulas in columns, and simplified formatting. In addition, if you create a chart from a table, the chart expands automatically as you add rows to the table. Particularly useful is working with columns of data in a table. Each column header is actu- ally a drop-down list that contains easy access for filtering or sorting (see Figure 2-24). Table rows that don’t meet the filter criteria are temporarily hidden. Figure 2-24: Excel’s new table feature makes it easy to sort and filter rows. External databases To work with external database tables, use the commands in the Data ➪ Get External Data group. Excel 2007 can work with a wide variety of external databases. Part I: Some Essential Background40 06_044018 ch02.qxp 2/28/07 6:35 PM Page 40 Internet Features Excel includes a number of features that relate to the Internet. For example, you can save a worksheet or an entire workbook in HyperText Markup Language (HTML) format, acces- sible in a Web browser. In addition, you can insert clickable hyperlinks (including e-mail addresses) directly in cells. CAUTION In previous versions, HTML was a “round-trip” file format. In other words, you could save a workbook in HTML format and then reopen it in Excel, and nothing would be lost. That’s no longer the case with Excel 2007. HTML is now considered an export-only format. You can also create Web queries to bring in data stored in a corporate intranet or on the Internet. Figure 2-25 shows an example of a Web query. Figure 2-25: Create a Web Query to import data into a worksheet. Analysis Tools Excel is certainly no slouch when it comes to analysis. After all, that’s what most people use a spreadsheet for. Most analysis tasks can be handled with formulas, but Excel offers many other options. Chapter 2: Excel in a Nutshell Part I 41 06_044018 ch02.qxp 2/28/07 6:35 PM Page 41 Outlines A worksheet outline is often an excellent way to work with hierarchical data such as bud- gets. Excel can create an outline (horizontal, vertical, or both) automatically, or you can do so manually. After the outline is created, you can collapse or expand it to display various levels of detail. Analysis ToolPak In previous versions of Excel, the Analysis ToolPak add-in provided additional special-pur- pose analysis tools and worksheet functions, primarily statistical in nature. In Excel 2007, these features are built in. These tools make Excel suitable for casual statistical analysis. Pivot tables Pivot tables are among Excel’s most powerful tools. A pivot table is capable of summariz- ing data in a handy table, and this table can be arranged in many ways. In addition, a pivot table can be manipulated entirely by VBA. Data for a pivot table comes from a worksheet database or an external database and is stored in a special cache, which enables Excel to recalculate rapidly after a pivot table is altered. Figure 2-26 shows a pivot table. CROSS-REFERENCE See Chapter 17 for information about manipulating pivot tables with VBA. Solver For specialized linear and nonlinear problems, Excel’s Solver add-in calculates solutions to what-if scenarios based on adjustable cells, constraint cells, and, optionally, cells that must be maximized or minimized. XML features One of the few new features introduced in Excel 2003 dealt with XML files. (XML is an accepted standard that enables exchange of data between different applications.) You can import data from an XML file and then map the data to specific worksheet cells. Part I: Some Essential Background42 06_044018 ch02.qxp 2/28/07 6:35 PM Page 42 Figure 2-26: Excel’s pivot table feature has many applications. Add-Ins An add-in is a program that’s attached to an application to give it additional functionality. To attach an Excel add-in, use the Add-Ins tab in the Excel Options dialog box. In addition to the add-ins that ship with Excel, you can download additional add-ins from Microsoft’s Web site (http://office.microsoft.com), and you can purchase or down- load many third-party add-ins from online services. You can use the coupon in the back of the book to acquire a free copy of the Power Utility Pak add-in. And, as I detail in Chapter 21, it’s very easy to create your own add-ins. Chapter 2: Excel in a Nutshell Part I 43 06_044018 ch02.qxp 2/28/07 6:35 PM Page 43 Macros and Programming Excel has two built-in macro programming languages: XLM and VBA. The original XLM macro language is obsolete, and it has been replaced by VBA. Excel 2003 can still execute most XLM macros, and you can even create new ones. However, you cannot record XLM macros. You’ll want to use VBA to develop new macros. CROSS-REFERENCE Part III of this book is devoted to the VBA language. File Format A key consideration is file compatibility. Excel 97 through Excel 2003 all use the same file format, so file compatibility is not a problem for these four versions. Excel 2007, however, uses a new file format. Fortunately, Microsoft has made a “compatibility pack” available for earlier versions of Excel. This compatibility pack enables older versions of Excel to read and write the new XLSX file format. It’s important to understand the difference between file compatibility and feature compati- bility. For example, even though the compatibility pack enables Excel 2003 to open files created by Excel 2007, it cannot handle features that were introduced in later versions. CROSS-REFERENCE Refer to Chapter 4 for more information about Excel’s file format, and read Chapter 26 for more information about compatibility issues for developers. Excel’s Help System One of Excel’s most important features is its Help system. When you get stuck, simply click the question mark below the title bar (or press F1). Excel’s Help window appears, and you can search or use the Table of Contents. TIP The Search button in the Help window is actually a drop-down control. Use the options to help narrow your search or to specify the source to search (see Figure 2-27). Part I: Some Essential Background44 06_044018 ch02.qxp 2/28/07 6:35 PM Page 44 Figure 2-27: Excel’s Help window. Chapter 2: Excel in a Nutshell Part I 45 06_044018 ch02.qxp 2/28/07 6:35 PM Page 45 06_044018 ch02.qxp 2/28/07 6:35 PM Page 46 Chapter Formula Tricks and Techniques In This Chapter This chapter provides an overview of Excel’s formula-related features and describes some techniques that might be new to you. ◆ An overview of Excel formulas ◆ Differentiating between absolute and relative references in formulas ◆ Understanding and using names ◆ Introducing array formulas ◆ Counting and summing cells ◆ Working with dates and times ◆ Creating megaformulas Virtually every successful spreadsheet application uses formulas. In fact, con- structing formulas can certainly be construed as a type of programming. NOTE For a much more comprehensive treatment of Excel formulas and functions, refer to my book, Excel 2007 Formulas (Wiley). 3 47 07_044018 ch03.qxp 2/28/07 6:35 PM Page 47 About Formulas Formulas, of course, are what make a spreadsheet a spreadsheet. If it weren’t for formulas, your worksheet would just be a static document — something that could be produced by a word processor that has great support for tables. Excel has a huge assortment of built-in functions, has excellent support for names, and even supports array formulas (a special type of formula that can perform otherwise impossi- ble calculations). A formula entered into a cell can consist of any of the following elements: • Operators such as + (for addition) and * (for multiplication) • Cell references (including named cells and ranges) • Numbers or text strings • Worksheet functions (such as SUM or AVERAGE) A formula in Excel 2007 can consist of up to 8,000 characters. After you enter a formula into a cell, the cell displays the result of the formula. The formula itself appears in the for- mula bar when the cell is activated. Calculating Formulas You’ve probably noticed that the formulas in your worksheet get calculated immediately. If you change a cell that a formula uses, the formula displays a new result with no effort on your part. This is what happens when the Excel Calculation mode is set to Automatic. In this mode (which is the default mode), Excel uses the following rules when calculating your worksheet: • When you make a change — enter or edit data or formulas, for example — Excel imme- diately calculates those formulas that depend on the new or edited data. • If it’s in the middle of a lengthy calculation, Excel temporarily suspends calculation when you need to perform other worksheet tasks; it resumes when you’re finished. • Formulas are evaluated in a natural sequence. In other words, if a formula in cell D12 depends on the result of a formula in cell D11, cell D11 is calculated before D12. Sometimes, however, you might want to control when Excel calculates formulas. For exam- ple, if you create a worksheet with thousands of complex formulas, operations can slow to a snail’s pace while Excel does its thing. In such a case, you should set Excel’s calculation mode to Manual. Use the Calculation Options control in the Formulas ➪ Calculation group. Part I: Some Essential Background48 07_044018 ch03.qxp 2/28/07 6:35 PM Page 48 When you’re working in Manual Calculation mode, Excel displays Calculate in the status bar when you have any uncalculated formulas. You can press the following shortcut keys to recalculate the formulas: • F9 calculates the formulas in all open workbooks. • Shift+F9 calculates the formulas in the active worksheet only. Other worksheets in the same workbook won’t be calculated. • Ctrl+Alt+F9 forces a recalculation of everything in all workbooks. Use it if Excel (for some reason) doesn’t seem to be calculating correctly, or if you want to force a recalcu- lation of formulas that use custom functions created with Visual Basic for Applications (VBA). • Ctrl+Alt+Shift+F9 rechecks all dependent formulas, and calculates all cells in all work- books (including cells not marked as needing to be calculated). NOTE Excel’s Calculation mode isn’t specific to a particular worksheet. When you change Excel’s Calculation mode, it affects all open workbooks, not just the active workbook. Cell and Range References Most formulas reference one or more cells. This reference can be made by using the cell’s or range’s address or name (if it has one). Cell references come in four styles: • Relative: The reference is fully relative. When the formula is copied, the cell reference adjusts to its new location. Example: A1. • Absolute: The reference is fully absolute. When the formula is copied, the cell reference does not change. Example: $A$1. • Row Absolute: The reference is partially absolute. When the formula is copied, the col- umn part adjusts, but the row part does not change. Example: A$1. • Column Absolute: The reference is partially absolute. When the formula is copied, the row part adjusts, but the column part does not change. Example: $A1. By default, all cell and range references are relative. To change a reference, you must man- ually add the dollar signs. Or, when editing a cell in the formula bar, move the cursor to a cell address and press F4 repeatedly to cycle through all four types of cell referencing. Chapter 3: Formula Tricks and Techniques Part I 49 07_044018 ch03.qxp 2/28/07 6:35 PM Page 49 Why use references that aren’t relative? If you think about it, you’ll realize that the only reason why you would ever need to change a reference is if you plan to copy the formula. Figure 3-1 demonstrates why this is so. The formula in cell C3 is =$B3*C$2 Figure 3-1: An example of using nonrelative references in a formula. This formula calculates the area for various lengths (listed in column B) and widths (listed in row 3). After the formula is entered, it can then be copied down to C7 and across to F7. Because the formula uses absolute references to row 2 and column B and relative refer- ences for other rows and columns, each copied formula produces the correct result. If the formula used only relative references, copying the formula would cause all the references to adjust and thus produce incorrect results. About R1C1 notation Normally, Excel uses what’s known as A1 notation: Each cell address consists of a column letter and a row number. However, Excel also supports R1C1 notation. In this system, cell A1 is referred to as cell R1C1, cell A2 as R2C1, and so on. To change to R1C1 notation, access the Formulas tab of the Excel Options dialog box. Place a check mark next to R1C1 Reference Style. After you do so, you’ll notice that the column letters all change to numbers. All the cell and range references in your formulas are also adjusted. Table 3-1 presents some examples of formulas that use standard notation and R1C1 nota- tion. The formula is assumed to be in cell B1 (also known as R1C2). Part I: Some Essential Background50 07_044018 ch03.qxp 2/28/07 6:35 PM Page 50 TABLE 3-1 COMPARING SIMPLE FORMULAS IN TWO NOTATIONS Standard R1C1 =A1+1 =RC[–1]+1 =$A$1+1 =R1C1+1 =$A1+1 =RC1+1 =A$1+1 =R1C[–1]+1 =SUM(A1:A10) =SUM(RC[–1]:R[9]C[–1]) =SUM($A$1:$A$10) =SUM(R1C1:R10C1) If you find R1C1 notation confusing, you’re not alone. R1C1 notation isn’t too bad when you’re dealing with absolute references. But when relative references are involved, the brackets can be very confusing. The numbers in brackets refer to the relative position of the references. For example, R[–5]C[–3] specifies the cell that’s five rows above and three columns to the left. On the other hand, R[5]C[3] references the cell that’s five rows below and three columns to the right. If the brackets are omitted, the notation specifies the same row or column. For example, R[5]C refers to the cell five rows below in the same column. Although you probably won’t use R1C1 notation as your standard system, it does have at least one good use. Using R1C1 notation makes it very easy to spot an erroneous formula. When you copy a formula, every copied formula is exactly the same in R1C1 notation. This is true regardless of the types of cell references that you use (relative, absolute, or mixed). Therefore, you can switch to R1C1 notation and check your copied formulas. If one looks different from its surrounding formulas, there’s a good chance that it might be incorrect. In addition, if you write VBA code to create worksheet formulas, you might find it easier to create the formulas by using R1C1 notation. Referencing other sheets or workbooks When a formula refers to other cells, the references need not be on the same sheet as the formula. To refer to a cell in a different worksheet, precede the cell reference with the sheet name followed by an exclamation point. Here’s an example of a formula that uses a cell reference in a different worksheet (Sheet2): =Sheet2!A1+1 Chapter 3: Formula Tricks and Techniques Part I 51 07_044018 ch03.qxp 2/28/07 6:35 PM Page 51 You can also create link formulas that refer to a cell in a different workbook. To do so, pre- cede the cell reference with the workbook name (in square brackets), the worksheet name, and an exclamation point. Here’s an example: =[Budget.xlsx]Sheet1!A1 If the workbook name in the reference includes one or more spaces, you must enclose it (and the sheet name) in single quotation marks. For example: =’[Budget For 2008.xlsx]Sheet1’!A1 Part I: Some Essential Background52 Excel 2007 supports a special type of range that has been designated as a table (using the Insert ➪ Tables ➪ Table command). Tables add a few new twists to formulas. When you enter a formula into a cell in a table, Excel automatically copies the formula to all of the other cells in the column — but only if the column was empty. This is known as a calculated column. If you add a new row to the table, the calculated column formula is entered automatically for the new row. Most of the time, this is exactly what you want. If you don’t like the idea of Excel entering formulas for you, use the SmartTag to turn this feature off. The SmartTag appears after Excel enters the calculated column formula. Excel 2007 also supports “structured referencing” for referring to cells within a table. The table in the accompanying figure is named Table1. You can create formulas that refer to cells within the table by using the column headers. In some cases, this may make your formulas easier to understand. But the real advantage is that your formulas will continue to be valid if rows are added or removed from the table. For example, these are all valid formulas: =SUM(Table1[Washington]) =Table1[[#Totals],[California]] =Table1[[#Headers],[California]] =SUM(Table1[[#This Row],[California]:[Washington]]) The last formula, which uses [#This Row], is valid only if it’s in a cell in one of the rows occupied by the table. Referencing Data in a Table 07_044018 ch03.qxp 2/28/07 6:35 PM Page 52 If the linked workbook is closed, you must add the complete path to the workbook refer- ence. Here’s an example: =’C:\Budgeting\Excel Files\[Budget For 2008.xlsx]Sheet1’!A1 Although you can enter link formulas directly, you can also create the reference by using normal pointing methods. To do so, the source file must be open. When you do so, Excel creates absolute cell references. If you plan to copy the formula to other cells, make the references relative. Working with links can be tricky. For example, if you choose the Office ➪ Save As com- mand to make a backup copy of the source workbook, you automatically change the link formulas to refer to the new file (not usually what you want to do). Another way to mess up your links is to rename the source workbook when the dependent workbook is not open. Using Names One of the most useful features in Excel is its ability to provide meaningful names for vari- ous items. For example, you can name cells, ranges, rows, columns, charts, and other objects. You can even name values or formulas that don’t appear in cells in your worksheet (see the “Naming constants” section, later in this chapter). Naming cells and ranges Excel provides several ways to name a cell or range: • Choose Formulas ➪ Named Cells ➪ Name a Range to display the New Name dialog box. • Use the Name Manager dialog box (Formulas ➪ Defined Names ➪ Name Manager or press Ctrl+F3). This is not the most efficient method because it requires clicking the New button in the Name Manger dialog box, which displays the New Name dialog box. • Select the cell or range and then type a name in the Name box and press Enter. The Name box is the drop-down control displayed to the left of the formula bar. • If your worksheet contains text that you would like to use for names of adjacent cells or ranges, select the text and the cells to be named and choose Formulas ➪ Defined Names ➪ Create from Selection. In Figure 3-2, for example, B3:E3 is named North, B4:E4 is named South, and so on. Vertically, B3:B6 is named Qtr1, C3:C6 is named Qtr2, and so on. Using names is especially important if you write VBA code that uses cell or range refer- ences. The reason? VBA does not automatically update its references if you move a cell or range that’s referred to in a VBA statement. For example, if your VBA code writes a value to Range(“C4”), the data will be written to the wrong cell if the user inserts a new row above or a new column to the left of cell C4. Using a reference to a named cell, such as Range(“InterestRate”), avoids these potential problems. Chapter 3: Formula Tricks and Techniques Part I 53 07_044018 ch03.qxp 2/28/07 6:35 PM Page 53 Figure 3-2: Excel makes it easy to create names that use descriptive text in your worksheet. Applying names to existing references When you create a name for a cell or a range, Excel doesn’t automatically use the name in place of existing references in your formulas. For example, assume that you have the fol- lowing formula in cell F10: =A1–A2 If you define the names Income for A1 and Expenses for A2, Excel will not automatically change your formula to =Income-Expenses However, it’s fairly easy to replace cell or range references with their corresponding names. Start by selecting the range that contains the formulas that you want to modify. Then choose the Formulas ➪ Defined Names ➪ Name a Range ➪ Apply Names. In the Apply Names dialog box, select the names that you want to apply and then click OK. Excel replaces the range references with the names in the selected cells. NOTE Unfortunately, there is no way to automatically unapply names. In other words, if a for- mula uses a name, you can’t convert the name to an actual cell or range reference. Even worse, if you delete a name that is used in a formula, the formula does not revert to the cell or range address — it simply returns a #NAME? error. My Power Utility Pak add-in (available for free by using the coupon in the back of the book) includes a utility that scans all formulas in a selection and automatically replaces names with their cell addresses. Part I: Some Essential Background54 07_044018 ch03.qxp 2/28/07 6:35 PM Page 54 Intersecting names Excel has a special operator called the intersection operator that comes into play when you’re dealing with ranges. This operator is a space character. Using names with the inter- section operator makes it very easy to create meaningful formulas. For this example, refer to Figure 3-2. If you enter the following formula into a cell =Qtr2 South the result is 732 — the intersection of the Qtr2 range and the South range. Naming columns and rows Excel lets you name complete rows and columns. In the preceding example, the name Qtr1 is assigned to the range B3:B6. Alternatively, Qtr1 could be assigned to all of column B, Qtr2 to column C, and so on. You also can do the same horizontally so that North refers to row 3, South to row 4, and so on. The intersection operator works exactly as before, but now you can add more regions or quarters without having to change the existing names. When naming columns and rows, make sure that you don’t store any extraneous informa- tion in named rows or columns. For example, remember that if you insert a value in cell C7, it is included in the Qtr1 range. Chapter 3: Formula Tricks and Techniques Part I 55 Some Excel macros and add-ins create hidden names. These are names that exist in a workbook but don’t appear in the Name Manager dialog box. For example, the Solver add-in creates a number of hidden names. Normally, you can just ignore these hidden names. However, sometimes these hidden names create a problem. If you copy a sheet to another workbook, the hidden names are also copied, and they might create a link that is very difficult to track down. You can use the following VBA procedure to delete all hidden names in the workbook: Sub DeleteHiddenNames() Dim n As Name Dim Count As Integer For Each n In ActiveWorkbook.Names If Not n.Visible Then n.Delete Count = Count + 1 End If Next n MsgBox Count & “ hidden names were deleted.” End Sub Hidden Names 07_044018 ch03.qxp 2/28/07 6:35 PM Page 55 Scoping names A named cell or range normally has a workbook-level scope. In other words, you can use the name in any worksheet in the workbook. Another option is to create names that have a worksheet-level scope. To create a work- sheet-level name, define the name by preceding it with the worksheet name followed by an exclamation point: for example, Sheet1!Sales. If the name is used on the sheet in which it is designed, you can omit the sheet qualifier when you reference the name. You can, however, reference a worksheet-level name on a different sheet if you precede the name with the sheet qualifier. The Name Manager dialog box (Formulas ➪ Defined Names ➪ Name Manager) makes it easy to identify names by their scope (see Figure 3-3). Note that you can sort the names within this dialog box. For example, click the Scope column header, and the names are sorted by scope. Figure 3-3: The Name Manager displays the scope for each defined name. Naming constants Virtually every experienced Excel user knows how to create cell and range names (although not all Excel users actually do so). But most Excel users do not know that you can use names to refer to values that don’t appear in your worksheet — that is, constants. Suppose that many formulas in your worksheet need to use a particular interest rate value. One approach is to type the interest rate into a cell and give that cell a name, such as InterestRate. After doing so, you can use that name in your formulas, like this: =InterestRate*A3 Part I: Some Essential Background56 07_044018 ch03.qxp 2/28/07 6:35 PM Page 56 An alternative is to call up the New Name dialog box (Formulas ➪ Defined Names ➪ Define Name) and enter the interest rate directly into the Refers To box (see Figure 3-4). Then you can use the name in your formulas just as if the value were stored in a cell. If the interest rate changes, just change the definition for InterestRate, and Excel updates all the cells that contain this name. Figure 3-4: Excel lets you name constants that don’t appear in worksheet cells. TIP This technique also works for text. For example, you can define the name IWC to stand for International Widget Corporation. Then you can enter =IWC into a cell, and the cell displays the full name. Naming formulas In addition to naming cells, ranges, and constants, you can also create named formulas. To do so, enter a formula directly into the Refers To field in the New Name dialog box. NOTE This is a very important point: The formula that you enter uses cell references relative to the active cell at the time that you create the named formula. Figure 3-5 shows a formula (=A1^B1) entered directly in the Refers To box in the New Name dialog box. In this case, the active cell is C1, so the formula refers to the two cells to its left. (Notice that the cell references are relative.) After this name is defined, entering =Power into a cell raises the value two cells to the left to the power represented by the cell directly to the left. For example, if B10 contains 3 and C10 contains 4, entering the following formula into cell D10 returns a value of 81 (3 to the 4th power): =Power Chapter 3: Formula Tricks and Techniques Part I 57 07_044018 ch03.qxp 2/28/07 6:35 PM Page 57 Figure 3-5: You can name a formula that doesn’t appear in any worksheet cell. When you display the New Name dialog box after creating the named formula, the Refers To box displays a formula that is relative to the current active cell. For example, if cell D32 is the active cell, the Refers To box displays =Sheet1!B32^Sheet1!C32 Notice that Excel appends the worksheet name to the cell references used in your formula. This, of course, will cause the named formula to produce incorrect results if you use it on a worksheet other than the one in which it was defined. If you would like to use this named formula on a sheet other than Sheet1, you need to remove the sheet references from the formula (but keep the exclamation points). For example: =!A1^!B1 After you understand the concept, you might discover some new uses for named formulas. One distinct advantage is apparent if you need to modify the formula. You can just change the formula one time rather than edit each occurrence of the formula. CD-ROM The companion CD-ROM contains a workbook with several examples of named formulas. The workbook is called named formulas.xlsx. TIP When you’re working in the New Name dialog box, the Refers To field is normally in “point mode,” which makes it easy to enter a range reference by clicking in the work- sheet. Press F2 to toggle between point mode and normal editing mode, which allows you to use the arrow keys to edit the formula. Part I: Some Essential Background58 07_044018 ch03.qxp 2/28/07 6:35 PM Page 58 Naming objects In addition to providing names for cells and ranges, you can give more meaningful names to objects such as pivot tables and shapes. This can make it easier to refer to such objects, especially when you refer to them in your VBA code. To change the name of a nonrange object, use the Name box, which is located to the left of the formula bar. Just select the object, type the new name in the Name box, and then press Enter. NOTE If you simply click elsewhere in your workbook after typing the name in the Name box, the name won’t stick. You must press Enter. For some reason, Excel 2007 does not allow you to use the Name box to rename a chart. You must use Chart Tools ➪ Layout ➪ Properties ➪ Chart Name. Formula Errors It’s not uncommon to enter a formula and receive an error in return. One possibility is that the formula you entered is the cause of the error. Another possibility is that the formula refers to a cell that has an error value. The latter scenario is known as the ripple effect — a single error value can make its way to lots of other cells that contain formulas that depend on the cell. The tools in the Formulas ➪ Formula Auditing group can help you trace the source of formula errors. Chapter 3: Formula Tricks and Techniques Part I 59 Excel users often refer to named ranges and named cells. In fact, I use these terms frequently throughout this chapter. Actually, this terminology is not quite accurate. Here’s the secret to understanding names: When you create a name for a cell or a range in Excel, you’re actually creating a named formula — a formula that doesn’t exist in a cell. Rather, these named formulas exist in Excel’s memory. When you work with the New Name dialog box, the Refers To field contains the formula, and the Name field contains the formula’s name. You’ll find that the contents of the Refers To field always begin with an equal sign — which makes it a formula. This is not exactly an earthshaking revelation, but keeping this “secret” in mind could help you understand what’s going on behind the scenes when you create and use names in your workbooks. The Secret to Understanding Cell and Range Names 07_044018 ch03.qxp 2/28/07 6:35 PM Page 59 Table 3-2 lists the types of error values that may appear in a cell that has a formula. TABLE 3-2 EXCEL ERROR VALUES Error Value Explanation #DIV/0! The formula is trying to divide by 0 (zero) (an operation that’s not allowed on this planet). This error also occurs when the formula attempts to divide by a cell that is empty. #N/A The formula is referring (directly or indirectly) to a cell that uses the NA worksheet function to signal the fact that data is not available. A LOOKUP function that can’t locate a value also returns #N/A. #NAME? The formula uses a name that Excel doesn’t recognize. This can happen if you delete a name that’s used in the formula or if you have unmatched quotes when using text. A formula will also display this error if it uses a function defined in an add-in and that add-in is not installed. #NULL! The formula uses an intersection of two ranges that don’t intersect. (This concept is described earlier in the chapter.) #NUM! There is a problem with a function argument; for example, the SQRT function is attempting to calculate the square root of a negative number. This error also appears if a calculated value is too large or small. Excel does not support non-zero values less than 1E–307 or greater than 1E+308 in absolute value. #REF! The formula refers to a cell that isn’t valid. This can happen if that cell has been deleted from the worksheet. #VALUE! The formula includes an argument or operand of the wrong type. An operand is a value or cell reference that a formula uses to calculate a result. This error also occurs if your formula uses a custom VBA worksheet function that contains an error. ##### A cell displays a series of hash marks under two conditions: the column is not wide enough to display the result, or the formula returns a negative date or time value. Array Formulas In Excel terminology, an array is a collection of cells or values that is operated on as a group. An array formula is a special type of formula that works with arrays. An array for- mula can produce a single result, or it can produce multiple results — with each result dis- played in a separate cell. Part I: Some Essential Background60 07_044018 ch03.qxp 2/28/07 6:35 PM Page 60 For example, when you multiply a 1 x 5 array by another 1 x 5 array, the result is a third 1 x 5 array. In other words, the result of this kind of operation occupies five cells; each ele- ment in the first array is multiplied by each corresponding element in the second array to create five new values, each getting its own cell. The array formula that follows multiplies the values in A1:A5 by the corresponding values in B1:B5. This array formula is entered into five cells simultaneously: {=A1:A5*B1:B5} NOTE You enter an array formula by pressing Ctrl+Shift+Enter. To remind you that a formula is an array formula, Excel surrounds it with curly braces in the formula bar. When I present an array formula in this book, I enclose it in curly braces to distinguish it from a normal formula. Don’t enter the braces yourself. An array formula example Excel’s array formulas enable you to perform individual operations on each cell in a range in much the same way that a programming language’s looping feature enables you to work with elements of an array. If you’ve never used array formulas before, this section will get your feet wet with a hands-on example. Figure 3-6 shows a worksheet with text in A1:A5. The goal of this exercise is to create a sin- gle formula that returns the sum of the total number of characters in the range. Without the single formula requirement, you would write a formula with the LEN function, copy it down the column, and then use the SUM function to add the results of the intermediate formulas. Figure 3-6: Cell B1 contains an array formula that returns the total number of characters contained in range A1:A5. Notice the brackets in the formula bar. Chapter 3: Formula Tricks and Techniques Part I 61 07_044018 ch03.qxp 2/28/07 6:35 PM Page 61 To demonstrate how an array formula can occupy more than one cell, create the worksheet shown in the figure and then try this: 1. Select the range B1:B5. 2. Type the following formula: =LEN(A1:A5) 3. Press Ctrl+Shift+Enter. The preceding steps enter a single array formula into five cells. Enter a SUM formula that adds the values in B1:B5, and you’ll see that the total number of characters in A1:A5 is 29. Here’s the key point: It’s not necessary to actually display those five array elements. Rather, Excel can store the array in memory. Knowing this, you can type the following sin- gle array formula in any blank cell (Remember: Don’t type the curly brackets, and make sure that you enter it by pressing Ctrl+Shift+Enter): {=SUM(LEN(A1:A5))} This formula essentially creates a five-element array (in memory) that consists of the length of each string in A1:A5. The SUM function uses this array as its argument, and the formula returns 29. An array formula calendar Figure 3-7 shows a worksheet set up to display a calendar for any month (change the month, and the calendar updates). Believe it or not, the calendar is created with a single array formula that occupies 42 cells. The array formula, entered in the range B5:H10, is: {=IF(MONTH(DATE(YEAR(B3),MONTH(B3),1))<>MONTH(DATE(YEAR(B3), MONTH(B3),1)-(WEEKDAY(DATE(YEAR(B3),MONTH(B3),1))-1) +{0;1;2;3;4;5}*7+{1,2,3,4,5,6,7}-1),””, DATE(YEAR(B3),MONTH(B3),1)-(WEEKDAY(DATE(YEAR(B3), MONTH(B3),1))-1)+{0;1;2;3;4;5}*7+{1,2,3,4,5,6,7}-1)} CD-ROM The companion CD-ROM contains a workbook with the calendar example, as well as sev- eral additional array formula examples. The file is named array formula examples.xlsx. Part I: Some Essential Background62 07_044018 ch03.qxp 2/28/07 6:35 PM Page 62 Figure 3-7: A single multicell array formula is all it takes to make a calendar for any month in any year. Array formula pros and cons The advantages of using array formulas rather than single-cell formulas include the following: • They can sometimes use less memory. • They can make your work much more efficient. • They can eliminate the need for intermediate formulas. • They can enable you to do things that would be difficult or impossible otherwise. A few disadvantages of using array formulas are the following: • Using many complex array formulas can sometimes slow your spreadsheet recalculation time to a crawl. • They can make your worksheet more difficult for others to understand. • You must remember to enter an array formula with a special key sequence (by pressing Ctrl+Shift+Enter). Counting and Summing Techniques I spend quite a bit of time reading the Excel newsgroups on the Internet, and it seems that many of the questions deal with conditional counting or summing. In an attempt to answer most of these questions, I present a number of formula examples that deal with counting various things on a worksheet, based on single or multiple criteria. You can adapt these formulas to your own needs. Chapter 3: Formula Tricks and Techniques Part I 63 07_044018 ch03.qxp 2/28/07 6:35 PM Page 63 NEW Excel 2007 includes two new counting and summing functions that aren’t available in previous versions (COUNTIFS and SUMIFS). Therefore, I present two versions of some formulas: an Excel 2007–only version and an array formula that works with all recent ver- sions of Excel. Figure 3-8 shows a simple worksheet to demonstrate the formulas that follow. The follow- ing range names are defined: • Month: A2:A10 • Region: B2:B10 • Sales: C2:C10 Figure 3-8: This simple worksheet demonstrates some useful formulas for counting and summing. CD-ROM This workbook (including the formula examples) is available on the companion CD-ROM. The file is named counting and summing examples.xlsx. Counting formula examples Table 3-3 contains formulas that demonstrate a variety of counting techniques. Part I: Some Essential Background64 07_044018 ch03.qxp 2/28/07 6:35 PM Page 64 TABLE 3-3 COUNTING FORMULA EXAMPLES Formula Description =COUNTIF(Region,”North”) Counts the number of rows in which Region = “North” =COUNTIF(Sales,300) Counts the number of rows in which Sales = 300 =COUNTIF(Sales,”>300”) Counts the number of rows in which Sales > 300 =COUNTIF(Sales,”<>100”) Counts the number of rows in which Sales <> 100 =COUNTIF(Region,”?????”) Counts the number of rows in which Region contains five letters =COUNTIF(Region,”*h*”) Counts the number of rows in which Region contains the letter H (not case- sensitive) =COUNTIFS(Month,”Jan”,Sales,”>200”) Counts the number of rows in which Month = “Jan” and Sales > 200 (Excel 2007 only) {=SUM((Month=”Jan”)*(Sales>200))} An array formula that counts the number of rows in which Month = “Jan” and Sales > 200 =COUNTIFS(Month,”Jan”,Region,”North”) Counts the number of rows in which Month = “Jan” and Region = “North” (Excel 2007 only) {=SUM((Month=”Jan”)*(Region=”North”))} An array formula that counts the number of rows in which Month = “Jan” and Region = “North” =COUNTIFS(Month,”Jan”,Region,”North”)+ Counts the number of rows in which COUNTIFS(Month,”Jan”,Region,”South”) Month = “Jan” and Region = “North” or “South” (Excel 2007 only) {=SUM((Month=”Jan”)*((Region=”North”)+ An array formula that counts the number (Region=”South”)))} of rows in which Month = “Jan” and Region = “North” or “South” =COUNTIFS(Sales,”>=300”,Sales,”<=400”) Counts the number of rows in which Sales is between 300 and 400 (Excel 2007 only) {=SUM((Sales>=300)*(Sales<=400))} An array formula that counts the number of rows in which Sales is between 300 and 400 Chapter 3: Formula Tricks and Techniques Part I 65 07_044018 ch03.qxp 2/28/07 6:35 PM Page 65 Summing formula examples Table 3-4 shows a number of formula examples that demonstrate a variety of summing techniques. TABLE 3-4 SUMMING FORMULA EXAMPLES Formula Description =SUMIF(Sales,”>200”) Sum of all Sales over 200 =SUMIF(Month,”Jan”,Sales) Sum of Sales in which Month = “Jan” =SUMIF(Month,”Jan”,Sales)+ Sum of Sales in which Month =”Jan” SUMIF(Month,”Feb”,Sales) or “Feb” =SUMIFS(Sales,Month,”Jan”,Region,”North”) Sum of Sales in which Month=”Jan” and Region=”North” =SUMIFS(Sales,Month,”Jan”,Region,”North”) Sum of Sales in which Month=”Jan” and Region=”North” (Excel 2007 only) {=SUM((Month=”Jan”)*(Region=”North”)*Sales)} An array formula that returns the sum of Sales in which Month=”Jan” and Region=”North” =SUMIFS(Sales,Month,”Jan”,Region,”<>North”) Sum of Sales in which Month=”Jan” and Region <> “North” (Excel 2007 only) {=SUM((Month=”Jan”)*(Region<>”North”)*Sales)} An array formula that returns the sum of Sales in which Month=”Jan” and Region <> “North” =SUMIFS(Sales,Month,”Jan”,Sales,”>=200”) Sum of Sales in which Month=”Jan” and Sales>=200 (Excel 2007 only) {=SUM((Month=”Jan”)*(Sales>=200)*(Sales))} An array formula that returns the sum of Sales in which Month=”Jan” and Sales>=200 =SUMIFS(Sales,Sales,”>=300”,Sales,”<=400”) Sum of Sales between 300 and 400 (Excel 2007 only) {=SUM((Sales>=300)*(Sales<=400)*(Sales))} An array formula that returns the sum of Sales between 300 and 400 Part I: Some Essential Background66 07_044018 ch03.qxp 2/28/07 6:35 PM Page 66 Other counting tools Other ways to count or sum cells that meet certain criteria are: • Filtering (using a table) • Advanced filtering • The DCOUNT and DSUM functions • Pivot tables For more information, consult the Help system. Working with Dates and Times Excel uses a serial number system to store dates. The earliest date that Excel can under- stand is January 1, 1900. This date has a serial number of 1. January 2, 1900, has a serial number of 2, and so on. Most of the time, you don’t have to be concerned with Excel’s serial number date system. You simply enter a date in a familiar date format, and Excel takes care of the details behind the scenes. For example, if you need to enter August 15, 2007, you can simply enter the date by typing August 15, 2007 (or use any of a number of different date formats). Excel interprets your entry and stores the value 39309, which is the serial number for that date. NOTE In this chapter, I assume the U.S. date system. If your computer uses a different date sys- tem, you’ll need to adjust accordingly. For example, you might need to enter 15 August 2007. Entering dates and times When working with times, you simply enter the time into a cell in a recognized format. Excel’s system for representing dates as individual values is extended to include decimals that represent portions or fractions of days. In other words, Excel perceives all time with the same system whether that time is a particular day, a certain hour, or a specific second. For example, the date serial number for August 15, 2007, is 39309. Noon (halfway through the day) is represented internally as 39309.5. Again, you normally don’t have to be con- cerned with these fractional serial numbers. Chapter 3: Formula Tricks and Techniques Part I 67 07_044018 ch03.qxp 2/28/07 6:35 PM Page 67 Because dates and times are stored as serial numbers, it stands to reason that you can add and subtract dates and times. For example, you can enter a formula to calculate the num- ber of days between two dates. If cells A1 and A2 both contain dates, the following formula returns the number of intervening days: =A2-A1 TIP When performing calculations with time, things get a bit trickier. When you enter a time without an associated date, the date is assumed to be January 0, 1900 (date serial num- ber 0). This is not a problem — unless your calculation produces a negative time value. When this happens, Excel displays an error (displayed as #########). The solution? Switch to the 1904 date system. Display the Excel Options dialog box, click the Advanced tab, and then enable the Use 1904 Date System check box. Be aware that switching to the 1904 date system can cause problems with dates already entered in your file or dates in workbooks that are linked to your file. TIP In some cases, you may need to use time values to represent duration, rather than a point in time. For example, you may need to sum the number of hours worked in a week. When you add time values, you can’t display more than 24 hours. For each 24-hour period, Excel simply adds another day to the total. The solution is to change the number formatting to use square brackets around the hour part of the format. The following number format, for example, displays more than 24 hours: [hh]:mm Using pre-1900 dates The world, of course, didn’t begin on January 1, 1900. People who work with historical information when using Excel often need to work with dates before January 1, 1900. Unfortunately, the only way to work with pre-1900 dates is to enter the date into a cell as text. For example, you can enter the following into a cell, and Excel won’t complain: July 4, 1776 You can’t, however, perform any manipulation on dates that are actually text. For example, you can’t change its formatting, you can’t determine which day of the week this date occurred on, and you can’t calculate the date that occurs seven days later. (See Figure 3-9.) Part I: Some Essential Background68 07_044018 ch03.qxp 2/28/07 6:35 PM Page 68 Figure 3-9: The Extended Date Functions add-in lets you work with pre-1900 dates. Creating Megaformulas Often, spreadsheets require intermediate formulas to produce a desired result. In other words, a formula may depend on other formulas, which in turn depend on other formulas. After you get all these formulas working correctly, it’s often possible to eliminate the inter- mediate formulas and use what I refer to as a single megaformula instead. The advantages? You use fewer cells (less clutter), the file size is smaller, and recalculation may even be a bit faster. The main disadvantage is that the formula may be impossible to decipher or modify. Here’s an example: Imagine a worksheet that has a column with thousands of people’s names. And suppose that you’ve been asked to remove all the middle names and middle initials from the names — but not all the names have a middle name or initial. Editing the cells manually would take hours, and even Excel’s Data ➪ Data Tools ➪ Convert Text to Table command isn’t much help. So you opt for a formula-based solution. Although this is not a difficult task, it normally involves several intermediate formulas. Figure 3-10 shows the results of the more conventional solution, which requires six inter- mediate formulas shown in Table 3-5. The names are in column A; the end result goes in column H. Columns B through G hold the intermediate formulas. Figure 3-10: Removing the middle names and initials requires six intermediate formulas. Chapter 3: Formula Tricks and Techniques Part I 69 07_044018 ch03.qxp 2/28/07 6:35 PM Page 69 TABLE 3-5 INTERMEDIATE FORMULAS WRITTEN IN ROW 2 IN FIGURE 3-9 Column Intermediate Formula What It Does B =TRIM(A2) Removes excess spaces. C =FIND(“ “,B2,1) Locates the first space. D =FIND(“ “,B2,C2+1) Locates the second space. Returns #VALUE! if there is no second space. E =IF(ISERROR(D2),C2,D2) Uses the first space if no second space exists. F =LEFT(B2,C2) Extracts the first name. G =RIGHT(B2,LEN(B2)-E2) Extracts the last name. H =F2&G2 Concatenates the two names. You can eliminate the six intermediate formulas by creating a megaformula. You do so by creating all the intermediate formulas and then going back into the final result formula and replacing each cell reference with a copy of the formula in the cell referred to (without the equal sign). Fortunately, you can use the Clipboard to copy and paste. Keep repeating this process until cell H2 contains nothing but references to cell A2. You end up with the fol- lowing megaformula in one cell: =LEFT(TRIM(A2),FIND (“ “,TRIM(A2),1))&RIGHT(TRIM(A2),LEN(TRIM(A2))- IF(ISERROR(FIND(“ “,TRIM(A2),FIND(“ “,TRIM(A2),1)+1)), FIND(“ “,TRIM(A2),1),FIND(“ “,TRIM(A2),FIND (“ “,TRIM(A2),1)+1))) When you’re satisfied that the megaformula is working, you can delete the columns that hold the intermediate formulas because they are no longer used. The megaformula performs exactly the same tasks as all the intermediate formulas — although it’s virtually impossible for anyone to figure out, even the author. If you decide to use megaformulas, make sure that the intermediate formulas are performing correctly before you start building a megaformula. Even better, keep a single copy of the intermedi- ate formulas somewhere in case you discover an error or need to make a change. Another way to approach this problem is to create a custom worksheet function in VBA. Then you could replace the megaformula with a simple formula, such as =NOMIDDLE(A1) In fact, I wrote such a function to compare it with intermediate formulas and megaformu- las. The listing follows. Part I: Some Essential Background70 07_044018 ch03.qxp 2/28/07 6:35 PM Page 70 Function NOMIDDLE(n) As String Dim FirstName As String, LastName As String n = Application.WorksheetFunction.Trim(n) FirstName = Left(n, InStr(1, n, “ “)) LastName = Right(n, Len(n) - InStrRev(n, “ “)) NOMIDDLE = FirstName & LastName End Function CD-ROM A workbook that contains the intermediate formulas, the megaformula, and the NOMID- DLE VBA function is available on the companion CD-ROM. The workbook is named megaformula.xlsm. Because a megaformula is so complex, you may think that using one would slow down recalculation. Actually, that’s not the case. As a test, I created a worksheet that used a megaformula to process 150,000 names. Then I created another worksheet that used six intermediate formulas. The megaformula version calculated a bit faster, and produced a much smaller file. The actual results will vary significantly, depending on system speed, amount of memory installed, and the actual formula. The VBA function was much slower — I abandoned the timed test after 10 minutes. This is fairly typical of VBA functions; they are always slower than built-in Excel functions. Chapter 3: Formula Tricks and Techniques Part I 71 07_044018 ch03.qxp 2/28/07 6:35 PM Page 71 07_044018 ch03.qxp 2/28/07 6:35 PM Page 72 Chapter Understanding Excel’s Files In This Chapter These topics are covered in this chapter. ◆ A description of the various ways to start Excel ◆ A discussion of the files that Excel can open and save ◆ An introduction to the new XML file format in Excel 2007 ◆ Details about how Excel uses the Windows Registry If you plan to do any advanced work with Excel, it’s critical that you become familiar with some of the internal workings of Excel and understand what hap- pens when the application is launched. It’s also important to have an under- standing of the various files used and generated by Excel. Starting Excel Excel can be started in various ways, depending on how it’s installed. These include clicking an icon on the Desktop, using the Windows Start button, and double-clicking a file associated with the Excel application. All methods ulti- mately launch the excel.exe executable file. 4 73 08_044018 ch04.qxp 2/28/07 6:36 PM Page 73 When Excel starts, it performs the following actions: • It reads its settings stored in the Windows Registry. • It opens the *.xlb menu/toolbar customization file. • It opens all add-ins that are installed (that is, those that are checked in the Add-Ins dia- log box). • It opens any workbooks that are in the XLStart directory. • It opens any workbooks that are in the alternate startup directory (specified in the Advanced tab of the Excel Options dialog box). • It displays an empty workbook — unless the user specified a workbook to open or one or more files were found in the XLStart or alternate startup directory. TIP If you want to change the default formats (or content) of blank workbooks that you create, create a default workbook and save it as a template with the name Book.xltx in your XLStart folder. For details on creating and using template files, refer to Excel’s Help. Excel can be installed in any location. But in most cases, the Excel executable file is located in the default installation directory: C:\Program Files\Microsoft Office\Office12\EXCEL.EXE You can create one or more shortcuts to this executable file, and the shortcuts can be cus- tomized with various parameters, or command line switches. These command line switches are listed in Table 4-1. TABLE 4-1 EXCEL COMMAND LINE SWITCHES Switch What It Does filename Opens the specified file. The filename is a parameter and does not require a switch. /r filename Opens the specified file in read-only mode. /t filename Opens the specified file as a template. /n filename Opens the specified file as a template (same as /t). /e Starts Excel without creating a new workbook and without displaying its splash screen. /p directory Sets the active path to a directory other than the default directory. Part I: Some Essential Background74 08_044018 ch04.qxp 2/28/07 6:36 PM Page 74 Switch What It Does /s Starts Excel in Safe mode and does not load any add-ins or files in the XLStart or alternate startup file directories. /embedded Starts an invisible instance of Excel (not recommended). /m Forces Excel to create a new workbook that contains a single Microsoft Excel 4.0 macro sheet (obsolete). You can experiment with these command line switches by using the Windows Start ➪ Run command. Put the path to Excel in quotes, followed by a space, and then the command line switch. Figure 4-1 shows an example. Figure 4-1: Starting Excel from the Windows Run dialog box. One way to specify any of these switches is to edit the properties of the shortcut that starts Excel. For example, if there are times when you’d like Excel to start and use a folder named c:\xlfiles as its default folder, you can customize a Windows shortcut. In this case, you need to use the /p switch and specify the folder. NOTE The instructions that follow are for Windows XP. Start with an icon that launches Excel. Right-click the icon and choose Properties. In the Shortcut Properties dialog box, click the Shortcut tab and enter the following in the Target field (see Figure 4-2): “C:\Program Files\Microsoft Office\Office12\EXCEL.EXE” /p c:\xlfiles Keep in mind that the path to excel.exe can vary for different installations and for differ- ent versions. You can also assign a shortcut key to launch Excel, which can be useful. If Excel is already running, pressing the shortcut key activates Excel. Chapter 4: Understanding Excel’s Files Part I 75 08_044018 ch04.qxp 2/28/07 6:36 PM Page 75 Figure 4-2: Customizing a shortcut to launch Excel. NOTE You can run multiple instances of Excel on a single system. Each instance is treated as a separate task. Most people have pretty good success running multiple versions of Excel on a single system. For best results, install the versions in the order of their release dates. File Types Although the Excel 2007 default file format is an XLSX workbook file, the program can also open and save a wide variety of other files. This section provides an overview of the file types that Excel 2007 can handle. NOTE Excel 2007 no longer supports Lotus or Quattro spreadsheet file formats. Excel file formats Excel 2007 uses a new default file format. However, it can still read and write older Excel file formats. Part I: Some Essential Background76 08_044018 ch04.qxp 2/28/07 6:36 PM Page 76 TIP To change the default file save setting, choose Office ➪ Excel Options and click the Save tab in the Excel Options dialog box. You’ll find a drop-down list that lets you select the default file format. Table 4-2 lists the Excel file types that Excel 2007 supports. Keep in mind that an Excel workbook or add-in file can have any extension that you like. In other words, these files don’t need to be stored with the standard extensions shown in the table. TABLE 4-2 EXCEL FILE TYPES File Type Extension Read/Write Notes Excel Workbook xlsx Yes/Yes The default Excel 2007 file format. It cannot store VBA or XLM macro code. Excel Macro- xlsm Yes/Yes The Excel 2007 file format for Enabled Workbook workbooks that contain macros. Excel Binary xlsb Yes/Yes The Excel 2007 binary file format Workbook (BIFF12). This is an updated version of the previous XLS (BIFF8) format. Template xltx Yes/Yes The Excel 2007 file format for a template. It cannot store VBA or XLM macro code. Macro-Enabled xltxm Yes/Yes The Excel 2007 file format for a Template template that contains macros. Excel Add-In xlam Yes/Yes The Excel 2007 file format for add-ins. It can store VBA and XLM macros. Excel 97–Excel xls Yes/Yes The Excel binary format (BIFF8) that’s 2003 Workbook compatible with Excel 97 through Excel 2003. Excel 97–Excel xlt Yes/Yes The Excel binary template format 2003 Template (BIFF8) that’s compatible with Excel 97 through Excel 2003. Excel 97–Excel xla Yes/Yes The Excel binary format (BIFF8) for 2003 Add-In add-ins that’s compatible with Excel 97 through Excel 2003. continued Chapter 4: Understanding Excel’s Files Part I 77 08_044018 ch04.qxp 2/28/07 6:36 PM Page 77 TABLE 4-2 EXCEL FILE TYPES (continued) File Type Extension Read/Write Notes Microsoft Excel xls Yes/Yes The Excel binary format (BIFF5) that’s 5.0/95 Workbook compatible with Excel 5.0 and Excel 95. XML Spreadsheet xml Yes/Yes Microsoft’s XML Spreadsheet 2003 file 2003 format (XMLSS). XML Data xml Yes/Yes A general XML file that contains data. NOTE Microsoft Office XP and Office 2003 users can install the Microsoft Office Compatibility Pack, which allows them to open and save documents in the Office 2007 file formats. The Compatibility Pack is available at http://office.microsoft.com. Text file formats When you attempt to load a text file into Excel, the Text Import Wizard might kick in to help you specify how you want the file retrieved. TIP To bypass the Text Import Wizard, press Shift when you click OK in the Open dialog box. Table 4-3 lists the text file types supported by Excel 2007. All text file formats are limited to a single worksheet. TABLE 4-3 TEXT FILE TYPES File Type Extension Read/Write Notes CSV (comma csv Yes/Yes Columns are delimited with a comma, and separated rows are delimited with a carriage return. variable) Excel supports subtypes for Macintosh and MS-DOS. Formatted prn Yes/Yes Columns are delimited with a space Text character, and rows are delimited with a carriage return. Part I: Some Essential Background78 08_044018 ch04.qxp 2/28/07 6:36 PM Page 78 File Type Extension Read/Write Notes Text txt Yes/Yes Columns are delimited with a tab, and rows are delimited with a carriage return. Excel supports subtypes for Macintosh, MS-DOS, and Unicode. Data dif Yes/Yes The file format originally used by VisiCalc. Interchange Format (DIF) Symbolic slk Yes/Yes The file format originally used by Multiplan. Link (SYLK) Database file formats Table 4-4 lists the database file types supported by Excel 2007. All database file formats are limited to a single worksheet. TABLE 4-4 DATABASE FILE TYPES File Type Extension Read/Write Notes Access mdb, mde, Yes/No You can open one table from the database. accdb, accde dBASE dbf Yes/No The file format originally created by Ashton-Tate Others Various Yes/No By using the commands in the Data ➪ Get External Data group, you can import data from various data sources that have connections or queries defined on your system. Other file formats Table 4-5 lists the other file types supported by Excel 2007. Chapter 4: Understanding Excel’s Files Part I 79 08_044018 ch04.qxp 2/28/07 6:36 PM Page 79 TABLE 4-5 OTHER FILE TYPES File Type Extension Read/Write Notes Hypertext Markup htm, html Yes/Yes Excel 2007 no longer supports Language (HTML) “round-tripping” for HTML files. Single File Web mht, mhtml Yes/Yes Also known as Archived Web Page Page. The only browser that can display these files is Microsoft Internet Explorer. Portable pdf No/Yes The file format originated by Document Adobe. Requires a free add-in Format (PDF) from Microsoft. XML Paper xps No/Yes Microsoft’s alternative to Adobe’s Specification PDF. Requires a free add-in from Microsoft. Working with Template Files A template is essentially a model that serves as the basis for something else. An Excel tem- plate is a workbook that’s used to create other workbooks. You can save any workbook as a template file (XLTX extension). Doing so is useful if you tend to create similar files on a Part I: Some Essential Background80 A workspace file is a special file that contains information about an Excel workspace. For example, if you have a project that uses two workbooks and you like to have the workbook windows arranged in a particular way, you can save an XLW file to save this window configuration. Then, whenever you open the XLW file, Excel restores the desired workspace. To save a workspace, choose View ➪ Window ➪ Save Workspace, and provide a name when prompted. To open a workspace file, use Office ➪ Open and select Workspaces (*.xlw) from the Files of Type drop-down list. It’s important to understand that a workspace file does not include the workbooks — only the configuration information that makes those workbooks visible in your Excel workspace. So if you need to distribute a workspace to someone else, make sure that you include the workbook files as well as the XLW file. Workspace Files 08_044018 ch04.qxp 2/28/07 6:36 PM Page 80 regular basis. For example, you might need to generate a monthly sales report. You can save some time by creating a template that holds the necessary formulas and charts for your report. When you start new files based on the template, you need only plug in the values. Viewing templates Excel 2007 gives you access to many templates. To explore the Excel templates, choose Office ➪ New to display the New Workbook dialog box. The template categories appear as tabs in the New Workbook dialog box. In addition, the right side of the dialog box displays a list of templates that you’ve used recently. The Microsoft Office Online section contains a number of categories. Click a category, and you’ll see the available templates. To use a template, select it and click Download. Figure 4-3 shows some of templates available in the Invoices category. Figure 4-3: Templates that you can use for invoices. Microsoft Office Online has a wide variety of templates, and some are better than others. If you download a few duds, don’t give up. Even though a template may not be perfect, you can often modify a template to meet your needs. Modifying an existing template is often easier than creating a workbook from scratch. Chapter 4: Understanding Excel’s Files Part I 81 08_044018 ch04.qxp 2/28/07 6:36 PM Page 81 NOTE The location of the Templates folder varies, depending on the version of Excel. To find the location of your Templates folder, execute the following VBA statement: MsgBox Application.TemplatesPath Creating templates Excel supports three types of templates: • The default workbook template: Used as the basis for new workbooks. This file is named book.xltx. • The default worksheet template: Used as the basis for new worksheets that are inserted into a workbook. This file is named sheet.xltx. • Custom workbook templates: Usually, these are ready-to-run workbooks that include for- mulas, but they can be as simple or as complex as you like. Typically, these templates are set up so that a user can simply plug in values and get immediate results. USING THE WORKBOOK TEMPLATE TO CHANGE WORKBOOK DEFAULTS Every new workbook that you create starts out with some default settings. For example, the workbook has three worksheets, the worksheets have gridlines, text appears in Calibri 11-point font, columns are 8.43 units wide, and so on. If you’re not happy with any of the default workbook settings, you can change them. Making changes to Excel’s default workbook is fairly easy to do, and it can save you lots of time in the long run. Here’s how you change Excel’s workbook defaults: 1. Open a new workbook. 2. Add or delete sheets to give the workbook the number of worksheets that you want. 3. Make any other changes that you want to make, which can include column widths, named styles, page setup options, and many of the settings that are available in the Options dialog box. To change the default formatting for cells, choose Home ➪ Styles ➪ Cell Styles and then modify the settings for the Normal style. For example, you can change the default font, size, or number format. 4. When your workbook is set up to your liking, choose Office ➪ Save As. 5. In the Save As dialog box, select Template (*.xltx) from the box labeled Save As Type. 6. Enter book.xltx for the filename. 7. Save the file in your \XLStart folder (not in your Templates folder). 8. Close the file. Part I: Some Essential Background82 08_044018 ch04.qxp 2/28/07 6:36 PM Page 82 TIP The \XLStart folder may be located in either of these directories: C:\Documents and Settings\\Application Data\Microsoft\Excel\XLStart C:\Program Files\Microsoft Office\Office12\XLStart To determine the location of \XLStart, execute this VBA statement: MsgBox Application.StartupPath After you perform the preceding steps, the new default workbook that appears when Excel is started is based on the book.xltx workbook template. You can also press Ctrl+N to create a workbook based on this template. If you ever want to revert back to the standard default workbook, just delete the book.xltx file. NOTE If you choose File ➪ New, and select Blank Workbook from the New Workbook dialog box, the workbook will not be based on the book.xltx template. I don’t know if this is a bug, or if it’s by design. USING THE WORKSHEET TEMPLATE TO CHANGE WORKSHEET DEFAULTS When you insert a new worksheet into a workbook, Excel uses its built-in worksheet defaults for the worksheet. This includes items such as column width, row height, and so on. If you don’t like the default settings for a new worksheet, you can change them by fol- lowing these steps: 1. Start with a new workbook and delete all the sheets except one. 2. Make any changes that you want to make, which can include column widths, named styles, page setup options, and many of the settings that are available in the Excel Options dialog box. 3. When your workbook is set up to your liking, select Office ➪ Save As. 4. In the Save As dialog box, select Template (*.xltx) from the Save As Type box. 5. Enter sheet.xltx for the filename. 6. Save the file in your \XLStart folder (not in your Templates folder). 7. Close the file. 8. Close and restart Excel. After performing this procedure, all new sheets that you insert by clicking the Insert Worksheet button (which is next to the last sheet tab) will be formatted like your sheet.xltx template. You can also press Shift+F11 to insert a new worksheet. Chapter 4: Understanding Excel’s Files Part I 83 08_044018 ch04.qxp 2/28/07 6:36 PM Page 83 Creating workbook templates The book.xltx and sheet.xltx templates discussed in the preceding section are two special types of templates that determine default settings for new workbooks and new worksheets. This section discusses other types of templates, referred to as workbook tem- plates, which are simply workbooks that you set up as the basis for new workbooks or worksheets. Why use a workbook template? The simple answer is that it saves you from repeating work. Assume that you create a monthly sales report that consists of your company’s sales by region, plus several summary calculations and charts. You can create a template file that consists of everything except the input values. Then, when it’s time to create your report, you can open a workbook based on the template, fill in the blanks, and be finished. NOTE You could, of course, just use the previous month’s workbook and save it with a different name. This is prone to errors, however, because you easily can forget to use the Save As command and accidentally overwrite the previous month’s file. Another option is to use the New From Existing icon in the New Workbook dialog box. This creates a new work- book from an existing one, but gives a different name to ensure that the old file is not overwritten. When you create a workbook that is based on a template, the default workbook name is the template name with a number appended. For example, if you create a new workbook based on a template named Sales Report.xltx, the workbook’s default name is Sales Report1.xlsx. The first time that you save a workbook that is created from a template, Excel displays its Save As dialog box so that you can give the template a new name if you want to. A custom template is essentially a normal workbook, and it can use any Excel feature, such as charts, formulas, and macros. Usually, a template is set up so that the user can enter values and get immediate results. In other words, most templates include everything but the data, which is entered by the user. NOTE If your template contains macros, it must be saved as an Excel Macro-Enabled Template, with an XLTM extension. Inside an Excel File As I’ve noted, Excel 2007 uses a new XML format for its workbooks, templates, and add- ins. These files are actually ZIP compressed files. As such, they can be “unzipped” and examined. Part I: Some Essential Background84 08_044018 ch04.qxp 2/28/07 6:36 PM Page 84 Previous versions of Excel used a binary file format. Although the binary file format speci- fications are known, working with binary files is not easy. The Excel 2007 XML file format, on the other hand, is an “open format.” As such, these files can be created and manipulated using other software. Dissecting a file In this section, I describe the various parts within a typical Excel XLSM (macro-enabled) workbook file. The workbook, named sample.xlsm, is shown in Figure 4-4. It has one worksheet, one chart sheet, and a simple VBA macro. The worksheet contains a table, a button (from the Forms controls), a SmartArt diagram, and a photo of a flower. CD The sample.xlsm workbook is available on the companion CD-ROM. Figure 4-4: A simple workbook. Chapter 4: Understanding Excel’s Files Part I 85 08_044018 ch04.qxp 2/28/07 6:36 PM Page 85 To view the innards of an Excel 2007 file, you need to open an Explorer window and add a ZIP extension to the filename. So the sample.xlsm file is renamed to sample.xlsm.zip. You can then open the file by using any unzipping program. I use the zip feature built into Windows XP. TIP You may prefer to extract the zipped files into an uncompressed directory. Doing so makes it easier to view the files. In Windows, right-click the filename and choose Extract All. The first thing that you notice is that the file contains a directory structure. The left panel of Figure 4-5 shows the fully expanded directory structure for the workbook file. The actual directories will vary with the workbook. Figure 4-5: The directory structure of the workbook file. With a few exceptions, all of the files are text files. More specifically, they are XML files. You can view them in a text file editor, an XML editor, a Web browser, or even in Excel. Figure 4-6 shows one of these files viewed in the Firefox browser. The non-XML files include graphic images and VBA projects (these are stored in binary format). This XML file has three root-level folders, and some of these have subfolders. You’ll notice that many of the folders contain a _rels folder. These folders contain XML files that define the relationships to other parts within the package. Part I: Some Essential Background86 08_044018 ch04.qxp 2/28/07 6:36 PM Page 86 Figure 4-6: Viewing an XML file in a Web browser. Following is a list of the folders in the sample.xlsm workbook: •_rels: Contains information about the package relationships. • docProps: Contains XML files that describe the file properties and application settings. • xl: This folder holds the meat of the file. The name varies with the Office document type (xl, ppt, word, and so on). You’ll find several XML files that contain settings for the workbook. And if your workbook contains VBA code, it will be in a binary file with a BIN extension. The xl folder has several subfolders (some workbooks may have more or fewer subfolders, depending on the content): • charts: Contains an XML file for each chart. This file contains the chart settings. • chartsheets: Contains an XML file with data for each chart sheet in the workbook. • diagrams: Contains XML files that describe the diagrams (SmartArt) in the workbook. • drawings: Contains an XML file with data for each “drawing.” Drawings include items such as buttons, charts, and images. Chapter 4: Understanding Excel’s Files Part I 87 08_044018 ch04.qxp 2/28/07 6:36 PM Page 87 • media: Contains embedded media, such GIF and JPG files. • tables: Contains an XML file with data for each table. • theme: Contains an XML file with data about the workbook’s theme. • worksheets: Contains an XML file for each worksheet in the workbook. TIP If you add a ZIP extension to an Excel file, you can still open it in Excel. Excel doesn’t care what the file’s extension is. Also, you can save a workbook with a ZIP extension. In the Save As dialog box, add a ZIP extension and then place double quotation marks around the entire file name. For example: “Myworkbook.xlsx.zip”. Why is the file format important? The new “open” XML file formats for Microsoft Office represent a significant step for the computing community. For the first time, it’s relatively easy to read and write Excel work- books using software other than Excel. For example, it’s possible to write a program to modify thousands of Excel workbook files without even opening Excel. Such a program could insert a new worksheet into every file. The programmer, of course, would need to have excellent knowledge of the XML file structures, but such a task is definitely doable. Importantly, the new file formats are much less prone to corruption (compared to the old binary formats). I saved a workbook file and then deleted one of the worksheet XML files. When I tried to reopen it in Excel, I got the message shown in Figure 4-7. Excel was able to tell that the file was damaged by comparing the information in the .res files with what’s actually in the file. In this case, Excel was able to repair the file and open it. The deleted worksheet was re-inserted, but it was empty. Figure 4-7: Excel can often repair a damaged workbook file. In addition, the zipped XML files are usually smaller than comparable binary files. And, finally, the structured nature of the files makes it possible to extract individual elements (for example, all graphic images). The typical Excel user won’t need to examine or modify the XML components of a work- book file. But, as a developer, you may want to write code that changes Excel’s Ribbon user interface. If that’s the case, you will need to be at least somewhat familiar with the structure of a workbook XML file. Part I: Some Essential Background88 08_044018 ch04.qxp 2/28/07 6:36 PM Page 88 CROSS-REFERENCE Refer to Chapter 22 for more information about modifying Excel’s Ribbon. The QAT File The only user interface element in Excel 2007 that’s customizable by the end user is the Quick Access Toolbar. The information for the QAT is stored in a file named Excel.qat, and this file is located here: C:\Documents and Settings\\Local Settings\Application Data\ Microsoft\OFFICE This file is updated whenever a change is made to the QAT. It’s updated immediately, not when Excel is closed. Excel.qat is an XML file, and you can view it using an XML editor, a Web browser, or Excel. To view this file in Excel, follow these steps: 1. Make a copy of the Excel.qat file. 2. Add an XML extension to the copy of the file so that the name is Excel.qat.xml. 3. Choose Office ➪ Open to open the file or just drag it into Excel’s window. 4. You’ll see a dialog box with some options; choose As an XML Table. Figure 4-8 shows an imported Excel.qat file (the file is displayed as a table). This QAT has three commands in addition to the eleven default commands. Of the default commands, only three are visible. Figure 4-8: Viewing a QAT data file in Excel. Chapter 4: Understanding Excel’s Files Part I 89 08_044018 ch04.qxp 2/28/07 6:36 PM Page 89 It’s possible to share a QAT with other users. For example, you may have customized your QAT with two dozen useful tools. If a colleague is impressed, just give him a copy of your Excel.qat file and tell him where to put it. Don’t attempt to modify the Excel.qat file unless you know what you’re doing. The XLB File Excel stores customized toolbar and menu bar configurations in an XLB file. Even though Excel 2007 doesn’t officially support custom toolbars and menus in the way that it did in previous versions, it still uses an XLB file. When you exit Excel, the current toolbar configuration is saved in a file named Excel12. xlb. This file is (most likely) located here: C:\Documents and Settings\\Application Data\Microsoft\Excel This binary file contains information regarding the position and visibility of all custom tool- bars and custom menu bars, plus modifications that you’ve made to built-in toolbars or menu bars. Add-In Files An add-in is essentially an Excel workbook file with a few important differences: • The workbook’s IsAddin property is True — which means that it can be loaded and unloaded by using the Add-Ins dialog box. Access this dialog box by choosing Office ➪ Excel Options. Click the Add-Ins tab, select Excel Add-Ins from the Manage list, and click Go. • The workbook is hidden and cannot be unhidden by the user. Consequently, an add-in is never the active workbook. • When using VBA, the workbook is not part of the Workbooks collection. Many add-ins provide new features or functions to Excel. You can access these new fea- tures as if they were built into the product. You can create your own add-ins from workbook files. In fact, creating add-ins is the pre- ferred method of distributing some types of Excel applications. Excel 2007 add-ins have an XLAM extension by default. Part I: Some Essential Background90 08_044018 ch04.qxp 2/28/07 6:36 PM Page 90 NOTE Besides XLAM add-ins, Excel supports XLL add-ins and COM add-ins. These types of add-ins are created using software other than Excel. This book discusses only XLAM add-ins. CROSS-REFERENCE Chapter 21 covers the topic of add-ins in detail. Excel Settings in the Registry The Excel Options dialog box has dozens of user-specified options. Excel uses the Windows Registry to store these settings and retrieve them when Excel is started. In this section, I provide some background information about the Windows Registry and discuss how Excel uses the Registry to store its settings. About the Registry The Windows Registry is essentially a central hierarchical database that is used by the operating system and by application software. The Registry first appeared in Windows 95 and replaces the old INI files that stored Windows and application settings. CROSS-REFERENCE Your VBA macros can also read and write information to the Registry. Refer to Chapter 11 for details. You can use the Registry Editor program to browse the Registry — and even edit its con- tents if you know what you’re doing. The Registry Editor is named regedit.exe. Before beginning your explorations, take a minute to read the upcoming sidebar (titled “Before You Edit the Registry . . .”). Figure 4-9 shows what the Registry Editor looks like. The Registry consists of keys and values, arranged in a hierarchy. The top-level keys are: • HKEY_CLASSES_ROOT • HKEY_CURRENT_USER • HKEY_LOCAL_MACHINE • HKEY_USERS • HKEY_CURRENT_CONFIG • HKEY_DYN_DATA Chapter 4: Understanding Excel’s Files Part I 91 08_044018 ch04.qxp 2/28/07 6:36 PM Page 91 Figure 4-9: The Registry Editor lets you browse and make changes to the Registry. Excel’s settings Information used by Excel 2007 is stored in this Registry section: HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Excel Part I: Some Essential Background92 You can use the regedit.exe program to change anything in the Registry, including information that is critical to your system’s operation. In other words, if you change the wrong piece of information, Windows may no longer work properly. Get into the habit of choosing the File ➪ Export command in Regedit. This command enables you to save an ASCII version of the entire Registry or just a specific branch of the Registry. If you find that you messed up something, you can always import the ASCII file to restore the Registry to its previous condition (choose the Registry ➪ Import Registry File command). Refer to the Help file for Regedit for details. Before You Edit the Registry . . . 08_044018 ch04.qxp 2/28/07 6:36 PM Page 92 In this section of the Registry, you’ll find a number of keys that contain specific values that determine how Excel operates. The Registry settings are updated automatically by Excel when Excel closes. NOTE It’s important to understand that Excel reads the Windows Registry only once — when it starts up. In addition, Excel updates the Registry settings only when Excel closes nor- mally. If Excel crashes (unfortunately, not an uncommon occurrence), the Registry infor- mation is not updated. For example, if you change one of Excel’s settings, such as the visibility of the Formula bar, this setting is not written to the Registry until Excel closes by normal means. Table 4-6 lists the Registry sections that are relevant to Excel 2007. You might not find all these sections in your Registry database. TABLE 4-6 EXCEL CONFIGURATION INFORMATION IN THE REGISTRY Section Description Add-in Manager Lists add-ins that appear in the Add-Ins dialog box. Add-ins that are included with Excel do not appear in this list. If you have an add-in entry in this list box that you no longer use, you can remove it by using the Registry Editor. Converters Lists additional (external) file converters that are not built into Excel. Error Checking Holds the settings for formula error checking. File MRU Holds information about the most recently used files (which appears in the Recent Documents list when you click the Office button). Options A catch-all section; holds a wide variety of settings. Recent Templates Stores the names of templates you’ve used recently. Resiliency Information used for recovering documents. Security Specifies the security options for opening files that contain macros. Spell Checker Stores information about your spelling checker options. StatusBar Stores the user choices for what appears in the status bar. UserInfo Stores information about the user. Chapter 4: Understanding Excel’s Files Part I 93 08_044018 ch04.qxp 2/28/07 6:36 PM Page 93 Although you can change most of the settings via the Excel Options dialog box, a few set- tings cannot be changed directly from Excel (but you can use the Registry Editor to make changes). For example, when you select a range of cells, you may prefer that the selected cells appear in high contrast white-on-black. There is no way to specify this in Excel, but you can add a new Registry key like this: 1. Open the Registry Editor and locate this section: HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Excel\Options 2. Right-click and select New ➪ DWORD Value. 3. Name this value Options6. 4. Right-click the Options6 key and select Modify. 5. In the Edit DWORD Value dialog box, click the Decimal option and enter 16 (see Figure 4-10). Figure 4-10: Setting a value for a Registry setting. When you restart Excel, range selections will appear with a black background rather than gray. If you don’t like this look, just delete the Options6 Registry entry. TIP If you have trouble starting Excel, it’s possible that the Registry keys have become cor- rupt. You can try using the Registry Editor to delete the entire Excel section: HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Excel The next time Excel is started, it will rebuild the Registry keys. You will, however, lose all of the customization information that was stored there. Part I: Some Essential Background94 08_044018 ch04.qxp 2/28/07 6:36 PM Page 94 Excel Application Development Chapter 5 What Is a Spreadsheet Application? Chapter 6 Essentials of Spreadsheet Application Development IIPart 09_044018 pt02.qxp 2/28/07 6:36 PM Page 95 09_044018 pt02.qxp 2/28/07 6:36 PM Page 96 Chapter What Is a Spreadsheet Application? In This Chapter In this chapter, I attempt to describe how people use spreadsheets in the real world. This is a topic that’s germane to this entire book because it can help you determine how much effort you should devote to a particular development proj- ect. By the time you finish this chapter, you should have a pretty good idea of what I mean by a spreadsheet application. And after you’ve made it through the rest of the book, you’ll be well on your way to developing your own spreadsheet applications with Excel. But, first, it’s time to get down to the basics: ◆ A working definition of a spreadsheet application ◆ The difference between a spreadsheet user and a spreadsheet developer ◆ A system for classifying spreadsheet users to help you conceptualize the audience for your applications ◆ A discussion of why people use spreadsheets ◆ A taxonomy of the basic types of spreadsheets You’ve probably been working with spreadsheets for several years, but chances are good that your primary focus has been on simply generating spreadsheets to get the job done. You probably never gave much thought to more global issues like those discussed in this chapter: the different types of spreadsheet users, how to classify various types of spreadsheets, and even basic questions 5 97 10_044018 ch05.qxp 2/28/07 6:36 PM Page 97 such as why people use spreadsheets. If the title of this book attracted your attention, it’s important for you to understand these issues so that you can become an effective power programmer. I first discuss the concept of a spreadsheet application. This is, after all, the desired result of your power-programming efforts. Spreadsheet Applications For the purposes of this book, a spreadsheet application is a spreadsheet file (or group of related files) that is designed so that someone other than the developer can perform useful work without extensive training. According to this definition, most of the spreadsheet files that you’ve developed probably don’t qualify as spreadsheet applications. You may have dozens or hundreds of spreadsheet files on your hard drive, but it’s a safe bet that most of them aren’t really designed for others to use. A good spreadsheet application has the following characteristics: • It enables the end user to perform a task that he or she probably would not be able to do otherwise. • It provides the appropriate solution to the problem. (A spreadsheet environment isn’t always the optimal approach.) • It accomplishes what it is supposed to do. This may be an obvious prerequisite, but it’s not at all uncommon for applications to fail this test. • It produces accurate results and is free of bugs. • It uses appropriate and efficient methods and algorithms to accomplish its job. • It traps errors before the user is forced to deal with them. NOTE Note that errors and bugs are not the same. Attempting to divide by zero is an error, whereas failure to identify that error before it occurs is a bug. • It does not allow the user to delete or modify important components accidentally (or intentionally). • Its user interface is clear and consistent so that the user always knows how to proceed. • Its formulas, macros, and user interface elements are well documented, allowing for subsequent changes, if necessary. • It is designed so that it can be modified in simple ways without making major changes. A basic fact of life is that a user’s needs change over time. Part II: Excel Application Development98 10_044018 ch05.qxp 2/28/07 6:36 PM Page 98 • It has an easily accessible help system that provides useful information on at least the major procedures. • It is designed to be portable and to run on any system that has the proper software (in this case, a copy of the appropriate version of Excel). It should come as no surprise that it is possible to create spreadsheet applications for many different usage levels, ranging from simple fill-in-the-blank templates to extremely complex applications that use a custom interface and that may not even look like spreadsheets. The Developer and the End User I’ve already used the terms developer and end user, and you will see them frequently throughout this book. Because you’ve gotten this far, I think I can safely assume that you’re either a spreadsheet application developer or a potential developer. My definitions regarding developers and end users are simple. The person who creates the spreadsheet application is the developer. For joint projects, there are multiple developers: a development team. The person who uses the results of the developer’s spreadsheet pro- gramming efforts is the end user (which I often shorten to simply user). In many cases, there will be multiple end users, and often the developer is one of the users. Who are developers? What do they do? I’ve spent about 20 years trading methodologies and hanging out (usually in a “virtual” manner online) with the motley crew of folks who call themselves spreadsheet developers. I divide them into two primary groups: • Insiders are developers who are intimately involved with the users and thoroughly under- stand their needs. In many cases, these developers are also users of the application. Often, they develop an application in response to a particular problem. • Outsiders are developers who are hired to produce a solution to a problem. In most cases, developers in this category are familiar with the business in general but not with the specifics of the application they are developing. In other cases, these developers are already employed by the company that requests the application (but they normally work in a different department). Some developers devote full time to development efforts. These developers may be either insiders or outsiders. A fair number of consultants (outsiders) make a decent living devel- oping spreadsheet applications on a freelance basis. Other spreadsheet developers don’t work full time at the task and may not even realize they are developing spreadsheet applications. These developers are often office computer gurus who seem to know everything about computers and software. These folks often Chapter 5: What Is a Spreadsheet Application? 99 Part II 10_044018 ch05.qxp 2/28/07 6:36 PM Page 99 create spreadsheet applications as a way to make their lives easier — the time spent developing a well-designed application for others can often save hours of training time and can greatly reduce the time spent answering others’ questions. Spreadsheet developers are typically involved in the following activities, often performing most or all of each task on their own: • Determining the needs of the user • Planning an application that meets these needs • Determining the most appropriate user interface • Creating the spreadsheet, formulas, macros, and user interface • Testing the application under all reasonable sets of conditions • Making the application relatively user-friendly (often based on results from the testing) • Making the application aesthetically appealing and intuitive • Documenting the development effort • Distributing the application to users • Updating the application if and when it’s necessary CROSS-REFERENCE I discuss these developer activities in more detail in Chapter 6. Developers must have a thorough understanding of their development environment (in this case, Excel). And there’s certainly a lot to know when it comes to Excel. Developing non- trivial spreadsheet applications with Excel requires an in-depth knowledge of formulas, functions, macros, custom dialog boxes, user interface elements, and add-ins. Most Excel users, of course, don’t meet these qualifications and have no intention of ever learning these details — which brings me to the next topic: classifying spreadsheet users. Classifying spreadsheet users Over the years, I’ve found that it’s often useful to classify people who use spreadsheets (including both developers and end users) along two dimensions: their degree of experience with spreadsheets and their interest in learning about spreadsheets. To keep things simple, each of these two dimensions has three levels. These levels can be combined in nine combinations, which are shown in Table 5-1. In reality, only seven seg- ments are worth thinking about because both moderately experienced and very experienced spreadsheet users generally have at least some interest in spreadsheets. (After all, that’s what motivated them to get their experience.) Users who have a lot of spreadsheet experi- ence and a low level of interest would make very bad developers. Part II: Excel Application Development100 10_044018 ch05.qxp 2/28/07 6:36 PM Page 100 TABLE 5-1 CLASSIFICATION OF SPREADSHEET USERS BY EXPERIENCE AND INTEREST No Interest Moderately Interested Very Interested Little Experience User User User/Potential Developer Moderately Experienced N/A User Developer Very Experienced N/A User Developer It should be clear that spreadsheet developers must have a great deal of experience with spreadsheets as well as a high interest in spreadsheets. Those with little spreadsheet experience but a great deal of interest are potential developers. All they need is more expe- rience. If you’re reading this book, you probably fall into one of the boxes in the last col- umn of the table. The audience for spreadsheet applications The remaining segments in the preceding table comprise spreadsheet end users, whom you can think of as the consumers of spreadsheet applications. When you develop a spread- sheet application for others to use, you need to know which of these groups of people will actually be using your application. Users with little experience and no interest in learning more about spreadsheets make up a large percentage of all spreadsheet users, probably the largest group of all. These are the people who need to use a spreadsheet for their jobs but who view the spreadsheet simply as a means to an end. Typically, they know very little about computers and software, and they usually have no interest in learning anything more than what’s required to get their work done. They might even feel a bit intimidated by computers. Often, these users don’t even know which version of Excel they use, and they are largely unfamiliar with what it can do. Obviously, applications developed for this group must be user-friendly. By that I mean straightforward, unintimidating, easy to use, and as foolproof as possible. From the developer’s point of view, a more interesting group is comprised of users who have little or moderate spreadsheet experience but who are interested in learning more. These users understand the concept of formulas, use worksheet functions, and generally have a good idea of what the product is capable of doing. These users generally appreciate the work that you put into an application and are often impressed by your efforts. Even bet- ter, they’ll often make excellent suggestions for improving your applications. Applications developed for this group should also be user-friendly, but they can also be more complex and customizable than applications designed for the less experienced and less interested groups. Chapter 5: What Is a Spreadsheet Application? 101 Part II 10_044018 ch05.qxp 2/28/07 6:36 PM Page 101 Solving Problems with Excel In the previous sections, I cover the basic concept of a spreadsheet application, discuss the end users and developers of such applications, and even attempt to figure out why people use spreadsheets at all. Now, it’s time to take a look at the types of tasks that are appro- priate for spreadsheet applications. You might already have a good idea of the types of tasks for which you can use a spread- sheet. Traditionally, spreadsheet software has been used for numerical applications that are largely interactive. Corporate budgets are an excellent example of this. After the model has been set up (that is, after formulas have been developed), working with a budget is simply a matter of plugging in amounts and observing the bottom-line totals. Often, bud- geters simply need to allocate fixed resources among various activities and present the results in a reasonably attractive (or at least legible) format. Excel, of course, is ideal for this. Budget-type problems, however, probably account for only a small percentage of your spreadsheet-development time. If you’re like me, you’ve learned that uses for Excel can often extend well beyond the types of tasks for which spreadsheets were originally designed. Here are just a few examples of nontraditional ways that Excel can be used: • As a presentation device: For example, with minimal effort, you can create an attractive, interactive, on-screen slideshow with only Excel. PowerPoint is a better choice, but Excel will do in a pinch. • As a data-entry tool: For repetitive data-entry tasks, a spreadsheet is often the most effi- cient route to take. The data can then be exported to a variety of formats for use in other programs. • As a database manager: If you’re dealing with a fairly small amount of data, you may find it much easier to manage it using Excel rather than a program like Access. • As a forms generator: For creating attractive printed forms, many find it easier to use Excel’s formatting capabilities than to learn a desktop publishing package. • As a text processor: Excel’s text functions and macro capability enable you to manipulate text in ways that are impossible using a word processor. • As a platform for simple games: Clearly, Excel was not designed with this in mind. However, I’ve downloaded (and written) some interesting strategy games by using the tools found in Excel and other spreadsheets. You can probably think of many more examples for this list. Ironically, the versatility of spreadsheets is a double-edged sword. On one hand, it’s tempt- ing to try to use a spreadsheet for every problem that crops up. On the other hand, you’ll often be spinning your wheels by trying to use a spreadsheet for a problem that’s better suited for a different solution. Part II: Excel Application Development102 10_044018 ch05.qxp 2/28/07 6:36 PM Page 102 Basic Spreadsheet Types In this section, I classify spreadsheets into several basic types to provide a better perspec- tive on how spreadsheet applications fit into the overall scheme of things. This is all quite arbitrary, of course, and is based solely on my own experience. Moreover, there is quite a bit of overlap between the categories, but they cover most of the spreadsheets that I’ve seen and developed. My names for these categories are as follows: • Quick-and-dirty • For-your-eyes-only • Single-user applications • Spaghetti applications • Utility applications • Add-ins that contain worksheet functions • Single-block budgets • What-if models • Data storage and access • Database front ends • Turnkey applications I discuss each of these categories in the following sections. Quick-and-dirty spreadsheets This is probably the most common type of spreadsheet. Most of the spreadsheets in this category are fairly small and are developed to quickly solve a problem or answer a ques- tion. Here’s an example: You’re about to buy a new car, and you want to figure out your monthly payment for various loan amounts. Or perhaps you need to generate a chart that shows your company’s sales by month, so you quickly enter 12 values and whip out a chart, which you paste into your word processor. In both of the preceding cases, you can probably input the entire model in a few minutes, and you certainly won’t take the time to document your work. You probably won’t even think of developing any macros or custom dialog boxes. In fact, you might not even deem these simple spreadsheets worthy of saving to disk. Obviously, spreadsheets in this cate- gory are not applications. Chapter 5: What Is a Spreadsheet Application? 103 Part II 10_044018 ch05.qxp 2/28/07 6:36 PM Page 103 For-your-eyes-only spreadsheets As the name implies, no one except you — the creator — will ever see or use the spread- sheets that fall into this category. An example of this type might be a file in which you keep information relevant to your income taxes. You open the file whenever a check comes in the mail, you incur an expense that can be justified as business, you buy tax-deductible Girl Scout cookies, and so on. Another example is a spreadsheet that you use to keep track of your employees’ time records (sick leave, vacation, and so on). Spreadsheets in this category differ from quick-and-dirty spreadsheets in that you use them more than once, so you save these spreadsheets to files. But, again, they’re not worth spending a great deal of time on. You might apply some simple formatting, but that’s about it. This type of spreadsheet also lacks any type of error detection because you understand how the formulas are set up; you know enough to avoid inputting data that will produce erroneous results. If an error does crop up, you immediately know what caused it. Spreadsheets in this category don’t qualify as applications, although they sometimes increase in sophistication over time. Single-user applications This is a spreadsheet application that only the developer uses, but its complexity extends beyond the spreadsheets in the for-your-eyes-only category. For example, I developed a workbook to keep track of registered users for my shareware applications. It started out as a simple worksheet database (for my eyes only), but then I realized that I could also use it to generate mailing labels and invoices. One day I spent an hour or so writing macros and then realized that I had converted this workbook from a for-your-eyes-only spreadsheet to a single-user application. Creating single-user applications for yourself is an excellent way to get practice with Excel’s developer’s tools. For example, you can learn to create custom dialog boxes, modify the user interface, write Visual Basic for Applications (VBA) macros, and so on. TIP Working on a meaningful project (even if it’s meaningful only to you) is the best way to learn advanced features in Excel — or any other software, for that matter. Spaghetti applications An all-too-common type of spreadsheet is what I call a spaghetti application. The term stems from the fact that the parts of the application are difficult to follow, much like a plate of spaghetti. Most of these spreadsheets begin life as a reasonably focused, single- user application. But over time, they are passed along to others who make their own modi- fications. As requirements change and employees come and go, new parts are added and others are ignored. Before too long, the original purpose of the workbook may have been forgotten. The result is a file that is used frequently, but no one really understands exactly how it all works. Part II: Excel Application Development104 10_044018 ch05.qxp 2/28/07 6:36 PM Page 104 Everyone who’s involved with it knows that the spaghetti application should be completely reworked. But because nobody really understands it, the situation tends to worsen over time. Spreadsheet consultants make a lot of money untangling such applications. I’ve found that, in many cases, the most efficient solution is to redefine the users’ needs and build a new application from scratch. Utility applications Good as it is, I still find quite a bit lacking in Excel. This brings me to the next category of spreadsheets: utility applications. Utilities are special tools designed to perform a single recurring task. For example, if you often import text into Excel, you may want some addi- tional text-handling commands, such as the ability to convert selected text to uppercase (without using formulas). The solution? Develop a text-handling utility that does exactly what you want. NOTE The Power Utility Pak is a collection of utility applications for Excel. I developed these utilities to extend Excel’s functionality. These utilities work just like normal Excel com- mands. You can download a trial version of the Power Utility Pak from my Web site (www.j-walk.com/ss), and you can get a free copy of the full version by using the coupon located at the back of the book. And if you’re interested, the complete VBA source code is also available for a small fee. The best utility applications are very general in nature. Most macros are designed to per- form a specific operation on a specific type of data found in a specific type of workbook. A good utility essentially works like a command normally found in Excel. In other words, the utility needs to recognize the context in which a command is executed and take appropri- ate action. This usually requires quite a bit of error-handling code so that the utility can handle any situation that comes up. Utility applications always use macros and may or may not use custom dialog boxes. Fortunately, Excel makes it relatively easy to create such utilities, and they can be converted to add-ins and attached to Excel’s user interface so that they appear to be part of Excel. CROSS-REFERENCE The topic of creating utilities is so important that I devote an entire chapter to it. Chapter 16 discusses how to create custom Excel utilities with VBA. Add-ins that contain worksheet functions As you know, Excel has many worksheet functions that you can use in formulas. Chances are that you’ve needed a particular function, only to find that it doesn’t exist. The solution? Create your own by using VBA. Custom worksheet functions can often simplify your formu- las and make your spreadsheet easier to maintain. Chapter 5: What Is a Spreadsheet Application? 105 Part II 10_044018 ch05.qxp 2/28/07 6:36 PM Page 105 CROSS-REFERENCE In Chapter 10, you’ll find everything you need to know about creating custom worksheet functions, including lots of examples. Single-block budgets By a single-block budget, I mean a spreadsheet (not necessarily a budget model) that essentially consists of one block of cells. The top row might contain names that correspond to time (months, quarters, or years), and the left column usually contains categories of some type. Typically, the bottom row and right column contain formulas that add the num- bers together. There may or may not be formulas that compute subtotals within the block. This is a very common type of spreadsheet. In most cases, simple single-block budget mod- els are not good candidates for applications because they are simple to begin with, but there are exceptions. For example, you might consider converting such a spreadsheet into an application if the model is an unwieldy 3-D spreadsheet, needs to include consolidations from other files, or will be used by departmental managers who might not understand spreadsheets. What-if models Many consider the what-if model category to be the epitome of spreadsheets at their best. The ability to instantly recalculate thousands of formulas makes spreadsheet software the ideal tool for financial modeling and other models that depend on the values of several variables. If you think about it, just about any spreadsheet that contains formulas is a what-if model (which are often distributed as templates). Changing the value of a cell used in a formula is akin to asking “what if . . .?” My view of this category, however, is a bit more sophisticated. It includes spreadsheets designed exclusively for systematically ana- lyzing the effects of various inputs. What-if models often benefit from additional work to make them more user-friendly, espe- cially if the model will be used for a lengthy period of time. Creating a good user interface on an application can make it very easy for anyone to use, including computer-illiterates. As an example, you might create an interface that lets users provide names for various sets of assumptions and then lets them instantly view the results of a selected scenario and create a perfectly formatted summary chart with the click of a button. Data storage and access spreadsheets A large percentage of Excel workbooks consist of one or more database tables (sometimes known as lists). These are used to track just about anything you can think of. Most people find that it’s much easier to view and manipulate data in a spreadsheet than it is using nor- mal database software. If the tables are set up properly, they can be summarized with a pivot table. Part II: Excel Application Development106 10_044018 ch05.qxp 2/28/07 6:36 PM Page 106 NEW Microsoft is aware of the large number of users who use Excel for tables, and Excel 2007 has improved support for tables. Spreadsheets in this category are often candidates for applications, especially if end users need to perform things like data validation and pivot table summaries. For more sophisticated database applications, such as those that use multiple tables with relationships between them, you’ll be better off using a real database program such as Access. Database front ends Increasingly, spreadsheet products are used to access external databases. Spreadsheet users can access data stored in external files, even if they come in a variety of formats, by using tools that Excel provides. When you create an application that does this, it’s some- times referred to as an executive information system, or EIS. This sort of system combines data from several sources and summarizes it for users. Accessing external databases from a spreadsheet often strikes fear in the hearts of begin- ning users. Creating an executive information system is therefore an ideal sort of Excel application because its chief goal is usually ease of use. Turnkey applications The final category of spreadsheet types is the most complex. By turnkey, I mean ready to go, with little or no preparation by the end user. For example, the user loads the file and is presented with a user interface that makes user choices perfectly clear. Turnkey applica- tions may not even look as if they are being powered by a spreadsheet, and, often, the user interacts completely with dialog boxes rather than cells. I’ve heard these types of applica- tions referred to as “dictator applications” because the user can perform only the opera- tions that the developer has allowed. Actually, many of the categories just described can be converted into turnkey applications. The critical common elements, as I discuss throughout the remainder of the book, are good planning, error handling, and user interface design. Chapter 5: What Is a Spreadsheet Application? 107 Part II 10_044018 ch05.qxp 2/28/07 6:36 PM Page 107 10_044018 ch05.qxp 2/28/07 6:36 PM Page 108 Chapter Essentials of Spreadsheet Application Development In This Chapter My goal in this chapter is to provide you with some general guidelines that you may find useful while you learn to create effective applications with Excel. ◆ A discussion of the basic steps involved in spreadsheet application development ◆ Determining end user needs and planning applications to meet those needs ◆ Guidelines for developing and testing your applications ◆ Documenting your development efforts and writing user documentation There is no simple, sure-fire recipe for developing an effective spreadsheet application. Everyone has his or her own style for creating such applications, and I haven’t discovered one best way that works for everyone. In addition, every project is different and, therefore, requires its own approach. Finally, the demands and technical expertise of the people you work with (or for) also play a role in how the development process proceeds. 6 109 11_044018 ch06.qxp 2/28/07 6:37 PM Page 109 As I mention in the preceding chapter, spreadsheet developers typically perform the follow- ing activities: • Determine the needs of the user(s) • Plan an application that meets these needs • Determine the most appropriate user interface • Create the spreadsheet, formulas, macros, and user interface • Test and debug the application • Attempt to make the application bulletproof • Make the application aesthetically appealing and intuitive • Document the development effort • Develop user documentation and help systems • Distribute the application to the user • Update the application when necessary Not all these steps are required for each application, and the order in which these activities are performed varies from project to project. Each of these activities is described in the pages that follow, and in most cases, the technical details are covered in subsequent chapters. Determining User Needs When you undertake a new Excel project, one of your first steps is to identify exactly what the end users require. Failure to thoroughly assess the end users’ needs early on often results in additional work later when you have to adjust the application so that it does what it was supposed to do in the first place. In some cases, you’ll be intimately familiar with the end users — you might even be an end user yourself. In other cases (for example, a consultant developing a project for a new client), you may know little or nothing about the users or their situations. How do you determine the needs of the user? If you’ve been asked to develop a spreadsheet application, it’s a good idea to meet with the end users and ask very specific questions. Better yet, get everything in writing, create flow diagrams, pay attention to minor details, and do anything else to ensure that the product you deliver is the product that is needed. Here are some guidelines that may help to make this phase easier: • Don’t presume that you know what the user needs. Second-guessing at this stage almost always causes problems later on. • If possible, talk directly to the end users of the application, not just their supervisor or manager. Part II: Excel Application Development110 11_044018 ch06.qxp 2/28/07 6:37 PM Page 110 • Learn what, if anything, is currently being done to meet the users’ needs. You might be able to save some work by simply adapting an existing application. At the very least, looking at current solutions will familiarize you with the operation. • Identify the resources available at the user’s site. For example, try to determine whether there are any hardware or software limitations that you must work around. • If possible, determine the specific hardware systems that will be used. If your applica- tion will be used on slower systems, you need to take that into account. See the later section “System speed.” • Identify which version(s) of Excel is (are) in use. Although Microsoft does everything in its power to urge users to upgrade to the latest version of the software, the majority of Excel users have not upgraded to the most recent version. • Understand the skill levels of the end users. This information will help you design the application appropriately. • Determine how long the application will be used and whether any changes are antici- pated during the lifetime of the project. Knowing this may influence the amount of effort that you put into the project and help you plan for changes. One final note: Don’t be surprised if the project specifications change before you complete the application. This is quite common, and you are in a better position if you expect changes rather than being surprised by them. Just make sure that your contract (if you have one) addresses the issue of changing specifications. Planning an Application That Meets User Needs After you determine the end users’ needs, it’s very tempting to jump right in and start fid- dling around in Excel. Take it from someone who suffers from this problem: Try to restrain yourself. Builders don’t construct a house without a set of blueprints, and you shouldn’t build a spreadsheet application without some type of plan. The formality of your plan depends on the scope of the project and your general style of working, but you should spend at least some time thinking about what you’re going to do and coming up with a plan of action. Before rolling up your sleeves and settling down at your keyboard, you’ll benefit by taking some time to consider the various ways you can approach the problem. Here is where a thorough knowledge of Excel pays off. Avoiding blind alleys before you stumble into them is always a good idea. If you ask a dozen Excel experts to design an application based on very precise specifica- tions, chances are that you’ll get a dozen different implementations of the project that meet those specifications. Of those solutions, some will definitely be better than the others because Excel often provides several different options to accomplish a task. If you know Chapter 6: Essentials of Spreadsheet Application Development 111 Part II 11_044018 ch06.qxp 2/28/07 6:37 PM Page 111 Excel inside and out, you’ll have a good idea of the potential methods at your disposal, and you can choose the one most appropriate for the project at hand. Often, a bit of creative thinking yields an unusual approach that’s vastly superior to other methods. So at the beginning stage of this planning period, consider some general options, such as these: • File structure: Think about whether you want to use one workbook with multiple sheets, several single-sheet workbooks, or a template file. • Data structure: You should always consider how your data will be structured. This includes using external database files versus storing everything in worksheets. • Formulas versus VBA: Should you use formulas or write Visual Basic for Applications (VBA) procedures to perform calculations? Both methods have advantages and disadvantages. • Add-in or workbook file: In some cases, an add-in might be the best choice for your final product. Or, perhaps you might use an add-in in conjunction with a standard workbook. • Version of Excel: Will your Excel application be used with Excel 2007 only? With Excel 2000 or Excel 2002? What about Excel 97, Excel 95, and Excel 5? Will it also be run on a Macintosh? These are very important considerations because each new version of Excel adds features that aren’t available in previous versions. The new user interface in Excel 2007 makes it more challenging than ever to create an application that works with older versions. • Error handling: Error handling is a major issue with applications. You need to determine how your application will detect and deal with errors. For example, if your application applies formatting to the active worksheet, you need to be able to handle a case in which a chart sheet is active. • Use of special features: If your application needs to summarize a lot of data, you might want to consider using Excel’s pivot table feature. Or, you might want to use Excel’s data validation feature as a check for valid data entry. • Performance issues: The time to start thinking about increasing the speed and efficiency of your application is at the development stage, not when the application is completed and users are complaining. • Level of security: As you may know, Excel provides several protection options to restrict access to particular elements of a workbook. For example, you can lock cells so that for- mulas cannot be changed, and you can assign a password to prevent unauthorized users from viewing or accessing specific files. Determining up front exactly what you need to protect — and what level of protection is necessary — will make your job easier. NOTE Be aware that Excel’s protection features are not 100-percent effective — far from it. If you desire complete and absolute security for your application, Excel probably isn’t the best platform. Part II: Excel Application Development112 11_044018 ch06.qxp 2/28/07 6:37 PM Page 112 You’ll probably have to deal with many other project-specific considerations in this phase. The important thing is that you consider all options and don’t settle on the first solution that comes to mind. Another design consideration is remembering to plan for change. You’ll do yourself a favor if you make your application as generic as possible. For example, don’t write a procedure that works with only a specific range of cells. Rather, write a procedure that accepts any range as an argument. When the inevitable changes are requested, such a design makes it easier for you to carry out the revisions. Also, you might find that the work that you do for one project is similar to the work that you do for another. Keeping reusability in mind when you are planning a project is always a good idea. One thing that I’ve learned from experience is to avoid letting the end user completely guide your approach to a problem. For example, suppose you meet with a manager who tells you that the department needs an application to write text files that will be imported Chapter 6: Essentials of Spreadsheet Application Development 113 Part II Now a few words about reality: Excel is a moving target. Excel’s upgrade cycle is approximately 18–24 months, which means that you have fewer than two years to get up to speed with its current innovations before you have even more innovations to contend with. Excel 5, which introduced VBA, represented a major paradigm shift for Excel developers. Thousands of people up until that point earned their living developing Excel applications (in Excel 2, 3, and 4) that were largely based on the XLM macro language. Beginning with Excel 5, dozens of new tools became available, and developers, for the most part, eagerly embraced them. When Excel 97 became available, developers faced yet another shift. This new version introduced a new file format, the Visual Basic Editor (VBE), and UserForms as a replacement for dialog sheets. Excel 2000, 2002, and 2003 introduced additional features, but these changes were not as radical as those in previous upgrades. Excel 2007 is perhaps the most significant upgrade ever. The key challenge is dealing with the new Ribbon user interface. In the past, creating custom menus and toolbars was relatively easy and could be done entirely using VBA. But, as you’ll see, modifying the Ribbon requires quite a bit of additional work, and you’ll need to go beyond VBA to make it happen. In addition, the new file formats will require some additional considerations. You may find it more efficient to create two versions of your applications: One for Excel 2007, and one for Excel 2003 and earlier versions. VBA is not difficult to learn, but it definitely takes time to become comfortable with it — and even more time to master it. Consequently, it’s not uncommon to be in the process of learning VBA while you’re developing applications with it. In fact, I think it’s impossible to learn VBA without developing applications. If you’re like me, you’ll find it much easier to learn VBA if you have a project that requires it. Learning VBA just for the sake of learning VBA usually doesn’t work. Learning While You Develop 11_044018 ch06.qxp 2/28/07 6:37 PM Page 113 into another application. Don’t confuse the user’s need with the solution. The user’s real need is to share data. Using an intermediate text file to do it is just one possible solution to the need. There might be other ways to approach the problem that are superior. In other words, don’t let the users define their problem by stating it in terms of a solution approach. Determining the best approach is your job. Determining the Most Appropriate User Interface When you develop spreadsheets that others will use, you need to pay special attention to the user interface. By user interface, I mean the method by which the user interacts with the application and executes your VBA macros. Excel 2007 makes some of these decisions irrelevant. Custom menus and toolbars are, for all intents and purposes, obsolete. This means that developers must learn how to work with the Ribbon. Excel provides several features that are relevant to user interface design: • Ribbon customizations • Shortcut menu customization • Shortcut keys • Custom dialog boxes (UserForms) • Controls (such as a ListBox or a CommandButton) placed directly on a worksheet I discuss these features briefly in the following sections and cover them more thoroughly in later chapters. Customizing the Ribbon The new Ribbon UI in Excel 2007 is a dramatic shift in user interface design. Fortunately, the developer has a fair amount of control over the Ribbon. Unfortunately, modifying the Ribbon isn’t a simple task. CROSS-REFERENCE See Chapter 22 for information about working with the Ribbon. Part II: Excel Application Development114 11_044018 ch06.qxp 2/28/07 6:37 PM Page 114 Chapter 6: Essentials of Spreadsheet Application Development 115 Part II Excel 2007 still supports custom menus and toolbars, but the way these UI elements are handled may not be to your liking. The following figure shows a custom menu and toolbar displayed in Excel 2003. The menu and toolbar were created using my Power Utility Pak add-in. Each menu item and toolbar button executes a macro. As shown in the following figure, when the Power Utility Pak add-in is installed in Excel 2007, the custom menu appears in a group labeled Add-Ins ➪ Menu Commands, and the custom toolbar is in a group labeled Add-Ins ➪ Custom Toolbars (the toolbars cannot be moved or resized). These Ribbon groups display the menu additions and toolbars for all the applications or add-ins that are loaded. The menu items and toolbar buttons still function, but the designer’s original UI conception has been compromised. continued Menu and Toolbar Compatibility 11_044018 ch06.qxp 2/28/07 6:37 PM Page 115 Customizing shortcut menus Excel 2007 still allows the VBA developer to customize the right-click shortcut menus. Figure 6-1 shows a customized shortcut menu that appears when you right-click a row number. Notice that this shortcut menu has six menu items (at the bottom) that aren’t nor- mally available. CROSS-REFERENCE Chapter 23 describes how to work with shortcut menus using VBA. Creating shortcut keys Another user interface option at your disposal is to create custom shortcut keys. Excel lets you assign a Ctrl key (or Shift+Ctrl key) combination (shortcut) to a macro. When the user presses the key combination, the macro executes. Part II: Excel Application Development116 continued 11_044018 ch06.qxp 2/28/07 6:37 PM Page 116 Figure 6-1: An example of a customized shortcut menu. Be aware, however, of these two caveats: First, you must make it clear to the user which keys are active and what they do; second, you need to be careful not to assign a key combi- nation that’s already used for something else. A key combination that you assign to a macro takes precedence over the built-in shortcut keys. For example, Ctrl+S is a built-in Excel shortcut key used to save the current file. If you assign this key combination to a macro, you lose the capability to save the file with Ctrl+S. Remember that shortcut keys are case-sensitive, so you can use a combination such as Ctrl+Shift+S. Creating custom dialog boxes Anyone who has used a personal computer for any length of time is undoubtedly familiar with dialog boxes. Consequently, custom Excel dialog boxes can play a major role in the user interfaces that you design for your applications. Figure 6-2 shows an example of a custom dialog box. Chapter 6: Essentials of Spreadsheet Application Development 117 Part II 11_044018 ch06.qxp 2/28/07 6:37 PM Page 117 Figure 6-2: A dialog box created with Excel’s UserForm feature. A custom dialog box is known as a UserForm. A UserForm can solicit user input, get a user’s options or preferences, and direct the flow of your entire application. You create and edit UserForms in the VBE. The elements that make up a UserForm (buttons, drop-down lists, check boxes, and so on) are called controls — more specifically, ActiveX controls. Excel provides a standard assortment of ActiveX controls, and you can also incorporate third-party controls. After adding a control to a dialog box, you can link it to a worksheet cell so that it doesn’t require any macros (except a simple macro to display the dialog box). Linking a control to a cell is easy, but it’s not always the best way to get user input from a dialog box. Most of the time, you want to develop VBA macros that work with your custom dialog boxes. CROSS-REFERENCE I cover UserForms in detail in Part IV. Using ActiveX controls on a worksheet Excel also lets you add the UserForm ActiveX controls to a worksheet’s drawing layer (an invisible layer on top of a sheet that holds pictures, charts, and other objects). Figure 6-3 shows a simple worksheet model with several UserForm controls inserted directly on the worksheet. This sheet contains the following ActiveX controls: a Checkbox, a ScrollBar, and two sets of OptionButtons. This workbook uses no macros. Rather, the controls are linked to worksheet cells. CD-ROM This workbook is available on the companion CD-ROM. The file is named worksheet controls.xlsx. Part II: Excel Application Development118 11_044018 ch06.qxp 2/28/07 6:37 PM Page 118 Figure 6-3: You can add dialog box controls to worksheets and link them to cells. Perhaps the most common control is a CommandButton. By itself, a CommandButton doesn’t do anything, so you need to attach a macro to each CommandButton. Using dialog box controls directly in a worksheet often eliminates the need for custom dia- log boxes. You can often greatly simplify the operation of a spreadsheet by adding a few ActiveX controls (or Form controls) to a worksheet. This lets the user make choices by operating familiar controls rather than making entries into cells. Access these controls by using the Developer ➪ Controls ➪ Insert command (see Fig- ure 6-4). Figure 6-4: Worksheet controls. The controls come in two types: Form Controls and ActiveX Controls. Both sets of controls have their advantages and disadvantages. Generally, the Form controls are easier to use, but the ActiveX controls are a bit more flexible. Table 6-1 summarizes these two classes of controls. Chapter 6: Essentials of Spreadsheet Application Development 119 Part II 11_044018 ch06.qxp 2/28/07 6:37 PM Page 119 TABLE 6-1 ACTIVEX CONTROLS VERSUS FORM CONTROLS ActiveX Controls Form Controls Excel versions 97, 2000, 2002, 2003, 2007 5, 95, 97, 2000, 2002, 2003, 2007 Controls available CheckBox, TextBox, Label, GroupBox, Button, CommandButton, CheckBox, OptionButton, ListBox, OptionButton, ListBox, DropDown (ComboBox), ScrollBar, ComboBox, ToggleButton, Spinner SpinButton, ScrollBar, Label, Image (and others can be added) Macro code storage In the code module for the In any standard VBA module Sheet Macro name Corresponds to the control Any name you specify name (for example, CommandButton1_Click) Correspond to . . . UserForm controls Pre–Excel 97 Dialog Sheet controls Customization Extensive, using the Minimal Properties box Respond to events Yes Click or Change events only Executing the development effort After you identify user needs, determine the approach that you’ll take to meet those needs, and decide on the components that you’ll use for the user interface, it’s time to get down to the nitty-gritty and start creating the application. This step, of course, comprises a great deal of the total time that you spend on a particular project. How you go about developing the application depends on your own personal style and the nature of the application. Except for simple fill-in-the-blanks template workbooks, your application will probably use macros. Developing the macros is the tough part. It’s easy to create macros in Excel, but it’s difficult to create good macros. Concerning Yourself with the End User In this section, I discuss the important development issues that surface as your application becomes more and more workable and as the time to package and distribute your work grows nearer. Part II: Excel Application Development120 11_044018 ch06.qxp 2/28/07 6:37 PM Page 120 Testing the application How many times have you used a commercial software application, only to have it bomb out on you at a crucial moment? Most likely, the problem was caused by insufficient testing that didn’t catch all the bugs. All nontrivial software has bugs, but in the best software, the bugs are simply more obscure. As you’ll see, you sometimes must work around the bugs in Excel to get your application to perform properly. After you create your application, you need to test it. This is one of the most crucial steps; it’s not uncommon to spend as much time testing and debugging an application as you did creating the application in the first place. Actually, you should be doing a great deal of testing during the development phase. After all, whether you’re writing a VBA routine or creating formulas in a worksheet, you want to make sure that the application is working the way it’s supposed to work. Like standard compiled applications, spreadsheet applications that you develop are prone to bugs. A bug can be defined as (1) something that does happen but shouldn’t happen while a program (or application) is running, or (2) something that doesn’t happen when it should happen. Both species of bugs are equally nasty, and you should plan on devoting a good portion of your development time to testing the application under all reasonable con- ditions and fixing any problems that you find. In some cases, unfortunately, the problems aren’t entirely your fault. Excel, too, has its problems (see the “Bugs? In Excel?” sidebar). Chapter 6: Essentials of Spreadsheet Application Development 121 Part II You might think that a product like Excel, which is used by millions of people throughout the world, would be relatively free of bugs. Think again. Excel is such a complex piece of software that it is only natural to expect some problems with it. And Excel does have some problems. Getting a product like Excel out the door is not easy, even for a company like Microsoft with seemingly unlimited resources. Releasing a software product involves compromises and trade-offs. It’s commonly known that most major software vendors release their products with full knowledge that they contain bugs. Most of the bugs are considered insignificant enough to ignore. Software companies could postpone their releases by a few months and fix many of them, but software, like everything else, is ruled by economics. The benefits of delaying a product’s release often do not exceed the costs involved. Although Excel definitely has its share of bugs, my guess is that the majority of Excel users never encounter one. In this book, I point out the problems with Excel that I know about. You’ll surely discover some more on your own. Some problems occur only with a particular version of Excel — and under a specific configuration involving hardware and/or software. These are the worst bugs of all because they aren’t easily reproducible. So what’s a developer to do? It’s called a workaround. If something that you try to do doesn’t work — and all indications say that it should work — it’s time to move on to Plan B. Frustrating? Sure. A waste of your time? Absolutely. It’s all part of being a developer. Bugs? In Excel? 11_044018 ch06.qxp 2/28/07 6:37 PM Page 121 I probably don’t need to tell you to thoroughly test any spreadsheet application that you develop for others. And depending on its eventual audience, you might want to make your application bulletproof. In other words, try to anticipate all the errors and screw-ups that could possibly occur, making concerted efforts to avoid them — or, at least, to handle them gracefully. This not only helps the end user but also makes it easier on you and protects your reputation. Also consider using beta testing; your end users are likely candidates because they are the ones who will be using your product. See the upcoming sidebar “What about Beta Testing?” Although you cannot conceivably test for all possibilities, your macros should be able to handle common types of errors. For example, what if the user enters a text string instead of a numeric value? What if the user tries to run your macro when a workbook isn’t open? What if he or she cancels a dialog box without making any selections? What happens if the user presses Ctrl+F6 and jumps to the next window? When you gain experience, issues like these become very familiar, and you account for them without even thinking. Making the application bulletproof If you think about it, it’s fairly easy to destroy a spreadsheet. Erasing one critical formula or value can cause errors throughout the entire worksheet — and perhaps even other dependent worksheets. Even worse, if the damaged workbook is saved, it replaces the good copy on disk. Unless a backup procedure is in place, the user of your application could be in trouble, and you’ll probably be blamed for it. Part II: Excel Application Development122 Software manufacturers typically have a rigorous testing cycle for new products. After extensive internal testing, the pre-release product is usually sent to a group of interested users for beta testing. This phase often uncovers additional problems that are usually corrected before the product’s final release. If you’re developing an Excel application that more than a few people will use, you might want to consider a beta test. This enables your application to be used in its intended setting on different hardware (usually) and by the intended users. The beta period should begin after you’ve completed all your own testing and you feel that the application is ready to distribute. You’ll need to identify a group of users to help you. The process works best if you distribute everything that will ultimately be included in your application: user documentation, the installation program, help, and so on. You can evaluate the beta test in a number of ways, including face-to-face discussions, questionnaires, and phone calls. You almost always become aware of problems that you need to correct or improvements that you need to make before you undertake a widespread distribution of the application. Of course, a beta testing phase takes additional time, and not all projects can afford that luxury. What about Beta Testing? 11_044018 ch06.qxp 2/28/07 6:37 PM Page 122 Obviously, it’s easy to see why you need to add some protection when users — especially novices — will be using your worksheets. Excel provides several techniques for protecting worksheets and parts of worksheets: • Lock specific cells: You can lock specific cells (by using the Protection tab in the Format Cells dialog box) so that they cannot be changed. This takes effect only when the docu- ment is protected with the Review ➪ Changes ➪ Protect Sheet command. The Protect Sheet dialog box has options that allow you to specify which actions can be performed on a protected sheet. See Figure 6-5. Figure 6-5: Using the Protect Sheet dialog box to specify what users can and cannot do. • Hide the formulas in specific cells: You can hide the formulas in specific cells (by using the Protection tab in the Format Cells dialog box) so that others can’t see them. Again, this takes effect only when the document is protected by choosing the Review ➪ Changes ➪ Protect Sheet command. • Protect an entire workbook: You can protect an entire workbook — the structure of the workbook, the window position and size, or both. Use the Review ➪ Changes ➪ Protect Workbook command for this purpose. • Lock objects on the worksheet: Use the Properties tab in the Size and Properties dialog box to lock objects (such as shapes) and prevent them from being moved or changed. To access the Size and Properties dialog box, select the object and then click the dialog box launcher in the Drawing Tools ➪ Format ➪ Size group. Locking objects takes effect only when the document is protected via the Review ➪ Changes ➪ Protect Sheet com- mand. By default, all objects are locked. • Hide rows, columns, sheets, and documents: You can hide rows, columns, sheets, and entire workbooks. This helps prevent the worksheet from looking cluttered and also provides some protection against prying eyes. Chapter 6: Essentials of Spreadsheet Application Development 123 Part II 11_044018 ch06.qxp 2/28/07 6:37 PM Page 123 • Designate an Excel workbook as read-only recommended: You can designate an Excel work- book as read-only recommended (and use a password) to ensure that the file cannot be overwritten with any changes. You do this in the General Options dialog box. Display this dialog box by choosing File ➪ Save As. In the Save As dialog box, click the Tools button and choose General Options. • Assign a password: You can assign a password to prevent unauthorized users from open- ing your file. You do this in the General Options dialog box. Display this dialog box by choosing File ➪ Save As. In the Save As dialog box, click the Tools button and choose General Options. • Use a password-protected add-in: You can use a password-protected add-in, which doesn’t allow the user to change anything on its worksheets. Making the application aesthetically appealing and intuitive If you’ve used many different software packages, you’ve undoubtedly seen examples of poorly designed user interfaces, difficult-to-use programs, and just plain ugly screens. If you’re developing spreadsheets for other people, you should pay particular attention to how the application looks. How a computer program looks can make all the difference in the world to users, and the same is true with the applications that you develop with Excel. Beauty, however, is in the eye of the beholder. If your skills lean more in the analytical direction, consider enlisting the assistance of someone with a more aesthetic sensibility to provide help with design. The good news is that Excel 2007 makes it relatively easy to create better-looking spread- sheets. If you stick with the pre-designed cell styles, your work stands a good chance of looking good. And, with the click of a mouse, you can apply a new theme that completely transforms the look of the workbook — and still looks good. Unfortunately, Excel 2007 adds nothing new in the area of UserForm design, so you’re on your own in that area. End users appreciate a good-looking user interface, and your applications will have a much more polished and professional look if you devote some additional time to design and aes- thetic considerations. An application that looks good demonstrates that its developer cared Part II: Excel Application Development124 As far as I know, Microsoft has never advertised Excel as a secure program. And for good reason: It’s actually quite easy to circumvent Excel’s password system. Several commercial programs are available that can break passwords. Excel 2002 and later versions seem to have stronger security than previous versions, but they can still be cracked by a determined user. Bottom line? Don’t think of password protection as foolproof. Sure, it will be effective for the casual user. But if someone really wants to break your password, he can probably do so. How Secure Are Excel’s Passwords? 11_044018 ch06.qxp 2/28/07 6:37 PM Page 124 enough about the product to invest some extra time and effort. Take the following sugges- tions into account: • Strive for consistency: When designing dialog boxes, for example, try to emulate Excel’s dialog box look and feel whenever possible. Be consistent with formatting, fonts, text size, and colors. • Keep it simple: A common mistake that developers make is trying to cram too much information into a single screen or dialog box. A good rule is to present only one or two chunks of information at a time. • Break down input screens: If you use an input screen to solicit information from the user, consider breaking it up into several, less crowded screens. If you use a complex dialog box, you might want to break it up by using a MultiPage control, which lets you create a familiar tabbed dialog box. • Don’t overdo color: Use color sparingly. It’s very easy to overdo it and make the screen look gaudy. • Monitor typography and graphics: Pay attention to numeric formats and use consistent typefaces, font sizes, and borders. Evaluating aesthetic qualities is very subjective. When in doubt, strive for simplicity and clarity. NOTE Previous versions of Excel used a pallet of 56 colors. That restriction has been removed, and Excel 2007 supports more than 16 million colors. Creating a user Help system With regard to user documentation, you basically have two options: paper-based documen- tation or electronic documentation. Providing electronic help is standard fare in Windows applications. Fortunately, your Excel applications can also provide help — even context- sensitive help. Developing help text takes quite a bit of additional effort, but for a large project, it may be worth it. Figure 6-6 shows an example of a custom Help system in com- piled HTML format. Another point to consider is support for your application. In other words, who gets the phone call if the user encounters a problem? If you aren’t prepared to handle routine ques- tions, you need to identify someone who is. In some cases, you want to arrange it so that only highly technical or bug-related issues escalate to the developer. CROSS-REFERENCE In Chapter 24, I discuss several alternatives for providing help for your applications. Chapter 6: Essentials of Spreadsheet Application Development 125 Part II 11_044018 ch06.qxp 2/28/07 6:37 PM Page 125 Figure 6-6: An example of custom help file for an Excel add-in. Documenting the development effort Putting a spreadsheet application together is one thing. Making it understandable for other people is another. As with traditional programming, it’s important that you thoroughly doc- ument your work. Such documentation helps you if you need to go back to it (and you will), and it helps anyone else whom you might pass it on to. TIP You might want to consider a couple of things when you document your project. For example, if you were hired to develop an Excel application, you might not want to share all your hard-earned secrets by thoroughly documenting everything. If this is the case, you should maintain two versions: one thoroughly documented (for your own reference) and the other partially documented (for other users). How do you document a workbook application? You can either store the information in a worksheet or use another file. You can even use a paper document if you prefer. Perhaps the easiest way is to use a separate worksheet to store your comments and key information for the project. For VBA code, use comments liberally. (VBA text preceded with an apostro- phe is ignored because that text is designated a comment.) Although an elegant piece of VBA code can seem perfectly obvious to you today, when you come back to it in a few months, your reasoning might be completely obscured unless you use the VBA comment feature. Part II: Excel Application Development126 11_044018 ch06.qxp 2/28/07 6:37 PM Page 126 Distributing the application to the user You’ve completed your project, and you’re ready to release it to the end users. How do you go about doing this? You can choose from many ways to distribute your application, and the method that you choose depends on many factors. You could just hand over a CD-ROM, scribble a few instructions, and be on your way. Or, you may want to install the application yourself — but this is not always feasible. Another option is to develop an official setup program that performs the task automatically. You can write such a program in a traditional programming language, purchase a generic setup pro- gram, or write your own in VBA. Excel 2000 and later incorporates technology to enable developers to digitally sign their applications. This process is designed to help end users identify the author of an applica- tion, to ensure that the project has not been altered, and to help prevent the spread of macro viruses or other potentially destructive code. To digitally sign a project, you first apply for a digital certificate from a formal certificate authority (or, you can self-sign your project by creating your own digital certificate). Refer to the Help system or the Microsoft Web site for additional information. Updating the application when necessary After you distribute your application, you’re finished with it, right? You can sit back, enjoy yourself, and try to forget about the problems that you encountered (and solved) during the course of developing your application. In rare cases, yes, you may be finished. More often, however, the users of your application will not be completely satisfied. Sure, your Chapter 6: Essentials of Spreadsheet Application Development 127 Part II When you distribute your application, you need to be sure that each end user has a licensed copy of the appropriate version of Excel. It’s illegal to distribute a copy of Excel along with your application. Why, you might ask, doesn’t Microsoft provide a runtime version of Excel? A runtime version is an executable program that can load files but not create them. With a runtime version, the end user wouldn’t need a copy of Excel to run your application. (This is common with database programs.) I’ve never seen a clear or convincing reason why Microsoft does not have a runtime version of Excel, and no other spreadsheet manufacturer offers a runtime version of its product, either. The most likely reason is that spreadsheet vendors fear that doing so would reduce sales of the software. Or, it could be that developing a runtime version would require a tremendous amount of programming that would just never pay off. On a related note . . . Microsoft does offer an Excel file viewer. This product lets you view Excel files if you don’t own a copy of Excel. Macros, however, will not execute. You can get a copy of this free file viewer from the Microsoft Web site (http:// office.microsoft.com/downloads). Why Is There No Runtime Version of Excel? 11_044018 ch06.qxp 2/28/07 6:37 PM Page 127 application adheres to all the original specifications, but things change. Seeing an applica- tion working frequently causes the user to think of other things that the application could be doing. I’m talking updates. When you need to update or revise your application, you’ll appreciate that you designed it well in the first place and that you fully documented your efforts. If not, well . . . we learn from our experiences. Other Development Issues You need to keep several other issues in mind when developing an application — especially if you don’t know exactly who will be using the application. If you’re developing an applica- tion that will have widespread use (a shareware application, for example), you have no way of knowing how the application will be used, what type of system it will run on, or what other software will be running concurrently. The user’s installed version of Excel With every new release of Excel, the issue of compatibility rears its head. As I write this, Excel 2007 has just been released — yet many large corporations are still using Excel 2000 and some use even earlier versions. Unfortunately, there is no guarantee that an application developed for, say, Excel 2000 will work perfectly with later versions of Excel. If you need your application to work with a variety of Excel versions, the best approach is to work with the lowest version — and then test it thoroughly with all other versions. Things get even more complicated when you consider Excel’s sub-versions. Microsoft dis- tributes service releases (SRs) to correct problems. For example, users might have the original Excel 2000, Excel 2000 with SR-1, or Excel 2000 with SR-2. And it gets even more complicated with Excel 2003. Excel 2007 has a quite a few known problems, and it’s likely that at least some of them will be corrected in a future service release. CROSS-REFERENCE I discuss compatibility issues in Chapter 26. Language issues Consider yourself very fortunate if all your end users have the English language version of Excel. Non-English versions of Excel aren’t always 100-percent compatible, so that means additional testing on your part. In addition, keep in mind that two users can both be using the English language version of Excel yet use different Windows regional settings. In some cases, you may need to be aware of potential problems. Part II: Excel Application Development128 11_044018 ch06.qxp 2/28/07 6:37 PM Page 128 CROSS-REFERENCE I briefly discuss language issues in Chapter 26. System speed You’re probably a fairly advanced computer user and tend to keep your hardware reason- ably up to date. In other words, you have a fairly powerful system that is probably better than the average user’s system. In some cases, you’ll know exactly what hardware the end users of your applications are using. If so, it’s vitally important that you test your applica- tion on that system. A procedure that executes almost instantaneously on your system may take several seconds on another system. In the world of computers, several seconds may be unacceptable. TIP When you gain more experience with VBA, you’ll discover that there are ways to get the job done, and there are ways to get the job done fast. It’s a good idea to get into the habit of coding for speed. Other chapters in this book will certainly help you out in this area. Video modes As you probably know, users’ video displays vary widely. A video resolution of 1024 x 768 is most common, but many systems are set up with an 800 x 600 display. Higher resolution displays and even dual displays are becoming increasingly common. Just because you have a super-high-resolution monitor, you can’t assume that everyone else does. Video resolution can be a problem if your application relies on specific information being displayed on a single screen. For example, if you develop an input screen that uses 1280 x 1024 mode, users with a 1024 x 768 display will not be able to see the whole input screen without scrolling or zooming. Also, it’s important to realize that a restored (that is, not maximized or minimized) workbook is displayed at its previous window size and position. In the extreme case, it’s possible that a window saved by using a high-resolution display may be completely off the screen when opened on a system running in a lower resolution. Unfortunately, there’s no way to automatically scale things so that they look the same regardless of the display resolution. In some cases, you can zoom the worksheet (using the Zoom control in the status bar), but doing so reliably may be difficult. Unless you’re certain of the video resolution that the users of your application will use, you should probably design your application so it works with the lowest common denominator — 800 x 600 mode. As you will learn later in the book (see Chapter 10), it’s possible to determine the user’s video resolution by using Windows API calls from VBA. In some cases, you may want to programmatically adjust things depending on the user’s video resolution. Chapter 6: Essentials of Spreadsheet Application Development 129 Part II 11_044018 ch06.qxp 2/28/07 6:37 PM Page 129 11_044018 ch06.qxp 2/28/07 6:37 PM Page 130 Understanding Visual Basic for Applications Chapter 7 Introducing Visual Basic for Applications Chapter 8 VBA Programming Fundamentals Chapter 9 Working with VBA Sub Procedures Chapter 10 Creating Function Procedures Chapter 11 VBA Programming Examples and Techniques IIIPart 12_044018 pt03.qxp 2/28/07 6:37 PM Page 131 12_044018 pt03.qxp 2/28/07 6:37 PM Page 132 Chapter Introducing Visual Basic for Applications In This Chapter This chapter introduces you to Visual Basic for Applications (VBA) and the objects that make up Excel. ◆ An introduction to VBA — the programming language built into Excel ◆ How VBA differs from traditional spreadsheet macro languages and how it differs from the Visual Basic language ◆ How to use the Visual Basic Editor (VBE) ◆ How to work in the Code windows in the VBE and customize the VBE environment ◆ How to use Excel’s macro recorder ◆ An overview of objects, collections, properties, and methods ◆ A case study of the Comment object ◆ Specific information and examples of working with Range objects ◆ How to access a lot of information about Excel objects, properties, and methods Programming Excel essentially boils down to manipulating objects, which you do by writing instructions in a language that Excel can understand: VBA. 7 133 13_044018 ch07.qxp 2/28/07 6:37 PM Page 133 Some BASIC Background Many hard-core programmers scoff at the idea of programming in BASIC. The name itself (an acronym for Beginner’s All-purpose Symbolic Instruction Code) suggests that it’s not a professional language. In fact, BASIC was first developed in the early 1960s as a way to teach programming techniques to college students. BASIC caught on quickly and is avail- able in hundreds of dialects for many types of computers. BASIC has evolved and improved over the years. For example, in many early implementa- tions, BASIC was an interpreted language. Each line was interpreted before it was executed, causing slow performance. Most modern dialects of BASIC allow the code to be compiled — converted to machine code — which results in faster and more efficient execution BASIC gained quite a bit of respectability in 1991 when Microsoft released Visual Basic for Windows. This product made it easy for the masses to develop standalone applications for Windows. Visual Basic has very little in common with early versions of BASIC, but Visual Basic is the foundation on which VBA was built. About VBA Excel 5 was the first application on the market to feature Visual Basic for Applications (VBA). VBA is best thought of as Microsoft’s common application scripting language, and it’s included with most Office 2007 applications and even in applications from other ven- dors. Therefore, if you master VBA by using Excel, you’ll be able to jump right in and write macros for other Microsoft (and some non-Microsoft) products. Even better, you’ll be able to create complete solutions that use features across various applications. Object models The secret to using VBA with other applications lies in understanding the object model for each application. VBA, after all, simply manipulates objects, and each product (Excel, Word, Access, PowerPoint, and so forth) has its own unique object model. You can program an application by using the objects that the application exposes. Excel’s object model, for example, exposes several very powerful data analysis objects, such as worksheets, charts, pivot tables, and numerous mathematical, financial, engineering, and general business functions. With VBA, you can work with these objects and develop auto- mated procedures. While you work with VBA in Excel, you gradually build an understanding of the object model. Warning: It will be very confusing at first. Eventually, however, the pieces come together — and all of a sudden, you realize that you’ve mastered it! Part III: Understanding Visual Basic for Applications134 13_044018 ch07.qxp 2/28/07 6:37 PM Page 134 VBA versus XLM Before version 5, Excel used a powerful (but very cryptic) macro language called XLM. Later versions of Excel (including Excel 2007) still execute XLM macros, but the capability to record macros in XLM was removed beginning with Excel 97. As a developer, you should be aware of XLM (in case you ever encounter macros written in that system), but you should use VBA for your development work. NOTE Don’t confuse the XLM macro language with eXtensible Markup Language (XML). Although these terms share the same letters, they have nothing in common. XML is a storage format for structured data. The Office 2007 applications use XML as their default file format. The Basics of VBA Before I get into the meat of things, I suggest that you read through the material in this section to get a broad overview of where I’m heading. These are the topics that I cover in the remainder of this chapter. Following is a quick-and-dirty summary of what VBA is all about: • Code: You perform actions in VBA by executing VBA code. You write (or record) VBA code, which is stored in a VBA module. • Module: VBA modules are stored in an Excel workbook, but you view or edit a module by using the Visual Basic Editor (VBE). A VBA module consists of procedures. Chapter 7: Introducing Visual Basic for Applications 135 Part III For the past few years, I’ve heard rumors that Microsoft is going to remove VBA from the Office applications and replace it with .NET. My understanding is that these rumors are completely unfounded. Sure, Microsoft has developed another way to automate Office applications, but VBA will be around for quite a while — at least in Excel for Windows. As I write this, Microsoft announced that VBA would no longer be part of Excel for Macintosh. Why will VBA survive? Because literally millions of VBA-based solutions are in use and VBA is much easier to learn and use than the alternative. Is VBA Becoming Obsolete? 13_044018 ch07.qxp 2/28/07 6:37 PM Page 135 • Procedures: A procedure is basically a unit of computer code that performs some action. VBA supports two types of procedures: Sub procedures and Function procedures. • Sub:A Sub procedure consists of a series of statements and can be executed in a number of ways. Here’s an example of a simple Sub procedure called Test: This procedure calculates a simple sum and then displays the result in a message box. Sub Test() Sum = 1 + 1 MsgBox “The answer is “ & Sum End Sub • Function: A VBA module can also have Function procedures. A Function pro- cedure returns a single value (or possibly an array). A Function can be called from another VBA procedure or used in a worksheet formula. Here’s an example of a Function named AddTwo: Function AddTwo(arg1, arg2) AddTwo = arg1 + arg2 End Function • Objects: VBA manipulates objects contained in its host application. (In this case, Excel is the host application.) Excel provides you with more than 100 classes of objects to manipulate. Examples of objects include a workbook, a worksheet, a range on a worksheet, a chart, and a drawn rectangle. Many more objects are at your disposal, and you can manipulate them by using VBA code. Object classes are arranged in a hierarchy. Objects can act as containers for other objects. For example, Excel is an object called Application, and it contains other objects, such as Workbook and CommandBar objects. The Workbook object contains other objects, such as Worksheet objects and Chart objects. A Worksheet object contains objects such as Range objects, PivotTable objects, and so on. The arrangement of these objects is referred to as Excel’s object model. • Collections: Like objects form a collection. For example, the Worksheets collection consists of all the worksheets in a particular workbook. The CommandBars collection consists of all CommandBar objects. Collections are objects in themselves. • Object hierarchy: When you refer to a contained or member object, you specify its position in the object hierarchy by using a period (also known as a dot) as a separator between the container and the member. For example, you can refer to a workbook named Book1.xlsx as Application.Workbooks(“Book1.xlsx”) Part III: Understanding Visual Basic for Applications136 13_044018 ch07.qxp 2/28/07 6:37 PM Page 136 This refers to the Book1.xlsx workbook in the Workbooks collection. The Workbooks collection is contained in the Excel Application object. Extending this to another level, you can refer to Sheet1 in Book1 as Application.Workbooks(“Book1.xlsx”).Worksheets(“Sheet1”) You can take it to still another level and refer to a specific cell as follows: Application.Workbooks(“Book1.xlsx”).Worksheets(“Sheet1”).Range(“A1”) • Active objects: If you omit a specific reference to an object, Excel uses the active objects. If Book1 is the active workbook, the preceding reference can be simplified as Worksheets(“Sheet1”).Range(“A1”) If you know that Sheet1 is the active sheet, you can simplify the reference even more: Range(“A1”) • Objects properties: Objects have properties. A property can be thought of as a setting for an object. For example, a range object has properties such as Value and Name. A chart object has properties such as HasTitle and Type. You can use VBA to determine object properties and also to change them. You refer to properties by combining the object with the property, separated by a period. For example, you can refer to the value in cell A1 on Sheet1 as Worksheets(“Sheet1”).Range(“A1”).Value • VBA variables: You can assign values to VBA variables. Think of a variable as a name that you can use to store a particular value. To assign the value in cell A1 on Sheet1 to a variable called Interest, use the fol- lowing VBA statement: Interest = Worksheets(“Sheet1”).Range(“A1”).Value • Object methods: Objects have methods. A method is an action that is performed with the object. For example, one of the methods for a Range object is ClearContents. This method clears the contents of the range. You specify methods by combining the object with the method, separated by a period. For example, to clear the contents of cell A1 on the active worksheet, use this: Range(“A1”).ClearContents • Standard programming constructs: VBA also includes all the constructs of modern pro- gramming languages, including arrays, looping, and so on. • Events: Some objects recognize specific events, and you can write VBA code that is exe- cuted when the event occurs. For example, opening a workbook triggers a Workbook_ Open event. Changing a cell in a worksheet triggers a Worksheet_Change event. Believe it or not, the preceding section pretty much describes VBA. Now it’s just a matter of learning the details. Chapter 7: Introducing Visual Basic for Applications 137 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 137 Part III: Understanding Visual Basic for Applications138 If you like analogies, here’s one for you. It might help you understand the relationships between objects, properties, and methods in VBA. In this analogy, I compare Excel with a fast-food restaurant chain. The basic unit of Excel is a Workbook object. In a fast-food chain, the basic unit is an individual restaurant. With Excel, you can add workbooks and close workbooks, and the set of all the open workbooks is known as Workbooks (a collection of Workbook objects). Similarly, the management of a fast-food chain can add restaurants and close restaurants — and all the restaurants in the chain can be viewed as the Restaurants collection — a collection of Restaurant objects. An Excel workbook is an object, but it also contains other objects, such as worksheets, charts, VBA modules, and so on. Furthermore, each object in a workbook can contain its own objects. For example, a Worksheet object can contain Range objects, PivotTable objects, Shape objects, and so on. Continuing with the analogy, a fast-food restaurant (like a workbook) contains objects such as the Kitchen, DiningArea, and Tables (a collection). Furthermore, management can add or remove objects from the Restaurant object. For example, management can add more tables to the Tables collection. Each of these objects can contain other objects. For example, the Kitchen object has a Stove object, VentilationFan object, Chef object, Sink object, and so on. So far, so good. This analogy seems to work. Let’s see whether I can take it further. Excel objects have properties. For example, a Range object has properties such as Value and Name, and a Shape object has properties such as Width, and Height. Not surprisingly, objects in a fast-food restaurant also have properties. The Stove object, for example, has properties such as Temperature and NumberofBurners. The VentilationFan has its own set of properties (TurnedOn, RPM, and so forth). Besides properties, Excel’s objects also have methods, which perform operations on objects. For example, the ClearContents method erases the contents of a Range object. An object in a fast-food restaurant also has methods. You can easily envision a ChangeThermostat method for a Stove object, or a SwitchOn method for a VentilationFan object. With Excel, methods sometimes change an object’s properties. The ClearContents method for a Range changes the Range Value property. Similarly, the ChangeThermostat method on a Stove object affects its Temperature property. With VBA, you can write procedures to manipulate Excel’s objects. In a fast-food restaurant, the management can give orders to manipulate the objects in the restaurants. (“Turn on the stove and switch the ventilation fan to high.”) Now is it clear? An Analogy 13_044018 ch07.qxp 2/28/07 6:37 PM Page 138 Introducing the Visual Basic Editor All your VBA work is done in the Visual Basic Editor (VBE). The VBE is a separate appli- cation that works seamlessly with Excel. By seamlessly, I mean that Excel takes care of the details of opening the VBE when you need it. You can’t run VBE separately; Excel must be running in order for the VBE to run. NOTE VBA modules are stored in workbook files. However, the VBA modules aren’t visible unless you activate the VBE. Displaying Excel’s Developer tab The Excel 2007 Ribbon does not display the Developer tab by default. If you’re going to be working with VBA, it’s essential that you turn on the Developer tab: 1. Choose Office ➪ Excel Options. 2. In the Excel Options dialog box, click the Popular tab. 3. Place a checkmark next to Show Developer Tab in the Ribbon. After you perform these steps, Excel displays a new tab, as shown in Figure 7-1. Figure 7-1: By default, the Developer tab is not displayed. Activating the VBE When you’re working in Excel, you can switch to the VBE by using either of the following techniques: • Press Alt+F11. • Choose Developer ➪ Code ➪ Visual Basic. In addition, you can access two special modules as follows. (These special VBA modules are used for event handler procedures, which I describe in Chapter 19.) Chapter 7: Introducing Visual Basic for Applications 139 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 139 • Right-click a sheet tab and choose View Code (this takes you to the code module for the sheet). • Right-click a workbook’s title bar and choose View Code (this takes you to the code module for the workbook). If the workbook window is maximized in Excel, the title bar is not visible. Figure 7-2 shows the VBE. Chances are that your VBE window won’t look exactly like the window shown in the figure. This window is highly customizable — you can hide windows, change their sizes, dock them, rearrange them, and so on. Figure 7-2: The Visual Basic Editor window. The VBE windows The VBE has a number of parts. I briefly describe some of the key components in the sec- tions that follow. Part III: Understanding Visual Basic for Applications140 Excel 2007 has dozens of significant new features, including a brand-spanking-new user interface. If you’re expecting new things in the VBE, you’re out of luck. The Excel 2007 VBE is exactly like the Excel 2003 VBE. What’s New in the VBE? 13_044018 ch07.qxp 2/28/07 6:37 PM Page 140 VBE MENU BAR The VBE menu bar works like every other menu bar that you’ve encountered. It contains commands that you use to work with the various components in the VBE. Also, you’ll find that many of the menu commands have shortcut keys associated with them. For example, the View ➪ Immediate Window command has a shortcut key of Ctrl+G. TIP The VBE also features shortcut menus. As you’ll discover, you can right-click virtually any- thing in a VBE window to get a shortcut menu of common commands. VBE TOOLBARS The Standard toolbar, which is directly under the menu bar by default, is one of six VBE toolbars available (the menu bar is also considered a toolbar). You can customize toolbars, move them around, display other toolbars, and so forth. Choose View ➪ Toolbars ➪ Customize to work with VBE toolbars. PROJECT EXPLORER WINDOW The Project Explorer window displays a tree diagram that consists of every workbook that is currently open in Excel (including add-ins and hidden workbooks). Each workbook is known as a project. I discuss the Project Explorer window in more detail in the next section (“Working with the Project Explorer”). If the Project Explorer window is not visible, press Ctrl+R. To hide the Project Explorer win- dow, click the Close button in its title bar or right-click anywhere in the Project Explorer window and select Hide from the shortcut menu. CODE WINDOW A Code window (sometimes known as a Module window) contains VBA code. Every item in a project’s tree has an associated code window. To view a code window for an object, dou- ble-click the object in the Project Explorer window. For example, to view the code window for the Sheet1 object, double-click Sheet1 in the Project Explorer window. Unless you’ve added some VBA code, the Code window is empty. Another way to view the Code window for an object is to select the object in the Project Explorer window and then click the View Code button in the toolbar at the top of the Project Explorer window. I discuss Code windows later on in this chapter (see “Working with Code Windows”). IMMEDIATE WINDOW The Immediate window is most useful for executing VBA statements directly, testing statements, and debugging your code. This window might or might not be visible. If the Immediate window isn’t visible, press Ctrl+G. To close the Immediate window, click the Close button in its title bar (or right-click anywhere in the Immediate window and select Hide from the shortcut menu). Chapter 7: Introducing Visual Basic for Applications 141 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 141 Working with the Project Explorer When you’re working in the VBE, each Excel workbook and add-in that’s currently open is considered a project. You can think of a project as a collection of objects arranged as an expandable tree. You can expand a project by clicking the plus sign (+) at the left of the project’s name in the Project Explorer window. You contract a project by clicking the minus sign (–) to the left of a project’s name. If you try to expand a project that’s protected with a password, you are prompted to enter the password. NOTE The top of the Project Explorer window contains three icons. The third icon, named Toggle Folder, controls whether the objects in a project are displayed in a hierarchy or are shown in a single non-hierarchical list. Figure 7-3 shows a Project Explorer window with three projects listed (one add-in and two workbooks). Figure 7-3: A Project Explorer window with three projects listed. CAUTION When you activate the VBE, you cannot assume that the code module that’s displayed corresponds to the highlighted object in the Project Explorer window. To make sure that you’re working in the correct code module, always double-click the object in the Project Explorer window. If you have many workbooks and add-ins loaded, the Project Explorer window can be a bit overwhelming. Unfortunately, you can’t hide projects in the Project Explorer window. However, you probably want to keep the project outlines contracted if you’re not working on them. Part III: Understanding Visual Basic for Applications142 13_044018 ch07.qxp 2/28/07 6:37 PM Page 142 When viewing the Project Explorer in folder view, every project expands to show at least one node called Microsoft Excel Objects. This node expands to show an item for each work- sheet and chart sheet in the workbook (each sheet is considered an object) and another object called ThisWorkbook (which represents the Workbook object). If the project has any VBA modules, the project listing also shows a Modules node, and the modules are listed there. A project can also contain a node called Forms that contains UserForm objects (also known as custom dialog boxes). If your project has any class modules, it dis- plays another node called Class Modules. Similarly, if your project has any references, you see another node called References. The References node is a bit misleading because refer- ences can’t contain any VBA code. Adding a new VBA module To add a new VBA module to a project, select the project’s name in the Project Explorer window and choose Insert ➪ Module. Or you can just right-click the project’s name and choose Insert ➪ Module from the shortcut menu. When you record a macro, Excel automatically inserts a VBA module to hold the recorded code. Removing a VBA module If you need to remove a VBA module or a class module from a project, select the module’s name in the Project Explorer window and choose File ➪ Remove xxx (where xxx is the name of the module). Or you can right-click the module’s name and choose Remove xxx from the shortcut menu. You are asked whether you want to export the module before removing it. See the next section for details. You cannot remove code modules associated with the workbook (the ThisWorkbook code module) or with a sheet (for example, the Sheet1 code module). Exporting and importing objects Except for those listed under the References node, every object in a project can be saved to a separate file. Saving an individual object in a project is called exporting. And it stands to reason that you can also import objects into a project. Exporting and importing objects might be useful if you want to use a particular object (such as a VBA module or a UserForm) in a different project. To export an object, select it in the Project Explorer window and choose File ➪ Export File (or press Ctrl+E). You get a dialog box that asks for a filename. Note that the object remains in the project (only a copy of it is exported). If you export a UserForm object, any code associated with the UserForm is also exported. To import a file into a project, select the project’s name in the Project Explorer window and choose File ➪ Import File. You get a dialog box that asks for a file. You can import only a file that has been exported by choosing the File ➪ Export File command. Chapter 7: Introducing Visual Basic for Applications 143 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 143 TIP If you would like to copy a module or UserForm object to another project, it’s not really necessary to export and then import the object. Make sure that both projects are open; then simply activate the Project Explorer and drag the object from one project to the other. Working with Code Windows When you become proficient with VBA, you’ll be spending lots of time working in code win- dows. Each object in a project has an associated code window. To summarize, these objects can be • The workbook itself (ThisWorkbook in the Project Explorer window) • A worksheet or chart sheet in a workbook (for example, Sheet1 or Chart1 in the Project Explorer window) • A VBA module • A class module (a special type of module that lets you create new object classes) • A UserForm Minimizing and maximizing windows Depending on how many workbooks and add-ins are open, the VBE can have lots of code windows, and things can get a bit confusing. Code windows are much like worksheet win- dows in Excel. You can minimize them, maximize them, hide them, rearrange them, and so on. Most people find it most efficient to maximize the Code window that they’re working in. Doing so enables you to see more code and keeps you from getting distracted. To maximize a Code window, click the maximize button in its title bar or just double-click its title bar. To restore a Code window (make it nonmaximized), click the Restore button in its title bar. Sometimes, you might want to have two or more Code windows visible. For example, you might want to compare the code in two modules or perhaps copy code from one module to another. To view two or more Code windows at once, make sure the active code window isn’t maximized. Then drag and resize the windows that you want to view. Minimizing a code window gets it out of the way. You can also click the Close button in a Code window’s title bar to close the window completely. To open it again, just double-click the appropriate object in the Project Explorer window. The VBE doesn’t let you close a workbook. You must reactivate Excel and close it from there. You can, however, use the Immediate window to close a workbook or an add-in. Just activate the Immediate window, type a VBA statement like the one that follows, and press Enter: Workbooks(“myaddin.xlam”).Close Part III: Understanding Visual Basic for Applications144 13_044018 ch07.qxp 2/28/07 6:37 PM Page 144 As you’ll see, this statement executes the Close method of the Workbook object, which closes a workbook. In this case, the workbook happens to be an add-in. Storing VBA code In general, a code window can hold four types of code: • Sub procedures: A procedure is a set of instructions that performs some action. • Function procedures: A function is a set of instructions that returns a single value or an array (similar in concept to a worksheet function, such as SUM). • Property procedures: These are special procedures used in class modules. • Declarations: A declaration is information about a variable that you provide to VBA. For example, you can declare the data type for variables you plan to use. A single VBA module can store any number of Sub procedures, Function procedures, and declarations. How you organize a VBA module is completely up to you. Some people prefer to keep all their VBA code for an application in a single VBA module; others like to split up the code into several different modules. NOTE Although you have lots of flexibility regarding where to store your VBA code, there are some restrictions. Event handler procedures must be located in the Code window for the object that responds to the event. For example, if you write a procedure that executes when the workbook is opened, that procedure must be located in the Code window for the ThisWorkbook object, and the procedure must have a special name. This concept will become clearer when I discuss events (Chapter 19) and UserForms (Part IV). Entering VBA code Before you can do anything meaningful, you must have some VBA code in a Code window. This VBA code must be within a procedure. A procedure consists of VBA statements. For now, I focus on one type of Code window: a VBA module. You can add code to a VBA module in three ways: • Enter the code manually: Use your keyboard to type your code. • Use the macro-recorder feature: Use Excel’s macro-recorder feature to record your actions and convert them into VBA code. • Copy and paste: Copy the code from another module and paste it into the module that you’re working in. Chapter 7: Introducing Visual Basic for Applications 145 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 145 ENTERING CODE MANUALLY Sometimes, the most direct route is the best one. Entering code directly involves . . . well, entering the code directly. In other words, you type the code by using your keyboard. You can use the Tab key to indent the lines that logically belong together — for example, the conditional statements between an If and an End If statement. This isn’t necessary, but it makes the code easier to read, so it’s a good habit to acquire. Entering and editing text in a VBA module works just as you would expect. You can select text, copy it or cut it, and then paste it to another location. A single instruction in VBA can be as long as you need it to be. For readability’s sake, how- ever, you might want to break a lengthy instruction into two or more lines. To do so, end the line with a space followed by an underscore character; then press Enter and continue the instruction on the following line. The following code, for example, is a single VBA statement split over four lines. MsgBox “Can’t find “ & UCase(SHORTCUTMENUFILE) _ & vbCrLf & vbCrLf & “The file should be located in “ _ & ThisWorkbook.Path & vbCrLf & vbCrLf _ & “You may need to reinstall BudgetMan”, vbCritical, APPNAME Notice that I indented the last three lines of this statement. Doing so is optional, but it helps clarify the fact that these four lines are, in fact, a single statement. TIP Like Excel, the VBE has multiple levels of Undo and Redo. Therefore, if you find that you deleted an instruction that you shouldn’t have, you can click the Undo button (or press Ctrl+Z) repeatedly until the instruction comes back. After undoing, you can click the Redo button (or press Ctrl+Y) to redo changes that were previously undone. This feature can be a lifesaver, so I recommend that you play around with it until you understand how it works. Try this: Insert a VBA module into a project and then enter the following procedure into the Code window of the module: Part III: Understanding Visual Basic for Applications146 Throughout this book, I use the terms routine, procedure, and macro. Programming people typically use the word procedure to describe an automated task. In Excel, a procedure is also known as a macro. Technically, a procedure can be a Sub procedure or a Function procedure, both of which are sometimes called routines. I use all these terms pretty much interchangeably. There is, however, an important difference between Sub procedures and Function procedures. This distinction becomes apparent in Chapters 9 and 10. Pause for a Terminology Break 13_044018 ch07.qxp 2/28/07 6:37 PM Page 146 Sub SayHello() Msg = “Is your name “ & Application.UserName & “?” Ans = MsgBox(Msg, vbYesNo) If Ans = vbNo Then MsgBox “Oh, never mind.” Else MsgBox “I must be clairvoyant!” End If End Sub Figure 7-4 shows how this looks in a VBA module. Figure 7-4: Your first VBA procedure. NOTE While you enter the code, notice that the VBE makes some adjustments to the text that you enter. For example, if you omit the space before or after an equal sign (=), VBE inserts the space for you. Also, the color of some of the text is changed. This is all per- fectly normal, and you’ll appreciate it later. To execute the SayHello procedure, make sure that the cursor is located anywhere within the text that you typed. Then do any of the following: • Press F5. • Choose Run ➪ Run Sub/UserForm. • Click the Run Sub/UserForm button on the Standard toolbar. Chapter 7: Introducing Visual Basic for Applications 147 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 147 If you entered the code correctly, the procedure executes, and you can respond to a simple dialog box (see Figure 7-5) that displays the username, as listed in Excel’s Options dialog box. Notice that Excel is activated when the macro executes. At this point, it’s not impor- tant that you understand how the code works; that becomes clear later in this chapter and in subsequent chapters. Figure 7-5: The result of running the procedure in Figure 7-4. NOTE Most of the time, you’ll be executing your macros from Excel. However, it’s often more efficient to test your macro by running it directly from the VBE. What you did was write a VBA Sub procedure (also known as a macro). When you issued the command to execute the macro, the VBE quickly compiled the code and executed it. In other words, each instruction was evaluated, and Excel simply did what it was told to do. You can execute this macro any number of times, although it tends to lose its appeal after a while. For the record, this simple procedure uses the following concepts (all of which I cover later in the book): • Declaring a procedure (the first line) • Assigning a value to variables (Msg and Ans) • Concatenating strings (using the & operator) • Using a built-in VBA function (MsgBox) • Using built-in VBA constants (vbYesNo and vbNo) • Using an If-Then-Else construct • Ending a procedure (the last line) Not bad for a first effort, eh? USING THE MACRO RECORDER Another way to get code into a VBA module is to record your actions by using the Excel macro recorder. No matter how hard you try, there is absolutely no way to record the SayHello procedure shown previously. As you’ll see, recording macros is very useful, but it has some limitations. Part III: Understanding Visual Basic for Applications148 13_044018 ch07.qxp 2/28/07 6:37 PM Page 148 In fact, when you record a macro, you almost always need to make some adjustments or enter some code manually. This next example shows how to record a macro that simply changes the page setup to land- scape orientation. If you want to try this, start with a blank workbook and follow these steps: 1. Activate a worksheet in the workbook (any worksheet will do). 2. Choose Developer ➪ Code ➪ Record Macro. Excel displays its Record Macro dialog box. 3. Click OK to accept the default setting for the macro. Excel automatically inserts a new VBA module into the workbook’s VBA project. From this point on, Excel converts your actions into VBA code. Notice that Excel’s status bar displays a blue square. You can click that control to stop recording. 4. Choose Page Layout ➪ Page Setup ➪ Orientation ➪ Landscape. 5. Select Developer ➪ Code ➪ Stop Recording or click the blue square in the status bar. Excel stops recording your actions. To take a look at the macro, activate the VBE (pressing Alt+F11 is the easiest way) and locate the project in the Project Explorer window. Double-click the Modules node to expand it. Then double-click the Module1 item to display the code window. (If the project already had a Module1, the new macro will be in Module2.) The code generated by this single command follows. Remember that code lines preceded by an apostrophe are com- ments and are not executed. Sub Macro1() ‘ ‘ Macro1 Macro ‘ ‘ With ActiveSheet.PageSetup .PrintTitleRows = “” .PrintTitleColumns = “” End With ActiveSheet.PageSetup.PrintArea = “” With ActiveSheet.PageSetup .LeftHeader = “” .CenterHeader = “” .RightHeader = “” .LeftFooter = “” .CenterFooter = “” .RightFooter = “” .LeftMargin = Application.InchesToPoints(0.7) .RightMargin = Application.InchesToPoints(0.7) .TopMargin = Application.InchesToPoints(0.75) Chapter 7: Introducing Visual Basic for Applications 149 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 149 .BottomMargin = Application.InchesToPoints(0.75) .HeaderMargin = Application.InchesToPoints(0.3) .FooterMargin = Application.InchesToPoints(0.3) .PrintHeadings = False .PrintGridlines = False .PrintComments = xlPrintNoComments .PrintQuality = 600 .CenterHorizontally = False .CenterVertically = False .Orientation = xlLandscape .Draft = False .PaperSize = xlPaperLetter .FirstPageNumber = xlAutomatic .Order = xlDownThenOver .BlackAndWhite = False .Zoom = 100 .PrintErrors = xlPrintErrorsDisplayed .OddAndEvenPagesHeaderFooter = False .DifferentFirstPageHeaderFooter = False .ScaleWithDocHeaderFooter = True .AlignMarginsHeaderFooter = True .EvenPage.LeftHeader.Text = “” .EvenPage.CenterHeader.Text = “” .EvenPage.RightHeader.Text = “” .EvenPage.LeftFooter.Text = “” .EvenPage.CenterFooter.Text = “” .EvenPage.RightFooter.Text = “” .FirstPage.LeftHeader.Text = “” .FirstPage.CenterHeader.Text = “” .FirstPage.RightHeader.Text = “” .FirstPage.LeftFooter.Text = “” .FirstPage.CenterFooter.Text = “” .FirstPage.RightFooter.Text = “” End With End Sub You might be surprised by the amount of code generated by this single command. (I know I was the first time I tried something like this.) Although you changed only one simple set- ting in the Page Setup tab, Excel generates code that affects dozens of print settings. This brings up an important concept. The Excel macro recorder is not the most efficient way to generate VBA code. More often than not, the code produced when you record a macro is overkill. Consider the recorded macro that switches to landscape mode. Practically every statement in that macro is extraneous. You can simplify this macro considerably by deleting the extraneous code. This makes the macro easier to read, and the macro also runs faster because it doesn’t do things that are unnecessary. In fact, this macro can be simplified to the following: Part III: Understanding Visual Basic for Applications150 13_044018 ch07.qxp 2/28/07 6:37 PM Page 150 Sub Macro1() With ActiveSheet.PageSetup .Orientation = xlLandscape End With End Sub I deleted all the code except for the line that sets the Orientation property. Actually, this macro can be simplified even more because the With-End With construct isn’t neces- sary when you’re changing only one property: Sub Macro1() ActiveSheet.PageSetup.Orientation = xlLandscape End Sub In this example, the macro changes the Orientation property of the PageSetup object on the active sheet. By the way, xlLandscape is a built-in constant that’s provided to make things easier for you. The variable xlLandscape has a value of 2, and xlPortrait has a value of 1. The following macro works the same as the preceding Macro1. Sub Macro1a() ActiveSheet.PageSetup.Orientation = 2 End Sub Most would agree that it’s easier to remember the name of the constant than the arbitrary numbers. You can use the Help system to learn the relevant constants for a particular command. You could have entered this procedure directly into a VBA module. To do so, you would have to know which objects, properties, and methods to use. Obviously, it’s much faster to record the macro, and this example has a built-in bonus: You also learned that the PageSetup object has an Orientation property. NOTE A point that I make clear throughout this book is that recording your actions is perhaps the best way to learn VBA. When in doubt, try recording. Although the result might not be exactly what you want, chances are that it will steer you in the right direction. You can use the Help system to check out the objects, properties, and methods that appear in the recorded code. CROSS-REFERENCE I discuss the macro recorder in more detail later in this chapter. See the section, “The Macro Recorder.” Unfortunately, some Excel actions simply can’t be recorded. For example, turn on the macro recorder and record your actions while you insert a Shape and apply formatting to it. Chapter 7: Introducing Visual Basic for Applications 151 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 151 You’ll find that the recorded macro is completely empty. Why? Because Microsoft didn’t deem enabling this type of action important enough to delay the release of Office 2007. COPYING VBA CODE So far, I’ve covered typing code directly into a module and recording your actions to gener- ate VBA code. The final method of getting code into a VBA module is to copy it from another module. For example, you may have written a procedure for one project that would also be useful in your current project. Rather than re-enter the code, you can simply open the workbook, activate the module, and use the normal Clipboard copy-and-paste procedures to copy it into your current VBA module. After you’ve finished pasting, you can modify the code as necessary. TIP As I note previously in this chapter, you can also import to a file an entire module that has been exported. Customizing the VBE Environment If you’re serious about becoming an Excel programmer, you’ll be spending a lot of time with the VBE window. To help make things as comfortable as possible, the VBE provides quite a few customization options. When the VBE is active, choose Tools ➪ Options. You see a dialog box with four tabs: Editor, Editor Format, General, and Docking. I discuss some of the most useful options on these tabs in the sections that follow. By the way, don’t confuse this with the Excel Options dialog box, which you bring up by choosing Office ➪ Excel Options in Excel. Using the Editor tab Figure 7-6 shows the options that you access by clicking the Editor tab of the Options dia- log box. AUTO SYNTAX CHECK OPTION The Auto Syntax Check setting determines whether the VBE pops up a dialog box if it dis- covers a syntax error while you’re entering your VBA code. The dialog box tells you roughly what the problem is. If you don’t choose this setting, VBE flags syntax errors by displaying them in a different color from the rest of the code, and you don’t have to deal with any dialog boxes popping up on your screen. I keep this setting turned off because I find the dialog boxes annoying, and I can usually figure out what’s wrong with an instruction. But if you’re new to VBA, you might find this assistance helpful. Part III: Understanding Visual Basic for Applications152 13_044018 ch07.qxp 2/28/07 6:37 PM Page 152 Figure 7-6: The Editor tab of the Options dialog box. REQUIRE VARIABLE DECLARATION OPTION If the Require Variable Declaration option is set, VBE inserts the following statement at the beginning of each new VBA module that you insert: Option Explicit If this statement appears in your module, you must explicitly define each variable that you use. This is an excellent habit to get into, although it does require some additional effort on your part. If you don’t declare your variables, they will all be of the Variant data type, which is flexible but not efficient in terms of storage or speed. I discuss variable declara- tion in more depth in Chapter 8. NOTE Changing the Require Variable Declaration option affects only new modules, not existing modules. AUTO LIST MEMBERS OPTION If the Auto List Members option is set, VBE provides some help when you’re entering your VBA code by displaying a list of member items for an object. These items include methods and properties for the object that you typed. This option is very helpful, and I always keep it turned on. Figure 7-7 shows an example of Auto List Members (which will make a lot more sense when you actually start writing VBA code). In this example, VBE is displaying a list of members for the Application object. You can just select an item from the list and press Tab, thus avoiding typing it (or, double- click an item). Using the Auto List Members list also ensures that the item is spelled correctly. Chapter 7: Introducing Visual Basic for Applications 153 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 153 Figure 7-7: An example of Auto List Members. AUTO QUICK INFO OPTION If the Auto Quick Info option is set, the VBE displays information about the arguments available for functions, properties, and methods while you type. This can be very helpful, and I always leave this setting on. Figure 7-8 shows this feature in action. It’s displaying the syntax for the Cells property. Figure 7-8: An example of Auto Quick Info offering help about the Cells property. AUTO DATA TIPS OPTION If the Auto Data Tips option is set, you can hover your mouse pointer over a variable, and VBE displays the value of the variable. This technique works only when the procedure is paused while debugging. When you enter the wonderful world of debugging, you’ll defi- nitely appreciate this option. I always keep this option turned on. AUTO INDENT OPTION The Auto Indent setting determines whether VBE automatically indents each new line of code by the same amount as the previous line. I’m a big fan of using indentations in my code, so I keep this option on. You can also specify the number of characters to indent; the default is four. Part III: Understanding Visual Basic for Applications154 13_044018 ch07.qxp 2/28/07 6:37 PM Page 154 TIP Use the Tab key, not the space bar, to indent your code. Using the Tab key results in more consistent spacing. In addition, you can use Shift+Tab to unindent a line of code. These keys also work if you select more than one statement. DRAG-AND-DROP TEXT EDITING OPTION The Drag-and-Drop Text Editing option, when enabled, lets you copy and move text by dragging and dropping. I keep this option turned on, but I never use drag-and-drop editing. I prefer to use keyboard shortcuts for copying and pasting. DEFAULT TO FULL MODULE VIEW OPTION The Default to Full Module View option specifies how procedures are viewed. If this option is set, procedures in the code window appear as a single scrollable window. If this option is turned off, you can see only one procedure at a time. I keep this setting turned on. PROCEDURE SEPARATOR OPTION When the Procedure Separator option is turned on, the VBE displays separator bars between procedures in a code window (assuming that the Default to Full Module View option is also selected). I like the visual cues that show where my procedures end, so I keep this option turned on. Using the Editor Format tab Figure 7-9 shows the Editor Format tab of the Options dialog box. The options on this tab control the appearance of the VBE itself. Figure 7-9: The Editor Format tab of the Options dialog box. Chapter 7: Introducing Visual Basic for Applications 155 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 155 CODE COLORS OPTION The Code Colors option lets you set the text color (foreground and background) and the indicator color displayed for various elements of VBA code. This is largely a matter of indi- vidual preference. Personally, I find the default colors to be just fine. But for a change of scenery, I occasionally play around with these settings. FONT OPTION The Font option lets you select the font that’s used in your VBA modules. For best results, stick with a fixed-width font (monofont) such as Courier New. In a fixed-width font, all char- acters are exactly the same width. This makes your code much more readable because the characters are nicely aligned vertically and you can easily distinguish multiple spaces. SIZE SETTING The Size setting specifies the size of the font in the VBA modules. This setting is a matter of personal preference determined by your video display resolution and your eyesight. The default size of 10 (points) works for me. MARGIN INDICATOR BAR OPTION The Margin Indicator Bar option controls the display of the vertical margin indicator bar in your modules. You should keep this turned on; otherwise, you won’t be able to see the help- ful graphical indicators when you’re debugging your code. Using the General tab Figure 7-10 shows the options available under the General tab in the Options dialog box. In almost every case, the default settings are just fine. Figure 7-10: The General tab of the Options dialog box. Part III: Understanding Visual Basic for Applications156 13_044018 ch07.qxp 2/28/07 6:37 PM Page 156 CROSS-REFERENCE The Error Trapping setting determines what happens when an error is encountered. If you write any error-handling code, make sure that the Break on Unhandled Errors option is set. If the Break on All Errors option is set, error-handling code is ignored (which is hardly ever what you want). I discuss error-handling techniques in Chapter 9. Using the Docking tab Figure 7-11 shows the Docking tab of the Options dialog box. These options determine how the various windows in the VBE behave. When a window is docked, it is fixed in place along one of the edges of the VBE window. This makes it much easier to identify and locate a particular window. If you turn off all docking, you have a big mess of windows that are very confusing. Generally, you’ll find that the default settings work fine. Figure 7-11: The Docking tab of the Options dialog box. To dock a window, just drag it to the desired location. For example, you might want to dock the Project Explorer window to the left side of the screen. Just drag its title bar to the left, and you see an outline that shows it docked. Release the mouse and it is docked. NOTE Docking windows in the VBE has always been a bit problematic. Often, you find that some windows simply refuse to be docked. I’ve found that if you persist long enough, the procedure will eventually work. Unfortunately, I don’t have any secret window- docking techniques. Chapter 7: Introducing Visual Basic for Applications 157 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 157 The Macro Recorder Earlier in this chapter, I discuss the macro recorder, which is a tool that converts your Excel actions into VBA code. This section covers the macro recorder in more detail. NOTE This is another reminder to make sure that Excel displays the Developer tab in the Ribbon. If you don’t see this tab, refer to “ Displaying Excel’s Developer tab” earlier in this chapter. The macro recorder is an extremely useful tool, but remember the following points: • The macro recorder is appropriate only for simple macros or for recording a small part of a more complex macro. • Not all the actions you make in Excel get recorded. • The macro recorder cannot generate code that performs looping (that is, repeating state- ments), assigns variables, executes statements conditionally, displays dialog boxes, and so on. • The macro recorder always creates Sub procedures. You cannot create a Function procedure by using the macro recorder. • The code that is generated depends on certain settings that you specify. • You’ll often want to clean up the recorded code to remove extraneous commands. What the macro recorder actually records The Excel macro recorder translates your mouse and keyboard actions into VBA code. I could probably write several pages describing how this is done, but the best way to show you is by example. Follow these steps: 1. Start with a blank workbook. 2. Make sure that the Excel window is not maximized. You don’t want it to fill the entire screen. 3. Press Alt+F11 to activate the VBE window. Note: Make sure that this window is not maximized. Otherwise, you won’t be able to see the VBE window and Excel’s window at the same time. 4. Resize and arrange Excel’s window and the VBE window so both are visible. (For best results, minimize any other applications that are running.) 5. Activate Excel, choose Developer ➪ Code ➪ Record Macro and then click OK to start the macro recorder. Part III: Understanding Visual Basic for Applications158 13_044018 ch07.qxp 2/28/07 6:37 PM Page 158 6. Activate the VBE window. 7. In the Project Explorer window, double-click Module1 to display that module in the code window. 8. Close the Project Explorer window in the VBE to maximize the view of the code window. Your screen layout should look something like the example in Figure 7-12. The size of the windows depends on your video resolution. Figure 7-12: A convenient window arrangement for watching the macro recorder do its thing. Now move around in the worksheet and select various Excel commands. Watch while the code is generated in the window that displays the VBA module. Select cells, enter data, format cells, use the Ribbon commands, create a chart, manipulate graphic objects, and so on. I guarantee that you’ll be enlightened while you watch the code being spit out before your very eyes. Relative or absolute? When recording your actions, Excel normally records absolute references to cells. In other words, when you select a cell, it will remember that exact cell (not the cell relative to the current active cell). To demonstrate how this works, perform these steps and examine the code: 1. Activate a worksheet and start the macro recorder. 2. Activate cell B1. 3. Enter Jan into cell B1. 4. Move to cell C1 and enter Feb. Chapter 7: Introducing Visual Basic for Applications 159 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 159 5. Continue this process until you’ve entered the first six months of the year in B1:G1. 6. Click cell B1 to activate it again. 7. Stop the macro recorder and examine the new code in the VBE. Excel generates the following code: Sub Macro1() Range(“B1”).Select ActiveCell.FormulaR1C1 = “Jan” Range(“C1”).Select ActiveCell.FormulaR1C1 = “Feb” Range(“D1”).Select ActiveCell.FormulaR1C1 = “Mar” Range(“E1”).Select ActiveCell.FormulaR1C1 = “Apr” Range(“F1”).Select ActiveCell.FormulaR1C1 = “May” Range(“G1”).Select ActiveCell.FormulaR1C1 = “Jun” Range(“B1”).Select End Sub To execute this macro, choose Developer ➪ Code ➪ Macros (or press Alt+F8) and select Macro1 (or whatever the macro is named) and click the Run button. The macro, when executed, re-creates the actions that you performed when you recorded it. These same actions occur regardless of which cell is active when you execute the macro. Recording a macro using absolute references always produces the exact same results. In some cases, however, you want your recorded macro to work with cell locations in a rel- ative manner. For example, you’d probably want such a macro to start entering the month names in the active cell. In such a case, you want to use relative recording to record the macro. You control how references are recorded by using the Developer ➪ Code ➪ Use Relative References button. This button is a toggle. When the button appears in a different color, the macro recorder records relative references. When the button appears in the standard color, the macro recorder records absolute references. You can change the recording method at any time, even in the middle of recording. To see how this works, erase the cells in B1:D1 and then perform the following steps: 1. Activate cell B1. 2. Choose Developer ➪ Code ➪ Record Macro. 3. Click OK to begin recording. 4. Click the Use Relative Reference button to change the recording mode to relative. Part III: Understanding Visual Basic for Applications160 13_044018 ch07.qxp 2/28/07 6:37 PM Page 160 After you click this button, it appears in a different color. 5. Enter the first six months’ names in B1:G1, as in the previous example. 6. Select cell B1. 7. Stop the macro recorder. With the recording mode set to relative, the code that Excel generates is quite different: Sub Macro2() ActiveCell.FormulaR1C1 = “Jan” ActiveCell.Offset(0, 1).Range(“A1”).Select ActiveCell.FormulaR1C1 = “Feb” ActiveCell.Offset(0, 1).Range(“A1”).Select ActiveCell.FormulaR1C1 = “Mar” ActiveCell.Offset(0, 1).Range(“A1”).Select ActiveCell.FormulaR1C1 = “Apr” ActiveCell.Offset(0, 1).Range(“A1”).Select ActiveCell.FormulaR1C1 = “May” ActiveCell.Offset(0, 1).Range(“A1”).Select ActiveCell.FormulaR1C1 = “Jun” ActiveCell.Offset(0, -5).Range(“A1”).Select End Sub You can execute this macro by activating a worksheet and then choosing the Developer ➪ Code ➪ Macros command. Select the macro name and then click the Run button. You’ll also notice that I varied the procedure slightly in this example: I activated the begin- ning cell before I started recording. This is an important step when you record macros that use the active cell as a base. Although it looks rather complicated, this macro is actually quite simple. The first state- ment simply enters Jan into the active cell. (It uses the active cell because it’s not pre- ceded by a statement that selects a cell.) The next statement uses the Select method (along with the Offset property) to move the selection one cell to the right. The next statement inserts more text, and so on. Finally, the original cell is selected by calculating a relative offset rather than an absolute cell. Unlike the preceding macro, this one always starts entering text in the active cell. NOTE You’ll notice that this macro generates code that appears to reference cell A1 — which might seem strange because cell A1 was not even involved in the macro. This is simply a by-product of how the macro recorder works. (I discuss the Offset property later in this chapter.) At this point, all you need to know is that the macro works as it should. The point here is that the recorder has two distinct modes, and you need to be aware of which mode you’re recording in. Otherwise, the result will not be what you expected. Chapter 7: Introducing Visual Basic for Applications 161 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 161 By the way, the code generated by Excel is more complex than it need be, and it’s not even the most efficient way to code the operation. The macro that follows, which I entered man- ually, is a simpler and faster way to perform this same operation. This example demon- strates that VBA doesn’t have to select a cell before it puts information into it — an important concept that can speed things up considerably. Sub Macro3() ActiveCell.Offset(0, 0) = “Jan” ActiveCell.Offset(0, 1) = “Feb” ActiveCell.Offset(0, 2) = “Mar” ActiveCell.Offset(0, 3) = “Apr” ActiveCell.Offset(0, 4) = “May” ActiveCell.Offset(0, 5) = “Jun” End Sub In fact, this macro can be made even more efficient by using the With-End With construct: Sub Macro4() With ActiveCell .Offset(0, 0) = “Jan” .Offset(0, 1) = “Feb” .Offset(0, 2) = “Mar” .Offset(0, 3) = “Apr” .Offset(0, 4) = “May” .Offset(0, 5) = “Jun” End With End Sub Or, if you’re a VBA guru, you can impress your colleagues by using a single statement: Sub Macro5() ActiveCell.Resize(,6)=Array(“Jan”,”Feb”,”Mar”,”Apr”,”May”,”Jun”) End Sub Recording options When you record your actions to create VBA code, you have several options in the Record Macro dialog box. The following paragraphs describe your options. MACRO NAME You can enter a name for the procedure that you are recording. By default, Excel uses the names Macro1, Macro2, and so on for each macro that you record. I usually just accept the default name and change the name of the procedure later. You, however, might prefer to name the macro before you record it. The choice is yours. Part III: Understanding Visual Basic for Applications162 13_044018 ch07.qxp 2/28/07 6:37 PM Page 162 SHORTCUT KEY The Shortcut key option lets you execute the macro by pressing a shortcut key combina- tion. For example, if you enter w (lowercase), you can execute the macro by pressing Ctrl+W. If you enter W(uppercase), the macro comes alive when you press Ctrl+Shift+W. Keep in mind that a shortcut key assigned to a macro overrides a built-in shortkey key (if one exists). For example, if you assign Ctrl+B to a macro, you won’t be able to use the key combination to toggle the bold attribute in cells. You can always add or change a shortcut key at any time, so you don’t need to set this option while recording a macro. STORE MACRO IN The Store Macro In option tells Excel where to store the macro that it records. By default, Excel puts the recorded macro in a module in the active workbook. If you prefer, you can record it in a new workbook (Excel opens a blank workbook) or in your Personal Macro Workbook. (Read more about this in the sidebar, “The Personal Macro Workbook.”) NOTE Excel remembers your choice, so the next time you record a macro, it defaults to the same location you used previously. Cleaning up recorded macros Earlier in this chapter, you see how recording your actions while you issue a single com- mand (the Page Layout ➪ Page Setup ➪ Orientation command) produces an enormous amount of VBA code. This is an example of how, in many cases, the recorded code includes extraneous commands that you can delete. Chapter 7: Introducing Visual Basic for Applications 163 Part III When you record a macro, one of your options is to record it to your Personal Macro Workbook. If you create some VBA macros that you find particularly useful, you might want to store these routines on your Personal Macro Workbook. This is a workbook named Personal.xlsb that is stored in your XLStart directory. Whenever you start Excel, this workbook is loaded, and you have access to the macros stored in the workbook. Personal.xlsb a hidden workbook, so it’s out of your way when you’re working in Excel. The Personal.xlsb file doesn’t exist until you record a macro to it. The Personal Macro Workbook 13_044018 ch07.qxp 2/28/07 6:37 PM Page 163 It’s also important to understand that the macro recorder doesn’t always generate the most efficient code. If you examine the generated code, you see that Excel generally records what is selected (that is, an object) and then uses the Selection object in subsequent statements. For example, here’s what is recorded if you select a range of cells and then use some buttons on the Home tab to change the numeric formatting and apply bold and italic: Range(“A1:C5”).Select Selection.Style = “Comma” Selection.Font.Bold = True Selection.Font.Italic = True Part III: Understanding Visual Basic for Applications164 Throughout this book, I present many small snippets of VBA code to make a point or to provide an example. Often, this code might consist of just a single statement. In some cases, the example consists of only an expression, which isn’t a valid instruction by itself. For example, the following is an expression: Range(“A1”).Value To test an expression, you must evaluate it. The MsgBox function is a handy tool for this: MsgBox Range(“A1”).Value To try out these examples, put the statement within a procedure in a VBA module, like this: Sub Test() ‘ statement goes here End Sub Then put the cursor anywhere within the procedure and press F5 to execute it. Also, make sure that the code is being executed within the proper context. For example, if a statement refers to Sheet1, make sure that the active workbook actually has a sheet named Sheet1. If the code is just a single statement, you can use the VBE Immediate window. The Immediate window is very useful for executing a statement “immediately” — without having to create a procedure. If the Immediate window is not displayed, press Ctrl+G in the VBE. Just type the VBA statement in the Immediate window and press Enter. To evaluate an expression in the Immediate window, precede the expression with a question mark (?). The question mark is a shortcut for Print. For example, you can type the following into the Immediate window: ? Range(“A1”).Value The result of this expression is displayed in the next line of the Immediate window. About the Code Examples 13_044018 ch07.qxp 2/28/07 6:37 PM Page 164 The recorded VBA code works, but it’s just one way to perform these actions. You can also use the more efficient With-End With construct, as follows: Range(“A1:C5”).Select With Selection .Style = “#,##0.00” .Font.Bold = True .Font.Italic = True End With Or you can avoid the Select method altogether and write the code even more efficiently, like this: With Range(“A1:C5”) .Style = “#,##0.00” .Font.Bold = True .Font.Italic = True End With If speed is essential in your application, you always want to examine any recorded VBA code closely to make sure that it’s as efficient as possible. You, of course, need to understand VBA thoroughly before you start cleaning up your recorded macros. But for now, just be aware that recorded VBA code isn’t always the best, most efficient code. About Objects and Collections If you’ve worked through the first part of this chapter, you have an overview of VBA and you know the basics of working with VBA modules in the VBE. You’ve also seen some VBA code and were exposed to concepts like objects and properties. This section gives you some additional details about objects and collections of objects. When you work with VBA, you must understand the concept of objects and Excel’s object model. It helps to think of objects in terms of a hierarchy. At the top of this model is the Application object — in this case, Excel itself. But if you’re programming in VBA with Microsoft Word, the Application object is Word. The object hierarchy The Application object (that is, Excel) contains other objects. Here are a few examples of objects contained in the Application object: • Workbooks (a collection of all Workbook objects) • Windows (a collection of all Window objects) • AddIns (a collection of all AddIn objects) Chapter 7: Introducing Visual Basic for Applications 165 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 165 Some objects can contain other objects. For example, the Workbooks collection consists of all open Workbook objects, and a Workbook object contains other objects, a few of which are as follows: • Worksheets (a collection of Worksheet objects) • Charts (a collection of Chart objects) • Names (a collection of Name objects) Each of these objects, in turn, can contain other objects. The Worksheets collection con- sists of all Worksheet objects in a Workbook. A Worksheet object contains many other objects, which include the following: • ChartObjects (a collection of ChartObject objects) • Range • PageSetup • PivotTables (a collection of PivotTable objects) If this seems confusing, trust me, it will make sense, and you’ll eventually realize that this object hierarchy setup is quite logical and well structured. By the way, the complete Excel object model is covered in the Help system. About collections Another key concept in VBA programming is collections. A collection is a group of objects of the same class, and a collection is itself an object. As I note earlier, Workbooks is a col- lection of all Workbook objects currently open. Worksheets is a collection of all Work- sheet objects contained in a particular Workbook object. You can work with an entire collection of objects or with an individual object in a collection. To reference a single object from a collection, you put the object’s name or index number in parentheses after the name of the collection, like this: Worksheets(“Sheet1”) If Sheet1 is the first worksheet in the collection, you could also use the following reference: Worksheets(1) You refer to the second worksheet in a Workbook as Worksheets(2), and so on. There is also a collection called Sheets, which is made up of all sheets in a workbook, whether they’re worksheets or chart sheets. If Sheet1 is the first sheet in the workbook, you can reference it as follows: Sheets(1) Part III: Understanding Visual Basic for Applications166 13_044018 ch07.qxp 2/28/07 6:37 PM Page 166 Referring to objects When you refer to an object using VBA, you often must qualify the object by connecting object names with a period (also known as a dot operator). What if you had two workbooks open and they both had a worksheet named Sheet1? The solution is to qualify the refer- ence by adding the object’s container, like this: Workbooks(“Book1”).Worksheets(“Sheet1”) Without the workbook qualifier, VBA would look for Sheet1 in the active workbook. To refer to a specific range (such as cell A1) on a worksheet named Sheet1 in a workbook named Book1, you can use the following expression: Workbooks(“Book1”).Worksheets(“Sheet1”).Range(“A1”) The fully qualified reference for the preceding example also includes the Application object, as follows: Application.Workbooks(“Book1”).Worksheets(“Sheet1”).Range(“A1”) Most of the time, however, you can omit the Application object in your references; it is assumed. If the Book1 object is the active workbook, you can even omit that object refer- ence and use this: Worksheets(“Sheet1”).Range(“A1”) And — I think you know where I’m going with this — if Sheet1 is the active worksheet, you can use an even simpler expression: Range(“A1”) NOTE Contrary to what you might expect, Excel does not have an object that refers to an indi- vidual cell that is called Cell. A single cell is simply a Range object that happens to con- sist of just one element. Simply referring to objects (as in these examples) doesn’t do anything. To perform anything meaningful, you must read or modify an object’s properties or else specify a method to be used with an object. Properties and Methods It’s easy to be overwhelmed with properties and methods; there are literally thousands available. In this section, I describe how to access properties and methods of objects. Chapter 7: Introducing Visual Basic for Applications 167 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 167 Object properties Every object has properties. For example, a Range object has a property called Value. You can write VBA code to display the Value property or write VBA code to set the Value property to a specific value. Here’s a procedure that uses the VBA MsgBox function to pop up a box that displays the value in cell A1 on Sheet1 of the active workbook: Sub ShowValue() Msgbox Worksheets(“Sheet1”).Range(“A1”).Value End Sub NOTE The VBA MsgBox function provides an easy way to display results while your VBA code is executing. I use it extensively throughout this book. The code in the preceding example displays the current setting of the Value property of a specific cell: cell A1 on a worksheet named Sheet1 in the active workbook. Note that if the active workbook does not have a sheet named Sheet1, the macro generates an error. What if you want to change the Value property? The following procedure changes the value displayed in cell A1 by changing the cell’s Value property: Sub ChangeValue() Worksheets(“Sheet1”).Range(“A1”).Value = 123.45 End Sub After executing this routine, cell A1 on Sheet1 has the value 123.45. You might want to enter these procedures into a module and experiment with them. NOTE Most objects have a default property. For a Range object, the default property is the Value property. Therefore, you can omit the .Value part from the preceding code, and it has the same effect. However, it’s usually considered good programming practice to include the property in your code, even if it is the default property. The statement that follows accesses the HasFormula and the Formula properties of a Range object. If Range(“A1”).HasFormula Then MsgBox Range(“A1”).Formula I use an If-Then construct to display a message box conditionally: If the cell has a for- mula, then display the formula by accessing the Formula property. If cell A1 does not have a formula, nothing happens. Part III: Understanding Visual Basic for Applications168 13_044018 ch07.qxp 2/28/07 6:37 PM Page 168 The Formula property is a read-write property, so you can also specify a formula by using VBA: Range(“D12”).Formula = “=RAND()*100” Object methods In addition to properties, objects also have methods. A method is an action that you per- form with an object. Here’s a simple example that uses the Clear method on a Range object. After you execute this procedure, A1:C3 on Sheet1 is empty and all cell formatting is removed. Sub ZapRange() Worksheets(“Sheet1”).Range(“A1:C3”).Clear End Sub Chapter 7: Introducing Visual Basic for Applications 169 Part III An issue that often leads to confusion among new VBA programmers concerns arguments for methods and properties. Some methods use arguments to further clarify the action to be taken, and some properties use arguments to further specify the property value. In some cases, one or more of the arguments are optional. If a method uses arguments, place the arguments after the name of the method, separated by commas. If the method uses optional arguments, you can insert blank placeholders for the optional arguments. Read on to discover how to insert these placeholders. Consider the Protect method for a workbook object. Check the Help system, and you’ll find that the Protect method takes three arguments: password, structure, and windows. These arguments correspond to the options in the Protect Workbook dialog box. If you want to protect a workbook named MyBook.xlsx, for example, you might use a statement like this: Workbooks(“MyBook.xlsx”).Protect “xyzzy”, True, False In this case, the workbook is protected with a password (argument 1). Its structure is protected (argument 2) but not its windows (argument 3). If you don’t want to assign a password, you can use a statement like this: Workbooks(“MyBook.xlsx”).Protect , True, False continued Specifying Arguments for Methods and Properties 13_044018 ch07.qxp 2/28/07 6:37 PM Page 169 If you’d like to delete the values in a range but keep the formatting, use the ClearContents method of the Range object. Most methods also take arguments to define the action further. Here’s an example that copies cell A1 to cell B1 by using the Copy method of the Range object. In this example, the Copy method has one argument (the destination of the copy). Notice that I use the line continuation character sequence (a space followed by an underscore) in this example. You can omit the line continuation sequence and type the statement on a single line. Sub CopyOne() Worksheets(“Sheet1”).Range(“A1”).Copy _ Worksheets(“Sheet1”).Range(“B1”) End Sub Part III: Understanding Visual Basic for Applications170 continued Notice that the first argument is omitted and that I specified the placeholder by using a comma. Another approach, which makes your code more readable, is to use named arguments. Here’s an example of how you use named arguments for the preceding example: Workbooks(“MyBook.xlsx”).Protect Structure:=True, Windows:=False Using named arguments is a good idea, especially for methods that have many optional arguments and also when you need to use only a few of them. When you use named arguments, there is no need to use a placeholder for missing arguments. For properties (and methods) that return a value, you must use parentheses around the arguments. For example, the Address property of a Range object takes five arguments, all of which are optional. Because the Address property returns a value, the following statement is not valid because the parentheses are omitted: MsgBox Range(“A1”).Address False ‘ invalid The proper syntax for such a statement requires parentheses, as follows: MsgBox Range(“A1”).Address(False) The statement could also be written by using a named argument: MsgBox Range(“A1”).Address(rowAbsolute:=False) These nuances will become clearer as you gain more experience with VBA. 13_044018 ch07.qxp 2/28/07 6:37 PM Page 170 The Comment Object: A Case Study To help you better understand the properties and methods available for an object, I focus on a particular object: the Comment object. In Excel, you create a Comment object when you choose the Review ➪ Comments ➪ New Comment command to enter a cell comment. In the sections that follow, you get a feel for working with objects. Viewing Help for the Comment object One way to learn about a particular object is to look it up in the Help system. Figure 7-13 shows some Help topics for the Comment object. I found this Help screen by typing com- ment in the VBE Type a Question for Help box (to the right of the menu bar). Notice that the Help Table of Contents displays the properties and methods for this object. Properties of a Comment object The Comment object has five properties. Table 7-1 contains a list of these properties, along with a brief description of each. If a property is read-only, your VBA code can read the prop- erty but cannot change it. Figure 7-13: The main help screen for the Comment object. Chapter 7: Introducing Visual Basic for Applications 171 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 171 TABLE 7-1 PROPERTIES OF A COMMENT OBJECT Property Read-Only Description Application Yes Returns an object that represents the application that created the comment (that is, Excel). Author Yes Returns the name of the person who created the comment. Parent Yes Returns the parent object for the comment. (It is always a Range object.) Shape Yes Returns a Shape object that represents the shape attached to the comment. Visible No Is True if the comment is visible. Methods of a Comment object Table 7-2 shows the methods that you can use with a Comment object. Again, these meth- ods perform common operations that you may have performed manually with a comment at some point . . . but you probably never thought of these operations as methods. Part III: Understanding Visual Basic for Applications172 The easiest way to get specific help about a particular object, property, or method is to type the word in a code window and press F1. If there is any ambiguity about the word that you typed, you get a dialog box like the one shown in the following figure. Unfortunately, the items listed in the dialog box are not always clear, so it may require some trial and error to locate the correct help topic. The dialog box in the figure appears when you type Comment and then press F1. In this case, although Comment is an object, it may behave like a property. Clicking the first item displays the help topic for the Comment object; clicking the second item displays the help topic for the Comment property. Using the Help System 13_044018 ch07.qxp 2/28/07 6:37 PM Page 172 TABLE 7-2 METHODS OF A COMMENT OBJECT Method Description Delete Deletes a comment. Next Returns a Comment object that represents the next comment in the worksheet. Previous Returns a Comment object that represents the previous comment in the worksheet. Text Returns or sets the text in a comment (takes three arguments). NOTE You might be surprised to see that Text is a method rather than a property. This leads to an important point: The distinction between properties and methods isn’t always clear-cut, and the object model isn’t perfectly consistent. In fact, it’s not really important that you distinguish between properties and methods. As long as you get the syntax cor- rect, it doesn’t matter whether a word in your code is a property or a method. The Comments collection Recall that a collection is a group of like objects. Every worksheet has a Comments collec- tion, which consists of all Comment objects on the worksheet. If the worksheet has no com- ments, this collection is empty. Comments appear in the collection based on their position in the worksheet: Left-to-right and then top-to-bottom. For example, the following code refers to the first comment on Sheet1 of the active workbook: Worksheets(“Sheet1”).Comments(1) The following statement displays the text contained in the first comment on Sheet1: MsgBox Worksheets(“Sheet1”).Comments(1).Text Unlike most objects, a Comment object does not have a Name property. Therefore, to refer to a specific comment, you must either use an index number or (more frequently) use the Comment property of a Range object to return a specific comment. The Comments collection is also an object and has its own set of properties and methods. For example, the Comments collection has a Count property that stores the number of items in the collection — which is the number of Comment objects in the active worksheet. The following statement displays the total number of comments on the active worksheet. MsgBox ActiveSheet.Comments.Count Chapter 7: Introducing Visual Basic for Applications 173 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 173 The next example shows the address of the cell that has the first comment: MsgBox ActiveSheet.Comments(1).Parent.Address Here, Comments(1) returns the first Comment object in the Comments collection. The Parent property of the Comment object returns its container, which is a Range object. The message box displays the Address property of the Range. The net effect is that the state- ment displays the address of the cell that contains the first comment. You can also loop through all the comments on a sheet by using the For Each-Next con- struct. (Looping is explained in Chapter 8.) Here’s an example that displays a separate message box for each comment on the active worksheet: For Each cmt in ActiveSheet.Comments MsgBox cmt.Text Next cmt If you’d rather not deal with a series of message boxes, use this procedure to print the comments to the Immediate window in the VBE: For Each cmt in ActiveSheet.Comments Debug.Print cmt.Text Next cmt About the Comment property In this section, I’ve been discussing the Comment object. If you dig through the Help sys- tem, you’ll find that a Range object has a property named Comment. If the cell contains a comment, the Comment property returns a Comment object. For example, the following statement refers to the Comment object in cell A1: Range(“A1”).Comment If this were the first comment on the sheet, you could refer to the same Comment object as follows: ActiveSheet.Comments(1) To display the comment in cell A1 in a message box, use a statement like this: MsgBox Range(“A1”).Comment.Text If cell A1 does not contain a comment, this statement generates an error. Part III: Understanding Visual Basic for Applications174 13_044018 ch07.qxp 2/28/07 6:37 PM Page 174 NOTE The fact that a property can return an object is a very important concept — a difficult one to grasp, perhaps, but critical to mastering VBA. Objects within a Comment object Working with properties is confusing at first because some properties actually return objects. Suppose that you want to determine the background color of a particular comment on Sheet1. If you look through the list of properties for a Comment object, you won’t find anything that relates to color. Rather, you must do this: 1. Use the Comment object’s Shape property to return the Shape object that’s contained in the comment. 2. Use the Shape object’s Fill property to return a FillFormat object. 3. Use the FillFormat object’s ForeColor property to return a ColorFormat object. 4. Use the ColorFormat object’s RGB property to get the color value. Put another way, getting at the interior color for a Comment object involves accessing other objects contained in the Comment object. Here’s a look at the object hierarchy that’s involved: Application (Excel) Workbook object Worksheet object Comment object Shape object FillFormat object ColorFormat object I’ll be the first to admit it: This can get very confusing! But, as an example of the elegance of VBA, the code to change the color of a comment can be written with a single statement: Worksheets(“Sheet1”).Comments(1).Shape.Fill.ForeColor _ .RGB = RGB(0, 255, 0) Or, if you use the SchemeColor property (which ranges from 0 to 80), the code is: Worksheets(“Sheet1”).Comments(1).Shape.Fill.ForeColor _ .SchemeColor = 12 Chapter 7: Introducing Visual Basic for Applications 175 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 175 This type of referencing is certainly not intuitive at first, but it will eventually make sense. Fortunately, recording your actions in Excel almost always yields some insights regarding the hierarchy of the objects involved. By the way, to change the color of the text in a comment, you need to access the Comment object’s TextFrame object, which contains the Characters object, which contains the Font object. Then you have access to the Font object’s Color or ColorIndex properties. Here’s an example that sets ColorIndex property to 5: Worksheets(“Sheet1”).Comments(1) _ .Shape.TextFrame.Characters.Font.ColorIndex = 5 CROSS-REFERENCE Refer to Chapter 30 for more information on colors. Determining whether a cell has a comment The following statement displays the comment in cell A1 of the active sheet: MsgBox Range(“A1”).Comment.Text If cell A1 does not have a comment, executing this statement generates a cryptic error message: Object variable or With block variable not set. To determine whether a particular cell has a comment, you can write code to check whether the Comment object is Nothing. (Yes, Nothing is a valid keyword.) The following statement displays True if cell A1 does not have a comment: MsgBox Range(“A1”).Comment Is Nothing Note that I use the Is keyword and not an equal sign. You can take this one step further and write a statement that displays the cell comment only if the cell actually has a comment (and does not generate an error if the cell lacks a comment). The statement that follows accomplishes this task: If Not Range(“A1”).Comment Is Nothing Then _ MsgBox Range(“A1”).Comment.Text Notice that I used the Not keyword, which negates the True value that’s returned if the cell has no comment. The statement, in essence, uses a double-negative to test a condition: If the comment is not nothing, then display it. If this is confusing, think about it for a while and it will make sense. Part III: Understanding Visual Basic for Applications176 13_044018 ch07.qxp 2/28/07 6:37 PM Page 176 Adding a new Comment object You may have noticed that the list of methods for the Comment object doesn’t include a method to add a new comment. This is because the AddComment method belongs to the Range object. The following statement adds a comment (an empty comment) to cell A1 on the active worksheet: Range(“A1”).AddComment If you consult the Help system, you discover that the AddComment method takes an argu- ment that represents the text for the comment. Therefore, you can add a comment and then add text to the comment with a single statement, like this: Range(“A1”).AddComment “Formula developed by JW.” NOTE The AddComment method generates an error if the cell already contains a comment. To avoid the error, your code can check whether the cell has a comment before adding one. CD-ROM If you’d like to see these Comment object properties and methods in action, check out the example workbook on the companion CD-ROM. This workbook, named comment object.xlsm, contains several examples that manipulate Comment objects with VBA code. You probably won’t understand all the code, but you will get a feel for how you can use VBA to manipulate an object. Some Useful Application Properties When you’re working with Excel, only one workbook at a time can be active. And if the sheet is a worksheet, one cell is the active cell (even if a multicell range is selected). VBA knows about active worksbooks, worksheets, and cells, and lets you refer to these active objects in a simplified manner. This is often useful because you won’t always know the exact workbook, worksheet, or range that you want to operate on. VBA handles this by providing properties of the Application object. For example, the Application object has an ActiveCell property that returns a reference to the active cell. The following instruction assigns the value 1 to the active cell: ActiveCell.Value = 1 Notice that I omitted the reference to the Application object in the preceding example because it is assumed. It’s important to understand that this instruction will fail if the active sheet is not a worksheet. For example, if VBA executes this statement when a chart sheet is active, the procedure halts and you get an error message. Chapter 7: Introducing Visual Basic for Applications 177 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 177 If a range is selected in a worksheet, the active cell is a cell within the selected range. In other words, the active cell is always a single cell (never a multicell range). The Application object also has a Selection property that returns a reference to what- ever is selected, which could be a single cell (the active cell), a range of cells, or an object such as ChartObject, TextBox, or Shape. Table 7-3 lists the other Application properties that are useful when working with cells and ranges. TABLE 7-3 SOME USEFUL PROPERTIES OF THE APPLICATION OBJECT Property Object Returned ActiveCell The active cell. ActiveChart The active chart sheet or chart contained in a ChartObject on a worksheet. This property is Nothing if a chart is not active. ActiveSheet The active sheet (worksheet or chart). ActiveWindow The active window. ActiveWorkbook The active workbook. Selection The object selected. (It could be a Range object, Shape, ChartObject, and so on.) ThisWorkbook The workbook that contains the VBA procedure being executed. The advantage of using these properties to return an object is that you don’t need to know which cell, worksheet, or workbook is active; nor do you need to provide a specific refer- ence to it. This allows you to write VBA code that is not specific to a particular workbook, sheet, or range. For example, the following instruction clears the contents of the active cell, even though the address of the active cell is not known: ActiveCell.ClearContents The example that follows displays a message that tells you the name of the active sheet: MsgBox ActiveSheet.Name If you want to know the name and directory path of the active workbook, use a statement like this: MsgBox ActiveWorkbook.FullName Part III: Understanding Visual Basic for Applications178 13_044018 ch07.qxp 2/28/07 6:37 PM Page 178 If a range on a worksheet is selected, you can fill the entire range with a value by executing a single statement. In the following example, the Selection property of the Application object returns a Range object that corresponds to the selected cells. The instruction simply modifies the Value property of this Range object, and the result is a range filled with a single value: Selection.Value = 12 Note that if something other than a range is selected (such as a ChartObject or a Shape), the preceding statement generates an error because ChartObject and Shape objects don’t have a Value property. The following statement, however, enters a value of 12 into the Range object that was selected before a non-Range object was selected. If you look up the RangeSelection property in the Help system, you find that this property applies only to a Window object. ActiveWindow.RangeSelection.Value = 12 To find out how many cells are selected in the active window, access the Count property. Here’s an example: MsgBox ActiveWindow.RangeSelection.Count Working with Range Objects Much of the work that you will do in VBA involves cells and ranges in worksheets. The earlier discussion on relative versus absolute macro recording (see “Relative or absolute?”) exposes you to working with cells in VBA, but you need to know a lot more. A Range object is contained in a Worksheet object and consists of a single cell or range of cells on a single worksheet. In the sections that follow, I discuss three ways of referring to Range objects in your VBA code: • The Range property of a Worksheet or Range class object • The Cells property of a Worksheet object • The Offset property of a Range object The Range property The Range property returns a Range object. If you consult the Help system for the Range property, you learn that this property has two syntaxes: object.Range(cell1) object.Range(cell1, cell2) Chapter 7: Introducing Visual Basic for Applications 179 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 179 Part III: Understanding Visual Basic for Applications180 Working with merged cells can be tricky. If a range contains merged cells, you may need to take some special action with the macros. For example, if cells A1:D1 are merged, the statement that follows selects columns A through D (not just column B, as you might expect): Columns(“B:B”).Select I don’t know if this unexpected behavior is intentional or if it’s a bug. However, it can cause your macro to behave in a manner that you didn’t expect. Merged cells also cause problems with sorting. To determine if a particular range contains any merged cells, you can use the following VBA function. The function returns True if any cell in the argument range is a merged cell (refer to Chapter 10 for more information about Function procedures). Function ContainsMergedCells(rng As Range) Dim cell As Range ContainsMergedCells = False For Each cell In rng If cell.MergeCells Then ContainsMergedCells = True Exit Function End If Next cell End Function To refer to merged cells, you can reference the entire merged range or just the upper- left cell within the merged range. For example, if a worksheet contains four cells merged into one (A1, B1, A2, and B1), reference the merged cells using either of the following expressions: Range(“A1:B2”) Range(“A1”) If you attempt to assign a value to a cell in a merged range that’s not the upper-left cell, VBA ignores the instruction and does not generate an error. For example, the following statement has no effect if A1:B2 is merged: Range(“B2”).Value = 43 Some operations cause Excel to display a confirmation message. For example, if A1:B2 is merged, the following statement generates a message: This operation will cause some merged cells to unmerge. Do you wish to continue? Range(“B2”).Delete Bottom line? Be careful with merged cells. Clearly, this feature was not very well thought out before it was implemented. Working with Merged Cells 13_044018 ch07.qxp 2/28/07 6:37 PM Page 180 The Range property applies to two types of objects: a Worksheet object or a Range object. Here, cell1 and cell2 refer to placeholders for terms that Excel recognizes as identify- ing the range (in the first instance) and delineating the range (in the second instance). Following are a few examples of using the Range property. You’ve already seen examples like the following one earlier in the chapter. The instruction that follows simply enters a value into the specified cell. In this case, it puts the value 12.3 into cell A1 on Sheet1 of the active workbook: Worksheets(“Sheet1”).Range(“A1”).Value = 12.3 The Range property also recognizes defined names in workbooks. Therefore, if a cell is named Input, you can use the following statement to enter a value into that named cell: Worksheets(“Sheet1”).Range(“Input”).Value = 100 The example that follows enters the same value into a range of 20 cells on the active sheet. If the active sheet is not a worksheet, this causes an error message: ActiveSheet.Range(“A1:B10”).Value = 2 The next example produces exactly the same result as the preceding example: Range(“A1”, “B10”) = 2 The sheet reference is omitted, however, so the active sheet is assumed. Also, the value property is omitted, so the default property (which is Value for a Range object) is assumed. This example also uses the second syntax of the Range property. With this syn- tax, the first argument is the cell at the top left of the range, and the second argument is the cell at the lower right of the range. The following example uses the Excel range intersection operator (a space) to return the intersection of two ranges. In this case, the intersection is a single cell, C6. Therefore, this statement enters 3 into cell C6: Range(“C1:C10 A6:E6”) = 3 And finally, this next example enters the value 4 into five cells: that is, a noncontiguous range. The comma serves as the union operator: Range(“A1,A3,A5,A7,A9”) = 4 So far, all the examples have used the Range property on a Worksheet object. As I men- tioned, you can also use the Range property on a Range object. This can be rather confus- ing, but bear with me. Following is an example of using the Range property on a Range object. (In this case, the Range object is the active cell.) This example treats the Range object as if it were the upper-left cell in the worksheet, and then it enters a value of 5 into the cell that would be Chapter 7: Introducing Visual Basic for Applications 181 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 181 B2. In other words, the reference returned is relative to the upper-left corner of the Range object. Therefore, the statement that follows enters a value of 5 into the cell directly to the right and one row below the active cell: ActiveCell.Range(“B2”) = 5 I said this is confusing. Fortunately, there is a much clearer way to access a cell relative to a range: the Offset property. I discuss this property after the next section. The Cells property Another way to reference a range is to use the Cells property. You can use the Cells property, like the Range property, on Worksheet objects and Range objects. Check the Help system, and you see that the Cells property has three syntaxes: object.Cells(rowIndex, columnIndex) object.Cells(rowIndex) object.Cells I’ll give you some examples that demonstrate how to use the Cells property. The first example enters the value 9 into cell A1 on Sheet1. In this case, I’m using the first syntax, which accepts the index number of the row (from 1 to 1048576) and the index number of the column (from 1 to 16384): Worksheets(“Sheet1”).Cells(1, 1) = 9 Here’s an example that enters the value 7 into cell D3 (that is, row 3, column 4) in the active worksheet: ActiveSheet.Cells(3, 4) = 7 You can also use the Cells property on a Range object. When you do so, the Range object returned by the Cells property is relative to the upper-left cell of the referenced Range. Confusing? Probably. An example might help clear this up. The following instruction enters the value 5 into the active cell. Remember, in this case, the active cell is treated as if it were cell A1 in the worksheet: ActiveCell.Cells(1, 1) = 5 NOTE The real advantage of this type of cell referencing will be apparent when I discuss vari- ables and looping (see Chapter 8). In most cases, you don’t use actual values for the arguments; rather, you use variables. Part III: Understanding Visual Basic for Applications182 13_044018 ch07.qxp 2/28/07 6:37 PM Page 182 To enter a value of 5 into the cell directly below the active cell, you can use the following instruction: ActiveCell.Cells(2, 1) = 5 Think of the preceding example as though it said this: “Start with the active cell and con- sider this cell as cell A1. Place 5 in the cell in the second row and the first column.” The second syntax of the Cells method uses a single argument that can range from 1 to 17,179,869,184. This number is equal to the number of cells in an Excel 2007 worksheet. The cells are numbered starting from A1 and continuing right and then down to the next row. The 16,384th cell is XFD1; the 16,385th is A2. The next example enters the value 2 into cell SZ1 (which is the 520th cell in the work- sheet) of the active worksheet: ActiveSheet.Cells(520) = 2 To display the value in the last cell in a worksheet (XFD1048576), use this statement: MsgBox ActiveSheet.Cells(17179869184) This syntax can also be used with a Range object. In this case, the cell returned is relative to the Range object referenced. For example, if the Range object is A1:D10 (40 cells), the Cells property can have an argument from 1 to 40 and can return one of the cells in the Range object. In the following example, a value of 2000 is entered into cell A2 because A2 is the fifth cell (counting from the top, to the right, and then down) in the referenced range: Range(“A1:D10”).Cells(5) = 2000 NOTE In the preceding example, the argument for the Cells property is not limited to values between 1 and 40. If the argument exceeds the number of cells in the range, the count- ing continues as if the range were taller than it actually is. Therefore, a statement like the preceding one could change the value in a cell that’s outside of the range A1:D10. The statement that follows, for example, changes the value in cell A11: Range(“A1:D10”).Cells(41)=2000 The third syntax for the Cells property simply returns all cells on the referenced work- sheet. Unlike the other two syntaxes, in this one, the return data is not a single cell. This example uses the ClearContents method on the range returned by using the Cells prop- erty on the active worksheet. The result is that the content of every cell on the worksheet is cleared: ActiveSheet.Cells.ClearContents Chapter 7: Introducing Visual Basic for Applications 183 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 183 The Offset property The Offset property, like the Range and Cells properties, also returns a Range object. But unlike the other two methods that I discussed, the Offset property applies only to a Range object and no other class. Its syntax is as follows: object.Offset(rowOffset, columnOffset) The Offset property takes two arguments that correspond to the relative position from the upper-left cell of the specified Range object. The arguments can be positive (down or to the right), negative (up or to the left), or zero. The example that follows enters a value of 12 into the cell directly below the active cell: ActiveCell.Offset(1,0).Value = 12 The next example enters a value of 15 into the cell directly above the active cell: ActiveCell.Offset(-1,0).Value = 15 If the active cell is in row 1, the Offset property in the preceding example generates an error because it cannot return a Range object that doesn’t exist. The Offset property is quite useful, especially when you use variables within looping pro- cedures. I discuss these topics in the next chapter. When you record a macro using the relative reference mode, Excel uses the Offset prop- erty to reference cells relative to the starting position (that is, the active cell when macro recording begins). For example, I used the macro recorder to generate the following code. I started with the cell pointer in cell B1, entered values into B1:B3, and then returned to B1. Sub Macro1() ActiveCell.FormulaR1C1 = “1” ActiveCell.Offset(1, 0).Range(“A1”).Select ActiveCell.FormulaR1C1 = “2” ActiveCell.Offset(1, 0).Range(“A1”).Select ActiveCell.FormulaR1C1 = “3” ActiveCell.Offset(-2, 0).Range(“A1”).Select End Sub Notice that the macro recorder uses the FormulaR1C1 property. Normally, you want to use the Value property to enter a value into a cell. However, using FormulaR1C1 or even Formula produces the same result. Also notice that the generated code references cell A1 — a cell that was even involved in the macro. This notation is a quirk in the macro recording procedure that makes the code more complex than necessary. You can delete all references to Range(“A1”), and the macro still works perfectly: Part III: Understanding Visual Basic for Applications184 13_044018 ch07.qxp 2/28/07 6:37 PM Page 184 Sub Modified Macro1() ActiveCell.FormulaR1C1 = “1” ActiveCell.Offset(1, 0).Select ActiveCell.FormulaR1C1 = “2” ActiveCell.Offset(1, 0).Select ActiveCell.FormulaR1C1 = “3” ActiveCell.Offset(-2, 0).Select End Sub In fact, here’s a much more efficient version of the macro (which I wrote myself) that doesn’t do any selecting: Sub Macro1() ActiveCell = 1 ActiveCell.Offset(1, 0) = 2 ActiveCell.Offset(2, 0) = 3 End Sub Things to Know about Objects The preceding sections introduced you to objects (including collections), properties, and methods. But I’ve barely scratched the surface. Essential concepts to remember In this section, I note some additional concepts that are essential for would-be VBA gurus. These concepts become clearer when you work with VBA and read subsequent chapters: • Objects have unique properties and methods. Each object has its own set of properties and methods. Some objects, however, share some properties (for example, Name) and some methods (such as Delete). • You can manipulate objects without selecting them. This might be contrary to how you normally think about manipulating objects in Excel. The fact is that it’s usually more efficient to perform actions on objects without select- ing them first. When you record a macro, Excel generally selects the object first. This is not necessary and may actually make your macro run more slowly. • It’s important that you understand the concept of collections. Most of the time, you refer to an object indirectly by referring to the collection that it’s in. For example, to access a Workbook object named Myfile, reference the Workbooks collection as follows: Workbooks(“Myfile.xlsx”) Chapter 7: Introducing Visual Basic for Applications 185 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 185 This reference returns an object, which is the workbook with which you are concerned. • Properties can return a reference to another object. For example, in the following statement, the Font property returns a Font object con- tained in a Range object. Bold is a property of the Font object, not the Range object. Range(“A1”).Font.Bold = True • There can be many different ways to refer to the same object. Assume that you have a workbook named Sales, and it’s the only workbook open. Then assume that this workbook has one worksheet, named Summary. You can refer to the sheet in any of the following ways: Workbooks(“Sales.xlsx”).Worksheets(“Summary”) Workbooks(1).Worksheets(1) Workbooks(1).Sheets(1) Application.ActiveWorkbook.ActiveSheet ActiveWorkbook.ActiveSheet ActiveSheet The method that you use is usually determined by how much you know about the work- space. For example, if more than one workbook is open, the second and third methods are not reliable. If you want to work with the active sheet (whatever it may be), any of the last three methods would work. To be absolutely sure that you’re referring to a spe- cific sheet on a specific workbook, the first method is your best choice. Learning more about objects and properties If this is your first exposure to VBA, you’re probably a bit overwhelmed by objects, proper- ties, and methods. I don’t blame you. If you try to access a property that an object doesn’t have, you get a runtime error, and your VBA code grinds to a screeching halt until you cor- rect the problem. Fortunately, there are several good ways to learn about objects, properties, and methods. READ THE REST OF THE BOOK Don’t forget, the name of this chapter is “Introducing Visual Basic for Applications.” The remainder of this book covers many additional details and provides many useful and infor- mative examples. RECORD YOUR ACTIONS The absolute best way to become familiar with VBA, without question, is to simply turn on the macro recorder and record some actions that you perform in Excel. This is a quick way to learn the relevant objects, properties, and methods for a task. It’s even better if the VBA module in which the code is being recorded is visible while you’re recording. Part III: Understanding Visual Basic for Applications186 13_044018 ch07.qxp 2/28/07 6:37 PM Page 186 USE THE HELP SYSTEM The main source of detailed information about Excel’s objects, methods, and procedures is the Help system. Many people forget about this resource. USE THE OBJECT BROWSER The Object Browser is a handy tool that lists every property and method for every object available. When the VBE is active, you can bring up the Object Browser in any of the fol- lowing three ways: • Press F2. • Choose the View ➪ Object Browser command from the menu. • Click the Object Browser tool on the Standard toolbar. The Object Browser is shown in Figure 7-14. Figure 7-14: The Object Browser is a great reference source. The drop-down list in the upper-left corner of the Object Browser includes a list of all object libraries that you have access to: • Excel itself • MSForms (used to create custom dialog boxes) Chapter 7: Introducing Visual Basic for Applications 187 Part III 13_044018 ch07.qxp 2/28/07 6:37 PM Page 187 • Office (objects common to all Microsoft Office applications) • Stdole (OLE automation objects) • VBA • The current project (the project that’s selected in the Project Explorer) and any work- books referenced by that project Your selection in this upper-left drop-down list determines what is displayed in the Classes window, and your selection in the Classes window determines what is visible in the Members Of window. After you select a library, you can search for a particular text string to get a list of proper- ties and methods that contain the text. You do so by entering the text in the second drop- down list and then clicking the binoculars (Search) icon. For example, assume that you’re working on a project that manipulates cell comments: 1. Select the library of interest. If you’re not sure which object library is appropriate, you can select . 2. Enter Comment in the drop-down list below the library list. 3. Click the binoculars icon to begin the text search. The Search Results window displays the matching text. Select an object to display its classes in the Classes window. Select a class to display its members (properties, methods, and constants). Pay attention to the bottom pane, which shows more information about the object. You can press F1 to go directly to the appropriate help topic. The Object Browser might seem complex at first, but its usefulness to you will increase over time. EXPERIMENT WITH THE IMMEDIATE WINDOW As I describe in the sidebar earlier in this chapter (see “About the Code Examples”), the Immediate window of the VBE is very useful for testing statements and trying out various VBA expressions. I generally keep the Immediate window visible at all times, and I use it frequently to test various expressions and to help in debugging code. Part III: Understanding Visual Basic for Applications188 13_044018 ch07.qxp 2/28/07 6:37 PM Page 188 Chapter VBA Programming Fundamentals In This Chapter In the preceding chapter, I introduce you to Visual Basic for Applications (VBA); now it’s time to get better acquainted. This chapter discusses some of the key language elements and programming concepts in VBA. ◆ Understanding VBA language elements, including variables, data types, constants, and arrays ◆ Using VBA built-in functions ◆ Manipulating objects and collections ◆ Controlling the execution of your procedures If you’ve used other programming languages, much of this information may sound familiar. VBA has a few unique wrinkles, however; so even experienced programmers may find some new information. VBA Language Elements: An Overview In Chapter 7, I present an overview of objects, properties, and methods, but I don’t tell you much about how to manipulate objects so that they do meaningful things. This chapter gently nudges you in that direction by exploring the VBA 8 189 14_044018 ch08.qxp 2/28/07 6:38 PM Page 189 language elements, which are the keywords and control structures that you use to write VBA routines. To get the ball rolling, I start by presenting a simple VBA Sub procedure. The following code, which is stored in a VBA module, calculates the sum of the first 100 positive inte- gers. When the code finishes executing, the procedure displays a message with the result. Sub VBA_Demo() ‘ This is a simple VBA Example Dim Total As Integer, i As Integer Total = 0 For i = 1 To 100 Total = Total + i Next i MsgBox Total End Sub This procedure uses some common VBA language elements, including: • A comment (the line that begins with an apostrophe) • A variable declaration statement (the line that begins with Dim) • Two variables (Total and i) • Two assignment statements (Total = 0 and Total = Total + i) • A looping structure (For-Next) • A VBA function (MsgBox) All these language elements are discussed in subsequent sections of this chapter. NOTE VBA procedures need not manipulate any objects. The preceding procedure, for exam- ple, doesn’t do anything with objects. It simply works with numbers. Part III: Understanding Visual Basic for Applications190 VBA code, which resides in a VBA module, consists of instructions. The accepted practice is to use one instruction per line. This standard is not a requirement, however; you can use a colon to separate multiple instructions on a single line. The following example combines four instructions on one line: Sub OneLine() x= 1: y= 2: z= 3: MsgBox x + y + z End Sub Entering VBA Code 14_044018 ch08.qxp 2/28/07 6:38 PM Page 190 Chapter 8: VBA Programming Fundamentals 191 Part III Most programmers agree that code is easier to read if you use one instruction per line: Sub OneLine() x = 1 y = 2 z = 3 MsgBox x + y + z End Sub Each line can be as long as you like; the VBA module window scrolls to the left when you reach the right side. For lengthy lines, you may want to use VBA’s line continuation sequence: a space followed by an underscore (_).For example: Sub LongLine() SummedValue = _ Worksheets(“Sheet1”).Range(“A1”).Value + _ Worksheets(“Sheet2”).Range(“A1”).Value End Sub When you record macros, Excel often uses underscores to break long statements into multiple lines. After you enter an instruction, VBA performs the following actions to improve readability: • It inserts spaces between operators. If you enter Ans=1+2 (without spaces), for example, VBA converts it to Ans = 1 + 2 • It adjusts the case of the letters for keywords, properties, and methods. If you enter the following text: Result=activesheet.range(“a1”).value=12 VBA converts it to Result = ActiveSheet.Range(“a1”).Value = 12 Notice that text within quotation marks (in this case, “a1”) is not changed. • Because VBA variable names are not case-sensitive, the interpreter by default adjusts the names of all variables with the same letters so that their case matches the case of letters that you most recently typed. For example, if you first specify a variable as myvalue (all lowercase) and then enter the variable as MyValue (mixed case), VBA changes all other occurrences of the variable to MyValue. An exception occurs if you declare the variable with Dim or a similar statement; in this case, the variable name always appears as it was declared. • VBA scans the instruction for syntax errors. If VBA finds an error, it changes the color of the line and might display a message describing the problem. Choose the Visual Basic Editor Tools ➪ Options command to display the Options dialog box, where you control the error color (use the Editor Format tab) and whether the error message is displayed (use the Auto Syntax Check option in the Editor tab). 14_044018 ch08.qxp 2/28/07 6:38 PM Page 191 Comments A comment is descriptive text embedded within your code and ignored by VBA. It’s a good idea to use comments liberally to describe what you’re doing because an instruction’s pur- pose is not always obvious. You can use a complete line for your comment, or you can insert a comment after an instruction on the same line. A comment is indicated by an apostrophe. VBA ignores any text that follows an apostrophe — except when the apostrophe is contained within quota- tion marks — up until the end of the line. For example, the following statement does not contain a comment, even though it has an apostrophe: Msg = “Can’t continue” The following example shows a VBA procedure with three comments: Sub Comments() ‘ This procedure does nothing of value x = 0 ‘x represents nothingness ‘ Display the result MsgBox x End Sub Although the apostrophe is the preferred comment indicator, you can also use the Rem key- word to mark a line as a comment. For example: Rem -- The next statement prompts the user for a filename The Rem keyword is essentially a holdover from old versions of BASIC and is included in VBA for the sake of compatibility. Unlike the apostrophe, Rem can be written only at the beginning of a line, not on the same line as another instruction. TIP Using comments is definitely a good idea, but not all comments are equally beneficial. To be useful, comments should convey information that’s not immediately obvious from reading the code. Otherwise, you’re just chewing up valuable bytes. Following are a few general tips on making the best use of comments: • Use comments to describe briefly the purpose of each procedure that you write. • Use comments to describe changes that you make to a procedure. • Use comments to indicate that you’re using functions or constructs in an unusual or nonstandard manner. Part III: Understanding Visual Basic for Applications192 14_044018 ch08.qxp 2/28/07 6:38 PM Page 192 • Use comments to describe the purpose of variables so that you and other people can decipher otherwise cryptic names. • Use comments to describe workarounds that you develop to overcome Excel bugs or limitations. • Write comments while you code rather than after. TIP In some cases, you might want to test a procedure without including a particular instruc- tion or group of instructions. Instead of deleting the instruction, simply turn it into a comment by inserting an apostrophe at the beginning. VBA then ignores the instruc- tion(s) when the routine is executed. To convert the comment back to an instruction, just delete the apostrophe. The Visual Basic Editor (VBE) Edit toolbar contains two very useful buttons. Select a group of instructions and then click the Comment Block button to convert the instruc- tions to comments. The Uncomment Block button converts a group of comments back to instructions. These buttons are very useful, so you might want to copy them to your Standard toolbar. Variables, Data Types, and Constants VBA’s main purpose in life is to manipulate data. Some data resides in objects, such as worksheet ranges. Other data is stored in variables that you create. A variable is simply a named storage location in your computer’s memory. Variables can accommodate a wide variety of data types — from simple Boolean values (True or False) to large, double-precision values (see the following section). You assign a value to a vari- able by using the equal sign operator (more about this in the upcoming section, “Assignment Statements”). You make your life easier if you get into the habit of making your variable names as descriptive as possible. VBA does, however, have a few rules regarding variable names: • You can use alphabetic characters, numbers, and some punctuation characters, but the first character must be alphabetic. • VBA does not distinguish between case. To make variable names more readable, programmers often use mixed case (for example, InterestRate rather than interestrate). • You cannot use spaces or periods. To make variable names more readable, programmers often use the underscore character (Interest_Rate). Chapter 8: VBA Programming Fundamentals 193 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 193 • Special type declaration characters (#, $, %, &, or !) cannot be embedded in a variable name. • Variable names can be as long as 254 characters — but using such long variable names is not recommended. The following list contains some examples of assignment expressions that use various types of variables. The variable names are to the left of the equal sign. Each statement assigns the value to the right of the equal sign to the variable on the left. x = 1 InterestRate = 0.075 LoanPayoffAmount = 243089.87 DataEntered = False x = x + 1 MyNum = YourNum * 1.25 UserName = “Bob Johnson” DateStarted = #12/14/2006# VBA has many reserved words, which are words that you cannot use for variable or proce- dure names. If you attempt to use one of these words, you get an error message. For exam- ple, although the reserved word Next might make a very descriptive variable name, the following instruction generates a syntax error: Next = 132 Unfortunately, syntax error messages aren’t always very descriptive. The preceding instruction generates this error message: Compile error: Syntax error. It would be nice if the error message were something like Reserved word used as a variable. So if an instruction produces a strange error message, check the VBA Help system to ensure that your variable name doesn’t have a special use in VBA. Defining data types VBA makes life easy for programmers because it can automatically handle all the details involved in dealing with data. Not all programming languages make it so easy. For exam- ple, some languages are strictly typed, which means that the programmer must explicitly define the data type for every variable used. Data type refers to how data is stored in memory — as integers, real numbers, strings, and so on. Although VBA can take care of data typing automatically, it does so at a cost: slower execution and less efficient use of memory. As a result, letting VBA handle data typing may present problems when you’re running large or complex applications. Another advan- tage of explicitly declaring your variables as a particular data type is that VBA can perform some additional error checking at the compile stage. These errors might otherwise be diffi- cult to locate. Part III: Understanding Visual Basic for Applications194 14_044018 ch08.qxp 2/28/07 6:38 PM Page 194 Table 8-1 lists VBA’s assortment of built-in data types. (Note that you can also define cus- tom data types, which I describe later in this chapter in “User-Defined Data Types.”) TABLE 8-1 VBA BUILT-IN DATA TYPES Data Type Bytes Used Range of Values Byte 1 byte 0 to 255 Boolean 2 bytes True or False Integer 2 bytes –32,768 to 32,767 Long 4 bytes –2,147,483,648 to 2,147,483,647 Single 4 bytes –3.402823E38 to –1.401298E-45 (for negative values); 1.401298E-45 to 3.402823E38 (for positive values) Double 8 bytes –1.79769313486232E308 to –4.94065645841247E-324 (negative values); 4.94065645841247E-324 to 1.79769313486232E308 (for positive values) Currency 8 bytes –922,337,203,685,477.5808 to 922,337,203,685,477.5807 Decimal 12 bytes +/–79,228,162,514,264,337,593,543,950,335 with no decimal point; +/–7.9228162514264337593543950335 with 28 places to the right of the decimal Date 8 bytes January 1, 0100 to December 31, 9999 Object 4 bytes Any object reference String (variable 10 bytes + 0 to approximately 2 billion characters length) string length String (fixed Length of 1 to approximately 65,400 characters length) string Variant (with 16 bytes Any numeric value up to the range of a double data numbers) type. It can also hold special values such as Empty, Error, Nothing, and Null. Variant (with 22 bytes + 0 to approximately 2 billion characters) string length User-defined Varies Varies by element Chapter 8: VBA Programming Fundamentals 195 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 195 Part III: Understanding Visual Basic for Applications196 To test whether data typing is important, I developed the following routine, which performs some meaningless calculations in a loop and then displays the procedure’s total execution time: Sub TimeTest() Dim x As Long, y As Long Dim A As Double, B As Double, C As Double Dim i As Long, j As Long Dim StartTime As Date, EndTime As Date ‘ Store the starting time StartTime = Timer ‘ Perform some calculations x = 0 y = 0 For i = 1 To 5000 x = x + 1 y = x + 1 For j = 1 To 5000 A = x + y + i B = y - x - i C = x / y * i Next j Next i ‘ Get ending time EndTime = Timer ‘ Display total time in seconds MsgBox Format(EndTime - StartTime, “0.0”) End Sub On my system, this routine took 5.1 seconds to run (the time will vary, depending on your system’s processor speed). I then commented out the Dim statements, which declare the data types. That is, I turned the Dim statements into comments by adding an apostrophe at the beginning of the lines. As a result, VBA used the default data type, Variant. I ran the procedure again. It took 14.7 seconds, almost three times as long as before. The moral is simple: If you want your VBA applications to run as fast as possible, declare your variables! A workbook that contains this code is available on the companion CD-ROM in a file named timing text.xlsm. Benchmarking Variant Data Types 14_044018 ch08.qxp 2/28/07 6:38 PM Page 196 NOTE The Decimal data type is rather unusual because you cannot actually declare it. In fact, it is a subtype of a variant. You need to use the VBA CDec function to convert a variant to the Decimal data type. Generally, it’s best to use the data type that uses the smallest number of bytes yet still can handle all the data that will be assigned to it. When VBA works with data, execution speed is a function of the number of bytes that VBA has at its disposal. In other words, the fewer bytes used by data, the faster VBA can access and manipulate the data. For worksheet calculation, Excel uses the Double data type, so that’s a good choice for processing numbers in VBA when you don’t want to lose any precision. For integer calculations, you can use the Integer type (which is limited to values less than or equal to 32,767). Otherwise, use the Long data type. In fact, using the Long data type even for values less than 32,767 is recommended, because this data type may be a bit faster than using the Integer type. When dealing with Excel worksheet row numbers, you want to use the Long data type because the number of rows in a worksheet exceeds the maximum value for the Integer data type. Declaring variables If you don’t declare the data type for a variable that you use in a VBA routine, VBA uses the default data type, Variant. Data stored as a Variant acts like a chameleon: It changes type, depending on what you do with it. The following procedure demonstrates how a variable can assume different data types: Sub VariantDemo() MyVar = “123” MyVar = MyVar / 2 MyVar = “Answer: “ & MyVar MsgBox MyVar End Sub In the VariantDemo procedure, MyVar starts out as a three-character string. Then this string is divided by two and becomes a numeric data type. Next, MyVar is appended to a string, converting MyVar back to a string. The MsgBox statement displays the final string: Answer: 61.5. To further demonstrate the potential problems in dealing with Variant data types, try exe- cuting this procedure: Sub VariantDemo2() MyVar = “123” MyVar = MyVar + MyVar MyVar = “Answer: “ & MyVar MsgBox MyVar End Sub Chapter 8: VBA Programming Fundamentals 197 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 197 The message box displays Answer: 123123. This is probably not what you wanted. When dealing with variants that contain text strings, the + operator performs string concatenation. DETERMINING A DATA TYPE You can use the VBA TypeName function to determine the data type of a variable. Here’s a modified version of the previous procedure. This version displays the data type of MyVar at each step. You see that it starts out as a string, is then converted to a double, and finally ends up as a string again. Sub VariantDemo2() MyVar = “123” MsgBox TypeName(MyVar) MyVar = MyVar / 2 MsgBox TypeName(MyVar) MyVar = “Answer: “ & MyVar MsgBox TypeName(MyVar) MsgBox MyVar End Sub Thanks to VBA, the data type conversion of undeclared variables is automatic. This process might seem like an easy way out, but remember that you sacrifice speed and memory — and you run the risk of errors that you may not even know about. Declaring each variable in a procedure before you use it is an excellent habit. Declaring a variable tells VBA its name and data type. Declaring variables provides two main benefits: • Your programs run faster and use memory more efficiently. The default data type, Variant, causes VBA to repeatedly perform time-consuming checks and reserve more memory than necessary. If VBA knows the data type, it doesn’t have to investigate, and it can reserve just enough memory to store the data. • You avoid problems involving misspelled variable names. This assumes that you use Option Explicit to force yourself to declare all variables (see the next section). Say that you use an undeclared variable named CurrentRate. At some point in your routine, however, you insert the statement CurentRate = .075. This misspelled variable name, which is very difficult to spot, will likely cause your routine to give incorrect results. FORCING YOURSELF TO DECLARE ALL VARIABLES To force yourself to declare all the variables that you use, include the following as the first instruction in your VBA module: Option Explicit When this statement is present, VBA will not even execute a procedure if it contains an undeclared variable name. VBA issues the error message shown in Figure 8-1, and you must declare the variable before you can proceed. Part III: Understanding Visual Basic for Applications198 14_044018 ch08.qxp 2/28/07 6:38 PM Page 198 Figure 8-1: VBA’s way of telling you that your procedure contains an undeclared variable. TIP To ensure that the Option Explicit statement is inserted automatically whenever you insert a new VBA module, enable the Require Variable Declaration option in the Editor tab of the VBE Options dialog box (choose Tools ➪ Options). I highly recommend doing so. Be aware, however, that this option does not affect existing modules. Scoping variables A variable’s scope determines in which modules and procedures the variable can be used. Table 8-2 lists the three ways in which a variable can be scoped. TABLE 8-2 VARIABLE SCOPE Scope How a Variable with This Scope Is Declared Single procedure Include a Dim or Static statement within the procedure. Single module Include a Dim or Private statement before the first procedure in a module. All modules Include a Public statement before the first procedure in a module. I discuss each scope further in the following sections. Chapter 8: VBA Programming Fundamentals 199 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 199 LOCAL VARIABLES A local variable is a variable declared within a procedure. Local variables can be used only in the procedure in which they are declared. When the procedure ends, the variable no longer exists, and Excel frees up its memory. If you need the variable to retain its value when the procedure ends, declare it as a Static variable. (See “Static variables,” later in this section.) The most common way to declare a local variable is to place a Dim statement between a Sub statement and an End Sub statement. Dim statements usually are placed right after the Sub statement, before the procedure’s code. NOTE If you’re curious about this word, Dim is a shortened form of Dimension. In old versions of BASIC, this statement was used exclusively to declare the dimensions for an array. In VBA, the Dim keyword is used to declare any variable, not just arrays. The following procedure uses six local variables declared by using Dim statements: Sub MySub() Dim x As Integer Dim First As Long Dim InterestRate As Single Dim TodaysDate As Date Dim UserName As String Dim MyValue ‘ - [The procedure’s code goes here] - End Sub Notice that the last Dim statement in the preceding example doesn’t declare a data type; it simply names the variable. As a result, that variable becomes a variant. You also can declare several variables with a single Dim statement. For example: Dim x As Integer, y As Integer, z As Integer Dim First As Long, Last As Double Part III: Understanding Visual Basic for Applications200 This chapter contains many examples of VBA code, usually presented in the form of simple procedures. These examples demonstrate various concepts as simply as possible. Most of these examples do not perform any particularly useful task; in fact, the task can often be performed in a different (perhaps more efficient) way. In other words, don’t use these examples in your own work. Subsequent chapters provide many more code examples that are useful. A Note about the Examples in This Chapter 14_044018 ch08.qxp 2/28/07 6:38 PM Page 200 CAUTION Unlike some languages, VBA does not let you declare a group of variables to be a partic- ular data type by separating the variables with commas. For example, the following statement, although valid, does not declare all the variables as integers: Dim i, j, k As Integer In VBA, only k is declared to be an integer; the other variables are declared variants. To declare i, j, and k as integers, use this statement: Dim i As Integer, j As Integer, k As Integer If a variable is declared with a local scope, other procedures in the same module can use the same variable name, but each instance of the variable is unique to its own procedure. In general, local variables are the most efficient because VBA frees up the memory that they use when the procedure ends. Chapter 8: VBA Programming Fundamentals 201 Part III Like most other dialects of BASIC, VBA lets you append a character to a variable’s name to indicate the data type. For example, you can declare the MyVar variable as an integer by tacking % onto the name: Dim MyVar% Type-declaration characters exist for most VBA data types. Data types not listed in the following table don’t have type-declaration characters. Data Type Type-Declaration Character Integer % Long & Single ! Double # Currency @ String $ This method of data typing is essentially a holdover from BASIC; it’s better to declare your variables by using the other techniques described in this chapter. I list these type declaration characters here just in case you encounter them in an older program. Another Way of Data-Typing Variables 14_044018 ch08.qxp 2/28/07 6:38 PM Page 201 MODULE-WIDE VARIABLES Sometimes, you want a variable to be available to all procedures in a module. If so, just declare the variable before the module’s first procedure (outside of any procedures or functions). In the following example, the Dim statement is the first instruction in the module. Both Procedure1 and Procedure2 have access to the CurrentValue variable. Dim CurrentValue as Integer Sub Procedure1() ‘ - [Code goes here] - End Sub Sub Procedure2() ‘ - [Code goes here] - End Sub Normally, the value of a module-wide variable does not change when a procedure ends nor- mally (that is, when it reaches the End Sub or End Function statement). An exception is if the procedure is halted with an End statement. When VBA encounters an End statement, all module-wide variables lose their values. PUBLIC VARIABLES To make a variable available to all the procedures in all the VBA modules in a project, declare the variable at the module level (before the first procedure declaration) by using the Public keyword rather than Dim. Here’s an example: Public CurrentRate as Long The Public keyword makes the CurrentRate variable available to any procedure in the VBA project, even those in other modules within the project. You must insert this state- ment before the first procedure in a module (any module). This type of declaration must appear in a standard VBA module, not in a code module for a sheet or a UserForm. STATIC VARIABLES Static variables are a special case. They are declared at the procedure level, and they retain their value when the procedure ends normally. However, if the procedure is halted by an End statement, static variables do lose their values. You declare static variables by using the Static keyword: Sub MySub() Static Counter as Integer - [Code goes here] - End Sub Part III: Understanding Visual Basic for Applications202 14_044018 ch08.qxp 2/28/07 6:38 PM Page 202 Working with constants A variable’s value may change while a procedure is executing (that’s why it’s called a variable). Sometimes, you need to refer to a named value or string that never changes: a constant. Using constants throughout your code in place of hard-coded values or strings is an excel- lent programming practice. For example, if your procedure needs to refer to a specific value (such as an interest rate) several times, it’s better to declare the value as a constant and use the constant’s name rather than its value in your expressions. This technique not only makes your code more readable, it also makes it easier to change should the need arise — you have to change only one instruction rather than several. Chapter 8: VBA Programming Fundamentals 203 Part III Some programmers name variables so that users can identify their data types by just looking at their names. Personally, I don’t use this technique very often because I think it makes the code more difficult to read, but you might find it helpful. The naming convention involves using a standard lowercase prefix for the variable’s name. For example, if you have a Boolean variable that tracks whether a workbook has been saved, you might name the variable bWasSaved. That way, it is clear that the variable is a Boolean variable. The following table lists some standard prefixes for data types: Data Type Prefix Boolean b Integer i Long l Single s Double d Currency c Date/Time dt String str Object obj Variant v User-defined u Variable Naming Conventions 14_044018 ch08.qxp 2/28/07 6:38 PM Page 203 DECLARING CONSTANTS You declare constants with the Const statement. Here are some examples: Const NumQuarters as Integer = 4 Const Rate = .0725, Period = 12 Const ModName as String = “Budget Macros” Public Const AppName as String = “Budget Application” The second example doesn’t declare a data type. Consequently, VBA determines the data type from the value. The Rate variable is a Double, and the Period variable is an Integer. Because a constant never changes its value, you normally want to declare your constants as a specific data type. Like variables, constants also have a scope. If you want a constant to be available within a single procedure only, declare it after the Sub or Function statement to make it a local constant. To make a constant available to all procedures in a module, declare it before the first procedure in the module. To make a constant available to all modules in the work- book, use the Public keyword and declare the constant before the first procedure in a module. For example: Public Const InterestRate As Double = 0.0725 NOTE If your VBA code attempts to change the value of a constant, you get an error (Assignment to constant not permitted). This is what you would expect. A con- stant is a constant, not a variable. USING PREDEFINED CONSTANTS Excel and VBA make available many predefined constants, which you can use without declaring. In fact, you don’t even need to know the value of these constants to use them. The macro recorder generally uses constants rather than actual values. The following pro- cedure uses a built-in constant (xlLandscape) to set the page orientation to landscape for the active sheet: Sub SetToLandscape() ActiveSheet.PageSetup.Orientation = xlLandscape End Sub I discovered the xlLandscape constant by recording a macro. I also could have found this information in the Help system. And, if you have the AutoList Members option turned on, you can often get some assistance while you enter your code (see Figure 8-2). In many cases, VBA lists all the constants that can be assigned to a property. Part III: Understanding Visual Basic for Applications204 14_044018 ch08.qxp 2/28/07 6:38 PM Page 204 Figure 8-2: VBA displays a list of constants that can be assigned to a property. The actual value for xlLandscape is 2 (which you can discover by using the Immediate window). The other built-in constant for changing paper orientation is xlPortrait, which has a value of 1. Obviously, if you use the built-in constants, you don’t really need to know their values. NOTE The Object Browser, which I discuss briefly in Chapter 7, can display a list of all Excel and VBA constants. In the VBE, press F2 to bring up the Object Browser. Working with strings Like Excel, VBA can manipulate both numbers and text (strings). There are two types of strings in VBA: • Fixed-length strings are declared with a specified number of characters. The maximum length is 65,535 characters. • Variable-length strings theoretically can hold up to 2 billion characters. Each character in a string requires 1 byte of storage, plus a small amount of storage for the header of each string. When you declare a variable with a Dim statement as data type String, you can specify the length if you know it (that is, a fixed-length string), or you can let VBA handle it dynamically (a variable-length string). In the following example, the MyString variable is declared to be a string with a maxi- mum length of 50 characters. YourString is also declared as a string; but it’s a variable- length string, so its length is unfixed. Dim MyString As String * 50 Dim YourString As String Chapter 8: VBA Programming Fundamentals 205 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 205 Working with dates You can use a string variable to store a date, but if you do, it’s not a real date (meaning you can’t perform date calculations with it). Using the Date data type is a better way to work with dates. A variable defined as a date uses 8 bytes of storage and can hold dates ranging from January 1, 0100 to December 31, 9999. That’s a span of nearly 10,000 years — more than enough for even the most aggressive financial forecast! The Date data type is also useful for storing time-related data. In VBA, you specify dates and times by enclosing them between two hash marks (#). NOTE The range of dates that VBA can handle is much larger than Excel’s own date range, which begins with January 1, 1900. Therefore, be careful that you don’t attempt to use a date in a worksheet that is outside of Excel’s acceptable date range. Here are some examples of declaring variables and constants as Date data types: Dim Today As Date Dim StartTime As Date Const FirstDay As Date = #1/1/2007# Const Noon = #12:00:00# CAUTION Dates are always defined using month/day/year format, even if your system is set up to display dates in a different format (for example, day/month/year). Part III: Understanding Visual Basic for Applications206 It is commonly known that Excel has a date bug: It incorrectly assumes that the year 1900 is a leap year. Even though there was no February 29, 1900, Excel accepts the following formula and displays the result as the 29th day of February, 1900: =Date(1900,2,29) VBA does not have this date bug. The VBA equivalent of Excel’s DATE function is DateSerial. The following expression (correctly) returns March 1, 1900: DateSerial(1900,2,29) Therefore, Excel’s date serial number system does not correspond exactly to the VBA date serial number system. These two systems return different values for dates between January 1, 1900 and February 28, 1900. About Excel’s Date Bug 14_044018 ch08.qxp 2/28/07 6:38 PM Page 206 If you use a message box to display a date, it is displayed according to your system’s short date format. Similarly, a time is displayed according to your system’s time format (either 12- or 24-hour). You can modify these system settings by using the Regional Settings option in the Windows Control Panel. CD-ROM The companion CD-ROM includes a workbook that contains several VBA worksheet func- tions that enable you to work with dates prior to January 1, 1900. The file is named extended date functions.xlsm. You’ll also find a Word document (extended date functions help.docx) that describes the functions.. Assignment Statements An assignment statement is a VBA instruction that makes a mathematical evaluation and assigns the result to a variable or an object. Excel’s Help system defines expression as “a combination of keywords, operators, variables, and constants that yields a string, number, or object. An expression can perform a calculation, manipulate characters, or test data.” I couldn’t have said it better myself. Much of the work done in VBA involves developing (and debugging) expressions. If you know how to create formulas in Excel, you’ll have no trouble creating expressions in VBA. With a worksheet formula, Excel displays the result in a cell. The result of a VBA expression, on the other hand, can be assigned to a variable or used as a property value. VBA uses the equal sign (=) as its assignment operator. The following are examples of assignment statements (the expressions are to the right of the equal sign): x = 1 x = x + 1 x = (y * 2) / (z * 2) FileOpen = True FileOpen = Not FileOpen Range(“TheYear”).Value = 2007 TIP Expressions can be very complex. You might want to use the line continuation sequence (space followed by an underscore) to make lengthy expressions easier to read. Often, expressions use functions. These functions can be built-in VBA functions, Excel’s worksheet functions, or custom functions that you develop in VBA. I discuss built-in VBA functions later in this chapter (see “Built-in Functions”). Chapter 8: VBA Programming Fundamentals 207 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 207 Operators play a major role in VBA. Familiar operators describe mathematical operations, including addition (+), multiplication (*), division (/), subtraction (–), exponentiation (^), and string concatenation (&). Less-familiar operators are the backslash (\) (used in integer division) and the Mod operator (used in modulo arithmetic). The Mod operator returns the remainder of one number divided by another. For example, the following expression returns 2: 17 Mod 3 VBA also supports the same comparison operators used in Excel formulas: equal to (=), greater than (>), less than (<), greater than or equal to (>=), less than or equal to (<=), and not equal to (<>). The order of precedence for operators in VBA is exactly the same as in Excel (see Table 8-3). And, of course, you can use parentheses to change the natural order of precedence. TABLE 8-3 OPERATOR PRECEDENCE Operator Operation Order of Precedence ^ Exponentiation 1 * and / Multiplication and division 2 + and - Addition and subtraction 3 & Concatenation 4 =, <, >, <=, >=, <> Comparison 5 In the statement that follows, x is assigned the value 10 because the multiplication opera- tor has a higher precedence than the addition operator. x = 4 + 3 * 2 To avoid ambiguity, you might prefer to write the statement as follows: x = 4 + (3 * 2) In addition, VBA provides a full set of logical operators, shown in Table 8-4. For complete details on these operators (including examples), use the VBA Help system. Part III: Understanding Visual Basic for Applications208 14_044018 ch08.qxp 2/28/07 6:38 PM Page 208 TABLE 8-4 VBA LOGICAL OPERATORS Operator What It Does Not Performs a logical negation on an expression And Performs a logical conjunction on two expressions Or Performs a logical disjunction on two expressions Xor Performs a logical exclusion on two expressions Eqv Performs a logical equivalence on two expressions Imp Performs a logical implication on two expressions The following instruction uses the Not operator to toggle the grid-line display in the active window. The DisplayGridlines property takes a value of either True or False. Therefore, using the Not operator changes False to True and True to False. ActiveWindow.DisplayGridlines = _ Not ActiveWindow.DisplayGridlines The following expression performs a logical And operation. The MsgBox statement displays True only when Sheet1 is the active sheet and the active cell is in Row 1. If either or both of these conditions are not true, the MsgBox statement displays False. MsgBox ActiveSheet.Name = “Sheet1” And ActiveCell.Row = 1 The following expression performs a logical Or operation. The MsgBox statement displays True when either Sheet1 or Sheet2 is the active sheet. MsgBox ActiveSheet.Name = “Sheet1” _ Or ActiveSheet.Name = “Sheet2” Arrays An array is a group of elements of the same type that have a common name. You refer to a specific element in the array by using the array name and an index number. For example, you can define an array of 12 string variables so that each variable corresponds to the name of a month. If you name the array MonthNames, you can refer to the first element of the array as MonthNames(0), the second element as MonthNames(1), and so on, up to MonthNames(11). Chapter 8: VBA Programming Fundamentals 209 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 209 Declaring arrays You declare an array with a Dim or Public statement, just as you declare a regular vari- able. You can also specify the number of elements in the array. You do so by specifying the first index number, the keyword To, and the last index number — all inside parentheses. For example, here’s how to declare an array comprising exactly 100 integers: Dim MyArray(1 To 100) As Integer TIP When you declare an array, you need specify only the upper index, in which case VBA assumes that 0 is the lower index. Therefore, the two statements that follow have the same effect: Dim MyArray(0 to 100) As Integer Dim MyArray(100) As Integer In both cases, the array consists of 101 elements. By default, VBA assumes zero-based arrays. If you would like VBA to assume that 1 is the lower index for all arrays that declare only the upper index, include the following state- ment before any procedures in your module: Option Base 1 Declaring multidimensional arrays The array examples in the preceding section are one-dimensional arrays. VBA arrays can have up to 60 dimensions, although it’s rare to need more than three dimensions (a 3-D array). The following statement declares a 100-integer array with two dimensions (2-D): Dim MyArray(1 To 10, 1 To 10) As Integer You can think of the preceding array as occupying a 10 x 10 matrix. To refer to a specific element in a 2-D array, you need to specify two index numbers. For example, here’s how you can assign a value to an element in the preceding array: MyArray(3, 4) = 125 Following is a declaration for a 3-D array that contains 1,000 elements (visualize this array as a cube). Dim MyArray(1 To 10, 1 To 10, 1 To 10) As Integer Part III: Understanding Visual Basic for Applications210 14_044018 ch08.qxp 2/28/07 6:38 PM Page 210 Reference an item within the array by supplying three index numbers: MyArray(4, 8, 2) = 0 Declaring dynamic arrays A dynamic array doesn’t have a preset number of elements. You declare a dynamic array with a blank set of parentheses: Dim MyArray() As Integer Before you can use a dynamic array in your code, however, you must use the ReDim state- ment to tell VBA how many elements are in the array. This is often done by using a vari- able, the value of which isn’t known until the procedure is executing. For example, if the variable x contains a number, you can define the array’s size by using this statement: ReDim MyArray (1 to x) You can use the ReDim statement any number of times, changing the array’s size as often as you need to. When you change an array’s dimensions the existing values are destroyed. If you would like to preserve the existing values, use ReDim Preserve. For example: ReDim Preserve MyArray (1 to y) Arrays crop up later in this chapter when I discuss looping (“Looping blocks of instructions”). Object Variables An object variable is a variable that represents an entire object, such as a range or a work- sheet. Object variables are important for two reasons: • They can simplify your code significantly. • They can make your code execute more quickly. Object variables, like normal variables, are declared with the Dim or Public statement. For example, the following statement declares InputArea as a Range object variable: Dim InputArea As Range Use the Set keyword to assign an object to the variable. For example: Set InputArea = Range(“C16:E16”) Chapter 8: VBA Programming Fundamentals 211 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 211 To see how object variables simplify your code, examine the following procedure, which does not use an object variable: Sub NoObjVar() Worksheets(“Sheet1”).Range(“A1”).Value = 124 Worksheets(“Sheet1”).Range(“A1”).Font.Bold = True Worksheets(“Sheet1”).Range(“A1”).Font.Italic = True End Sub This routine enters a value into cell A1 of Sheet1 on the active workbook and then bold- faces and italicizes the cell’s contents. That’s a lot of typing. To reduce wear and tear on your fingers (and make your code more efficient), you can condense the routine with an object variable: Sub ObjVar() Dim MyCell As Range Set MyCell = Worksheets(“Sheet1”).Range(“A1”) MyCell.Value = 124 MyCell.Font.Bold = True MyCell.Font.Italic = True End Sub After the variable MyCell is declared as a Range object, the Set statement assigns an object to it. Subsequent statements can then use the simpler MyCell reference in place of the lengthy Worksheets(“Sheet1”).Range(“A1”) reference. TIP After an object is assigned to a variable, VBA can access it more quickly than it can a nor- mal, lengthy reference that has to be resolved. So when speed is critical, use object vari- ables. One way to think about this is in terms of dot processing. Every time VBA encounters a dot, as in Sheets(1).Range(“A1”), it takes time to resolve the refer- ence. Using an object variable reduces the number of dots to be processed. The fewer the dots, the faster the processing time. Another way to improve the speed of your code is by using the With-End With construct, which also reduces the number of dots to be processed. I discuss this construct later in this chapter. The true value of object variables becomes apparent when I discuss looping later in this chapter. User-Defined Data Types VBA lets you create custom, or user-defined, data types (a concept much like Pascal records or C structures). A user-defined data type can ease your work with some types of data. For example, if your application deals with customer information, you might want to create a user-defined data type named CustomerInfo, as follows: Part III: Understanding Visual Basic for Applications212 14_044018 ch08.qxp 2/28/07 6:38 PM Page 212 Type CustomerInfo Company As String Contact As String RegionCode As Long Sales As Double End Type NOTE You define custom data types at the top of your module before any procedures. After you create a user-defined data type, you use a Dim statement to declare a variable as that type. Usually, you define an array. For example: Dim Customers(1 To 100) As CustomerInfo Each of the 100 elements in this array consists of four components (as specified by the user-defined data type, CustomerInfo). You can refer to a particular component of the record as follows: Customers(1).Company = “Acme Tools” Customers(1).Contact = “Tim Robertson” Customers(1).RegionCode = 3 Customers(1).Sales = 150674.98 You can also work with an element in the array as a whole. For example, to copy the infor- mation from Customers(1) to Customers(2), use this instruction: Customers(2) = Customers(1) The preceding example is equivalent to the following instruction block: Customers(2).Company = Customers(1).Company Customers(2).Contact = Customers(1).Contact Customers(2).RegionCode = Customers(1).RegionCode Customers(2).Sales = Customers(1).Sales Built-in Functions Like most programming languages, VBA has a variety of built-in functions that simplify calculations and operations. Many VBA functions are similar (or identical) to Excel worksheet functions. For example, the VBA function UCase, which converts a string argu- ment to uppercase, is equivalent to the Excel worksheet function UPPER. Chapter 8: VBA Programming Fundamentals 213 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 213 CROSS-REFERENCE Appendix B contains a complete list of VBA functions, with a brief description of each. All are thoroughly described in the VBA Help system. TIP To get a list of VBA functions while you’re writing your code, type VBA followed by a period (.). The VBE displays a list of all its members, including functions (see Figure 8-3). The functions are preceded by a green icon. If this technique doesn’t work for you, make sure that the Auto List Members option is selected. Choose Tools ➪ Options and then click the Editor tab. Figure 8-3: Displaying a list of VBA functions in the VBE. You use functions in VBA expressions in much the same way that you use functions in worksheet formulas. Here’s a simple procedure that calculates the square root of a variable (using the VBA Sqr function), stores the result in another variable, and then displays the result: Sub ShowRoot() Dim MyValue As Double Dim SquareRoot As Double MyValue = 25 SquareRoot = Sqr(MyValue) MsgBox SquareRoot End Sub The VBA Sqr function is equivalent to the Excel SQRT worksheet function. Part III: Understanding Visual Basic for Applications214 14_044018 ch08.qxp 2/28/07 6:38 PM Page 214 You can use many (but not all) of Excel’s worksheet functions in your VBA code. The WorksheetFunction object, which is contained in the Application object, holds all the worksheet functions that you can call from your VBA procedures. To use a worksheet function in a VBA statement, just precede the function name with Application.WorksheetFunction Chapter 8: VBA Programming Fundamentals 215 Part III The MsgBox function is one of the most useful VBA functions. Many of the examples in this chapter use this function to display the value of a variable. This function often is a good substitute for a simple custom dialog box. It’s also an excellent debugging tool because you can insert MsgBox functions at any time to pause your code and display the result of a calculation or assignment. Most functions return a single value, which you assign to a variable. The MsgBox function not only returns a value, but also displays a dialog box that the user can respond to. The value returned by the MsgBox function represents the user’s response to the dialog. You can use the MsgBox function even when you have no interest in the user’s response but want to take advantage of the message display. The official syntax of the MsgBox function has five arguments (those in square brackets are optional): MsgBox(prompt[, buttons][, title][, helpfile, context]) • prompt: (Required) The message displayed in the pop-up display. • buttons: (Optional) A value that specifies which buttons and which icons, if any, appear in the message box. Use built-in constants — for example, vbYesNo. • title: (Optional) The text that appears in the message box’s title bar. The default is Microsoft Excel. • helpfile: (Optional) The name of the help file associated with the message box. • context: (Optional) The context ID of the help topic. This represents a specific help topic to display. If you use the context argument, you must also use the helpfile argument. You can assign the value returned to a variable, or you can use the function by itself without an assignment statement. This example assigns the result to the variable Ans. Ans = MsgBox(“Continue?”, vbYesNo + vbQuestion, “Tell me”) If Ans = vbNo Then Exit Sub continued The MsgBox Function 14_044018 ch08.qxp 2/28/07 6:38 PM Page 215 The following example demonstrates how to use an Excel worksheet function in a VBA pro- cedure. Excel’s infrequently used ROMAN function converts a decimal number into a Roman numeral. Sub ShowRoman() Dim DecValue As Long Dim RomanValue As String DecValue = 2007 RomanValue = Application.WorksheetFunction.Roman(DecValue) MsgBox RomanValue End Sub When you execute this procedure, the MsgBox function displays the string MMVII. Fans of old movies are often dismayed when they learn that Excel doesn’t have a function to con- vert a Roman numeral to its decimal equivalent. Keep in mind that you cannot use worksheet functions that have an equivalent VBA func- tion. For example, VBA cannot access the Excel SQRT worksheet function because VBA has its own version of that function: Sqr. Therefore, the following statement generates an error: MsgBox Application.WorksheetFunction.Sqrt(123) ‘error CROSS-REFERENCE As I describe in Chapter 10, you can use VBA to create custom worksheet functions that work just like Excel’s built-in worksheet functions. Part III: Understanding Visual Basic for Applications216 continued Notice that I used the sum of two built-in constants (vbYesNo + vbQuestion) for the buttons argument. Using vbYesNo displays two buttons in the message box: one labeled Yes and one labeled No. (See the results in the accompanying figure.) Adding vbQuestion to the argument also displays a question mark icon (see the accompanying figure). When the first statement is executed, Ans contains one of two values, represented by the constant vbYes or vbNo. In this example, if the user clicks the No button, the procedure ends. See Chapter 12 for more information about the MsgBox function. 14_044018 ch08.qxp 2/28/07 6:38 PM Page 216 Manipulating Objects and Collections As an Excel programmer, you’ll spend a lot of time working with objects and collections. Therefore, you want to know the most efficient ways to write your code to manipulate these objects and collections. VBA offers two important constructs that can simplify work- ing with objects and collections: • With-End With constructs • For Each-Next constructs With-End With constructs The With-End With instruction construct enables you to perform multiple operations on a single object. To start understanding how the With-End With construct works, examine the following procedure, which modifies five properties of a selection’s formatting (the selection is assumed to be a Range object): Sub ChangeFont1() Selection.Font.Name = “Cambria” Selection.Font.Bold = True Selection.Font.Italic = True Selection.Font.Size = 12 Selection.Font.Underline = xlUnderlineStyleSingle Selection.Font.ThemeColor = xlThemeColorAccent1 End Sub This procedure can be rewritten using the With-End With construct. The following proce- dure performs exactly like the preceding one: Sub ChangeFont2() With Selection.Font .Name = “Cambria” .Bold = True .Italic = True .Size = 12 .Underline = xlUnderlineStyleSingle .ThemeColor = xlThemeColorAccent1 End With End Sub Some people think that the second incarnation of the procedure is actually more difficult to read. Remember, though, that the objective is increased speed. Although the first version may be more straightforward and easier to understand, a procedure that uses the With-End With construct to change several properties of an object can be faster than the equivalent procedure that explicitly references the object in each statement. Chapter 8: VBA Programming Fundamentals 217 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 217 NOTE When you record a VBA macro, Excel uses the With-End With construct every chance it gets. To see a good example of this construct, try recording your actions while you change the page orientation using the Page Layout ➪ Page Setup ➪ Orientation command. For Each-Next constructs Recall from the preceding chapter that a collection is a group of related objects. For exam- ple, the Workbooks collection is a collection of all open Workbook objects, and there are many other collections that you can work with. Suppose that you want to perform some action on all objects in a collection. Or suppose that you want to evaluate all objects in a collection and take action under certain condi- tions. These are perfect occasions for the For Each-Next construct because you don’t have to know how many elements are in a collection to use the For Each-Next construct. The syntax of the For Each-Next construct is For Each element In collection [instructions] [Exit For] [instructions] Next [element] The following procedure uses the For Each-Next construct with the Worksheets collec- tion in the active workbook. When you execute the procedure, the MsgBox function dis- plays each worksheet’s Name property. (If there are five worksheets in the active workbook, the MsgBox function is called five times.) Sub CountSheets() Dim Item as Worksheet For Each Item In ActiveWorkbook.Worksheets MsgBox Item.Name Next Item End Sub NOTE In the preceding example, Item is an object variable (more specifically, a Worksheet object). There’s nothing special about the name Item; you can use any valid variable name in its place. The next example uses For Each-Next to cycle through all objects in the Windows collec- tion and count the number of windows that are hidden. Sub HiddenWindows() Dim Cnt As Integer Part III: Understanding Visual Basic for Applications218 14_044018 ch08.qxp 2/28/07 6:38 PM Page 218 Dim Win As Window Cnt = 0 For Each Win In Windows If Not Win.Visible Then Cnt = Cnt + 1 Next Win MsgBox Cnt & “ hidden windows.” End Sub For each window, if the window is hidden, the Cnt variable is incremented. When the loop ends, the message box displays the value of Cnt. Here’s an example that closes all workbooks except the active workbook. This procedure uses the If-Then construct to evaluate each workbook in the Workbooks collection. Sub CloseInactive() Dim Book as Workbook For Each Book In Workbooks If Book.Name <> ActiveWorkbook.Name Then Book.Close Next Book End Sub A common use for the For Each-Next construct is to loop through all cells in a range. The next example of For Each-Next is designed to be executed after the user selects a range of cells. Here, the Selection object acts as a collection that consists of Range objects because each cell in the selection is a Range object. The procedure evaluates each cell and uses the VBA UCase function to convert its contents to uppercase. (Numeric cells are not affected.) Sub MakeUpperCase() Dim Cell as Range For Each Cell In Selection Cell.Value = UCase(Cell.Value) Next Cell End Sub VBA provides a way to exit a For-Next loop before all the elements in the collection are evaluated. Do this with an Exit For statement. The example that follows selects the first negative value in Row 1 of the active sheet. Sub SelectNegative() Dim Cell As Range For Each Cell In Range(“1:1”) If Cell.Value < 0 Then Cell.Select Exit For End If Next Cell End Sub Chapter 8: VBA Programming Fundamentals 219 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 219 This example uses an If-Then construct to check the value of each cell. If a cell is nega- tive, it is selected, and then the loop ends when the Exit For statement is executed. Controlling Code Execution Some VBA procedures start at the top and progress line by line to the bottom. Macros that you record, for example, always work in this fashion. Often, however, you need to control the flow of your routines by skipping over some statements, executing some statements multiple times, and testing conditions to determine what the routine does next. The preceding section describes the For Each-Next construct, which is a type of loop. This section discusses the additional ways of controlling the execution of your VBA procedures: • GoTo statements • If-Then constructs • Select Case constructs • For-Next loops • Do While loops • Do Until loops GoTo statements The most straightforward way to change the flow of a program is to use a GoTo statement. This statement simply transfers program execution to a new instruction, which must be preceded by a label (a text string followed by a colon, or a number with no colon). VBA pro- cedures can contain any number of labels, but a GoTo statement cannot branch outside of a procedure. The following procedure uses the VBA InputBox function to get the user’s name. If the name is not Howard, the procedure branches to the WrongName label and ends. Otherwise, the procedure executes some additional code. The Exit Sub statement causes the proce- dure to end. Sub GoToDemo() UserName = InputBox(“Enter Your Name:”) If UserName <> “Howard” Then GoTo WrongName MsgBox (“Welcome Howard...”) ‘ -[More code here] - Exit Sub WrongName: MsgBox “Sorry. Only Howard can run this.” End Sub Part III: Understanding Visual Basic for Applications220 14_044018 ch08.qxp 2/28/07 6:38 PM Page 220 This simple procedure works, but it’s not an example of good programming. In general, you should use the GoTo statement only when there is no other way to perform an action. In fact, the only time you really need to use a GoTo statement in VBA is for error handling (refer to Chapter 9). Finally, it goes without saying that the preceding example is not intended to demonstrate an effective security technique! If-Then constructs Perhaps the most commonly used instruction grouping in VBA is the If-Then construct. This common instruction is one way to endow your applications with decision-making capa- bility. Good decision making is the key to writing successful programs. The basic syntax of the If-Then construct is If condition Then true_instructions [Else false_instructions] The If-Then construct is used to execute one or more statements conditionally. The Else clause is optional. If included, the Else clause lets you execute one or more instructions when the condition that you’re testing is not True. The following procedure demonstrates an If-Then structure without an Else clause. The example deals with time, and VBA uses a date-and-time serial number system similar to Excel’s. The time of day is expressed as a fractional value — for example, noon is repre- sented as .5. The VBA Time function returns a value that represents the time of day, as reported by the system clock. In the following example, a message is displayed if the time is before noon. If the current system time is greater than or equal to .5, the procedure ends, and nothing happens. Sub GreetMe1() If Time < 0.5 Then MsgBox “Good Morning” End Sub Another way to code this routine is to use multiple statements, as follows: Sub GreetMe1a() If Time < 0.5 Then MsgBox “Good Morning” End If End Sub Notice that the If statement has a corresponding End If statement. In this example, only one statement is executed if the condition is True. You can, however, place any number of statements between the If and End If statements. Chapter 8: VBA Programming Fundamentals 221 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 221 If you want to display a different greeting when the time of day is after noon, add another If-Then statement, like so: Sub GreetMe2() If Time < 0.5 Then MsgBox “Good Morning” If Time >= 0.5 Then MsgBox “Good Afternoon” End Sub Notice that I used >= (greater than or equal to) for the second If-Then statement. This covers the remote chance that the time is precisely 12:00 noon. Another approach is to use the Else clause of the If-Then construct. For example, Sub GreetMe3() If Time < 0.5 Then MsgBox “Good Morning” Else _ MsgBox “Good Afternoon” End Sub Notice that I used the line continuation sequence; If-Then-Else is actually a single statement. If you need to execute multiple statements based on the condition, use this form: Sub GreetMe3a() If Time < 0.5 Then MsgBox “Good Morning” ‘ Other statements go here Else MsgBox “Good Afternoon” ‘ Other statements go here End If End Sub If you need to expand a routine to handle three conditions (for example, morning, after- noon, and evening), you can use either three If-Then statements or a form that uses ElseIf. The first approach is the simpler: Sub GreetMe4() If Time < 0.5 Then MsgBox “Good Morning” If Time >= 0.5 And Time < 0.75 Then MsgBox “Good Afternoon” If Time >= 0.75 Then MsgBox “Good Evening” End Sub The value 0.75 represents 6:00 p.m. — three-quarters of the way through the day and a good point at which to call it an evening. In the preceding examples, every instruction in the procedure gets executed, even if the first condition is satisfied (that is, it’s morning). A more efficient procedure would include a structure that ends the routine when a condition is found to be True. For example, it Part III: Understanding Visual Basic for Applications222 14_044018 ch08.qxp 2/28/07 6:38 PM Page 222 might display the Good Morning message in the morning and then exit without evaluating the other, superfluous conditions. True, the difference in speed is inconsequential when you design a procedure as small as this routine. But for more complex applications, you need another syntax: If condition Then [true_instructions] [ElseIf condition-n Then [alternate_instructions]] [Else [default_instructions]] End If Here’s how you can use this syntax to rewrite the GreetMe procedure: Sub GreetMe5() If Time < 0.5 Then MsgBox “Good Morning” ElseIf Time >= 0.5 And Time < 0.75 Then MsgBox “Good Afternoon” Else MsgBox “Good Evening” End If End Sub With this syntax, when a condition is True, the conditional statements are executed and the If-Then construct ends. In other words, the extraneous conditions are not evaluated. Although this syntax makes for greater efficiency, some find the code to be more difficult to understand. The following procedure demonstrates yet another way to code this example. It uses nested If-Then-Else constructs (without using ElseIf). This procedure is efficient and also easy to understand. Note that each If statement has a corresponding End If statement. Sub GreetMe6() If Time < 0.5 Then MsgBox “Good Morning” Else If Time >= 0.5 And Time < 0.75 Then MsgBox “Good Afternoon” Else If Time >= 0.75 Then MsgBox “Good Evening” End If End If End If End Sub Chapter 8: VBA Programming Fundamentals 223 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 223 The following is another example that uses the simple form of the If-Then construct. This procedure prompts the user for a value for Quantity and then displays the appropriate discount based on that value. Note that Quantity is declared as a Variant data type. This is because Quantity contains an empty string (not a numeric value) if the InputBox is cancelled. To keep it simple, this procedure does not perform any other error checking. For example, it does not ensure that the quantity entered is a non-negative numeric value. Sub Discount1() Dim Quantity As Variant Dim Discount As Double Quantity = InputBox(“Enter Quantity: “) If Quantity = “” Then Exit Sub If Quantity >= 0 Then Discount = 0.1 If Quantity >= 25 Then Discount = 0.15 If Quantity >= 50 Then Discount = 0.2 If Quantity >= 75 Then Discount = 0.25 MsgBox “Discount: “ & Discount End Sub Notice that each If-Then statement in this procedure is always executed, and the value for Discount can change. The final value, however, is the desired value. The following procedure is the previous one rewritten to use the alternate syntax. In this case, the procedure ends after executing the True instruction block. Sub Discount2() Dim Quantity As Variant Dim Discount As Double Quantity = InputBox(“Enter Quantity: “) If Quantity = “” Then Exit Sub If Quantity >= 0 And Quantity < 25 Then Discount = 0.1 ElseIf Quantity < 50 Then Discount = 0.15 ElseIf Quantity < 75 Then Discount = 0.2 Else Discount = 0.25 End If MsgBox “Discount: “ & Discount End Sub I find nested If-Then structures rather cumbersome. As a result, I usually use the If- Then structure only for simple binary decisions. When you need to choose among three or more alternatives, the Select Case structure (discussed next) is often a better construct to use. Part III: Understanding Visual Basic for Applications224 14_044018 ch08.qxp 2/28/07 6:38 PM Page 224 Select Case constructs The Select Case construct is useful for choosing among three or more options. This con- struct also works with two options and is a good alternative to If-Then-Else. The syntax for Select Case is as follows: Select Case testexpression [Case expressionlist-n [instructions-n]] [Case Else [default_instructions]] End Select The following example of a Select Case construct shows another way to code the GreetMe examples that I presented in the preceding section: Sub GreetMe() Dim Msg As String Select Case Time Case Is < 0.5 Msg = “Good Morning” Case 0.5 To 0.75 Msg = “Good Afternoon” Case Else Chapter 8: VBA Programming Fundamentals 225 Part III VBA offers an alternative to the If-Then construct: the IIf function. This function takes three arguments and works much like Excel’s IF worksheet function. The syntax is IIf(expr, truepart, falsepart) • expr: (Required) Expression you want to evaluate. • truepart: (Required) Value or expression returned if expr is True. • falsepart: (Required) Value or expression returned if expr is False. The following instruction demonstrates the use of the IIf function. The message box displays Zero if cell A1 contains a zero or is empty and displays Nonzero if cell A1 contains anything else. MsgBox IIf(Range(“A1”) = 0, “Zero”, “Nonzero”) It’s important to understand that the third argument (falsepart) is always evaluated, even if the first argument (expr) is True. Therefore, the following statement generates an error if the value of n is 0 (zero): MsgBox IIf(n = 0, 0, 1 / n) VBA’s IIf Function 14_044018 ch08.qxp 2/28/07 6:38 PM Page 225 Msg = “Good Evening” End Select MsgBox Msg End Sub And here’s a rewritten version of the Discount example using a Select Case construct. This procedure assumes that Quantity is always an integer value. For simplicity, the pro- cedure performs no error checking. Sub Discount3() Dim Quantity As Variant Dim Discount As Double Quantity = InputBox(“Enter Quantity: “) Select Case Quantity Case “” Exit Sub Case 0 To 24 Discount = 0.1 Case 25 To 49 Discount = 0.15 Case 50 To 74 Discount = 0.2 Case Is >= 75 Discount = 0.25 End Select MsgBox “Discount: “ & Discount End Sub The Case statement also can use a comma to separate multiple values for a single case. The following procedure uses the VBA WeekDay function to determine whether the current day is a weekend (that is, the Weekday function returns 1 or 7). The procedure then dis- plays an appropriate message. Sub GreetUser1() Select Case Weekday(Now) Case 1, 7 MsgBox “This is the weekend” Case Else MsgBox “This is not the weekend” End Select End Sub The following example shows another way to code the previous procedure: Sub GreetUser2() Select Case Weekday(Now) Case 2, 3, 4, 5, 6 MsgBox “This is not the weekend” Part III: Understanding Visual Basic for Applications226 14_044018 ch08.qxp 2/28/07 6:38 PM Page 226 Case Else MsgBox “This is the weekend” End Select End Sub Any number of instructions can be written below each Case statement, and they all are executed if that case evaluates to True. If you use only one instruction per case, as in the preceding example, you might want to put the instruction on the same line as the Case keyword (but don’t forget the VBA statement-separator character, the colon). This tech- nique makes the code more compact. For example: Sub Discount3() Dim Quantity As Variant Dim Discount As Double Quantity = InputBox(“Enter Quantity: “) Select Case Quantity Case “”: Exit Sub Case 0 To 24: Discount = 0.1 Case 25 To 49: Discount = 0.15 Case 50 To 74: Discount = 0.2 Case Is >= 75: Discount = 0.25 End Select MsgBox “Discount: “ & Discount End Sub TIP VBA exits a Select Case construct as soon as a True case is found. Therefore, for max- imum efficiency, you should check the most likely case first. Select Case structures can also be nested. The following procedure, for example, uses the VBA TypeName function to determine what is selected (a range, nothing, or anything else). If a range is selected, the procedure executes a nested Select Case and tests for the number of cells in the range. If one cell is selected, it displays One cell is selected. Otherwise, it displays a message with the number of selected rows. Sub SelectionType() Select Case TypeName(Selection) Case “Range” Select Case Selection.Count Case 1 MsgBox “One cell is selected” Case Else MsgBox Selection.Rows.Count & “ rows” End Select Case “Nothing” MsgBox “Nothing is selected” Chapter 8: VBA Programming Fundamentals 227 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 227 Case Else MsgBox “Something other than a range” End Select End Sub This procedure also demonstrates the use of Case Else, a catch-all case. You can nest Select Case constructs as deeply as you need, but make sure that each Select Case statement has a corresponding End Select statement. This procedure demonstrates the value of using indentation in your code to clarify the structure. For example, take a look at the same procedure without the indentations: Sub SelectionType() Select Case TypeName(Selection) Case “Range” Select Case Selection.Count Case 1 MsgBox “One cell is selected” Case Else MsgBox Selection.Rows.Count & “ rows” End Select Case “Nothing” MsgBox “Nothing is selected” Case Else MsgBox “Something other than a range” End Select End Sub Fairly incomprehensible, eh? Looping blocks of instructions Looping is the process of repeating a block of instructions. You might know the number of times to loop, or the number could be determined by the values of variables in your program. The following code, which enters consecutive numbers into a range, demonstrates what I call a bad loop. The procedure uses two variables to store a starting value (StartVal) and the total number of cells to fill (NumToFill). This loop uses the GoTo statement to control the flow. If the Cnt variable, which keeps track of how many cells are filled, is less than the value of NumToFill, program control loops back to DoAnother. Sub BadLoop() Dim StartVal As Integer Dim NumToFill As Integer Dim Cnt As Integer StartVal = 1 NumToFill = 100 ActiveCell.Value = StartVal Part III: Understanding Visual Basic for Applications228 14_044018 ch08.qxp 2/28/07 6:38 PM Page 228 Cnt = 1 DoAnother: ActiveCell.Offset(Cnt, 0).Value = StartVal + Cnt Cnt = Cnt + 1 If Cnt < NumToFill Then GoTo DoAnother Else Exit Sub End Sub This procedure works as intended, so why is it an example of bad looping? Programmers generally frown on using a GoTo statement when not absolutely necessary. Using GoTo statements to loop is contrary to the concept of structured coding (see the “What Is Struc- tured Programming?” sidebar). In fact, a GoTo statement makes the code much more diffi- cult to read because it’s almost impossible to represent a loop using line indentations. In addition, this type of unstructured loop makes the procedure more susceptible to error. Furthermore, using lots of labels results in spaghetti code — code that appears to have lit- tle or no structure and flows haphazardly. Because VBA has several structured looping commands, you almost never have to rely on GoTo statements for your decision making. FOR-NEXT LOOPS The simplest type of a good loop is a For-Next loop. Its syntax is For counter = start To end [Step stepval] [instructions] [Exit For] [instructions] Next [counter] Following is an example of a For-Next loop that doesn’t use the optional Step value or the optional Exit For statement. This routine executes the Sum = Sum + Sqr(Count) statement 100 times and displays the result — that is, the sum of the square roots of the first 100 integers. Sub SumSquareRoots() Dim Sum As Double Dim Count As Integer Sum = 0 For Count = 1 To 100 Sum = Sum + Sqr(Count) Next Count MsgBox Sum End Sub In this example, Count (the loop counter variable) starts out as 1 and increases by 1 each time the loop repeats. The Sum variable simply accumulates the square roots of each value of Count. Chapter 8: VBA Programming Fundamentals 229 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 229 CAUTION When you use For-Next loops, it’s important to understand that the loop counter is a normal variable — nothing special. As a result, it’s possible to change the value of the loop counter within the block of code executed between the For and Next statements. This is, however, a bad practice and can cause unpredictable results. In fact, you should take precautions to ensure that your code does not change the loop counter. You can also use a Step value to skip some values in the loop. Here’s the same procedure rewritten to sum the square roots of the odd numbers between 1 and 100: Sub SumOddSquareRoots() Dim Sum As Double Dim Count As Integer Sum = 0 For Count = 1 To 100 Step 2 Sum = Sum + Sqr(Count) Next Count MsgBox Sum End Sub In this procedure, Count starts out as 1 and then takes on values of 3, 5, 7, and so on. The final value of Count used within the loop is 99. When the loop ends, the value of Count is 101. Part III: Understanding Visual Basic for Applications230 Hang around with programmers, and sooner or later you’ll hear the term structured programming. You’ll also discover that structured programs are considered superior to unstructured programs. So what is structured programming? And can you do it with VBA? The basic premise of structured programming is that a routine or code segment should have only one entry point and one exit point. In other words, a body of code should be a standalone unit, and program control should not jump into or exit from the middle of this unit. As a result, structured programming rules out the GoTo statement. When you write structured code, your program progresses in an orderly manner and is easy to follow — as opposed to spaghetti code, in which a program jumps around. A structured program is easier to read and understand than an unstructured one. More important, it’s also easier to modify. VBA is a structured language. It offers standard structured constructs, such as If- Then-Else and Select Case and the For-Next, Do Until, and Do While loops. Furthermore, VBA fully supports modular code construction. If you’re new to programming, it’s a good idea to form good structured-programming habits early. What Is Structured Programming? 14_044018 ch08.qxp 2/28/07 6:38 PM Page 230 A Step value in a For-Next loop can also be negative. The procedure that follows deletes Rows 2, 4, 6, 8, and 10 of the active worksheet: Sub DeleteRows() Dim RowNum As Long For RowNum = 10 To 2 Step -2 Rows(RowNum).Delete Next RowNum End Sub You may wonder why I used a negative Step value in the DeleteRows procedure. If you use a positive Step value, as shown in the following procedure, incorrect rows are deleted. That’s because the row numbers below a deleted row get a new row number. For example, when Row 2 is deleted, Row 3 becomes the new Row 2. Using a negative Step value ensures that the correct rows are deleted. Sub DeleteRows2() Dim RowNum As Long For RowNum = 2 To 10 Step 2 Rows(RowNum).Delete Next RowNum End Sub The following procedure performs the same task as the BadLoop example found at the beginning of the “Looping blocks of instructions” section. I eliminate the GoTo statement, however, converting a bad loop into a good loop that uses the For-Next structure. Sub GoodLoop() Dim StartVal As Integer Dim NumToFill As Integer Dim Cnt As Integer StartVal = 1 NumToFill = 100 For Cnt = 0 To NumToFill - 1 ActiveCell.Offset(Cnt, 0).Value = StartVal + Cnt Next Cnt End Sub For-Next loops can also include one or more Exit For statements within the loop. When this statement is encountered, the loop terminates immediately and control passes to the statement following the Next statement of the current For-Next loop. The following example demonstrates use of the Exit For statement. This procedure determines which cell has the largest value in Column A of the active worksheet: Sub ExitForDemo() Dim MaxVal As Double Dim Row As Long MaxVal = Application.WorksheetFunction.Max(Range(“A:A”)) Chapter 8: VBA Programming Fundamentals 231 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 231 For Row = 1 To 1048576 If Cells(Row, 1).Value = MaxVal Then Exit For End If Next Row MsgBox “Max value is in Row “ & Row Cells(Row, 1).Activate End Sub The maximum value in the column is calculated by using the Excel MAX function, and the value is assigned to the MaxVal variable. The For-Next loop checks each cell in the col- umn. If the cell being checked is equal to MaxVal, the Exit For statement terminates the loop and the statements following the Next statement are executed. These statements dis- play the row of the maximum value and activate the cell. NOTE The ExitForDemo procedure is presented to demonstrate how to exit from a For- Next loop. However, it is not the most efficient way to activate the largest value in a range. In fact, a single statement does the job: Range(“A:A”).Find(Application.WorksheetFunction.Max _ (Range(“A:A”))).Activate The previous examples use relatively simple loops. But you can have any number of state- ments in the loop, and you can even nest For-Next loops inside other For-Next loops. Here’s an example that uses nested For-Next loops to initialize a 10 x 10 x 10 array with the value –1. When the procedure is finished, each of the 1,000 elements in MyArray con- tains –1. Sub NestedLoops() Dim MyArray(1 to 10, 1 to 10, 1 to 10) Dim i As Integer, j As Integer, k As Integer For i = 1 To 10 For j = 1 To 10 For k = 1 To 10 MyArray(i, j, k) = -1 Next k Next j Next i End Sub DOWHILE LOOPS This section describes another type of looping structure available in VBA. Unlike a For- Next loop, a Do While loop executes as long as a specified condition is met. Part III: Understanding Visual Basic for Applications232 14_044018 ch08.qxp 2/28/07 6:38 PM Page 232 A Do While loop can have either of two syntaxes: Do [While condition] [instructions] [Exit Do] [instructions] Loop or Do [instructions] [Exit Do] [instructions] Loop [While condition] As you can see, VBA lets you put the While condition at the beginning or the end of the loop. The difference between these two syntaxes involves the point in time when the condi- tion is evaluated. In the first syntax, the contents of the loop may never be executed. In the second syntax, the statements inside the loop are always executed at least one time. The following examples insert a series of dates into the active worksheet. The dates corre- spond to the days in the current month, and the dates are entered in a column beginning at the active cell. NOTE These examples use some VBA date-related functions: • Date returns the current date. • Month returns the month number for a date supplied as its argument. • DateSerial returns a date for the year, month, and day supplied as arguments. The first example demonstrates a Do While loop that tests the condition at the beginning of the loop: The EnterDates1 procedure writes the dates of the current month to a work- sheet column, beginning with the active cell. Sub EnterDates1() ‘ Do While, with test at the beginning Dim TheDate As Date TheDate = DateSerial(Year(Date), Month(Date), 1) Do While Month(TheDate) = Month(Date) ActiveCell = TheDate TheDate = TheDate + 1 ActiveCell.Offset(1, 0).Activate Loop End Sub Chapter 8: VBA Programming Fundamentals 233 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 233 This procedure uses a variable, TheDate, which contains the dates that are written to the worksheet. This variable is initialized with the first day of the current month. Inside of the loop, the value of TheDate is entered into the active cell, TheDate is incremented, and the next cell is activated. The loop continues while the month of TheDate is the same as the month of the current date. The following procedure has the same result as the EnterDates1 procedure, but it uses the second Do While loop syntax, which checks the condition at the end of the loop. Sub EnterDates2() ‘ Do While, with test at the end Dim TheDate As Date TheDate = DateSerial(Year(Date), Month(Date), 1) Do ActiveCell = TheDate TheDate = TheDate + 1 ActiveCell.Offset(1, 0).Activate Loop While Month(TheDate) = Month(Date) End Sub The following is another Do While loop example. This procedure opens a text file, reads each line, converts the text to uppercase, and then stores it in the active sheet, beginning with cell A1 and continuing down the column. The procedure uses the VBA EOF function, which returns True when the end of the file has been reached. The final statement closes the text file. Sub DoWhileDemo1() Dim LineCt As Long Dim LineOfText As String Open “c:\data\textfile.txt” For Input As #1 LineCt = 0 Do While Not EOF(1) Line Input #1, LineOfText Range(“A1”).Offset(LineCt, 0) = UCase(LineOfText) LineCt = LineCt + 1 Loop Close #1 End Sub CROSS-REFERENCE For additional information about reading and writing text files using VBA, see Chapter 27. Do While loops can also contain one or more Exit Do statements. When an Exit Do state- ment is encountered, the loop ends immediately and control passes to the statement fol- lowing the Loop statement. Part III: Understanding Visual Basic for Applications234 14_044018 ch08.qxp 2/28/07 6:38 PM Page 234 DOUNTIL LOOPS The Do Until loop structure is very similar to the Do While structure. The difference is evident only when the condition is tested. In a Do While loop, the loop executes while the condition is True; in a Do Until loop, the loop executes until the condition is True. Do Until also has two syntaxes: Do [Until condition] [instructions] [Exit Do] [instructions] Loop or Do [instructions] [Exit Do] [instructions] Loop [Until condition] The two examples that follow perform the same action as the Do While date entry exam- ples in the previous section. The difference in these two procedures is where the condition is evaluated (at the beginning or at the end of the loop). Sub EnterDates3() ‘ Do Until, with test at beginning Dim TheDate As Date TheDate = DateSerial(Year(Date), Month(Date), 1) Do Until Month(TheDate) <> Month(Date) ActiveCell = TheDate TheDate = TheDate + 1 ActiveCell.Offset(1, 0).Activate Loop End Sub Sub EnterDates4() ‘ Do Until, with test at end Dim TheDate As Date TheDate = DateSerial(Year(Date), Month(Date), 1) Do ActiveCell = TheDate TheDate = TheDate + 1 ActiveCell.Offset(1, 0).Activate Loop Until Month(TheDate) <> Month(Date) End Sub Chapter 8: VBA Programming Fundamentals 235 Part III 14_044018 ch08.qxp 2/28/07 6:38 PM Page 235 The following example was originally presented for the Do While loop but has been rewrit- ten to use a Do Until loop. The only difference is the line with the Do statement. This example makes the code a bit clearer because it avoids the negative required in the Do While example. Sub DoUntilDemo1() Dim LineCt As Long Dim LineOfText As String Open “c:\data\textfile.txt” For Input As #1 LineCt = 0 Do Until EOF(1) Line Input #1, LineOfText Range(“A1”).Offset(LineCt, 0) = UCase(LineOfText) LineCt = LineCt + 1 Loop Close #1 End Sub NOTE VBA supports yet another type of loop, While Wend. This looping structure is included primarily for compatibility purposes. I mention it here in case you ever encounter such a loop. Here’s how the date entry procedure looks when it’s coded to use a While Wend loop: Sub EnterDates5() Dim TheDate As Date TheDate = DateSerial(Year(Date), Month(Date), 1) While Month(TheDate) = Month(Date) ActiveCell = TheDate TheDate = TheDate + 1 ActiveCell.Offset(1, 0).Activate Wend End Sub Part III: Understanding Visual Basic for Applications236 14_044018 ch08.qxp 2/28/07 6:38 PM Page 236 Chapter Working with VBA Sub Procedures In This Chapter A procedure holds a group of Visual Basic for Applications (VBA) statements that accomplishes a desired task. Most VBA code is contained in procedures. This chapter focuses on Sub procedures. ◆ Declaring and creating VBA Sub procedures ◆ Executing procedures ◆ Passing arguments to a procedure ◆ Using error-handling techniques ◆ An example of developing a useful procedure CROSS-REFERENCE VBA also supports Function procedures, which I discuss in Chapter 10. Chapter 11 has many additional examples of procedures, both Sub and Function, that you can incorporate into your work. About Procedures A procedure is a series of VBA statements that resides in a VBA module, which you access in the Visual Basic Editor (VBE). A module can hold any number of procedures. 9 237 15_044018 ch09.qxp 2/28/07 6:39 PM Page 237 You have a number of ways to call, or execute, procedures. A procedure is executed from beginning to end, but it can also be ended prematurely. TIP A procedure can be any length, but many people prefer to avoid creating extremely long procedures that perform many different operations. You may find it easier to write sev- eral smaller procedures, each with a single purpose. Then, design a main procedure that calls those other procedures. This approach can make your code easier to maintain. Some procedures are written to receive arguments. An argument is simply information that is used by the procedure and that is passed to the procedure when it is executed. Procedure arguments work much like the arguments that you use in Excel worksheet func- tions. Instructions within the procedure generally perform logical operations on these arguments, and the results of the procedure are usually based on those arguments. Declaring a Sub procedure A procedure declared with the Sub keyword must adhere to the following syntax: [Private | Public][Static] Sub name ([arglist]) [instructions] [Exit Sub] [instructions] End Sub • Private: (Optional) Indicates that the procedure is accessible only to other procedures in the same module. • Public: (Optional) Indicates that the procedure is accessible to all other procedures in all other modules in the workbook. If used in a module that contains an Option Private Module statement, the procedure is not available outside the project. • Static: (Optional) Indicates that the procedure’s variables are preserved when the pro- cedure ends. • Sub: (Required) The keyword that indicates the beginning of a procedure. • name: (Required) Any valid procedure name. • arglist: (Optional) Represents a list of variables, enclosed in parentheses, that receive arguments passed to the procedure. Use a comma to separate arguments. If the procedure uses no arguments, a set of empty parentheses is required. • instructions: (Optional) Represents valid VBA instructions. • Exit Sub: (Optional) A statement that forces an immediate exit from the procedure prior to its formal completion. • End Sub: (Required) Indicates the end of the procedure. Part III: Understanding Visual Basic for Applications238 15_044018 ch09.qxp 2/28/07 6:39 PM Page 238 NOTE With a few exceptions, all VBA instructions in a module must be contained within proce- dures. Exceptions include module-level variable declarations, user-defined data type def- initions, and a few other instructions that specify module-level options (for example, Option Explicit). Scoping a procedure In the preceding chapter, I note that a variable’s scope determines the modules and proce- dures in which the variable can be used. Similarly, a procedure’s scope determines which other procedures can call it. PUBLIC PROCEDURES By default, procedures are public — that is, they can be called by other procedures in any module in the workbook. It’s not necessary to use the Public keyword, but programmers often include it for clarity. The following two procedures are both public: Sub First() ‘ ... [code goes here] ... End Sub Public Sub Second() ‘ ... [code goes here] ... End Sub PRIVATE PROCEDURES Private procedures can be called by other procedures in the same module but not by proce- dures in other modules. Chapter 9: Working with VBA Sub Procedures 239 Part III Every procedure must have a name. The rules governing procedure names are generally the same as for variable names. Ideally, a procedure’s name should describe what its contained processes do. A good rule is to use a name that includes a verb and a noun (for example, ProcessDate, PrintReport, Sort_Array, or CheckFilename). Avoid meaningless names such as DoIt, Update, and Fix. Some programmers use sentence-like names that describe the procedure (for example, WriteReportToTextFile and Get_Print_Options_ and_Print_Report). Naming Procedures 15_044018 ch09.qxp 2/28/07 6:39 PM Page 239 NOTE When a user displays the Macro dialog box, Excel shows only the public procedures. Therefore, if you have procedures that are designed to be called only by other proce- dures in the same module, you should make sure that those procedures are declared as Private. Doing so prevents the user from running these procedures from the Macro dialog box. The following example declares a private procedure named MySub: Private Sub MySub() ‘ ... [code goes here] ... End Sub TIP You can force all procedures in a module to be private — even those declared with the Public keyword — by including the following statement before your first Sub statement: Option Private Module If you write this statement in a module, you can omit the Private keyword from your Sub declarations. Excel’s macro recorder normally creates new Sub procedures called Macro1, Macro2, and so on. These procedures are all public procedures, and they will never use any arguments. Executing Sub Procedures In this section, I describe the various ways to execute, or call, a VBA Sub procedure: • With the Run Sub/UserForm command (in the VBE). Or you can press the F5 shortcut key or use the Run Sub/UserForm button on the Standard toolbar. • From Excel’s Macro dialog box. • By using the Ctrl key shortcut assigned to the procedure (assuming that you assigned one). • By clicking a button or a shape on a worksheet. The button or shape must have the pro- cedure assigned to it. • From another procedure that you write. Sub and Function procedures can execute other procedures. • From a custom control in the Ribbon. In addition, built-in Ribbon controls can be “repur- posed” to execute a macro. Part III: Understanding Visual Basic for Applications240 15_044018 ch09.qxp 2/28/07 6:39 PM Page 240 • From a customized shortcut menu. • When an event occurs. These events include opening the workbook, saving the work- book, closing the workbook, changing a cell’s value, activating a sheet, and many other things. • From the Immediate window in the VBE. Just type the name of the procedure, including any arguments that may apply, and press Enter. I discuss these methods of executing procedures in the following sections. NOTE In many cases, a procedure will not work properly unless it is executed in the appropri- ate context. For example, if a procedure is designed to work with the active worksheet, it will fail if a chart sheet is active. A good procedure incorporates code that checks for the appropriate context and exits gracefully if it can’t proceed. Executing a procedure with the Run Sub/UserForm command The VBE Run Sub/UserForm menu command is used primarily to test a procedure while you are developing it. You would never require a user to activate the VBE to execute a pro- cedure. Choose Run ➪ Run Sub/UserForm in the VBE to execute the current procedure (in other words, the procedure that contains the cursor). Or, press F5, or use the Run Sub/UserForm button on the Standard toolbar. If the cursor is not located within a procedure when you issue the Run Sub/UserForm com- mand, VBE displays its Macro dialog box so that you can select a procedure to execute. Executing a procedure from the Macro dialog box Choosing Excel’s Developer ➪ Code ➪ Macros command displays the Macro dialog box, as shown in Figure 9-1. (You can also press Alt+F8 to access this dialog box.) Use the Macros In drop-down box to limit the scope of the macros displayed (for example, show only the macros in the active workbook). The Macro dialog box does not display • Function procedures • Sub procedures declared with the Private keyword • Sub procedures that require one or more arguments • Sub procedures contained in add-ins Chapter 9: Working with VBA Sub Procedures 241 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 241 Figure 9-1: The Macro dialog box. TIP Even though procedures stored in an add-in are not listed in the Macro dialog box, you still can execute such a procedure if you know the name. Simply type the procedure name in the Macro Name field in the Macro dialog box and then click Run. Executing a procedure with a Ctrl+shortcut key combination You can assign a Ctrl+shortcut key combination to any procedure that doesn’t use any arguments. If you assign the Ctrl+U key combo to a procedure named UpdateCustomerList, for example, pressing Ctrl+U executes the that procedure. When you begin recording a macro, the Record Macro dialog box gives you the opportunity to assign a shortcut key. However, you can assign a shortcut key at any time. To assign a Ctrl shortcut key to a procedure (or to change a procedure’s shortcut key), follow these steps: 1. Activate Excel and choose Developer ➪ Code ➪ Macros. 2. Select the appropriate procedure from the list box in the Macro dialog box. 3. Click the Options button to display the Macro Options dialog box (see Figure 9-2). 4. Enter a character into the Ctrl+ text box. Note: The character that you enter into the Ctrl+ text box is case-sensitive. If you enter a lowercase s, the shortcut key combo is Ctrl+S. If you enter an uppercase S, the short- cut key combo is Ctrl+Shift+S. Part III: Understanding Visual Basic for Applications242 15_044018 ch09.qxp 2/28/07 6:39 PM Page 242 Figure 9-2: The Macro Options dialog box lets you assign a Ctrl key shortcut and an optional description to a procedure. 5. Enter a description (optional). If you enter a description for a macro, it is displayed at the bottom of the Macro dialog box when the procedure is selected in the list box. 6. Click OK to close the Macro Options dialog box, and then click Close to close the Macro dialog box. CAUTION If you assign one of Excel’s predefined shortcut key combinations to a procedure, your key assignment takes precedence over the predefined key assignment. For example, Ctrl+S is the Excel predefined shortcut key for saving the active workbook. But if you assign Ctrl+S to a procedure, pressing Ctrl+S no longer saves the active workbook. TIP The following keyboard keys are not used by Excel 2007 for Ctrl+key combinations: E, J, M, and Q. Excel doesn’t use too many Ctrl+Shift+key combinations. In fact, you can use any of them except F, L, N, O, P, and W. Executing a procedure from the Ribbon If you’re willing to go through a bit of effort, you can write XML code to add a new button (or other control) to the Ribbon and assign your macro to that control. Note that modifying the Ribbon is done outside of Excel, and it cannot be done using VBA. CROSS-REFERENCE Refer to Chapter 22 for more information about customizing the Ribbon. Chapter 9: Working with VBA Sub Procedures 243 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 243 Executing a procedure from a customized shortcut menu A macro can also be executed by clicking a menu item in a customized shortcut menu. A shortcut menu appears when you right-click an object or range in Excel. CROSS-REFERENCE Refer to Chapter 23 for more information about customizing shortcut menus. Executing a procedure from another procedure One of the most common ways to execute a procedure is from another VBA procedure. You have three ways to do this: • Enter the procedure’s name, followed by its arguments (if any) separated by commas. • Use the Call keyword followed by the procedure’s name and then its arguments (if any) enclosed in parentheses and separated by commas. • Use the Run method of the Application object. The Run method is useful when you need to run a procedure whose name is assigned to a variable. You can then pass the variable as an argument to the Run method. The following example demonstrates the first method. In this case, the MySub procedure processes some statements (not shown), executes the UpdateSheet procedure, and then executes the rest of the statements. Sub MySub() ‘ ... [code goes here] ... UpdateSheet ‘ ... [code goes here] ... End Sub Sub UpdateSheet() ‘ ... [code goes here] ... End Sub The following example demonstrates the second method. The Call keyword executes the Update procedure, which requires one argument; the calling procedure passes the argu- ment to the called procedure. I discuss procedure arguments later in this chapter (see “Passing Arguments to Procedures”). Sub MySub() MonthNum = InputBox(“Enter the month number: “) Call UpdateSheet(MonthNum) Part III: Understanding Visual Basic for Applications244 15_044018 ch09.qxp 2/28/07 6:39 PM Page 244 ‘ ... [code goes here] ... End Sub Sub UpdateSheet(MonthSeq) ‘ ... [code goes here] ... End Sub TIP Even though it’s optional, some programmers always use the Call keyword just to make it perfectly clear that another procedure is being called. The next example uses the Run method to execute the UpdateSheet procedure and then to pass MonthNum as the argument: Sub MySub() MonthNum = InputBox(“Enter the month number: “) Application.Run “UpdateSheet”, MonthNum ‘ ... [code goes here] ... End Sub Sub UpdateSheet(MonthSeq) ‘ ... [code goes here] ... End Sub Perhaps the best reason to use the Run method is when the procedure name is assigned to a variable. In fact, it’s the only way to execute a procedure in such a way. The following example demonstrates this. The Main procedure uses the VBA WeekDay function to deter- mine the day of the week (an integer between 1 and 7, beginning with Sunday). The SubToCall variable is assigned a string that represents a procedure name. The Run method then calls the appropriate procedure (either WeekEnd or Daily). Sub Main() Dim SubToCall As String Select Case WeekDay(Now) Case 1, 7: SubToCall = “WeekEnd” Case Else: SubToCall = “Daily” End Select Application.Run SubToCall End Sub Sub WeekEnd() MsgBox “Today is a weekend” ‘ Code to execute on the weekend ‘ goes here Chapter 9: Working with VBA Sub Procedures 245 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 245 End Sub Sub Daily() MsgBox “Today is not a weekend” ‘ Code to execute on the weekdays ‘ goes here End Sub CALLING A PROCEDURE IN A DIFFERENT MODULE If VBA can’t locate a called procedure in the current module, it looks for public procedures in other modules in the same project. If you need to call a private procedure from another procedure, both procedures must reside in the same module. You can’t have two procedures with the same name in the same module, but you can have identically named procedures in different modules within the project. You can force VBA to execute an ambiguously named procedure — that is, another procedure in a different mod- ule that has the same name. To do so, precede the procedure name with the module name and a dot. For example, say that you define procedures named MySub in Module1 and Module2. If you want a procedure in Module2 to call the MySub in Module1, you can use either of the following statements: Module1.MySub Call Module1.MySub If you do not differentiate between procedures that have the same name, you get an Ambiguous name detected error message. CALLING A PROCEDURE IN A DIFFERENT WORKBOOK In some cases, you may need your procedure to execute another procedure defined in a dif- ferent workbook. To do so, you have two options: Either establish a reference to the other workbook or use the Run method and specify the workbook name explicitly. To add a reference to another workbook, choose the VBE’s Tools ➪ References command. Excel displays the References dialog box (see Figure 9-3), which lists all available references, including all open workbooks. Simply check the box that corresponds to the workbook that you want to add as a reference and then click OK. After you establish a ref- erence, you can call procedures in the workbook as if they were in the same workbook as the calling procedure. A referenced workbook does not have to be open; it is treated like a separate object library. Use the Browse button in the References dialog box to establish a reference to a workbook that isn’t open. Part III: Understanding Visual Basic for Applications246 15_044018 ch09.qxp 2/28/07 6:39 PM Page 246 Figure 9-3: The References dialog box lets you establish a reference to another workbook. The workbook names that appear in the list of references are listed by their VBE project names. By default, every project is initially named VBAProject. Therefore, the list may con- tain several identically named items. To distinguish a project, change its name in the Project Properties dialog box. Click the project name in the Project window and then choose Tools ➪ xxxx Properties (where xxxx is the current project name). In the Project Properties dialog box, click the General tab and change the name displayed in the Project Name field. The list of references displayed in the References dialog box also includes object libraries and ActiveX controls that are registered on your system. Your Excel 2007 workbooks always include references to the following object libraries: • Visual Basic for Applications • Microsoft Excel 12.0 Object Library • OLE Automation • Microsoft Office 12.0 Object Library • Microsoft Forms 2.0 Object Library (optional, included only if your project includes a UserForm) NOTE Any additional references to other workbooks that you add are also listed in your project outline in the Project Explorer window in the VBE. These references are listed under a node called References. Chapter 9: Working with VBA Sub Procedures 247 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 247 If you’ve established a reference to a workbook that contains the procedure YourSub, for example, you can use either of the following statements to call YourSub: YourSub Call YourSub To precisely identify a procedure in a different workbook, specify the project name, module name, and procedure name by using the following syntax: MyProject.MyModule.MySub Alternatively, you can use the Call keyword: Call MyProject.MyModule.MySub Part III: Understanding Visual Basic for Applications248 If you’re new to programming, you may wonder why anyone would ever want to call a procedure from another procedure. You may ask, “Why not just put the code from the called procedure into the calling procedure and keep things simple?” One reason is to clarify your code. The simpler your code, the easier it is to maintain and modify. Smaller routines are easier to decipher and then debug. Examine the accompanying procedure, which does nothing but call other procedures. This procedure is very easy to follow. Sub Main() Call GetUserOptions Call ProcessData Call CleanUp Call CloseItDown End Sub Calling other procedures also eliminates redundancy. Suppose that you need to perform an operation at ten different places in your routine. Rather than enter the code ten times, you can write a procedure to perform the operation and then simply call the procedure ten times. Also, you may have a series of general-purpose procedures that you use frequently. If you store these in a separate module, you can import the module to your current project and then call these procedures as needed — which is much easier than copying and pasting the code into your new procedures. Creating several small procedures rather than a single large one is often considered good programming practice. A modular approach not only makes your job easier but also makes life easier for the people who wind up working with your code. Why Call Other Procedures? 15_044018 ch09.qxp 2/28/07 6:39 PM Page 248 Another way to call a procedure in a different workbook is to use the Run method of the Application object. This technique does not require that you establish a reference, but the workbook that contains the procedure must be open. The following statement executes the Consolidate procedure located in a workbook named budget macros.xlsm: Application.Run “‘budget macros.xlsm’!Consolidate” Executing a procedure by clicking an object Excel provides a variety of objects that you can place on a worksheet or chart sheet, and you can attach a macro to any of these objects. These objects fall into several classes: • ActiveX controls • Forms controls • Inserted objects (Shapes, SmartArt, WordArt, charts, and pictures) NOTE The Developer ➪ Controls ➪ Insert drop-down list contains two types of controls that you can insert on a worksheet: Form controls and ActiveX controls. The ActiveX controls are similar to the controls that you use in a UserForm. The Forms controls were designed for Excel 5 and Excel 95, but they can still be used in later versions (and may be prefer- able in some cases). Unlike the Form controls, the ActiveX controls cannot be used to execute an arbitrary macro. An ActiveX control executes a specially-named macro. For example, if you insert an ActiveX button control named CommandButton1, clicking the button executes a macro named CommandButton1_Click, which must be located in the code module for the sheet on which the control was inserted. Refer to Chapter 13 for information about using controls on worksheets. To assign a procedure to a Button object from the Form controls, follow these steps: 1. Select Developer ➪ Controls ➪ Insert and click the button in the Form Controls group. 2. Click the worksheet to create the button. Or, you can drag your mouse on the worksheet to change the default size of the button. Excel jumps right in and displays the Assign Macro dialog box (see Figure 9-4). It pro- poses a macro that’s based on the button’s name. 3. Select or enter the macro that you want to assign to the button and then click OK. You can always change the macro assignment by right-clicking the button and choosing Assign Macro. Chapter 9: Working with VBA Sub Procedures 249 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 249 Figure 9-4: Assigning a macro to a button. To assign a macro to a Shape, SmartArt, WordArt, chart, or picture, right-click the object and choose Assign Macro from the shortcut menu. Executing a procedure when an event occurs You might want a procedure to execute when a particular event occurs. Examples of events include opening a workbook, entering data into a worksheet, saving a workbook, clicking a CommandButton ActiveX control, and many others. A procedure that is executed when an event occurs is an event handler procedure. Event handler procedures are characterized by the following: • They have special names that are made up of an object, an underscore, and the event name. For example, the procedure that is executed when a workbook is opened is Workbook_Open. • They are stored in the Code module for the particular object. CROSS-REFERENCE Chapter 19 is devoted to event handler procedures. Executing a procedure from the Immediate window You also can execute a procedure by entering its name in the Immediate window of the VBE. If the Immediate window is not visible, press Ctrl+G. The Immediate window exe- cutes VBA statements while you enter them. To execute a procedure, simply enter the name of the procedure in the Immediate window and press Enter. Part III: Understanding Visual Basic for Applications250 15_044018 ch09.qxp 2/28/07 6:39 PM Page 250 This method can be quite useful when you’re developing a procedure because you can insert commands to display results in the Immediate window. The following procedure demonstrates this technique: Sub ChangeCase() Dim MyString As String MyString = “This is a test” MyString = UCase(MyString) Debug.Print MyString End Sub Figure 9-5 shows what happens when you enter ChangeCase in the Immediate window: The Debug.Print statement displays the result immediately. Figure 9-5: Executing a procedure by entering its name in the Immediate window. Passing Arguments to Procedures A procedure’s arguments provide it with data that it uses in its instructions. The data that’s passed by an argument can be any of the following: • A variable • A constant • An array • An object Chapter 9: Working with VBA Sub Procedures 251 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 251 The use of arguments by procedures is very similar to worksheet functions in the following respects: • A procedure may not require any arguments. • A procedure may require a fixed number of arguments. • A procedure may accept an indefinite number of arguments. • A procedure may require some arguments, leaving others optional. • A procedure may have all optional arguments. For example, a few of Excel’s worksheet functions, such as RAND and NOW, use no argu- ments. Others, such as COUNTIF, require two arguments. Others still, such as SUM, can use up to 255 arguments. Still other worksheet functions have optional arguments. The PMT function, for example, can have five arguments (three are required; two are optional). Most of the procedures that you’ve seen so far in this book have been declared without arguments. They were declared with just the Sub keyword, the procedure’s name, and a set of empty parentheses. Empty parentheses indicate that the procedure does not accept arguments. The following example shows two procedures. The Main procedure calls the ProcessFile procedure three times (the Call statement is in a For-Next loop). Before calling ProcessFile, however, a three-element array is created. Inside the loop, each element of the array becomes the argument for the procedure call. The ProcessFile procedure takes one argument (named TheFile). Notice that the argument goes inside parentheses in the Sub statement. When ProcessFile finishes, program control continues with the state- ment after the Call statement. Sub Main() Dim File(1 To 3) As String Dim i as Integer File(1) = “dept1.xlsx” File(2) = “dept2.xlsx” File(3) = “dept3.xlsx” For i = 1 To 3 Call ProcessFile(File(i)) Next i End Sub Sub ProcessFile(TheFile) Workbooks.Open FileName:=TheFile ‘ ...[more code here]... End Sub Part III: Understanding Visual Basic for Applications252 15_044018 ch09.qxp 2/28/07 6:39 PM Page 252 You can also, of course, pass literals (that is, not variables) to a procedure. For example: Sub Main() Call ProcessFile(“budget.xlsx”) End Sub You can pass an argument to a procedure in two ways: • By reference: Passing an argument by reference (the default method) simply passes the memory address of the variable. Changes to the argument within the procedure are made to the original variable. • By value: Passing an argument by value passes a copy of the original variable. Consequently, changes to the argument within the procedure are not reflected in the original variable. The following example demonstrates this concept. The argument for the Process proce- dure is passed by reference (the default method). After the Main procedure assigns a value of 10 to MyValue, it calls the Process procedure and passes MyValue as the argument. The Process procedure multiplies the value of its argument (named YourValue) by 10. When Process ends and program control passes back to Main, the MsgBox function dis- plays MyValue: 100. Sub Main() Dim MyValue As Integer MyValue = 10 Call Process(MyValue) MsgBox MyValue End Sub Sub Process(YourValue) YourValue = YourValue * 10 End Sub If you don’t want the called procedure to modify any variables passed as arguments, you can modify the called procedure’s argument list so that arguments are passed to it by value rather than by reference. To do so, precede the argument with the ByVal keyword. This technique causes the called routine to work with a copy of the passed variable’s data — not the data itself. In the following procedure, for example, the changes made to YourValue in the Process procedure do not affect the MyValue variable in Main. As a result, the MsgBox function displays 10 and not 100. Sub Process(ByVal YourValue) YourValue = YourValue * 10 End Sub Chapter 9: Working with VBA Sub Procedures 253 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 253 In most cases, you’ll be content to use the default reference method of passing arguments. However, if your procedure needs to use data passed to it in an argument — and you must keep the original data intact — you’ll want to pass the data by value. A procedure’s arguments can mix and match by value and by reference. Arguments pre- ceded with ByVal are passed by value; all others are passed by reference. NOTE If you pass a variable defined as a user-defined data type to a procedure, it must be passed by reference. Attempting to pass it by value generates an error. Part III: Understanding Visual Basic for Applications254 In Chapter 8, I point out how a variable declared as Public (at the top of the module) is available to all procedures in the module. In some cases, you might want to access a Public variable rather than pass the variable as an argument when calling another procedure. For example, the procedure that follows passes the value of MonthVal to the ProcessMonth procedure: Sub MySub() Dim MonthVal as Integer ‘ ... [code goes here] MonthVal = 4 Call ProcessMonth(MonthVal) ‘ ... [code goes here] End Sub An alternative approach, which doesn’t use an argument, is Public MonthVal as Integer Sub MySub() ‘ ... [code goes here] MonthVal = 4 Call ProcessMonth2 ‘ ... [code goes here] End Sub In the revised code, because MonthVal is a public variable, the ProcessMonth2 procedure can access it, thus eliminating the need for an argument for the ProcessMonth2 procedure. Using Public Variables versus Passing Arguments to a Procedure 15_044018 ch09.qxp 2/28/07 6:39 PM Page 254 Because I didn’t declare a data type for any of the arguments in the preceding examples, all the arguments have been of the Variant data type. But a procedure that uses arguments can define the data types directly in the argument list. The following is a Sub statement for a procedure with two arguments of different data types. The first is declared as an integer, and the second is declared as a string. Sub Process(Iterations As Integer, TheFile As String) When you pass arguments to a procedure, the data that is passed as the argument must match the argument’s data type. For example, if you call Process in the preceding exam- ple and pass a string variable for the first argument, you get an error: ByRef argument type mismatch. NOTE Arguments are relevant to both Sub procedures and Function procedures. In fact, arguments are more often used in Function procedures. In Chapter 10, where I focus on Function procedures, I provide additional examples of using arguments with your routines, including how to handle optional arguments. Error-Handling Techniques When a VBA procedure is running, errors can occur, as you undoubtedly know. These include either syntax errors (which you must correct before you can execute a procedure) or runtime errors (which occur while the procedure is running). This section deals with run- time errors. CAUTION For error-handling procedures to work, the Break on All Errors setting must be turned off. In the VBE, choose Tools ➪ Options and click the General tab in the Options dialog box. If Break on All Errors is selected, VBA ignores your error-handling code. You’ll usu- ally want to use the Break on Unhandled Errors option. Normally, a runtime error causes VBA to stop, and the user sees a dialog box that displays the error number and a description of the error. A good application doesn’t make the user deal with these messages. Rather, it incorporates error-handling code to trap errors and take appropriate actions. At the very least, your error-handling code can display a more meaningful error message than the one VBA pops up. CROSS-REFERENCE Appendix C lists all the VBA error codes and descriptions. Chapter 9: Working with VBA Sub Procedures 255 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 255 Trapping errors You can use the On Error statement to specify what happens when an error occurs. Basically, you have two choices: • Ignore the error and let VBA continue. Your code can later examine the Err object to determine what the error was and then take action if necessary. • Jump to a special error-handling section of your code to take action. This section is placed at the end of the procedure and is also marked by a label. To cause your VBA code to continue when an error occurs, insert the following statement in your code: On Error Resume Next Some errors are inconsequential and can be ignored without causing a problem. But you may want to determine what the error was. When an error occurs, you can use the Err object to determine the error number. The VBA Error function can be used to display the text that corresponds to the Err.Number value. For example, the following statement dis- plays the same information as the normal Visual Basic error dialog box (the error number and the error description): MsgBox “Error “ & Err & “: “ & Error(Err.Number) Figure 9-6 shows a VBA error message, and Figure 9-7 shows the same error displayed in a message box. You can, of course, make the error message a bit more meaningful to your end users by using more descriptive text. Figure 9-6: VBA error messages aren’t always user friendly. Part III: Understanding Visual Basic for Applications256 15_044018 ch09.qxp 2/28/07 6:39 PM Page 256 Figure 9-7: You can create a message box to display the error code and description. NOTE Referencing Err is equivalent to accessing the Number property of the Err object. Therefore, the following two statements have the same effect: MsgBox Err MsgBox Err.Number You also use the On Error statement to specify a location in your procedure to jump to when an error occurs. You use a label to mark the location. For example: On Error GoTo ErrorHandler Error-handling examples The first example demonstrates an error that can safely be ignored. The SpecialCells method selects cells that meet a certain criterion. NOTE The SpecialCells method is equivalent to choosing the Home ➪ Editing ➪ Find & Select ➪ Go To Special command. The Go To Special dialog box provides you with a number of choices. For example, you can select cells that contain a numeric constant (non-formula). In the example that follows, the SpecialCells method selects all the cells in the current range selection that contain a formula that returns a number. If no cells in the selection qualify, VBA displays the error message shown in Figure 9-8. Using the On Error Resume Next statement simply prevents the error message from appearing. Sub SelectFormulas() On Error Resume Next Selection.SpecialCells(xlFormulas, xlNumbers).Select On Error GoTo 0 ‘ ...[more code goes here] End Sub The On Error GoTo 0 statement restores normal error handling for the remaining state- ments in the procedure. Chapter 9: Working with VBA Sub Procedures 257 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 257 Figure 9-8: The SpecialCells method generates this error if no cells are found. The following procedure uses an additional statement to determine whether an error did occur: Sub SelectFormulas2() On Error Resume Next Selection.SpecialCells(xlFormulas, xlNumbers).Select If Err.Number = 1004 Then MsgBox “No formula cells were found.” On Error GoTo 0 ‘ ...[more code goes here] End Sub If the Number property of Err is equal to anything except 0, then an error occurred. The If statement checks to see if Err.Number is equal to 1004 and displays a message box if it is. In this example, the code is checking for a specific error number. To check for any error, use a statement like this: If Err.Number <> 0 Then MsgBox “An error occurred.” The next example demonstrates error handling by jumping to a label. Sub ErrorDemo() On Error GoTo Handler Selection.Value = 123 Exit Sub Handler: MsgBox “Cannot assign a value to the selection.” End Sub The procedure attempts to assign a value to the current selection. If an error occurs (for example, a range is not selected or the sheet is protected), the assignment statement results in an error. The On Error statement specifies a jump to the Handler label if an error occurs. Notice the use of the Exit Sub statement before the label. This prevents the error-handling code from being executed if no error occurs. If this statement is omitted, the error message is displayed even if an error does not occur. Part III: Understanding Visual Basic for Applications258 15_044018 ch09.qxp 2/28/07 6:39 PM Page 258 Sometimes, you can take advantage of an error to get information. The example that follows simply checks whether a particular workbook is open. It does not use any error handling. Sub CheckForFile1() Dim FileName As String Dim FileExists As Boolean Dim book As Workbook FileName = “BUDGET.XLSX” FileExists = False ‘ Cycle through all workbooks For Each book In Workbooks If UCase(book.Name) = FileName Then FileExists = True Next book ‘ Display appropriate message If FileExists Then MsgBox FileName & “ is open.” Else MsgBox FileName & “ is not open.” End If End Sub Here, a For Each-Next loop cycles through all objects in the Workbooks collection. If the workbook is open, the FileExists variable is set to True. Finally, a message is displayed that tells the user whether the workbook is open. The preceding routine can be rewritten to use error handling to determine whether the file is open. In the example that follows, the On Error Resume Next statement causes VBA to ignore any errors. The next instruction attempts to reference the workbook by assigning the workbook to an object variable (by using the Set keyword). If the workbook is not open, an error occurs. The If-Then-Else structure checks the value property of Err and displays the appropriate message. Sub CheckForFile() Dim FileName As String Dim x As Workbook FileName = “BUDGET.XLSX” On Error Resume Next Set x = Workbooks(FileName) If Err = 0 Then MsgBox FileName & “ is open.” Else MsgBox FileName & “ is not open.” End If On Error GoTo 0 End Sub Chapter 9: Working with VBA Sub Procedures 259 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 259 CROSS-REFERENCE Chapter 11 presents several additional examples that use error handling. A Realistic Example That Uses Sub Procedures In this chapter, I describe the basics of creating Sub procedures. Most of the previous examples, I will admit, have been rather wimpy. The remainder of this chapter is a real-life exercise that demonstrates many of the concepts covered in this and the preceding two chapters. This section describes the development of a useful utility that qualifies as an application as defined in Chapter 5. More important, I demonstrate the process of analyzing a problem and then solving it with VBA. I wrote this section with VBA newcomers in mind. As a result, I don’t simply present the code, but I also show how to find out what you need to know to develop the code. CD-ROM The completed application can be found on the companion CD-ROM. The goal The goal of this exercise is to develop a utility that rearranges a workbook by alphabetiz- ing its sheets (something that Excel cannot do on its own). If you tend to create workbooks that consist of many sheets, you know that it can be difficult to locate a particular sheet. If the sheets are ordered alphabetically, however, it’s easier to find a desired sheet. Project requirements Where to begin? One way to get started is to list the requirements for your application. When you develop your application, you can check your list to ensure that you’re covering all the bases. Here’s the list of requirements that I compiled for this example application: 1. It should sort the sheets (that is, worksheets and chart sheets) in the active workbook in ascending order of their names. 2. It should be easy to execute. 3. It should always be available. In other words, the user shouldn’t have to open a work- book to use this utility. Part III: Understanding Visual Basic for Applications260 15_044018 ch09.qxp 2/28/07 6:39 PM Page 260 4. It should work properly for any workbook that’s open. 5. It should not display any VBA error messages. What you know Often, the most difficult part of a project is figuring out where to start. In this case, I started by listing things that I know about Excel that may be relevant to the project requirements: • Excel doesn’t have a command that sorts sheets. Therefore, recording a macro to alpha- betize the sheets is not an option. • I can move a sheet easily by dragging its sheet tab. Mental note: Turn on the macro recorder and drag a sheet to a new location to find out what kind of code this action generates. • Excel also has a Move or Copy dialog box, which is displayed when I right-click a sheet tab and choose Move or Copy. Would recording a macro of this command generate differ- ent code than moving a sheet manually? • I’ll need to know how many sheets are in the active workbook. I can get this informa- tion with VBA. • I’ll need to know the names of all the sheets. Again, I can get this information with VBA. • Excel has a command that sorts data in worksheet cells. Mental note: Maybe I can transfer the sheet names to a range and use this feature. Or, maybe VBA has a sorting method that I can take advantage of. • Thanks to the Macro Options dialog box, it’s easy to assign a shortcut key to a macro. • If a macro is stored in the Personal Macro Workbook, it will always be available. • I need a way to test the application while I develop it. For certain, I don’t want to be testing it using the same workbook in which I’m developing the code. Mental note: Create a dummy workbook for testing purposes. • If I develop the code properly, VBA won’t display any errors. Mental note: Wishful thinking . . . The approach Although I still didn’t know exactly how to proceed, I could devise a preliminary, skeleton plan that describes the general tasks required: 1. Identify the active workbook. 2. Get a list of all the sheet names in the workbook. Chapter 9: Working with VBA Sub Procedures 261 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 261 3. Count the sheets. 4. Sort the sheet names (somehow). 5. Rearrange the sheets so they correspond to the sorted sheet names. What you need to know I saw a few holes in the plan. I knew that I had to determine the following: • How to identify the active workbook • How to count the sheets in the active workbook • How to get a list of the sheet names • How to sort the list • How to rearrange the sheets according to the sorted list TIP When you lack critical information about specific methods or properties, you can consult this book or the VBA Help system. You may eventually discover what you need to know. Your best bet, however, is to turn on the macro recorder and examine the code that it generates when you perform some relevant actions. You will almost always get some clues as to how to proceed. Some preliminary recording Here’s an example of using the macro recorder to learn about VBA. I started with a work- book that contained three worksheets. Then I turned on the macro recorder and specified my Personal Macro Workbook as the destination for the macro. With the macro recorder running, I dragged the third worksheet to the first sheet position. Here’s the code that was generated by the macro recorder: Sub Macro1() Sheets(“Sheet3”).Select Sheets(“Sheet3”).Move Before:=Sheets(1) End Sub I searched the VBA Help for Move and discovered that it’s a method that moves a sheet to a new location in the workbook. It also takes an argument that specifies the location for the sheet. This information is very relevant to the task at hand. Curious, I then turned on the macro recorder to see whether using the Move or Copy dialog box would generate different code. It didn’t. Part III: Understanding Visual Basic for Applications262 15_044018 ch09.qxp 2/28/07 6:39 PM Page 262 Next, I needed to find out how many sheets were in the active workbook. I searched Help for the word Count and found out that it’s a property of a collection. I activated the Immediate window in the VBE and typed the following statement: ? ActiveWorkbook.Count Error! After a little more thought, I realized that I needed to get a count of the sheets within a workbook. So I tried this: ? ActiveWorkbook.Sheets.Count Success. Figure 9-9 shows the result. More useful information. Figure 9-9: Use the VBE Immediate window to test a statement. What about the sheet names? Time for another test. I entered the following statement in the Immediate window: ? ActiveWorkbook.Sheets(1).Name This told me that the name of the first sheet is Sheet3, which is correct (because I’d moved it). More good information to keep in mind. Then I remembered something about the For Each-Next construct: It is useful for cycling through each member of a collection. After consulting the Help system, I created a short procedure to test it: Sub Test() For Each Sht In ActiveWorkbook.Sheets MsgBox Sht.Name Next Sht End Sub Another success. This macro displayed three message boxes, each showing a different sheet name. Finally, it was time to think about sorting options. From the Help system, I learned that the Sort method applies to a Range object. So one option was to transfer the sheet names to a range and then sort the range, but that seemed like overkill for this application. I thought that a better option was to dump the sheet names into an array of strings and then sort the array by using VBA code. Chapter 9: Working with VBA Sub Procedures 263 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 263 Initial setup Now I knew enough to get started writing some serious code. Before doing so, however, I needed to do some initial setup work. To re-create my steps, follow these instructions: 1. Create an empty workbook with five worksheets, named Sheet1, Sheet2, Sheet3, Sheet4, and Sheet5. 2. Move the sheets around randomly so that they aren’t in any particular order. 3. Save the workbook as Test.xlsx. 4. Activate the VBE and select the Personal.xlsb project in the Project Window. If Personal.xlsb doesn’t appear in the Project window in the VBE, it means that you’ve never used the Personal Macro Workbook. To have Excel create this workbook for you, simply record a macro (any macro) and specify the Personal Macro Workbook as the destination for the macro. 5. Insert a new VBA module in Personal.xlsb (choose Insert ➪ Module). 6. Create an empty Sub procedure called SortSheets (see Figure 9-10). Figure 9-10: An empty procedure in a module located in the Personal Macro Workbook. Actually, you can store this macro in any module in the Personal Macro Workbook. However, it’s a good idea to keep each macro (or group of related macros) in a separate Part III: Understanding Visual Basic for Applications264 15_044018 ch09.qxp 2/28/07 6:39 PM Page 264 module. That way, you can easily export the module and import it into a different project later on. 7. Activate Excel. Choose Developer ➪ Code ➪ Macros to display the Macro dialog box. 8. In the Macro dialog box, select the SortSheets procedure and click the Options button to assign a shortcut key to this macro. The Ctrl+Shift+S key combination is a good choice. Code writing Now it’s time to write some code. I knew that I needed to put the sheet names into an array of strings. Because I don’t know yet how many sheets are in the active workbook, I used a Dim statement with empty parentheses to declare the array. I knew that I could use ReDim afterward to redimension the array for the actual number of elements. I entered the following code, which inserts the sheet names into the SheetNames array. I also added a MsgBox function within the loop just to assure me that the sheets’ names were indeed being entered into the array. Sub SortSheets() ‘ Sorts the sheets of the active workbook Dim SheetNames() as String Dim i as Long Dim SheetCount as Long SheetCount = ActiveWorkbook.Sheets.Count ReDim SheetNames(1 To SheetCount) For i = 1 To SheetCount SheetNames(i) = ActiveWorkbook.Sheets(i).Name MsgBox SheetNames(i) Next i End Sub To test the preceding code, I activated the Test.xlsx workbook and pressed Ctrl+Shift+S. Five message boxes appeared, each displaying the name of a sheet in the active workbook. So far, so good. By the way, I’m a major proponent of testing your work as you go. When you’re convinced that your code is working correctly, remove the MsgBox statement. (These message boxes become annoying after a while.) TIP Rather than use the MsgBox function to test your work, you can use the Print method of the Debug object to display information in the Immediate window. For this example, use the following statement in place of the MsgBox statement: Debug.Print SheetNames(i) This technique is much less intrusive than using MsgBox statements. Just make sure that you remember to remove the statement when you’re finished. Chapter 9: Working with VBA Sub Procedures 265 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 265 At this point, the SortSheets procedure simply creates an array of sheet names corre- sponding to the sheets in the active workbook. Two steps remain: Sort the elements in the SheetNames array and then rearrange the sheets to correspond to the sorted array. Writing the Sort procedure It was time to sort the SheetNames array. One option was to insert the sorting code in the SortSheets procedure, but I thought a better approach was to write a general-purpose sorting procedure that I could reuse with other projects (sorting arrays is a common opera- tion). You might be a bit daunted by the thought of writing a sorting procedure. The good news is that it’s relatively easy to find commonly used routines that you can use or adapt. The Internet, of course, is a great source for such information. You can sort an array in many ways. I chose the bubble sort method; although it’s not a par- ticularly fast technique, it’s easy to code. Blazing speed is not really a requirement in this particular application. The bubble sort method uses a nested For-Next loop to evaluate each array element. If the array element is greater than the next element, the two elements swap positions. This evaluation is repeated for every pair of items (that is, n – 1 times). CROSS-REFERENCE In Chapter 11, I present some other sorting routines and compare them in terms of speed. Here’s the sorting procedure I developed (after consulting a few programming Web sites to get some ideas): Sub BubbleSort(List() As String) Dim First As Long, Last As Long Dim i As Long, j As Long Dim Temp As String First = LBound(List) Last = UBound(List) For i = First To Last - 1 For j = i + 1 To Last If List(i) > List(j) Then Temp = List(j) List(j) = List(i) List(i) = Temp End If Next j Next i End Sub Part III: Understanding Visual Basic for Applications266 15_044018 ch09.qxp 2/28/07 6:39 PM Page 266 This procedure accepts one argument: a one-dimensional array named List. An array passed to a procedure can be of any length. I used the LBound and UBound functions to define the lower bound and upper bound of the array to the variables First and Last, respectively. Here’s a little temporary procedure that I used to test the BubbleSort procedure: Sub SortTester() Dim x(1 To 5) As String Dim i As Long x(1) = “dog” x(2) = “cat” x(3) = “elephant” x(4) = “aardvark” x(5) = “bird” Call BubbleSort(x) For i = 1 To 5 Debug.Print i, x(i) Next i End Sub The SortTester routine creates an array of five strings, passes the array to BubbleSort, and then displays the sorted array in the Immediate window. I eventually deleted this code because it served its purpose. After I was satisfied that this procedure worked reliably, I modified SortSheets by adding a call to the BubbleSort procedure, passing the SheetNames array as an argument. At this point, my module looked like this: Sub SortSheets() Dim SheetNames() As String Dim SheetCount as Long Dim i as Long SheetCount = ActiveWorkbook.Sheets.Count ReDim SheetNames(1 To SheetCount) For i = 1 To SheetCount SheetNames(i) = ActiveWorkbook.Sheets(i).Name Next i Call BubbleSort(SheetNames) End Sub Sub BubbleSort(List() As String) ‘ Sorts the List array in ascending order Dim First As Long, Last As Long Dim i As Long, j As Long Dim Temp As String First = LBound(List) Last = UBound(List) Chapter 9: Working with VBA Sub Procedures 267 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 267 For i = First To Last - 1 For j = i + 1 To Last If List(i) > List(j) Then Temp = List(j) List(j) = List(i) List(i) = Temp End If Next j Next i End Sub When the SheetSort procedure ends, it contains an array that consists of the sorted sheet names in the active workbook. To verify this, you can display the array contents in the VBE Immediate window by adding the following code at the end of the SortSheets procedure (if the Immediate window is not visible, press Ctrl+G): For i = 1 To SheetCount Debug.Print SheetNames(i) Next i So far, so good. Next step: Write some code to rearrange the sheets to correspond to the sorted items in the SheetNames array. The code that I recorded earlier proved useful. Remember the instruction that was recorded when I moved a sheet to the first position in the workbook? Sheets(“Sheet3”).Move Before:=Sheets(1) After a little thought, I was able to write a For-Next loop that would go through each sheet and move it to its corresponding sheet location, specified in the SheetNames array: For i = 1 To SheetCount Sheets(SheetNames(i)).Move Before:=Sheets(i) Next i For example, the first time through the loop, the loop counter i is 1. The first element in the SheetNames array is (in this example) Sheet1. Therefore, the expression for the Move method within the loop evaluates to Sheets(“Sheet1”).Move Before:= Sheets(1) The second time through the loop, the expression evaluates to Sheets(“Sheet2”).Move Before:= Sheets(2) Part III: Understanding Visual Basic for Applications268 15_044018 ch09.qxp 2/28/07 6:39 PM Page 268 I then added the new code to the SortSheets procedure: Sub SortSheets() Dim SheetNames() As String Dim SheetCount as Long Dim i as Long SheetCount = ActiveWorkbook.Sheets.Count ReDim SheetNames(1 To SheetCount) For i = 1 To SheetCount SheetNames(i) = ActiveWorkbook.Sheets(i).Name Next i Call BubbleSort(SheetNames) For i = 1 To SheetCount ActiveWorkbook.Sheets(SheetNames(i)).Move _ Before:=ActiveWorkbook.Sheets(i) Next i End Sub I did some testing, and it seemed to work just fine for the Test.xlsx workbook. Time to clean things up. I made sure that all the variables used in the procedures were declared, and then I added a few comments and blank lines to make the code easier to read. The SortSheets procedure looked like the following: Sub SortSheets() ‘ This routine sorts the sheets of the ‘ active workbook in ascending order. ‘ Use Ctrl+Shift+S to execute Dim SheetNames() As String Dim SheetCount As Long Dim i As Long ‘ Determine the number of sheets & ReDim array SheetCount = ActiveWorkbook.Sheets.Count ReDim SheetNames(1 To SheetCount) ‘ Fill array with sheet names For i = 1 To SheetCount SheetNames(i) = ActiveWorkbook.Sheets(i).Name Next i ‘ Sort the array in ascending order Call BubbleSort(SheetNames) ‘ Move the sheets Chapter 9: Working with VBA Sub Procedures 269 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 269 For i = 1 To SheetCount ActiveWorkbook.Sheets(SheetNames(i)).Move _ Before:= ActiveWorkbook.Sheets(i) Next i End Sub Everything seemed to be working. To test the code further, I added a few more sheets to Test.xlsx and changed some of the sheet names. It worked like a charm. More testing I was tempted to call it a day. However, just because the procedure worked with the Test.xlsx workbook didn’t mean that it would work with all workbooks. To test it further, I loaded a few other workbooks and retried the routine. I soon discovered that the applica- tion was not perfect. In fact, it was far from perfect. I identified the following problems: • Workbooks with many sheets took a long time to sort because the screen was continu- ally updated during the move operations. • The sorting didn’t always work. For example, in one of my tests, a sheet named SUMMARY (all uppercase) appeared before a sheet named Sheet1. This problem was caused by the BubbleSort procedure — an uppercase U is “greater than” a lower- case h. • If Excel had no visible workbook windows, pressing the Ctrl+Shift+S shortcut key combo caused the macro to fail. • If the workbook’s structure was protected, the Move method failed. • After sorting, the last sheet in the workbook became the active sheet. Changing the user’s active sheet is not a good practice; it’s better to keep the user’s original sheet active. • If I interrupted the macro by pressing Ctrl+Break, VBA displayed an error message. • The macro cannot be reversed (that is, the Undo command is disabled). If the user acci- dentally presses Ctrl+Shift+S, the workbook sheets are sorted and the only way to get them back to their original order is by doing it manually. Fixing the problems Fixing the screen-updating problem was a breeze. I inserted the following instruction to turn off screen updating while the sheets are being moved: Application.ScreenUpdating = False This statement causes Excel’s windows to freeze while the macro is running. A beneficial side effect is that it also speeds up the macro considerably. After the macro completes it operation, screen updating is turned back on automatically. Part III: Understanding Visual Basic for Applications270 15_044018 ch09.qxp 2/28/07 6:39 PM Page 270 It was also easy to fix the problem with the BubbleSort procedure: I used VBA’s UCase function to convert the sheet names to uppercase for the comparison. This caused all the comparisons were made by using uppercase versions of the sheet names. The corrected line read as follows: If UCase(List(i)) > UCase(List(j)) Then TIP Another way to solve the “case” problem is to add the following statement to the top of your module: Option Compare Text This statement causes VBA to perform string comparisons based on a case-insensitive text sort order. In other words, A is considered the same as a. To prevent the error message that appears when no workbooks are visible, I added some error checking. I used On Error Resume Next to ignore the error and then checked the value of Err. If Err is not equal to 0, it means that an error occurred. Therefore, the pro- cedure ends. The error-checking code is On Error Resume Next SheetCount = ActiveWorkbook.Sheets.Count If Err <> 0 Then Exit Sub ‘ No active workbook It occurred to me that I could avoid using On Error Resume Next. The following state- ment is a more direct approach to determining whether a workbook is not visible and doesn’t require any error handling. This statement can go at the top of the SortSheets procedure: If ActiveWorkbook Is Nothing Then Exit Sub There’s usually a good reason that a workbook’s structure is protected. I decided that the best approach was to not attempt to unprotect the workbook. Rather, the code should dis- play a message box warning and let the user unprotect the workbook and re-execute the macro. Testing for a protected workbook structure was easy — the ProtectStructure property of a Workbook object returns True if a workbook is protected. I added the follow- ing block of code: ‘ Check for protected workbook structure If ActiveWorkbook.ProtectStructure Then MsgBox ActiveWorkbook.Name & “ is protected.”, _ vbCritical, “Cannot Sort Sheets.” Exit Sub End If Chapter 9: Working with VBA Sub Procedures 271 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 271 If the workbook’s structure is protected, the user sees the message box shown in Fig- ure 9-11. Figure 9-11: This message box tells the user that the sheets cannot be sorted. To reactivate the original active sheet after the sorting was performed, I wrote code that assigned the original sheet to an object variable (OldActiveSheet) and then activated that sheet when the routine was finished. Here’s the statement that assigns the variable: Set OldActive = ActiveSheet This statement activates the original active worksheet: OldActive.Activate Pressing Ctrl+Break normally halts a macro, and VBA usually displays an error message. But because one of my goals was to avoid VBA error messages, I inserted a command to prevent this situation. From the online help, I discovered that the Application object has an EnableCancelKey property that can disable Ctrl+Break. So I added the following statement at the top of the routine: Application.EnableCancelKey = xlDisabled CAUTION Be very careful when you disable the Cancel key. If your code gets caught in an infinite loop, there is no way to break out of it. For best results, insert this statement only after you’re sure that everything is working properly. To prevent the problem of accidentally sorting the sheets, I added the following statement to the procedure, before the Ctrl+Break key is disabled: If MsgBox(“Sort the sheets in the active workbook?”, _ vbQuestion + vbYesNo) <> vbYes Then Exit Sub When the user executes the SortSheets procedure, he sees the message box in Fig- ure 9-12. Part III: Understanding Visual Basic for Applications272 15_044018 ch09.qxp 2/28/07 6:39 PM Page 272 Figure 9-12: This message box appears before the sheets are sorted. After I made all these corrections, the SortSheets procedure looked like this: Option Explicit Sub SortSheets() ‘ This routine sorts the sheets of the ‘ active workbook in ascending order. ‘ Use Ctrl+Shift+S to execute Dim SheetNames() As String Dim i As Long Dim SheetCount As Long Dim OldActiveSheet As Object If ActiveWorkbook Is Nothing Then Exit Sub ‘ No active workbook SheetCount = ActiveWorkbook.Sheets.Count ‘ Check for protected workbook structure If ActiveWorkbook.ProtectStructure Then MsgBox ActiveWorkbook.Name & “ is protected.”, _ vbCritical, “Cannot Sort Sheets.” Exit Sub End If ‘ Make user verify If MsgBox(“Sort the sheets in the active workbook?”, _ vbQuestion + vbYesNo) <> vbYes Then Exit Sub ‘ Disable Ctrl+Break Application.EnableCancelKey = xlDisabled ‘ Get the number of sheets SheetCount = ActiveWorkbook.Sheets.Count ‘ Redimension the array ReDim SheetNames(1 To SheetCount) ‘ Store a reference to the active sheet Chapter 9: Working with VBA Sub Procedures 273 Part III 15_044018 ch09.qxp 2/28/07 6:39 PM Page 273 Set OldActiveSheet = ActiveSheet ‘ Fill array with sheet names For i = 1 To SheetCount SheetNames(i) = ActiveWorkbook.Sheets(i).Name Next i ‘ Sort the array in ascending order Call BubbleSort(SheetNames) ‘ Turn off screen updating Application.ScreenUpdating = False ‘ Move the sheets For i = 1 To SheetCount ActiveWorkbook.Sheets(SheetNames(i)).Move _ Before:=ActiveWorkbook.Sheets(i) Next i ‘ Reactivate the original active sheet OldActiveSheet.Activate End Sub Utility availability Because the SortSheets macro is stored in the Personal Macro Workbook, it is available whenever Excel is running. At this point, the macro can be executed by selecting the macro’s name from the Macro dialog box (Alt+F8 displays this dialog box) or by pressing Ctrl+Shift+S. Evaluating the project So there you have it. The utility meets all the original project requirements: It sorts all sheets in the active workbook, it can be executed easily, it’s always available, it seems to work for any workbook, and I have yet to see it display a VBA error message. NOTE The procedure still has one slight problem: The sorting is strict and may not always be “logical.” For example, after sorting, Sheet10 is placed before Sheet2. Most would want Sheet2 to be listed before Sheet10. Part III: Understanding Visual Basic for Applications274 15_044018 ch09.qxp 2/28/07 6:39 PM Page 274 Chapter Creating Function Procedures In This Chapter A function is a VBA procedure that performs some calculations and returns a value. You can use these functions in your Visual Basic for Applications (VBA) code or in formulas. ◆ The difference between Sub procedures and Function procedures ◆ How to create custom functions ◆ About Function procedures and function arguments ◆ How to create a function that emulates Excel’s SUM function ◆ How to debug functions, deal with the Insert Function dialog box, and use add-ins to store custom functions ◆ How to call the Windows Application Programming Interface (API) to per- form otherwise impossible feats VBA enables you to create Sub procedures and Function procedures. I cover Sub procedures in the preceding chapter, and in this chapter I discuss Function procedures. CROSS-REFERENCE Chapter 11 has many useful and practical examples of Function proce- dures. You can incorporate many of these techniques into your work. 10 275 16_044018 ch10.qxp 2/28/07 6:39 PM Page 275 Sub Procedures versus Function Procedures You can think of a Sub procedure as a command that can be executed either by the user or by another procedure. Function procedures, on the other hand, usually return a single value (or an array), just like Excel worksheet functions and VBA built-in functions. As with built-in functions, your Function procedures can use arguments. Function procedures are quite versatile and can be used in two situations: • As part of an expression in a VBA procedure • In formulas that you create in a worksheet In fact, you can use a Function procedure anywhere that you can use an Excel worksheet function or a VBA built-in function. As far as I know, the only exception is that you can’t use a VBA function in a data validation formula. Why Create Custom Functions? You are undoubtedly familiar with Excel worksheet functions; even novices know how to use the most common worksheet functions, such as SUM, AVERAGE, and IF. By my count, Excel contains 340 predefined worksheet functions. If that’s not enough, however, you can create custom functions by using VBA. NEW Excel 2007 incorporates all the functions that are included in the Analysis ToolPak add-in. With all the functions available in Excel and VBA, you might wonder why you would ever need to create new functions. The answer: to simplify your work. With a bit of planning, custom functions are very useful in worksheet formulas and VBA procedures. Often, for example, you can create a custom function that can significantly shorten your formulas. And shorter formulas are more readable and easier to work with. I should also point out, however, that custom functions used in your formulas are usually much slower than built-in functions. And, of course, the user must enable macros in order to use these functions. When you create applications, you may notice that some procedures repeat certain calcula- tions. In such cases, consider creating a custom function that performs the calculation. Then you can simply call the function from your procedure. A custom function can elimi- nate the need for duplicated code, thus reducing errors. Part III: Understanding Visual Basic for Applications276 16_044018 ch10.qxp 2/28/07 6:39 PM Page 276 Also, co-workers often can benefit from your specialized functions. And some may be will- ing to pay you to create custom functions that save them time and work. Although many cringe at the thought of creating custom worksheet functions, the process is not difficult. In fact, I enjoy creating custom functions. I especially like how my custom functions appear in the Insert Function dialog box along with Excel built-in functions, as if I’m re-engineering the software in some way. In this chapter, I tell you what you need to know to start creating custom functions, and I provide lots of examples. An Introductory Function Example Without further ado, this section presents an example of a VBA Function procedure. A custom function The following is a custom function defined in a VBA module. This function, named RemoveVowels, uses a single argument. The function returns the argument, but with all the vowels removed. Function RemoveVowels(Txt) As String ‘ Removes all vowels from the Txt argument Dim i As Long RemoveVowels = “” For i = 1 To Len(Txt) If Not UCase(Mid(Txt, i, 1)) Like “[AEIOU]” Then RemoveVowels = RemoveVowels & Mid(Txt, i, 1) End If Next i End Function This certainly isn’t the most useful function I’ve written, but it demonstrates some key concepts related to functions. I explain how this function works later, in the “Analyzing the custom function” section. CAUTION When you create custom functions that will be used in a worksheet formula, make sure that the code resides in a normal VBA module. If you place your custom functions in a code module for a UserForm, a Sheet, or ThisWorkbook, they will not work in your formulas. Chapter 10: Creating Function Procedures 277 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 277 Using the function in a worksheet When you enter a formula that uses the RemoveVowels function, Excel executes the code to get the value. Here’s an example of how you would use the function in a formula: =RemoveVowels(A1) See Figure 10-1 for examples of this function in action. The formulas are in column B, and they use the text in column A as their arguments. As you can see, the function returns the single argument, but with the vowels removed. Figure 10-1: Using a custom function in a worksheet formula. Actually, the function works pretty much like any built-in worksheet function. You can insert it in a formula by choosing Formulas ➪ Function Library ➪ Insert Function or by clicking the Insert Function Wizard icon to the left of the formula bar. Either of these actions displays the Insert Function dialog box. In the Insert Function dialog box, your custom functions are located, by default, in the User Defined category. You can also nest custom functions and combine them with other elements in your formu- las. For example, the following formula nests the RemoveVowels function inside Excel’s UPPER function. The result is the original string (sans vowels), converted to uppercase. =UPPER(RemoveVowels(A1)) Using the function in a VBA procedure In addition to using custom functions in worksheet formulas, you can also use them in other VBA procedures. The following VBA procedure, which is defined in the same module as the custom RemoveVowels function, first displays an input box to solicit some text from the user. Then the procedure uses the VBA built-in MsgBox function to display the user input after it’s processed by the RemoveVowels function (see Figure 10-2). The original input appears as the caption in the message box. Part III: Understanding Visual Basic for Applications278 16_044018 ch10.qxp 2/28/07 6:39 PM Page 278 Sub ZapTheVowels() Dim UserInput as String UserInput = InputBox(“Enter some text:”) MsgBox RemoveVowels(UserInput), , UserInput End Sub In the example shown in Figure 10-2, the string entered in response to the InputBox func- tion was Excel Power Programming With VBA. The MsgBox function displays the text without vowels. Figure 10-2: Using a custom function in a VBA procedure. Analyzing the custom function Function procedures can be as complex as you need them to be. Most of the time, they are more complex and much more useful than this sample procedure. Nonetheless, an analysis of this example may help you understand what is happening. Here’s the code, again: Function RemoveVowels(Txt) As String ‘ Removes all vowels from the Txt argument Dim i As Long RemoveVowels = “” For i = 1 To Len(Txt) If Not UCase(Mid(Txt, i, 1)) Like “[AEIOU]” Then RemoveVowels = RemoveVowels & Mid(Txt, i, 1) End If Next i End Function Notice that the procedure starts with the keyword Function, rather than Sub, followed by the name of the function (RemoveVowels). This custom function uses only one argument (Txt), enclosed in parentheses. As String defines the data type of the function’s return value. Excel uses the Variant data type if no data type is specified. The second line is simply an optional comment that describes what the function does. This is followed by a Dim statement, which declares the variable (i) used in the procedure as type Long. Chapter 10: Creating Function Procedures 279 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 279 NOTE Notice that I use the function name as a variable here. When a function ends, it always returns the current value of the variable that corresponds to the function’s name. The next five instructions make up a For-Next loop. The procedure loops through each character in the input and builds the string. The first instruction within the loop uses VBA’s Mid function to return a single character from the input string and converts this character to uppercase. That character is then compared to a list of characters by using Excel’s Like operator. In other words, the If clause is true if the character is not A, E, I, O, or U. In such a case, the character is appended to the RemoveVowels variable. When the loop is finished, RemoveVowels consists of the input string with all the vowels removed. This string is the value that the function returns. The procedure ends with an End Function statement. Keep in mind that the coding for this function can be done in a number of different ways. Here’s a function that accomplishes the same result but is coded differently: Part III: Understanding Visual Basic for Applications280 When you develop custom functions, it’s important to understand a key distinction between functions that you call from other VBA procedures and functions that you use in worksheet formulas. Function procedures used in worksheet formulas must be passive. For example, code within a Function procedure cannot manipulate ranges or change things on the worksheet. An example can help make this clear. You might be tempted to write a custom worksheet function that changes a cell’s formatting. For example, it could be useful to have a formula that uses a custom function to change the color of text in a cell based on the cell’s value. Try as you might, however, such a function is impossible to write. No matter what you do, the function won’t change the worksheet. Remember, a function simply returns a value. It cannot perform actions with objects. That said, I should point out one notable exception. It is possible to change the text in a cell comment by using a custom VBA function. Here’s the function: Function ModifyComment(Cell As Range, Cmt As String) Cell.Comment.Text Cmt End Function Here’s an example of using this function in a formula. The formula replaces the comment in cell A1 with new text. The function will not work if cell A1 doesn’t have a comment. =ModifyComment(A1,”Hey, I changed your comment”) What Custom Worksheet Functions Can’t Do 16_044018 ch10.qxp 2/28/07 6:39 PM Page 280 Function RemoveVowels(txt) As String ‘ Removes all vowels from the Txt argument Dim i As Long Dim TempString As String TempString = “” For i = 1 To Len(txt) Select Case ucase(Mid(txt, i, 1)) Case “A”, “E”, “I”, “O”, “U” ‘Do nothing Case Else TempString = TempString & Mid(txt, i, 1) End Select Next i RemoveVowels = TempString End Function In this version, I used a string variable (TempString) to store the vowel-less string as it is being constructed. Then, before the procedure ends, I assigned the contents of TempString to the function’s name. This version also uses a Select Case construct rather than an If- Then construct. CD-ROM Both versions of this function are available on the companion CD-ROM. The file is named remove vowels.xlsm. Function Procedures A custom Function procedure has much in common with a Sub procedure. (For more information on Sub procedures, see Chapter 9.) Declaring a function The syntax for declaring a function is as follows: [Public | Private][Static] Function name ([arglist])[As type] [instructions] [name = expression] [Exit Function] [instructions] [name = expression] End Function Chapter 10: Creating Function Procedures 281 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 281 The Function procedure contains the following elements: • Public: (Optional) Indicates that the Function procedure is accessible to all other procedures in all other modules in all active Excel VBA projects. • Private: (Optional) Indicates that the Function procedure is accessible only to other procedures in the same module. • Static: (Optional) Indicates that the values of variables declared in the Function procedure are preserved between calls. • Function: (Required) Indicates the beginning of a procedure that returns a value or other data. • name:(Required) Represents any valid Function procedure name, which must follow the same rules as a variable name. • arglist:(Optional) Represents a list of one or more variables that represent argu- ments passed to the Function procedure. The arguments are enclosed in parentheses. Use a comma to separate pairs of arguments. • type: (Optional) Is the data type returned by the Function procedure. • instructions: (Optional) Are any number of valid VBA instructions. • Exit Function: (Optional) Is a statement that forces an immediate exit from the Function procedure prior to its completion. • End Function: (Required) Is a keyword that indicates the end of the Function procedure. The main thing to remember about a custom function written in VBA is that a value is always assigned to its name a minimum of one time, generally when it has completed execution. To create a custom function, start by inserting a VBA module. (Or you can use an existing module.) Enter the keyword Function, followed by the function name and a list of its arguments (if any) in parentheses. You can also declare the data type of the return value by using the As keyword (this is optional, but recommended). Insert the VBA code that per- forms the work, making sure that the appropriate value is assigned to the term correspond- ing to the function name at least once within the body of the Function procedure. End the function with an End Function statement. Function names must adhere to the same rules as variable names. If you plan to use your custom function in a worksheet formula, be careful if the function name is also a cell address. For example, if you use something like J21 as a function name, it must be entered with apostrophes: =’J21’(A1) Part III: Understanding Visual Basic for Applications282 16_044018 ch10.qxp 2/28/07 6:39 PM Page 282 The best advice is to avoid using function names that are also cell references, including named ranges. And, avoid using function names that correspond to Excel’s built-in function names. If there is a function name conflict, Excel always uses its built-in function. A function’s scope In Chapter 9, I discuss the concept of a procedure’s scope (public or private). The same discussion applies to functions: A function’s scope determines whether it can be called by procedures in other modules or in worksheets. Here are a few things to keep in mind about a function’s scope: • If you don’t declare a function’s scope, its default is Public. • Functions declared As Private do not appear in Excel’s Paste Function dialog box. Therefore, when you create a function that should be used only in a VBA procedure, you should declare it Private so that users don’t try to use it in a formula. • If your VBA code needs to call a function that’s defined in another workbook, set up a reference to the other workbook by choosing the Visual Basic Editor (VBE) Tools ➪ References command. Executing function procedures Although you can execute a Sub procedure in many ways, you can execute a Function procedure in only three ways: • Call it from another procedure • Use it in a worksheet formula • Call it from the VBE Immediate window FROM A PROCEDURE You can call custom functions from a procedure the same way that you call built-in func- tions. For example, after you define a function called SumArray, you can enter a statement like the following: Total = SumArray(MyArray) This statement executes the SumArray function with MyArray as its argument, returns the function’s result, and assigns it to the Total variable. You also can use the Run method of the Application object. Here’s an example: Total = Application.Run (“SumArray”, “MyArray”) Chapter 10: Creating Function Procedures 283 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 283 The first argument for the Run method is the function name. Subsequent arguments repre- sent the argument(s) for the function. The arguments for the Run method can be literal strings (as shown above), numbers, or variables. IN A WORKSHEET FORMULA Using custom functions in a worksheet formula is like using built-in functions except that you must ensure that Excel can locate the Function procedure. If the Function proce- dure is in the same workbook, you don’t have to do anything special. If it’s in a different workbook, you may have to tell Excel where to find it. You can do so in three ways: • Precede the function name with a file reference. For example, if you want to use a function called CountNames that’s defined in an open workbook named Myfuncs.xlsm, you can use the following reference: =Myfuncs.xlsm!CountNames(A1:A1000) If you insert the function with the Insert Function dialog box, the workbook reference is inserted automatically. • Set up a reference to the workbook. You do so by choosing the VBE Tools ➪ References command. If the function is defined in a referenced workbook, you don’t need to use the worksheet name. Even when the dependent workbook is assigned as a reference, the Paste Function dialog box continues to insert the workbook reference (although it’s not necessary). • Create an add-in. When you create an add-in from a workbook that has Function proce- dures, you don’t need to use the file reference when you use one of the functions in a formula. The add-in must be installed, however. I discuss add-ins in Chapter 21. You’ll notice that unlike Sub procedures, your Function procedures do not appear in the Macro dialog box when you issue the Tools ➪ Macro ➪ Macros command. In addition, you can’t choose a function when you issue the VBE Run ➪ Sub/UserForm command (or press F5) if the cursor is located in a Function procedure. (You get the Macro dialog box that lets you choose a macro to run.) As a result, you need to do a bit of extra up-front work to test your functions while you’re developing them. One approach is to set up a simple proce- dure that calls the function. If the function is designed to be used in worksheet formulas, you’ll want to enter a simple formula to test it. FROM THE VBE IMMEDIATE WINDOW The final way to call Function procedure is from the VBE Immediate window. This method is generally used only for testing purposes. Figure 10-3 shows an example. Part III: Understanding Visual Basic for Applications284 16_044018 ch10.qxp 2/28/07 6:39 PM Page 284 Figure 10-3: Calling a Function procedure from the Immediate Window. Function Arguments Keep in mind the following points about Function procedure arguments: • Arguments can be variables (including arrays), constants, literals, or expressions. • Some functions do not have arguments. • Some functions have a fixed number of required arguments (from 1 to 60). • Some functions have a combination of required and optional arguments. NOTE If your formula uses a custom worksheet function and it returns #VALUE!, there is an error in your function. The error could be caused by logical errors in your code or by passing incorrect arguments to the function. See “Debugging Functions” later in this chapter. Chapter 10: Creating Function Procedures 285 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 285 Function Examples In this section, I present a series of examples that demonstrate how to use arguments effectively with functions. By the way, this discussion also applies to Sub procedures. Functions with no argument Like Sub procedures, Function procedures need not have arguments. Excel, for example, has a few built-in functions that don’t use arguments, including RAND, TODAY, and NOW. You can create similar functions. Part III: Understanding Visual Basic for Applications286 Just for fun, I wrote my own version of Excel’s UPPER function (which converts a string to all uppercase) and named it UpCase: Function UpCase(InString As String) As String ‘ Converts its argument to all uppercase. Dim StringLength As Integer Dim i As Integer Dim ASCIIVal As Integer Dim CharVal As Integer StringLength = Len(InString) UpCase = InString For i = 1 To StringLength ASCIIVal = Asc(Mid(InString, i, 1)) CharVal = 0 If ASCIIVal >= 97 And ASCIIVal <= 122 Then CharVal = -32 Mid(UpCase, i, 1) = Chr(ASCIIVal + CharVal) End If Next i End Function Note: A workbook that contains this function is on the companion CD-ROM in a file named upper case.xlsm. Notice that I resisted the urge to take the easy route — using the VBA UCase function. I was curious to see how the custom function differed from the built-in function, so I created a worksheet that called the function 20,000 times, using random names. The worksheet took about 40 seconds to calculate. I then substituted Excel’s UPPER function and ran the test again. The recalculation time was virtually instantaneous. I don’t claim that my UpCase function is the optimal algorithm for this task, but it’s safe to say that a custom function will never match the speed of Excel’s built-in functions. Reinventing the Wheel 16_044018 ch10.qxp 2/28/07 6:39 PM Page 286 This section contains examples of functions that don’t use an argument. CD-ROM A workbook that contains these functions is available on the companion CD-ROM. The file is named no argument.xlsm. Here’s a simple example of a function that doesn’t use an argument. The following function returns the UserName property of the Application object. This name appears in the Options dialog box (General tab) and is stored in the Windows Registry. Function User() ‘ Returns the name of the current user User = Application.UserName End Function When you enter the following formula, the cell returns the name of the current user (assuming that it’s listed properly in the Registry): =User() NOTE When you use a function with no arguments in a worksheet formula, you must include a set of empty parentheses. This requirement is not necessary if you call the function in a VBA procedure, although including the empty parentheses does make it clear that you’re calling a function. To use this function in another procedure, you can assign it to a variable, use it in an expression, or use it as an argument for another function. The following example calls the User function and uses the return value as an argument for the MsgBox statement. The concatenation operator (&) joins the literal string with the result of the User function. Sub ShowUser() MsgBox “Your name is “ & User() End Sub This example demonstrates how you can create a wrapper function that simply returns a property or the result of a VBA function. Following are three additional wrapper functions that take no argument. Function ExcelDir() As String ‘ Returns the directory in which Excel is installed ExcelDir = Application.Path Chapter 10: Creating Function Procedures 287 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 287 End Function Function SheetCount() ‘ Returns the number of sheets in the workbook SheetCount = Application.Caller.Parent.Parent.Sheets.Count End Function Function SheetName() ‘ Returns the name of the worksheet SheetName = Application.Caller.Parent.Name End Function Part III: Understanding Visual Basic for Applications288 When you use a custom function in a worksheet formula, when is it recalculated? Custom functions behave like Excel’s built-in worksheet functions. Normally, a custom function is recalculated only when it needs to be — which is only when any of the function’s arguments are modified. You can, however, force functions to recalculate more frequently. Adding the following statement to a Function procedure makes the function recalculate whenever the sheet is recalculated. If you’re using automatic calculation mode, a calculation occurs whenever any cell is changed. Application.Volatile True The Volatile method of the Application object has one argument (either True or False). Marking a Function procedure as volatile forces the function to be calculated whenever recalculation occurs for any cell in the worksheet. For example, the custom StaticRand function can be changed to emulate Excel’s RAND function using the Volatile method, as follows: Function NonStaticRand() ‘ Returns a random number that ‘ changes with each calculation Application.Volatile True NonStaticRand = Rnd() End Function Using the False argument of the Volatile method causes the function to be recalculated only when one or more of its arguments change as a result of a recalculation. (If a function has no arguments, this method has no effect.) To force an entire recalculation, including nonvolatile custom functions, press Ctrl+Alt+F9. This key combination will, for example, generate new random numbers for the StaticRand function presented in this chapter. Controlling Function Recalculation 16_044018 ch10.qxp 2/28/07 6:39 PM Page 288 Here’s another example of a function that doesn’t take an argument. I used to use Excel’s RAND function to quickly fill a range of cells with values. But I didn’t like the fact that the random numbers change whenever the worksheet is recalculated. So I remedied this by converting the formulas to values. Then I realized that I could create a custom function that returned random numbers that didn’t change. I used the VBA built-in Rnd function, which returns a random number between 0 and 1. The custom function is as follows: Function StaticRand() ‘ Returns a random number that doesn’t ‘ change when recalculated StaticRand = Rnd() End Function If you want to generate a series of random integers between 0 and 1,000, you can use a formula such as this: =INT(StaticRand()*1000) The values produced by this formula never change when the worksheet is calculated nor- mally. However, you can force the formula to recalculate by pressing Ctrl+Alt+F9 A function with one argument This section describes a function for sales managers who need to calculate the commis- sions earned by their sales forces. The calculations in this example are based on the fol- lowing table: Monthly Sales Commission Rate 0–$9,999 8.0% $10,000–$19,999 10.5% $20,000–$39,999 12.0% $40,000+ 14.0% Note that the commission rate is nonlinear and also depends on the month’s total sales. Employees who sell more earn a higher commission rate. Chapter 10: Creating Function Procedures 289 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 289 There are several ways to calculate commissions for various sales amounts entered into a worksheet. If you’re not thinking too clearly, you might waste lots of time and come up with a lengthy formula such as this: =IF(AND(A1>=0,A1<=9999.99),A1*0.08, IF(AND(A1>=10000,A1<=19999.99),A1*0.105, IF(AND(A1>=20000,A1<=39999.99),A1*0.12, IF(A1>=40000,A1*0.14,0)))) This is a bad approach for a couple of reasons. First, the formula is overly complex, making it difficult to understand. Second, the values are hard-coded into the formula, making the formula difficult to modify. A better (non-VBA) approach is to use a lookup table function to compute the commissions. For example, the following formula uses VLOOKUP to retrieve the commission value from a range named Table and multiplies that value by the value in cell A1. =VLOOKUP(A1,Table,2)*A1 Yet another approach (which eliminates the need to use a lookup table) is to create a cus- tom function such as the following: Function Commission(Sales) Const Tier1 = 0.08 Const Tier2 = 0.105 Const Tier3 = 0.12 Const Tier4 = 0.14 ‘ Calculates sales commissions Select Case Sales Case 0 To 9999.99: Commission = Sales * Tier1 Case 1000 To 19999.99: Commission = Sales * Tier2 Case 20000 To 39999.99: Commission = Sales * Tier3 Case Is >= 40000: Commission = Sales * Tier4 End Select End Function After you enter this function in a VBA module, you can use it in a worksheet formula or call the function from other VBA procedures. Entering the following formula into a cell produces a result of 3,000; the amount — 25,000 — qualifies for a commission rate of 12 percent: =Commission(25000) Even if you don’t need custom functions in a worksheet, creating Function procedures can make your VBA coding much simpler. For example, if your VBA procedure calculates sales commissions, you can use the exact same function and call it from a VBA procedure. Part III: Understanding Visual Basic for Applications290 16_044018 ch10.qxp 2/28/07 6:39 PM Page 290 Here’s a tiny procedure that asks the user for a sales amount and then uses the Commission function to calculate the commission due: Sub CalcComm() Dim Sales as Long Sales = InputBox(“Enter Sales:”) MsgBox “The commission is “ & Commission(Sales) End Sub The CalcComm procedure starts by displaying an input box that asks for the sales amount. Then it displays a message box with the calculated sales commission for that amount. This Sub procedure works, but it is rather crude. Following is an enhanced version that displays formatted values and keeps looping until the user clicks No (see Figure 10-4). Figure 10-4: Using a function to display the result of a calculation. Sub CalcComm() Dim Sales As Long Dim Msg As String, Ans As String ‘ Prompt for sales amount Sales = Val(InputBox(“Enter Sales:”, _ “Sales Commission Calculator”)) ‘ Build the Message Msg = “Sales Amount:” & vbTab & Format(Sales, “$#,##0.00”) Msg = Msg & vbCrLf & “Commission:” & vbTab Msg = Msg & Format(Commission(Sales), “$#,##0.00”) Msg = Msg & vbCrLf & vbCrLf & “Another?” ‘ Display the result and prompt for another Ans = MsgBox(Msg, vbYesNo, “Sales Commission Calculator”) If Ans = vbYes Then CalcComm End Sub This function uses two VBA built-in constants: vbTab represents a tab (to space the out- put), and vbCrLf specifies a carriage return and line feed (to skip to the next line). VBA’s Format function displays a value in a specified format (in this case, with a dollar sign, comma, and two decimal places). Chapter 10: Creating Function Procedures 291 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 291 In both of these examples, the Commission function must be available in the active work- book; otherwise, Excel displays an error message saying that the function is not defined. A function with two arguments Imagine that the aforementioned hypothetical sales managers implement a new policy to help reduce turnover: The total commission paid is increased by 1 percent for every year that the salesperson has been with the company. I modified the custom Commission function (defined in the preceding section) so that it takes two arguments. The new argument represents the number of years. Call this new function Commission2: Function Commission2(Sales, Years) ‘ Calculates sales commissions based on ‘ years in service Const Tier1 = 0.08 Const Tier2 = 0.105 Const Tier3 = 0.12 Const Tier4 = 0.14 Select Case Sales Case 0 To 9999.99: Commission2 = Sales * Tier1 Case 1000 To 19999.99: Commission2 = Sales * Tier2 Case 20000 To 39999.99: Commission2 = Sales * Tier3 Case Is >= 40000: Commission2 = Sales * Tier4 End Select Commission2 = Commission2 + (Commission2 * Years / 100) End Function Part III: Understanding Visual Basic for Applications292 All ranges that are used in a custom function should be passed as arguments. Consider the following function, which returns the value in A1, multiplied by 2: Function DoubleCell() DoubleCell = Range(“A1”) * 2 End Function Although this function works, there are times when it may return an incorrect result. Excel’s calculation engine cannot account for ranges in your code that are not passed as arguments. Therefore, in some cases, all precedents may not be calculated before the function’s value is returned. The DoubleCell function should be written as follows, with A1 passed as the argument: Function DoubleCell(cell) DoubleCell = cell * 2 End Function Use Arguments, Not Cell References 16_044018 ch10.qxp 2/28/07 6:39 PM Page 292 Pretty simple, eh? I just added the second argument (Years) to the Function statement and included an additional computation that adjusts the commission. Here’s an example of how you can write a formula using this function (it assumes that the sales amount is in cell A1 and the number of years the salesperson has worked is in cell B1): =Commission2(A1,B1) CD-ROM All of these commission-related procedures are available on the companion CD-ROM in a file named commission functions.xlsm. A function with an array argument A Function procedure also can accept one or more arrays as arguments, process the array(s), and return a single value. The array can also consist of a range of cells. The following function accepts an array as its argument and returns the sum of its elements: Function SumArray(List) As Double Dim Item As Variant SumArray = 0 For Each Item In List If WorksheetFunction.IsNumber(Item) Then _ SumArray = SumArray + Item Next Item End Function Excel’s ISNUMBER function checks to see whether each element is a number before adding it to the total. Adding this simple error-checking statement eliminates the type-mismatch error that occurs when you try to perform arithmetic with something other than a number. The following procedure demonstrates how to call this function from a Sub procedure. The MakeList procedure creates a 100-element array and assigns a random number to each element. Then the MsgBox function displays the sum of the values in the array by calling the SumArray function. Sub MakeList() Dim Nums(1 To 100) As Double Dim i as Integer For i = 1 To 100 Nums(i) = Rnd * 1000 Next i MsgBox SumArray(Nums) End Sub Chapter 10: Creating Function Procedures 293 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 293 Notice that the SumArray function doesn’t declare the data type of its argument (it’s a variant). Because it’s not declared as a specific numeric type, the function also works in your worksheet formulas in which the argument is a Range object. For example, the follow- ing formula returns the sum of the values in A1:C10: =SumArray(A1:C10) You might notice that, when used in a worksheet formula, the SumArray function works very much like Excel’s SUM function. One difference, however, is that SumArray does not accept multiple arguments. Understand that this example is for educational purposes only. Using the SumArray function in a formula offers absolutely no advantages over the Excel SUM function. CD This example, named array argument.xlsm, is available on the companion CD-ROM. A function with optional arguments Many of Excel’s built-in worksheet functions use optional arguments. An example is the LEFT function, which returns characters from the left side of a string. Its syntax is LEFT(text,num_chars) The first argument is required, but the second is optional. If the optional argument is omit- ted, Excel assumes a value of 1. Therefore, the following two formulas return the same result: =LEFT(A1,1) =LEFT(A1) The custom functions that you develop in VBA also can have optional arguments. You spec- ify an optional argument by preceding the argument’s name with the keyword Optional. In the argument list, optional arguments must appear after any required arguments. Following is a simple function example that returns the user’s name. The function’s argu- ment is optional. Function User(Optional UpperCase As Variant) If IsMissing(UpperCase) Then UpperCase = False User = Application.UserName If UpperCase Then User = UCase(User) End Function If the argument is False or omitted, the user’s name is returned without any changes. If the argument is True, the user’s name is converted to uppercase (using the VBA UCase Part III: Understanding Visual Basic for Applications294 16_044018 ch10.qxp 2/28/07 6:39 PM Page 294 function) before it is returned. Notice that the first statement in the procedure uses the VBA IsMissing function to determine whether the argument was supplied. If the argu- ment is missing, the statement sets the UpperCase variable to False (the default value). All the following formulas are valid, and the first two produce the same result: =User() =User(False) =User(True) NOTE If you need to determine whether an optional argument was passed to a function, you must declare the optional argument as a Variant data type. Then you can use the IsMissing function within the procedure, as demonstrated in this example. The following is another example of a custom function that uses an optional argument. This function randomly chooses one cell from an input range and returns that cell’s contents. If the second argument is True, the selected value changes whenever the worksheet is recal- culated (that is, the function is made volatile). If the second argument is False (or omitted), the function is not recalculated unless one of the cells in the input range is modified. Function DrawOne(Rng As Variant, Optional Recalc As Variant = False) ‘ Chooses one cell at random from a range ‘ Make function volatile if Recalc is True Application.Volatile Recalc ‘ Determine a random cell DrawOne = Rng(Int((Rng.Count) * Rnd + 1)) End Function Notice that the second argument for DrawOne includes the Optional keyword, along with a default value. All the following formulas are valid, and the first two have the same effect: =DrawOne(A1:A100) =DrawOne(A1:A100,False) =DrawOne(A1:A100,True) This function might be useful for choosing lottery numbers, picking a winner from a list of names, and so on. CD-ROM This function is available on the companion CD-ROM. The filename is draw.xlsm. Chapter 10: Creating Function Procedures 295 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 295 A function that returns a VBA array VBA includes a useful function called Array. The Array function returns a variant that contains an array (that is, multiple values). If you’re familiar with array formulas in Excel, you have a head start on understanding VBA’s Array function. You enter an array formula into a cell by pressing Ctrl+Shift+Enter. Excel inserts curly braces around the formula to indicate that it’s an array formula. CROSS-REFERENCE See Chapter 3 for more details on array formulas. NOTE It’s important to understand that the array returned by the Array function is not the same as a normal array that’s made up of elements of the Variant data type. In other words, a variant array is not the same as an array of variants. The MonthNames function, which follows, is a simple example that uses VBA’s Array func- tion in a custom function: Function MonthNames() MonthNames = Array(“Jan”, “Feb”, “Mar”, “Apr”,”May”, “Jun”, _ “Jul”, “Aug”, “Sep”, “Oct”, “Nov”, “Dec”) End Function The MonthNames function returns a horizontal array of month names. You can create a multicell array formula that uses the MonthNames function. Here’s how to use it: Make sure that the function code is present in a VBA module. Then in a worksheet, select multi- ple cells in a row (start by selecting 12 cells). Then enter the formula that follows (without the braces) and press Ctrl+Shift+Enter: {=MonthNames()} What if you’d like to generate a vertical list of month names? No problem; just select a ver- tical range, enter the following formula (without the braces), and then press Ctrl+Shift+Enter: {=TRANSPOSE(MonthNames())} This formula uses the Excel TRANSPOSE function to convert the horizontal array to a ver- tical array. Part III: Understanding Visual Basic for Applications296 16_044018 ch10.qxp 2/28/07 6:39 PM Page 296 The following example is a variation on the MonthNames function: Function MonthNames(Optional MIndex) Dim AllNames As Variant Dim MonthVal As Long AllNames = Array(“Jan”, “Feb”, “Mar”, “Apr”, _ “May”, “Jun”, “Jul”, “Aug”, “Sep”, “Oct”, _ “Nov”, “Dec”) If IsMissing(MIndex) Then MonthNames = AllNames Else Select Case MIndex Case Is >= 1 ‘ Determine month value (for example, 13=1) MonthVal = ((MIndex - 1) Mod 12) MonthNames = AllNames(MonthVal) Case Is <= 0 ‘ Vertical array MonthNames = Application.Transpose(AllNames) End Select End If End Function Notice that I use the VBA IsMissing function to test for a missing argument. In this situ- ation, it is not possible to specify the default value for the missing argument in the argu- ment list of the function because the default value is defined within the function. You can use the IsMissing function only if the optional argument is a variant. This enhanced function uses an optional argument that works as follows: • If the argument is missing, the function returns a horizontal array of month names. • If the argument is less than or equal to 0, the function returns a vertical array of month names. It uses Excel’s TRANSPOSE function to convert the array. • If the argument is greater than or equal to 1, it returns the month name that corresponds to the argument value. NOTE This procedure uses the Mod operator to determine the month value. The Mod operator returns the remainder after dividing the first operand by the second. Keep in mind that the AllNames array is zero-based and that indices range from 0 to 11. In the statement that uses the Mod operator, 1 is subtracted from the function’s argument. Therefore, an argument of 13 returns 0 (corresponding to Jan), and an argument of 24 returns 11 (cor- responding to Dec). Chapter 10: Creating Function Procedures 297 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 297 You can use this function in a number of ways, as illustrated in Figure 10-5. Figure 10-5: Different ways of passing an array or a single value to a worksheet. Range A1:L1 contains the following formula entered as an array. Start by selecting A1:L1, enter the formula (without the braces), and then press Ctrl+Shift+Enter. {=MonthNames()} Range A3:A14 contains integers from 1 to 12. Cell B3 contains the following (nonarray) formula, which was copied to the 11 cells below it: =MonthNames(A3) Range D3:D14 contains the following formula entered as an array: {=MonthNames(-1)} Range F3 contains this (nonarray) formula: =MonthNames(3) Remember: To enter an array formula, you must press Ctrl+Shift+Enter. NOTE The lower bound of an array, created using the Array function, is determined by the lower bound specified with the Option Base statement at the top of the module. If there is no Option Base statement, the default lower bound is 0. CD-ROM A workbook that demonstrates the MonthNames function is available on the companion CD-ROM. The file is named month names.xslm. Part III: Understanding Visual Basic for Applications298 16_044018 ch10.qxp 2/28/07 6:39 PM Page 298 A function that returns an error value In some cases, you might want your custom function to return a particular error value. Consider the RemoveVowels function, which I presented earlier in this chapter: Function RemoveVowels(Txt) As String ‘ Removes all vowels from the Txt argument Dim i As Long RemoveVowels = “” For i = 1 To Len(Txt) If Not UCase(Mid(Txt, i, 1)) Like “[AEIOU]” Then RemoveVowels = RemoveVowels & Mid(Txt, i, 1) End If Next i End Function When used in a worksheet formula, this function removes the vowels from its single-cell argument. If the argument is a numeric value, this function returns the value as a string. You may prefer that the function returns an error value (#N/A), rather than the numeric value converted to a string. You might be tempted simply to assign a string that looks like an Excel formula error value. For example: RemoveVowels = “#N/A” Although the string looks like an error value, it is not treated as such by other formulas that may reference it. To return a real error value from a function, use the VBA CVErr function, which converts an error number to a real error. Fortunately, VBA has built-in constants for the errors that you want to return from a cus- tom function. These errors are Excel formula error values and not VBA runtime error val- ues. These constants are as follows: • xlErrDiv0 (for #DIV/0!) • xlErrNA (for #N/A) • xlErrName (for #NAME?) • xlErrNull (for #NULL!) • xlErrNum (for #NUM!) • xlErrRef (for #REF!) • xlErrValue (for #VALUE!) Chapter 10: Creating Function Procedures 299 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 299 To return a #N/A error from a custom function, you can use a statement like this: RemoveVowels = CVErr(xlErrNA) The revised RemoveVowels function follows. This function uses an If-Then construct to take a different action if the argument is not text. It uses Excel’s ISTEXT function to determine whether the argument is text. If the argument is text, the function proceeds nor- mally. If the cell doesn’t contain text (or is empty), the function returns the #N/A error. Function RemoveVowels(Txt) As Variant ‘ Removes all vowels from the Txt argument ‘ Returns #VALUE if Txt is not a string Dim i As Long RemoveVowels = “” If Application.WorksheetFunction.IsText(Txt) Then For i = 1 To Len(Txt) If Not UCase(Mid(Txt, i, 1)) Like “[AEIOU]” Then RemoveVowels = RemoveVowels & Mid(Txt, i, 1) End If Next i Else RemoveVowels = CVErr(xlErrNA) End If End Function NOTE Notice that I also changed the data type for the function’s return value. Because the function can now return something other than a string, I changed the data type to Variant. A function with an indefinite number of arguments Some Excel worksheet functions take an indefinite number of arguments. A familiar exam- ple is the SUM function, which has the following syntax: SUM(number1,number2...) The first argument is required, but you can have as many as 254 additional arguments in Excel 2007. Here’s an example of a SUM function with four range arguments: =SUM(A1:A5,C1:C5,E1:E5,G1:G5) Part III: Understanding Visual Basic for Applications300 16_044018 ch10.qxp 2/28/07 6:39 PM Page 300 You can even mix and match the argument types. For example, the following example uses three arguments: the first is a range, the second is a value, and the third is an expression. =SUM(A1:A5,12,24*3) You can create Function procedures that have an indefinite number of arguments. The trick is to use an array as the last (or only) argument, preceded by the keyword ParamArray. NOTE ParamArray can apply only to the last argument in the procedure’s argument list. It is always a Variant data type, and it is always an optional argument (although you don’t use the Optional keyword). Following is a function that can have any number of single-value arguments. (It doesn’t work with multicell range arguments.) It simply returns the sum of the arguments. Function SimpleSum(ParamArray arglist() As Variant) As Double For Each arg In arglist SimpleSum = SimpleSum + arg Next arg End Function To modify this function so it works with multicell range arguments, you need to add another loop, which processes each cell in each of the arguments: Function SimpleSum(ParamArray arglist() As Variant) As Double Dim cell As Range For Each arg In arglist For Each cell In arg SimpleSum = SimpleSum + cell Next cell Next arg End Function The SimpleSum function is similar to Excel’s SUM function, but it’s not nearly as flexible. Try it out by using various types of arguments, and you’ll see that it fails if any of the cells contains a non-value. Emulating Excel’s SUM Function In this section, I present a custom function called MySum. Unlike the SimpleSum function listed in the previous section, the MySum function emulates Excel’s SUM function (almost) perfectly. Chapter 10: Creating Function Procedures 301 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 301 Before you look at the code for MySum, take a minute to think about the Excel SUM func- tion. It is, in fact, very versatile. It can have as many as 255 arguments (even “missing” arguments), and the arguments can be numerical values, cells, ranges, text representa- tions of numbers, logical values, and even embedded functions. For example, consider the following formula: =SUM(B1,5,”6”,,TRUE,SQRT(4),A1:A5,D:D,C2*C3) This perfectly valid formula contains all the following types of arguments, listed here in the order of their presentation: • A single cell reference • A literal value • A string that looks like a value • A missing argument • A logical TRUE value • An expression that uses another function • A simple range reference • A range reference that includes an entire column • An expression that calculates the product of two cells The MySum function (see Listing 10-1) handles all these argument types. CD-ROM A workbook containing the MySum function is available on the companion CD-ROM. The file is named mysum function.xlsm. Listing 10-1: MySum Function Function MySum(ParamArray args() As Variant) As Variant ‘ Emulates Excel’s SUM function ‘ Variable declarations Dim i As Variant Dim TempRange As Range, cell As Range Dim ECode As String Dim m, n MySum = 0 Part III: Understanding Visual Basic for Applications302 16_044018 ch10.qxp 2/28/07 6:39 PM Page 302 ‘ Process each argument For i = 0 To UBound(args) ‘ Skip missing arguments If Not IsMissing(args(i)) Then ‘ What type of argument is it? Select Case TypeName(args(i)) Case “Range” ‘ Create temp range to handle full row or column ranges Set TempRange = Intersect(args(i).Parent.UsedRange, _ args(i)) For Each cell In TempRange If IsError(cell) Then MySum = cell ‘ return the error Exit Function End If If cell = True Or cell = False Then MySum = MySum + 0 Else If IsNumeric(cell) Or IsDate(cell) Then _ MySum = MySum + cell End If Next cell Case “Variant()” n = args(i) For m = LBound(n) To UBound(n) MySum = MySum(MySum, n(m)) ‘recursive call Next m Case “Null” ‘ignore it Case “Error” ‘return the error MySum = args(i) Exit Function Case “Boolean” ‘ Check for literal TRUE and compensate If args(i) = “True” Then MySum = MySum + 1 Case “Date” MySum = MySum + args(i) Case Else MySum = MySum + args(i) End Select End If Next i End Function Figure 10-6 shows a workbook with various formulas that use SUM and MySum. As you can see, the functions return identical results. Chapter 10: Creating Function Procedures 303 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 303 Figure 10-6: Comparing SUM with MySum. If you’re interested in learning how this function works, create a formula that uses the function. Then, set a breakpoint in the code and step through the statements line by line. (See “Debugging Functions,” later in this chapter.) Try this for several different argument types, and you’ll soon have a good feel for how this function works. As you study the code for MySum, keep the following points in mind: • Missing arguments (determined by the IsMissing function) are simply ignored. • The procedure uses VBA’s TypeName function to determine the type of argument (Range, Error, and so on). Each argument type is handled differently. • For a range argument, the function loops through each cell in the range, determines the type of data in the cell, and (if appropriate) adds its value to a running total. • The data type for the function is Variant because the function needs to return an error if any of its arguments is an error value. • If an argument contains an error (for example, #DIV/0!), the MySum function simply returns the error — just as Excel’s SUM function does. • Excel’s SUM function considers a text string to have a value of 0 unless it appears as a literal argument (that is, as an actual value, not a variable). Therefore, MySum adds the cell’s value only if it can be evaluated as a number. (VBA’s IsNumeric function is used for this.) • For range arguments, the function uses the Intersect method to create a temporary range that consists of the intersection of the range and the sheet’s used range. This han- dles cases in which a range argument consists of a complete row or column, which would take forever to evaluate. You might be curious about the relative speeds of SUM and MySum. MySum, of course, is much slower, but just how much slower depends on the speed of your system and the for- mulas themselves. On my system, a worksheet with 1,000 SUM formulas recalculates instantly. After I replace the SUM functions with MySum functions, it takes about eight sec- onds. MySum may be improved a bit, but it can never come close to SUM’s speed. By the way, I hope you understand that the point of this example is not to create a new SUM function. Rather, it demonstrates how to create custom worksheet functions that look and work like those built into Excel. Part III: Understanding Visual Basic for Applications304 16_044018 ch10.qxp 2/28/07 6:39 PM Page 304 Debugging Functions When you’re using a formula in a worksheet to test a Function procedure, VBA runtime errors do not appear in the all-too-familiar, pop-up error box. If an error occurs, the for- mula simply returns an error value (#VALUE!). Luckily, this does not present a problem for debugging functions because you have several possible workarounds: • Place MsgBox functions at strategic locations to monitor the value of specific variables. Message boxes in Function procedures do pop up when the procedure is executed. But make sure that you have only one formula in the worksheet that uses your function, or message boxes will appear for each formula that is evaluated, which is a repetition that will quickly become annoying. • Test the procedure by calling it from a Sub procedure, not from a worksheet formula. Runtime errors are displayed in the usual manner, and you can either fix the problem (if you know it) or jump right into the Debugger. • Set a breakpoint in the function and then step through the function. You then can access all the standard VBA debugging tools. To set a breakpoint, move the cursor to the state- ment at which you want to pause execution and then choose Debug ➪ Toggle Breakpoint (or press F9). When the function is executing, press F8 to step through the procedure line-by-line. • Use one or more temporary Debug.Print statements in your code to write values to the VBE Immediate window. For example, if you want to monitor a value inside of a loop, use something like the following routine: Function VowelCount(r) As Long Dim Count As Long Dim i As Long Dim Ch As String * 1 Count = 0 For i = 1 To Len(r) Ch = UCase(Mid(r, i, 1)) If Ch Like “[AEIOU]” Then Count = Count + 1 Debug.Print Ch, i End If Next i VowelCount = Count End Function In this case, the values of two variables, Ch and i, are printed to the Immediate window whenever the Debug.Print statement is encountered. Figure 10-7 shows the result when the function has an argument of Tucson Arizona. Chapter 10: Creating Function Procedures 305 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 305 Figure 10-7: Use the Immediate window to display results while a function is running. Dealing with the Insert Function Dialog Box Excel’s Insert Function dialog box is a handy tool. When you are creating a worksheet for- mula, this tool lets you select a particular worksheet function from a list of functions (see Figure 10-8). These functions are grouped into various categories to make it easier to locate a particular function. The Insert Function dialog box also displays your custom worksheet functions, and the Function Arguments dialog box prompts you for a function’s arguments. NOTE Custom Function procedures defined with the Private keyword do not appear in the Insert Function dialog box. If you develop a function that’s intended to be used only in your other VBA procedures, you should declare it by using the Private keyword. However, declaring the function as Private does not prevent it from being used in a worksheet formula. It just prevents the function from displaying in the Insert Function dialog box. Part III: Understanding Visual Basic for Applications306 16_044018 ch10.qxp 2/28/07 6:39 PM Page 306 Figure 10-8: Inserting a custom function into a formula. By default, custom functions are listed under the User Defined category, but you can have them appear under a different category if you like. You also can add some text to describe the function. (I highly recommend this step.) In the Insert Function dialog box, notice that the workbook name is also displayed for functions that are defined in a workbook other than the active workbook. NOTE The Insert Function dialog box enables you to search for a function by keyword. Unfortunately, this search feature cannot be used to locate custom functions created in VBA. Specifying a function category Oddly, Excel does not provide a direct way to assign a custom function to a category. If you would like your custom function to appear in a function category other than User Defined, you must write and execute some VBA code. The following statement assigns the function named Commission to the Financial cate- gory (category number 1): Application.MacroOptions Macro:=”Commission”, Category:=1 NOTE You need to execute this statement only one time (not each time the workbook is opened). From then on, every time the workbook is opened, the function will appear in the category that you specified. Chapter 10: Creating Function Procedures 307 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 307 Table 10-1 lists the category numbers that you can use. Notice that a few of these cate- gories (10 through 13) are normally not displayed in the Insert Function dialog box. If you assign your function to one of these categories, the category will appear in the dialog box. TABLE 10-1 FUNCTION CATEGORIES Category Number Category Name 0 All (no specific category) 1 Financial 2 Date & Time 3 Math & Trig 4 Statistical 5 Lookup & Reference 6 Database 7 Text 8 Logical 9 Information 10 Commands 11 Customizing 12 Macro Control 13 DDE/External 14 User Defined 15 Engineering 16 Cube* *The Cube category is new to Excel 2007 Adding a function description When you select a function in the Insert Function dialog box, a brief description of the function appears. You can specify a description for your custom function in two ways: Either use the Macro dialog box or write VBA code. Part III: Understanding Visual Basic for Applications308 16_044018 ch10.qxp 2/28/07 6:39 PM Page 308 NOTE If you don’t provide a description for your custom function, the Insert Function dialog box displays the following text: No help available. DESCRIBING YOUR FUNCTION IN THE MACRO DIALOG BOX Follow these steps to provide a description for a custom function: 1. Create your function in the VBE. 2. Activate Excel, making sure that the workbook that contains the function is the active workbook. 3. Choose Developer ➪ Code ➪ Macros (or press Alt+F8). The Macro dialog box lists available procedures, but your functions will not be in the list. 4. Type the name of your function in the Macro Name box. 5. Click the Options button to display the Macro Options dialog box. 6. Enter the function description in the Description box (see Figure 10-9). The Shortcut Key field is irrelevant for functions. Figure 10-9: Provide a function description in the Macro Options dialog box. 7. Click OK and then click Cancel. After you perform the preceding steps, the Insert Function dialog box displays the descrip- tion that you entered in Step 6 when the function is selected. CROSS-REFERENCE For information on creating a custom help topic accessible from the Insert Function dia- log box, refer to Chapter 24. Chapter 10: Creating Function Procedures 309 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 309 When you use the Insert Function dialog box to enter a function, the Function Arguments dialog box is displayed after you click OK. For built-in functions, the Function Arguments dialog box displays a description for each of the function’s arguments. Unfortunately, you cannot provide such descriptions for custom function arguments. DESCRIBING YOUR FUNCTION WITH VBA CODE Another way to provide a description for a custom function is to write VBA code. The fol- lowing statement assigns a description for the function named Commission: Application.MacroOptions _ Macro:= “Commission”, _ Description:= “Calculates sales commissions” You need to execute this statement only one time (not each time the workbook is opened). Using Add-ins to Store Custom Functions You might prefer to store frequently used custom functions in an add-in file. A primary advantage of doing this is that you can use the functions in any workbook. In addition, the functions can be used in formulas without a filename qualifier. Assume that you have a custom function named ZapSpaces, and that it’s stored in Myfuncs.xlsm. To use this function in a formula in a workbook other than Myfuncs.xlsm, you need to enter the following formula: =Myfuncs.xlsm!ZapSpaces(A1:C12) If you create an add-in from Myfuncs.xlsm and the add-in is loaded, you can omit the file reference and enter a formula such as the following: =ZapSpaces(A1:C12) CROSS-REFERENCE I discuss add-ins in Chapter 21. CAUTION A potential problem with using add-ins to store custom functions is that your workbook is dependent on the add-in file. If you need to share your workbook with a colleague, you also need to share a copy of the add-in that contains the functions. Part III: Understanding Visual Basic for Applications310 16_044018 ch10.qxp 2/28/07 6:39 PM Page 310 Using the Windows API VBA can borrow methods from other files that have nothing to do with Excel or VBA — for example, the Dynamic Link Library (DLL) files that Windows and other software use. As a result, you can do things with VBA that would otherwise be outside the language’s scope. The Windows Application Programming Interface (API) is a set of functions available to Windows programmers. When you call a Windows function from VBA, you’re accessing the Windows API. Many of the Windows resources used by Windows programmers are avail- able in DLLs, which store programs and functions and are linked at runtime rather than at compile time. Excel itself uses several DLLs, for example. The code in many of these DLLs could have been compiled right into the excel.exe executable file, but the designers chose to store it in DLLs, which are loaded only when needed. This technique makes Excel’s main exe- cutable file smaller. In addition, it is a more efficient use of memory because the library is loaded only when it’s needed. DLLs are also used to share code. For example, most Windows programs use dialog boxes to open and save files. Windows comes with a DLL that has the code to generate several standard dialog boxes. Programmers thus can call this DLL rather than write their own routines. If you’re a C programmer, you can produce your own DLLs and use them from VBA. In addition, Microsoft’s Visual Basic language (but not VBA) also has the capability to create DLL files that can be called from Excel. Windows API examples Before you can use a Windows API function, you must declare the function at the top of your code module. If the code module is for a UserForm, Sheet, or ThisWorkbook, you must declare the API function as Private. An API function must be declared precisely. The declaration statement tells VBA: • Which API function you’re using • In which library the API function is located • The API function’s arguments After you declare an API function, you can use it in your VBA code. Determining the Windows directory Following is an example of an API function declaration: Declare Function GetWindowsDirectoryA Lib “kernel32” _ (ByVal lpBuffer As String, ByVal nSize As Long) As Long Chapter 10: Creating Function Procedures 311 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 311 This function, which has two arguments, returns the name of the directory in which Windows is installed (something that is not normally possible using VBA). After calling the function, the Windows directory is contained in lpBuffer, and the length of the directory string is contained in nSize. After inserting the Declare statement at the top of your module, you can access the func- tion by calling the GetWindowsDirectoryA function. The following is an example of call- ing the function and displaying the result in a message box: Sub ShowWindowsDir() Dim WinPath As String * 255 Dim WinDir As String WinPath = Space(255) WinDir = Left(WinPath, GetWindowsDirectoryA _ (WinPath, Len(WinPath))) MsgBox WinDir, vbInformation, “Windows Directory” End Sub Executing the ShowWindowsDir procedure displays a message box with the Windows directory. Often, you’ll want to create a wrapper for API functions. In other words, you create your own function that uses the API function. This greatly simplifies using the API function. Here’s an example of a wrapper VBA function: Function WindowsDir() As String ‘ Returns the Windows directory Dim WinPath As String * 255 WinPath = Space(255) WindowsDir = Left(WinPath, GetWindowsDirectoryA _ (WinPath, Len(WinPath))) End Function After declaring this function, you can call it from another procedure: MsgBox WindowsDir() You can even use the function in a worksheet formula: =WindowsDir() CD-ROM This example is available on the companion CD-ROM. The filename is windows directory.xlsm. Part III: Understanding Visual Basic for Applications312 16_044018 ch10.qxp 2/28/07 6:39 PM Page 312 The reason for using API calls is to perform actions that would otherwise be impossible (or at least very difficult). If your application needs to find the path of the Windows directory, you could search all day and not find a function in Excel or VBA to do the trick. But know- ing how to access the Windows API may solve your problem. CAUTION When you work with API calls, system crashes during testing are not uncommon, so save your work often. Detecting the Shift key Here’s another example: Suppose you’ve written a VBA macro that will be executed from but- ton on a worksheet. Furthermore, suppose you want the macro to perform differently if the user presses the Shift key when the button is clicked. Normally, there is no way to detect whether the Shift key is pressed. But you can use the GetKeyState API function to find out. The GetKeyState function tells you whether a particular key is pressed. It takes a sin- gle argument, nVirtKey, which represents the code for the key that you are interested in. CROSS-REFERENCE Chapter 11 has several additional examples of using Windows API functions. The following code demonstrates how to detect whether the Shift key is pressed when the Button_Click event handler procedure is executed. Notice that I define a constant for the Shift key (using a hexadecimal value) and then use this constant as the argument for GetKeyState. If GetKeyState returns a value less than zero, it means that the Shift key was pressed; otherwise, the Shift key was not pressed. Declare Function GetKeyState Lib “user32” _ (ByVal nVirtKey As Long) As Integer Sub Button_Click() Const VK_SHIFT As Integer = &H10 If GetKeyState(VK_SHIFT) < 0 Then MsgBox “Shift is pressed” Else MsgBox “Shift is not pressed” End If End Sub CD-ROM A workbook named key press.xlsm on the companion CD-ROM demonstrates how to detect the following keys (as well as any combinations): Ctrl, Shift, and Alt. Chapter 10: Creating Function Procedures 313 Part III 16_044018 ch10.qxp 2/28/07 6:39 PM Page 313 Learning more about API functions Working with the Windows API functions can be tricky. Many programming reference books list the declarations for common API calls and often provide examples. Usually, you can simply copy the declarations and use the functions without really understanding the details. In reality (at least the reality that I’ve seen), most Excel programmers take a cook- book approach to API functions. The Internet has hundreds of examples that can be copied and pasted and that work quite reliably. CD-ROM The companion CD-ROM includes a file named win32api.txt, which is a text file that contains Windows API declarations and constants. You can open this file with a text edi- tor and copy the appropriate declarations to a VBA module. Part III: Understanding Visual Basic for Applications314 16_044018 ch10.qxp 2/28/07 6:39 PM Page 314 Chapter VBA Programming Examples and Techniques In This Chapter I believe that learning programming concepts is accelerated by a heavy empha- sis on examples. And based on the feedback that I’ve received from readers of previous editions of this book, I have plenty of company. VBA programmers especially benefit from a hands-on approach. A well-thought-out example usu- ally communicates a concept much better than a description of the underlying theory. I decided, therefore, not to write a reference book that painstakingly describes every nuance of VBA. Rather, I prepared numerous examples to demonstrate useful Excel programming techniques. ◆ Examples of using VBA to work with ranges ◆ Examples of using VBA to work with workbooks and sheets ◆ Custom functions for use in your VBA procedures and in worksheet formulas ◆ Examples of miscellaneous VBA tricks and techniques ◆ Examples of using Windows Application Programming Interface (API) functions The previous chapters in this part provide enough information to get you started. The Help system provides all the details that I left out. In this chapter, I pick up the pace and present examples that solve practical problems while furthering your knowledge of VBA. 11 315 17_044018 ch11.qxp 2/28/07 6:40 PM Page 315 I’ve categorized this chapter’s examples into six groups: • Working with ranges • Working with workbooks and sheets • VBA techniques • Functions that are useful in your VBA procedures • Functions that you can use in worksheet formulas • Windows API calls CROSS-REFERENCE Subsequent chapters in this book present additional feature-specific examples: charts, pivot tables, events, UserForms, and so on. Working with Ranges The examples in this section demonstrate how to manipulate worksheet ranges with VBA. Copying a range Excel’s macro recorder is useful not so much for generating usable code, but for discover- ing the names of relevant objects, methods, and properties. The code that’s generated by the macro recorder isn’t always the most efficient, but it can usually provide you with sev- eral clues. For example, recording a simple copy-and-paste operation generates five lines of VBA code: Sub Macro1() Range(“A1”).Select Selection.Copy Part III: Understanding Visual Basic for Applications316 Not all the examples in this chapter are intended to be standalone programs. They are, however, set up as usable procedures that you can adapt for your own applications. I urge you to follow along on your computer as you read this chapter. Better yet, modify the examples and see what happens. I guarantee that this hands-on experience will help you more than reading a reference book. Using the Examples in This Chapter 17_044018 ch11.qxp 2/28/07 6:40 PM Page 316 Range(“B1”).Select ActiveSheet.Paste Application.CutCopyMode = False End Sub Notice that the generated code selects cell A1, copies it, and then selects cell B1 and per- forms the paste operation. But in VBA, it’s not necessary to select an object to work with it. You would never learn this important point by mimicking the preceding recorded macro code, where two statements incorporate the Select method. This procedure can be replaced with the following much simpler routine, which doesn’t select any cells. It also takes advantage of the fact that the Copy method can use an argument that represents the destination for the copied range: Sub CopyRange() Range(“A1”).Copy Range(“B1”) End Sub Both of these macros assume that a worksheet is active and that the operation takes place on the active worksheet. To copy a range to a different worksheet or workbook, simply qualify the range reference for the destination. The following example copies a range from Sheet1 in File1.xlsx to Sheet2 in File2.xlsx. Because the references are fully quali- fied, this example works regardless of which workbook is active. Sub CopyRange2() Workbooks(“File1.xlsx”).Sheets(“Sheet1”).Range(“A1”).Copy _ Workbooks(“File2.xlsx”).Sheets(“Sheet2”).Range(“A1”) End Sub Another way to approach this task is to use object variables to represent the ranges, as shown in the code that follows: Sub CopyRange3() Dim Rng1 As Range, Rng2 As Range Set Rng1 = Workbooks(“File1.xlsx”).Sheets(“Sheet1”).Range(“A1”) Set Rng2 = Workbooks(“File2.xlsx”).Sheets(“Sheet2”).Range(“A1”) Rng1.Copy Rng2 End Sub As you might expect, copying is not limited to one single cell at a time. The following pro- cedure, for example, copies a large range. Notice that the destination consists of only a sin- gle cell (which represents the upper-left cell for the destination). Sub CopyRange4() Range(“A1:C800”).Copy Range(“D1”) End Sub Chapter 11: VBA Programming Examples and Techniques 317 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 317 Moving a range The VBA instructions for moving a range are very similar to those for copying a range, as the following example demonstrates. The difference is that you use the Cut method instead of the Copy method. Note that you need to specify only the upper-left cell for the destina- tion range. The following example moves 18 cells (in A1:C6) to a new location, beginning at cell H1: Sub MoveRange1() Range(“A1:C6”).Cut Range(“H1”) End Sub Copying a variably sized range In many cases, you need to copy a range of cells, but you don’t know the exact row and col- umn dimensions of the range. For example, you might have a workbook that tracks weekly sales, and the number of rows changes weekly when you add new data. Figure 11-1 shows a common type of worksheet. This range consists of several rows, and the number of rows changes each week. Because you don’t know the exact range address at any given time, writing a macro to copy the range requires some additional coding. Figure 11-1: The number of rows in the data range changes every week. The following macro demonstrates how to copy this range from Sheet1 to Sheet2 (begin- ning at cell A1). It uses the CurrentRegion property, which returns a Range object that corresponds to the block of cells around a particular cell (in this case, A1). Sub CopyCurrentRegion2() Range(“A1”).CurrentRegion.Copy Sheets(“Sheet2”).Range(“A1”) End Sub Part III: Understanding Visual Basic for Applications318 17_044018 ch11.qxp 2/28/07 6:40 PM Page 318 NOTE Using the CurrentRegion property is equivalent to choosing the Home ➪ Editing ➪ Find & Select ➪ Go To Special command and selecting the Current Region option (or by using the Ctrl+Shift+* shortcut). To see how this works, record your actions while you issue that command. Generally, the CurrentRegion property setting consists of a rec- tangular block of cells surrounded by one or more blank rows or columns. Selecting or otherwise identifying various types of ranges Much of the work that you will do in VBA will involve working with ranges — either selecting a range or identifying a range so that you can do something with the cells. Chapter 11: VBA Programming Examples and Techniques 319 Part III When you work with ranges, keep the following points in mind: • Your code doesn’t need to select a range in order to work with it. • If your code does select a range, its worksheet must be active. You can use the Activate method of the Worksheets collection to activate a particular sheet. • The macro recorder doesn’t always generate the most efficient code. Often, you can create your macro by using the recorder and then edit the code to make it more efficient. • It’s a good idea to use named ranges in your VBA code. For example, referring to Range(“Total”) is better than Range(“D45”). In the latter case, if you add a row above row 45, the cell address will change. You would then need to modify the macro so that it uses the correct range address (D46). • If you rely on the macro recorder when selecting ranges, make sure that you record the macro using relative references. Use the Developer ➪ Code ➪ Use Relative References control. • When running a macro that works on each cell in the current range selection, the user might select entire columns or rows. In most cases, you don’t want to loop through every cell in the selection. Your macro should create a subset of the selec- tion consisting of only the nonblank cells. See “Looping through a selected range efficiently,” later in this chapter. • Excel allows multiple selections. For example, you can select a range, press Ctrl, and select another range. You can test for this in your macro and take appropriate action. See “Determining the type of selected range,” later in this chapter. Tips for Working with Ranges 17_044018 ch11.qxp 2/28/07 6:40 PM Page 319 In addition to the CurrentRegion property (which I discussed earlier), you should also be aware of the End method of the Range object. The End method takes one argument, which determines the direction in which the selection is extended. The following statement selects a range from the active cell to the last non-empty cell: Range(ActiveCell, ActiveCell.End(xlDown)).Select As you might expect, three other constants simulate key combinations in the other direc- tions: xlUp, xlToLeft, and xlToRight. CAUTION Be careful when using the End method. If the active cell is at the perimeter of a range or if the range contains one or more empty cells, the End method may not produce the desired results. CD-ROM The companion CD-ROM includes a workbook that demonstrates several common types of range selections. When you open this workbook, named range selections.xlsm, the code adds a new menu item to the shortcut menu that appears when you right-click a cell: Selection Demo. This menu contains commands that enable the user to make various types of selections, as shown in Figure 11-2. The following macro is in the example workbook. The SelectCurrentRegion macro sim- ulates pressing Ctrl+Shift+*. Sub SelectCurrentRegion() ActiveCell.CurrentRegion.Select End Sub Often, you won’t want to actually select the cells. Rather, you’ll want to work with them in some way (for example, format them). The cell-selecting procedures can easily be adapted. The following procedure was adapted from SelectCurrentRegion. This procedure doesn’t select cells; it applies formatting to the range that’s defined as the current region around the active cell. The other procedures in the example workbook can also be adapted in this manner. Sub FormatCurrentRegion() ActiveCell.CurrentRegion.Font.Bold = True End Sub Part III: Understanding Visual Basic for Applications320 17_044018 ch11.qxp 2/28/07 6:40 PM Page 320 Figure 11-2: This workbook uses a custom shortcut menu to demonstrate how to select variably sized ranges by using VBA. Prompting for a cell value The following procedure demonstrates how to ask the user for a value and then insert it into cell A1 of the active worksheet: Sub GetValue1() Range(“A1”).Value = InputBox(“Enter the value”) End Sub Figure 11-3 shows how the input box looks. Chapter 11: VBA Programming Examples and Techniques 321 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 321 Figure 11-3: The InputBox function gets a value from the user to be inserted into a cell. This procedure has a problem, however. If the user clicks the Cancel button in the input box, the procedure deletes any data already in the cell. The following modification takes no action if the Cancel button is clicked: Sub GetValue2() Dim UserEntry As Variant UserEntry = InputBox(“Enter the value”) If UserEntry <> “” Then Range(“A1”).Value = UserEntry End Sub In many cases, you’ll need to validate the user’s entry in the input box. For example, you may require a number between 1 and 12. The following example demonstrates one way to validate the user’s entry. In this example, an invalid entry is ignored, and the input box is displayed again. This cycle keeps repeating until the user enters a valid number or clicks Cancel. Sub GetValue3() Dim UserEntry As Variant Dim Msg As String Const MinVal As Integer = 1 Const MaxVal As Integer = 12 Msg = “Enter a value between “ & MinVal & “ and “ & MaxVal Do UserEntry = InputBox(Msg) If UserEntry = “” Then Exit Sub If IsNumeric(UserEntry) Then If UserEntry >= MinVal And UserEntry <= MaxVal Then Exit Do End If Msg = “Your previous entry was INVALID.” Msg = Msg & vbNewLine Msg = Msg & “Enter a value between “ & MinVal & “ and “ & MaxVal Loop ActiveSheet.Range(“A1”).Value = UserEntry End Sub As you can see in Figure 11-4, the code also changes the message displayed if the user makes an invalid entry. Part III: Understanding Visual Basic for Applications322 17_044018 ch11.qxp 2/28/07 6:40 PM Page 322 Figure 11-4: Validate a user’s entry with the VBA InputBox function. CD-ROM The three GetValue procedures are available on the companion CD-ROM. The filename is inputbox demo.xlsm. Entering a value in the next empty cell A common requirement is to enter a value into the next empty cell in a column or row. The following example prompts the user for a name and a value and then enters the data into the next empty row (see Figure 11-5). Sub GetData() Dim NextRow As Long Dim Entry1 As String, Entry2 As String Do ‘Determine next empty row NextRow = Cells(Rows.Count, 1).End(xlUp).Row + 1 ‘ Prompt for the data Entry1 = InputBox(“Enter the name”) If Entry1 = “” Then Exit Sub Entry2 = InputBox(“Enter the amount”) If Entry2 = “” Then Exit Sub ‘ Write the data Cells(NextRow, 1) = Entry1 Cells(NextRow, 2) = Entry2 Loop End Sub To keep things simple, this procedure doesn’t perform any validation. Notice that the loop continues indefinitely. I use Exit Sub statements to get out of the loop when the user clicks Cancel in the input box. Chapter 11: VBA Programming Examples and Techniques 323 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 323 Figure 11-5: A macro for inserting data into the next empty row in a worksheet. CD-ROM The GetData procedure is available on the companion CD-ROM. The filename is next empty cell.xlsm. Notice the statement that determines the value of the NextRow variable. If you don’t understand how this works, try the manual equivalent: Activate the last cell in column A, press End, and then press the up-arrow key. At this point, the last nonblank cell in column A will be selected. The Row property returns this row number, and it is incremented by 1 in order to get the row of the cell below it (the next empty row). Rather than hard-code the last cell in column A, I used Rows.Count so this procedure will work with previous ver- sions of Excel (which have fewer rows). Note that this technique of selecting the next empty cell has a slight glitch. If the column is completely empty, it will calculate row 2 as the next empty row. It would be fairly easy to write additional code to account for this possibility. Pausing a macro to get a user-selected range In some situations, you may need an interactive macro. For example, you can create a macro that pauses while the user specifies a range of cells. The procedure in this section describes how to do this with Excel’s InputBox method. NOTE Do not confuse Excel’s InputBox method with VBA’s InputBox function. Although these two items have the same name, they are not the same. The Sub procedure that follows demonstrates how to pause a macro and let the user select a range. The code then inserts a formula into each cell of the specified range. Part III: Understanding Visual Basic for Applications324 17_044018 ch11.qxp 2/28/07 6:40 PM Page 324 Sub GetUserRange() Dim UserRange As Range Prompt = “Select a range for the random numbers.” Title = “Select a range” ‘ Display the Input Box On Error Resume Next Set UserRange = Application.InputBox( _ Prompt:=Prompt, _ Title:=Title, _ Default:=ActiveCell.Address, _ Type:=8) ‘Range selection On Error GoTo 0 ‘ Was the Input Box canceled? If UserRange Is Nothing Then MsgBox “Canceled.” Else UserRange.Formula = “=RAND()” End If End Sub The input box is shown in Figure 11-6. Figure 11-6: Use an input box to pause a macro. CD-ROM This example, named prompt for a range.xlsm, is available on the companion CD-ROM. Chapter 11: VBA Programming Examples and Techniques 325 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 325 Specifying a Type argument of 8 for the InputBox method is the key to this procedure. Also, note the use of On Error Resume Next. This statement ignores the error that occurs if the user clicks the Cancel button. If so, the UserRange object variable is not defined. This example displays a message box with the text Canceled. If the user clicks OK, the macro continues. Using On Error GoTo 0 resumes normal error handling. By the way, it’s not necessary to check for a valid range selection. Excel takes care of this for you. CAUTION Make sure that screen updating is not turned off when you use the InputBox method to select a range. Otherwise, you won’t be able to make a worksheet selection. Use the ScreenUpdating property of the Application object to control screen updating while a macro is running. Counting selected cells You can create a macro that works with the selected range of cells. Use the Count prop- erty of the Range object to determine how many cells are contained in a range selection (or any range, for that matter). For example, the following statement displays a message box that contains the number of cells in the current selection: MsgBox Selection.Count CAUTION With the larger worksheet size in Excel 2007, the Count property can generate an error. The Count property uses the Long data type, so the largest value that it can store is 2,147,483,647. For example, if the user selects 2,048 complete columns (2,147,483,648 cells), the Count property generates an error. Fortunately, Microsoft added a new prop- erty: CountLarge. CountLarge uses the Double data type, which can handle values up to 1.79+E^308. Bottom line? In the vast majority of situations, the Count property will work fine. If there’s a chance that you may need to count more cells (such as all cells in a worksheet), use CountLarge instead of Count. If the active sheet contains a range named data, the following statement assigns the num- ber of cells in the data range to a variable named CellCount: CellCount = Range(“data”).Count You can also determine how many rows or columns are contained in a range. The following expression calculates the number of columns in the currently selected range: Selection.Columns.Count Part III: Understanding Visual Basic for Applications326 17_044018 ch11.qxp 2/28/07 6:40 PM Page 326 And, of course, you can also use the Rows property to determine the number of rows in a range. The following statement counts the number of rows in a range named data and assigns the number to a variable named RowCount: RowCount = Range(“data”).Rows.Count Determining the type of selected range Excel supports several types of range selections: • A single cell • A contiguous range of cells • One or more entire columns • One or more entire rows • The entire worksheet • Any combination of the above (that is, a multiple selection) As a result, when your VBA procedure processes a user-selected range, you can’t make any presumptions about what that range might be. In the case of a multiple range selection, the Range object comprises separate areas. To determine whether a selection is a multiple selection, use the Areas method, which returns an Areas collection. This collection represents all the ranges within a multiple range selection. You can use an expression like the following to determine whether a selected range has multiple areas: NumAreas = Selection.Areas.Count If the NumAreas variable contains a value greater than 1, the selection is a multiple selection. Following is a function named AreaType, which returns a text string that describes the type of range selection. Function AreaType(RangeArea As Range) As String ‘ Returns the type of a range in an area Select Case True Case RangeArea.Cells.CountLarge = 1 AreaType = “Cell” Case RangeArea.CountLarge = Cells.CountLarge AreaType = “Worksheet” Case RangeArea.Rows.Count = Cells.Rows.Count AreaType = “Column” Chapter 11: VBA Programming Examples and Techniques 327 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 327 Case RangeArea.Columns.Count = Cells.Columns.Count AreaType = “Row” Case Else AreaType = “Block” End Select End Function This function accepts a Range object as its argument and returns one of five strings that describe the area: Cell, Worksheet, Column, Row, or Block. The function uses a Select Case construct to determine which of five comparison expressions is True. For example, if the range consists of a single cell, the function returns Cell. If the number of cells in the range is equal to the number of cells in the worksheet, it returns Worksheet. If the num- ber of rows in the range equals the number of rows in the worksheet, it returns Column. If the number of columns in the range equals the number of columns in the worksheet, the function returns Row. If none of the Case expressions is True, the function returns Block. Notice that I used the CountLarge property when counting cells. As I noted previously in this chapter, the number of selected cells in Excel 2007 could potentially exceed the limit of the Count property. CD-ROM This example is available on the companion CD-ROM in a file named about range selection.xlsm. The workbook contains a procedure (named RangeDescription) that uses the AreaType function to display a message box that describes the current range selection. Figure 11-7 shows an example. Understanding how this routine works will give you a good foundation for working with Range objects. Figure 11-7: A VBA procedure analyzes the currently selected range. Part III: Understanding Visual Basic for Applications328 17_044018 ch11.qxp 2/28/07 6:40 PM Page 328 NOTE You might be surprised to discover that Excel allows multiple selections to be identical. For example, if you hold down Ctrl and click five times in cell A1, the selection will have five identical areas. The RangeDescription procedure takes this into account and does not count the same cell multiple times. Looping through a selected range efficiently A common task is to create a macro that evaluates each cell in a range and performs an operation if the cell meets a certain criterion. The procedure that follows is an example of such a macro. The ColorNegative procedure sets the cell’s background color to red for cells that contain a negative value. For non-negative value cells, it sets the background color to none. NOTE This example is for educational purposes only. Using Excel’s conditional formatting is a much better approach. Sub ColorNegative() ‘ Makes negative cells red Dim cell As Range If TypeName(Selection) <> “Range” Then Exit Sub Application.ScreenUpdating = False For Each cell In Selection If cell.Value < 0 Then cell.Interior.Color = RGB(255, 0, 0) Else cell.Interior.Color = xlNone End If Next cell End Sub The ColorNegative procedure certainly works, but it has a serious flaw. For example, what if the used area on the worksheet were small, but the user selects an entire column? Or ten columns? Or the entire worksheet? There’s no need to process all of those empty cells, and the user would probably give up before all the cells were evaluated. A better solution (ColorNegative2) follows. In this revised procedure, I create a Range object variable, WorkRange, which consists of the intersection of the selected range and the worksheet’s used range. Figure 11-8 shows an example; the entire column D is selected (1,048,576 cells). The worksheet’s used range, however, consists of the range B2:I18. Therefore, the intersection of these ranges is D2:D18, which is a much smaller range than the original selection. The time difference between processing 16 cells versus processing 1,048,576 cells is significant. Chapter 11: VBA Programming Examples and Techniques 329 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 329 Figure 11-8: Using the intersection of the used range and the selected ranged results in fewer cells to process. Sub ColorNegative2() ‘ Makes negative cells red Dim WorkRange As Range Dim cell As Range If TypeName(Selection) <> “Range” Then Exit Sub Application.ScreenUpdating = False Set WorkRange = Application.Intersect(Selection, _ ActiveSheet.UsedRange) For Each cell In WorkRange If cell.Value < 0 Then cell.Interior.Color = RGB(255, 0, 0) Else cell.Interior.Color = xlNone End If Next cell End Sub The ColorNegative2 procedure is an improvement, but it’s still not as efficient as it could be because it processes empty cells. A third revision, ColorNegative3, is quite a bit longer, but it’s much more efficient. I use the SpecialCells method to generate two subsets of the selection: One subset (ConstantCells) includes only the cells with Part III: Understanding Visual Basic for Applications330 17_044018 ch11.qxp 2/28/07 6:40 PM Page 330 numeric constants; the other subset (FormulaCells) includes only the cells with numeric formulas. The code processes the cells in these subsets by using two For Each-Next constructs. The net effect: Only non-blank, non-text cells are evaluated, thus speeding up the macro considerably. Sub ColorNegative3() ‘ Makes negative cells red Dim FormulaCells As Range, ConstantCells As Range Dim cell As Range If TypeName(Selection) <> “Range” Then Exit Sub Application.ScreenUpdating = False ‘ Create subsets of original selection On Error Resume Next Set FormulaCells = Selection.SpecialCells(xlFormulas, xlNumbers) Set ConstantCells = Selection.SpecialCells(xlConstants, xlNumbers) On Error GoTo 0 ‘ Process the formula cells If Not FormulaCells Is Nothing Then For Each cell In FormulaCells If cell.Value < 0 Then cell.Interior.Color = RGB(255, 0, 0) Else cell.Interior.Color = xlNone End If Next cell End If ‘ Process the constant cells If Not ConstantCells Is Nothing Then For Each cell In ConstantCells If cell.Value < 0 Then cell.Interior.Color = RGB(255, 0, 0) Else cell.Interior.Color = xlNone End If Next cell End If End Sub NOTE The On Error statement is necessary because the SpecialCells method generates an error if no cells qualify. Chapter 11: VBA Programming Examples and Techniques 331 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 331 CD-ROM A workbook that contains the three ColorNegative procedures is available on the companion CD-ROM. The file is named efficient looping.xlsm. Deleting all empty rows The following procedure deletes all empty rows in the active worksheet. This routine is fast and efficient because it doesn’t check all rows. It checks only the rows in the used range, which is determined by using the UsedRange property of the Worksheet object. Sub DeleteEmptyRows() Dim LastRow As Long Dim r As Long Dim Counter As Long Application.ScreenUpdating = False LastRow = ActiveSheet.UsedRange.Rows.Count + _ ActiveSheet.UsedRange.Rows(1).Row - 1 For r = LastRow To 1 Step -1 If Application.WorksheetFunction.CountA(Rows(r)) = 0 Then Rows(r).Delete Counter = Counter + 1 End If Next r Application.ScreenUpdating = True MsgBox Counter & “ empty rows were deleted.” End Sub The first step is to determine the last used row and then assign this row number to the LastRow variable. This is not as simple as you might think because the used range may or may not begin in row 1. Therefore, LastRow is calculated by determining the number of rows in the used range, adding the first row number in the used range, and subtracting 1. The procedure uses Excel’s COUNTA worksheet function to determine whether a row is empty. If this function returns 0 for a particular row, the row is empty. Notice that the pro- cedure works on the rows from bottom to top and also uses a negative step value in the For-Next loop. This is necessary because deleting rows causes all subsequent rows to move up in the worksheet. If the looping occurred from top to bottom, the counter within the loop would not be accurate after a row is deleted. The macro uses another variable, Counter, to keep track of how many rows were deleted. This number is displayed in a message box when the procedure ends. CD-ROM A workbook that contains this example is available on the companion CD-ROM in a file named delete empty rows.xlsm. Part III: Understanding Visual Basic for Applications332 17_044018 ch11.qxp 2/28/07 6:40 PM Page 332 Duplicating rows a variable number of times The example in this section demonstrates how to use VBA to create duplicates of a row. Figure 11-9 shows a worksheet for an office raffle. Column A contains the name, and col- umn B contains the number of tickets purchased by each person. Column C contains a ran- dom number (generated by the RAND function). The winner will be determined by sorting the data based on column 3 (the highest random number wins). Figure 11-9: The goal is to duplicate rows based on the value in column B. The goal is to duplicate the rows so each person will have a row for each ticket purchased. For example, Barbara purchased two tickets, so she should have two rows. The procedure to insert the new rows is shown here: Sub DupeRows() Dim cell As Range ‘ 1st cell with number of tickets Set cell = Range(“B2”) Do While Not IsEmpty(cell) If cell > 1 Then Range(cell.Offset(1, 0), cell.Offset(cell.Value - 1, _ 0)).EntireRow.Insert Range(cell, cell.Offset(cell.Value - 1, 1)).EntireRow.FillDown End If Set cell = cell.Offset(cell.Value, 0) Loop End Sub The cell object variable is initialized to cell B2, the first cell that has a number. The loop inserts new rows and then copies the row using the FillDown method. The cell variable is incremented to the next person, and the loop continues until an empty cell is encoun- tered. Figure 11-10 shows the worksheet after running this procedure. Chapter 11: VBA Programming Examples and Techniques 333 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 333 Figure 11-10: New rows were added, according to the value in column B. CD-ROM A workbook that contains this example is available on the companion CD-ROM. The file is named duplicate rows.xlsm. Determining whether a range is contained in another range The following InRange function accepts two arguments, both Range objects. The function returns True if the first range is contained in the second range. Function InRange(rng1, rng2) As Boolean ‘ Returns True if rng1 is a subset of rng2 InRange = False If rng1.Parent.Parent.Name = rng2.Parent.Parent.Name Then If rng1.Parent.Name = rng2.Parent.Name Then If Union(rng1, rng2).Address = rng2.Address Then InRange = True End If End If End If End Function The InRange function may appear a bit more complex than it needs to be because the code needs to ensure that the two ranges are in the same worksheet and workbook. Notice that Part III: Understanding Visual Basic for Applications334 17_044018 ch11.qxp 2/28/07 6:40 PM Page 334 the procedure uses the Parent property, which returns an object’s container object. For example, the following expression returns the name of the worksheet for the rng1 object reference: rng1.Parent.Name The following expression returns the name of the workbook for rng1: rng1.Parent.Parent.Name VBA’s Union function returns a Range object that represents the union of two Range objects. The union consists of all the cells from both ranges. If the address of the union of the two ranges is the same as the address of the second range, the first range is contained within the second range. CD-ROM A workbook that contains this function is available on the companion CD-ROM in a file named inrange function.xlsm. Determining a cell’s data type Excel provides a number of built-in functions that can help determine the type of data con- tained in a cell. These include ISTEXT, ISLOGICAL, and ISERROR. In addition, VBA includes functions such as IsEmpty, IsDate, and IsNumeric. The following function, named CellType, accepts a range argument and returns a string (Blank, Text, Logical, Error, Date, Time, or Number) that describes the data type of the upper-left cell in the range. You can use this function in a worksheet formula or from another VBA procedure. Function CellType(Rng) ‘ Returns the cell type of the upper left ‘ cell in a range Dim TheCell As Range Set TheCell = Rng.Range(“A1”) Select Case True Case IsEmpty(TheCell) CellType = “Blank” Case Application.IsText(TheCell) CellType = “Text” Case Application.IsLogical(TheCell) CellType = “Logical” Case Application.IsErr(TheCell) CellType = “Error” Case IsDate(TheCell) CellType = “Date” Chapter 11: VBA Programming Examples and Techniques 335 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 335 Case InStr(1, TheCell.Text, “:”) <> 0 CellType = “Time” Case IsNumeric(TheCell) CellType = “Number” End Select End Function Notice the use of the Set TheCell statement.. The CellType function accepts a range argument of any size, but this statement causes it to operate on only the upper-left cell in the range (which is represented by the TheCell variable). CD-ROM A workbook that contains this function is available on the companion CD-ROM. The file is named celltype function.xlsm. Reading and writing ranges Many VBA tasks involve transferring values either from an array to a range or from a range to an array. For some reason, Excel reads from ranges much faster than it writes to ranges. The WriteReadRange procedure that follows demonstrates the relative speeds of writing and reading a range. This procedure creates an array and then uses For-Next loops to write the array to a range and then read the range back into the array. It calculates the time required for each operation by using the Excel Timer function. Sub WriteReadRange() Dim MyArray() Dim Time1 As Double Dim NumElements As Long, i As Long Dim WriteTime As String, ReadTime As String Dim Msg As String NumElements = 60000 ReDim MyArray(1 To NumElements) ‘ Fill the array For i = 1 To NumElements MyArray(i) = i Next i ‘ Write the array to a range Time1 = Timer For i = 1 To NumElements Cells(i, 1) = MyArray(i) Next i Part III: Understanding Visual Basic for Applications336 17_044018 ch11.qxp 2/28/07 6:40 PM Page 336 WriteTime = Format(Timer - Time1, “00:00”) ‘ Read the range into the array Time1 = Timer For i = 1 To NumElements MyArray(i) = Cells(i, 1) Next i ReadTime = Format(Timer - Time1, “00:00”) ‘ Show results Msg = “Write: “ & WriteTime Msg = Msg & vbCrLf Msg = Msg & “Read: “ & ReadTime MsgBox Msg, vbOKOnly, NumElements & “ Elements” End Sub On my system, it took 12 seconds to write a 60,000-element array to a range but only 1 second to read the range into an array. A better way to write to a range The example in the preceding section uses a For-Next loop to transfer the contents of an array to a worksheet range. In this section, I demonstrate a more efficient way to accom- plish this. Start with the example that follows, which illustrates the most obvious (but not the most effi- cient) way to fill a range. This example uses a For-Next loop to insert its values in a range. Sub LoopFillRange() ‘ Fill a range by looping through cells Dim CellsDown As Long, CellsAcross As Integer Dim CurrRow As Long, CurrCol As Integer Dim StartTime As Double Dim CurrVal As Long ‘ Get the dimensions CellsDown = InputBox(“How many cells down?”) If CellsDown = 0 Then Exit Sub CellsAcross = InputBox(“How many cells across?”) If CellsAcross = 0 Then Exit Sub ‘ Record starting time StartTime = Timer ‘ Loop through cells and insert values CurrVal = 1 Chapter 11: VBA Programming Examples and Techniques 337 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 337 Application.ScreenUpdating = False For CurrRow = 1 To CellsDown For CurrCol = 1 To CellsAcross ActiveCell.Offset(CurrRow - 1, _ CurrCol - 1).Value = CurrVal CurrVal = CurrVal + 1 Next CurrCol Next CurrRow ‘ Display elapsed time Application.ScreenUpdating = True MsgBox Format(Timer - StartTime, “00.00”) & “ seconds” End Sub The example that follows demonstrates a much faster way to produce the same result. This code inserts the values into an array and then uses a single statement to transfer the con- tents of an array to the range. Sub ArrayFillRange() ‘ Fill a range by transferring an array Dim CellsDown As Long, CellsAcross As Integer Dim i As Long, j As Integer Dim StartTime As Double Dim TempArray() As Long Dim TheRange As Range Dim CurrVal As Long ‘ Get the dimensions CellsDown = InputBox(“How many cells down?”) If CellsDown = 0 Then Exit Sub CellsAcross = InputBox(“How many cells across?”) If CellsAcross = 0 Then Exit Sub ‘ Record starting time StartTime = Timer ‘ Redimension temporary array ReDim TempArray(1 To CellsDown, 1 To CellsAcross) ‘ Set worksheet range Set TheRange = ActiveCell.Range(Cells(1, 1), _ Cells(CellsDown, CellsAcross)) ‘ Fill the temporary array CurrVal = 0 Part III: Understanding Visual Basic for Applications338 17_044018 ch11.qxp 2/28/07 6:40 PM Page 338 Application.ScreenUpdating = False For i = 1 To CellsDown For j = 1 To CellsAcross TempArray(i, j) = CurrVal + 1 CurrVal = CurrVal + 1 Next j Next i ‘ Transfer temporary array to worksheet TheRange.Value = TempArray ‘ Display elapsed time Application.ScreenUpdating = True MsgBox Format(Timer - StartTime, “00.00”) & “ seconds” End Sub On my system, using the loop method to fill a 500 x 256–cell range (128,000 cells) took 12.81 seconds. The array transfer method took only 0.20 seconds to generate the same results — more than 60 times faster! The moral of this story? If you need to transfer large amounts of data to a worksheet, avoid looping whenever possible. CD-ROM A workbook that contains the WriteReadRange, LoopFillRange, and ArrayFillRange procedures is available on the companion CD-ROM. The file is named loop vs array fill range.xlsm. Transferring one-dimensional arrays The example in the preceding section involves a two-dimensional array, which works out nicely for row-and-column-based worksheets. When transferring a one-dimensional array to a range, the range must be horizontal — that is, one row with multiple columns. If you need the data in a vertical range instead, you must first transpose the array to make it vertical. You can use Excel’s TRANSPOSE func- tion to do this. The following example transfers a 100-element array to a vertical work- sheet range (A1:A100): Range(“A1:A100”).Value = Application.WorksheetFunction.Transpose(MyArray) Transferring a range to a variant array This section discusses yet another way to work with worksheet data in VBA. The following example transfers a range of cells to a two-dimensional variant array. Then message boxes display the upper bounds for each dimension of the variant array. Chapter 11: VBA Programming Examples and Techniques 339 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 339 Sub RangeToVariant() Dim x As Variant x = Range(“A1:L600”).Value MsgBox UBound(x, 1) MsgBox UBound(x, 2) End Sub In this example, the first message box displays 600 (the number of rows in the original range), and the second message box displays 12 (the number of columns). You’ll find that transferring the range data to a variant array is virtually instantaneous. The following example reads a range (named data) into a variant array, performs a simple multiplication operation on each element in the array, and then transfers the variant array back to the range. Sub RangeToVariant2() Dim x As Variant Dim r As Long, c As Integer ‘ Read the data into the variant x = Range(“data”).Value ‘ Loop through the variant array For r = 1 To UBound(x, 1) For c = 1 To UBound(x, 2) ‘ Multiply by 2 x(r, c) = x(r, c) * 2 Next c Next r ‘ Transfer the variant back to the sheet Range(“data”) = x End Sub You’ll find that this procedure runs amazingly fast. CD-ROM A workbook that contains this example is available on the companion CD-ROM. The file is named variant transfer.xlsm. Selecting cells by value The example in this section demonstrates how to select cells based on their value. Oddly enough, Excel does not provide a direct way to perform this operation. My SelectByValue procedure follows. In this example, the code selects cells that contain a negative value, but this can be changed easily. Part III: Understanding Visual Basic for Applications340 17_044018 ch11.qxp 2/28/07 6:40 PM Page 340 Sub SelectByValue() Dim Cell As Object Dim FoundCells As Range Dim WorkRange As Range If TypeName(Selection) <> “Range” Then Exit Sub ‘ Check all or selection? If Selection.CountLarge = 1 Then Set WorkRange = ActiveSheet.UsedRange Else Set WorkRange = Application.Intersect(Selection, ActiveSheet.UsedRange) End If ‘ Reduce the search to numeric cells only On Error Resume Next Set WorkRange = WorkRange.SpecialCells(xlConstants, xlNumbers) If WorkRange Is Nothing Then Exit Sub On Error GoTo 0 ‘ Loop through each cell, add to the FoundCells range if it qualifies For Each Cell In WorkRange If Cell.Value < 0 Then If FoundCells Is Nothing Then Set FoundCells = Cell Else Set FoundCells = Union(FoundCells, Cell) End If End If Next Cell ‘ Show message, or select the cells If FoundCells Is Nothing Then MsgBox “No cells qualify.” Else FoundCells.Select End If End Sub The procedure starts by checking the selection. If it’s a single cell, then the entire work- sheet is searched. If the selection is at least two cells, then only the selected range is searched. The range to be searched is further refined by using the SpecialCells method to create a Range object that consists only of the numeric constants. The code within the For-Next loop examines the cell’s value. If it meets the criterion (less than 0), then the cell is added to the FoundCells Range object by using the Union method. Note that you can’t use the Union method for the first cell. If the FoundCells Chapter 11: VBA Programming Examples and Techniques 341 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 341 range contains no cells, attempting to use the Union method will generate an error. Therefore, the code checks whether FoundCells is Nothing. When the loop ends, the FoundCells object will consist of the cells that meet the crite- rion (or will be Nothing if no cells were found). If no cells are found, a message box appears. Otherwise, the cells are selected. CD-ROM This example is available on the companion CD-ROM. The file is named select by value.xlsm. Copying a noncontiguous range If you’ve ever attempted to copy a noncontiguous range selection, you discovered that Excel doesn’t support such an operation. Attempting to do so brings up an error message: That command cannot be used on multiple selections. When you encounter a limitation in Excel, you can often circumvent it by creating a macro. The example is this section is a VBA procedure that allows you to copy a multiple selection to another location. Sub CopyMultipleSelection() Dim SelAreas() As Range Dim PasteRange As Range Dim UpperLeft As Range Dim NumAreas As Long, i As Long Dim TopRow As Long, LeftCol As Long Dim RowOffset As Long, ColOffset As Long If TypeName(Selection) <> “Range” Then Exit Sub ‘ Store the areas as separate Range objects NumAreas = Selection.Areas.Count ReDim SelAreas(1 To NumAreas) For i = 1 To NumAreas Set SelAreas(i) = Selection.Areas(i) Next ‘ Determine the upper-left cell in the multiple selection TopRow = ActiveSheet.Rows.Count LeftCol = ActiveSheet.Columns.Count For i = 1 To NumAreas If SelAreas(i).Row < TopRow Then TopRow = SelAreas(i).Row If SelAreas(i).Column < LeftCol Then LeftCol = SelAreas(i).Column Part III: Understanding Visual Basic for Applications342 17_044018 ch11.qxp 2/28/07 6:40 PM Page 342 Next Set UpperLeft = Cells(TopRow, LeftCol) ‘ Get the paste address On Error Resume Next Set PasteRange = Application.InputBox _ (Prompt:=”Specify the upper-left cell for the paste range:”, _ Title:=”Copy Multiple Selection”, _ Type:=8) On Error GoTo 0 ‘ Exit if canceled If TypeName(PasteRange) <> “Range” Then Exit Sub ‘ Make sure only the upper-left cell is used Set PasteRange = PasteRange.Range(“A1”) ‘ Copy and paste each area For i = 1 To NumAreas RowOffset = SelAreas(i).Row - TopRow ColOffset = SelAreas(i).Column - LeftCol SelAreas(i).Copy PasteRange.Offset(RowOffset, ColOffset) Next i End Sub Figure 11-11 shows the prompt to select the destination location. Figure 11-11: Using Excel’s InputBox method to prompt for a cell location. Chapter 11: VBA Programming Examples and Techniques 343 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 343 CD-ROM The companion CD-ROM contains a workbook with this example, plus another version that warns the user if data will be overwritten. The file is named copy multiple selection.xlsm. Working with Workbooks and Sheets The examples in this section demonstrate various ways to use VBA to work with work- books and worksheets. Saving all workbooks The following procedure loops through all workbooks in the Workbooks collection and saves each file that has been saved previously: Public Sub SaveAllWorkbooks() Dim Book As Workbook For Each Book In Workbooks If Book.Path <> “” Then Book.Save Next Book End Sub Notice the use of the Path property. If a workbook’s Path property is empty, the file has never been saved (it’s a new workbook). This procedure ignores such workbooks and saves only the workbooks that have a non-empty Path property. Saving and closing all workbooks The following procedure loops through the Workbooks collection. The code saves and closes all workbooks. Sub CloseAllWorkbooks() Dim Book As Workbook For Each Book In Workbooks If Book.Name <> ThisWorkbook.Name Then Book.Close savechanges:=True End If Next Book ThisWorkbook.Close savechanges:=True End Sub Part III: Understanding Visual Basic for Applications344 17_044018 ch11.qxp 2/28/07 6:40 PM Page 344 The procedure uses an If statement within the For-Next loop to determine whether the workbook is the workbook that contains the code. This is necessary because closing the workbook that contains the procedure would end the code, and subsequent workbooks would not be affected. Hiding all but the selection The example in this section hides all rows and columns except those in the current range selection. Figure 11-12 shows an example. Figure 11-12: All rows and columns are hidden, except for a range (G8:K17). Sub HideRowsAndColumns() Dim row1 As Long, row2 As Long Dim col1 As Long, col2 As Long If TypeName(Selection) <> “Range” Then Exit Sub ‘ If last row or last column is hidden, unhide all and quit If Rows(Rows.Count).EntireRow.Hidden Or _ Columns(Columns.Count).EntireColumn.Hidden Then Cells.EntireColumn.Hidden = False Cells.EntireRow.Hidden = False Exit Sub End If row1 = Selection.Rows(1).Row row2 = row1 + Selection.Rows.Count - 1 col1 = Selection.Columns(1).Column Chapter 11: VBA Programming Examples and Techniques 345 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 345 col2 = col1 + Selection.Columns.Count - 1 Application.ScreenUpdating = False On Error Resume Next ‘ Hide rows Range(Cells(1, 1), Cells(row1 - 1, 1)).EntireRow.Hidden = True Range(Cells(row2 + 1, 1), Cells(Rows.Count, 1)).EntireRow.Hidden = True ‘ Hide columns Range(Cells(1, 1), Cells(1, col1 - 1)).EntireColumn.Hidden = True Range(Cells(1, col2 + 1), Cells(1, Columns.Count)).EntireColumn.Hidden = True End Sub If the range selection consists of a noncontiguous range, the first Area is used as the basis for hiding rows and columns. CD-ROM A workbook with this example is available on the companion CD-ROM. The file is named hide rows and columns.xlsm. Synchronizing worksheets If you use multisheet workbooks, you probably know that Excel cannot synchronize the sheets in a workbook. In other words, there is no automatic way to force all sheets to have the same selected range and upper-left cell. The VBA macro that follows uses the active worksheet as a base and then performs the following on all other worksheets in the workbook: • Selects the same range as the active sheet. • Makes the upper-left cell the same as the active sheet. Following is the listing for the subroutine: Sub SynchSheets() ‘ Duplicates the active sheet’s active cell and upper left cell ‘ Across all worksheets If TypeName(ActiveSheet) <> “Worksheet” Then Exit Sub Dim UserSheet As Worksheet, sht As Worksheet Dim TopRow As Long, LeftCol As Integer Dim UserSel As String Application.ScreenUpdating = False ‘ Remember the current sheet Part III: Understanding Visual Basic for Applications346 17_044018 ch11.qxp 2/28/07 6:40 PM Page 346 Set UserSheet = ActiveSheet ‘ Store info from the active sheet TopRow = ActiveWindow.ScrollRow LeftCol = ActiveWindow.ScrollColumn UserSel = ActiveWindow.RangeSelection.Address ‘ Loop through the worksheets For Each sht In ActiveWorkbook.Worksheets If sht.Visible Then ‘skip hidden sheets sht.Activate Range(UserSel).Select ActiveWindow.ScrollRow = TopRow ActiveWindow.ScrollColumn = LeftCol End If Next sht ‘ Restore the original position UserSheet.Activate Application.ScreenUpdating = True End Sub CD-ROM A workbook with this example is available on the companion CD-ROM in a file named synchronize sheets.xlsm. VBA Techniques The examples in this section illustrate common VBA techniques that you might be able to adapt to your own projects. Toggling a Boolean property A Boolean property is one that is either True or False. The easiest way to toggle a Boolean property is to use the Not operator, as shown in the following example, which tog- gles the WrapText property of a selection. Sub ToggleWrapText() ‘ Toggles text wrap alignment for selected cells If TypeName(Selection) = “Range” Then Selection.WrapText = Not ActiveCell.WrapText End If End Sub Chapter 11: VBA Programming Examples and Techniques 347 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 347 Note that the active cell is used as the basis for toggling. When a range is selected and the property values in the cells are inconsistent (for example, some cells are bold, and others are not), it is considered mixed, and Excel uses the active cell to determine how to toggle. If the active cell is bold, for example, all cells in the selection are made not bold when you click the Bold button. This simple procedure mimics the way Excel works, which is usually the best practice. Note also that this procedure uses the TypeName function to check whether the selection is a range. If the selection isn’t a range, nothing happens. You can use the Not operator to toggle many other properties. For example, to toggle the display of row and column borders in a worksheet, use the following code: ActiveWindow.DisplayHeadings = Not _ ActiveWindow.DisplayHeadings To toggle the display of grid lines in the active worksheet, use the following code: ActiveWindow.DisplayGridlines = Not _ ActiveWindow.DisplayGridlines Determining the number of printed pages If you need to determine the number of printed pages for a worksheet printout, you can use Excel’s Print Preview feature and view the page count displayed at the bottom of the screen. The VBA procedure that follows calculates the number of printed pages for the active sheet by counting the number of horizontal and vertical page breaks: Sub PageCount() MsgBox (ActiveSheet.HPageBreaks.Count + 1) * _ (ActiveSheet.VPageBreaks.Count + 1) & “ pages” End Sub The following VBA procedure loops through all worksheets in the active workbook and dis- plays the total number of printed pages, as shown in Figure 11-13: Sub ShowPageCount() Dim PageCount As Integer Dim sht As Worksheet PageCount = 0 For Each sht In Worksheets PageCount = PageCount + (sht.HPageBreaks.Count + 1) * _ (sht.VPageBreaks.Count + 1) Next sht MsgBox “Total printed pages = “ & PageCount End Sub Part III: Understanding Visual Basic for Applications348 17_044018 ch11.qxp 2/28/07 6:40 PM Page 348 Figure 11-13: Using VBA to count the number of printed pages in a workbook. CD-ROM A workbook that contains this example is on the companion CD-ROM in a file named page count.xlsm. Displaying the date and time If you understand the serial number system that Excel uses to store dates and times, you won’t have any problems using dates and times in your VBA procedures. The DateAndTime procedure displays a message box with the current date and time, as depicted in Figure 11-14. This example also displays a personalized message in the mes- sage box title bar. Figure 11-14: A message box displaying the date and time. The procedure uses the Date function as an argument for the Format function. The result is a string with a nicely formatted date. I used the same technique to get a nicely formatted time. Sub DateAndTime() Dim TheDate As String, TheTime As String Dim Greeting As String Dim FullName As String, FirstName As String Dim SpaceInName As Long TheDate = Format(Date, “Long Date”) TheTime = Format(Time, “Medium Time”) ‘ Determine greeting based on time Select Case Time Case Is < TimeValue(“12:00”): Greeting = “Good Morning, “ Chapter 11: VBA Programming Examples and Techniques 349 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 349 Case Is >= TimeValue(“17:00”): Greeting = “Good Evening, “ Case Else: Greeting = “Good Afternoon, “ End Select ‘ Append user’s first name to greeting FullName = Application.UserName SpaceInName = InStr(1, FullName, “ “, 1) ‘ Handle situation when name has no space If SpaceInName = 0 Then SpaceInName = Len(FullName) FirstName = Left(FullName, SpaceInName) Greeting = Greeting & FirstName ‘ Show the message MsgBox TheDate & vbCrLf & vbCrLf & “It’s “ & TheTime, vbOKOnly, Greeting End Sub In the preceding example, I used named formats (Long Date and Medium Time) to ensure that the macro will work properly regardless of the user’s international settings. You can, however, use other formats. For example, to display the date in mm/dd/yy format, you can use a statement like the following: TheDate = Format(Date, “mm/dd/yy”) I used a Select Case construct to base the greeting displayed in the message box’s title bar on the time of day. VBA time values work just as they do in Excel. If the time is less than .5 (noon), it’s morning. If it’s greater than .7083 (5 p.m.), it’s evening. Otherwise, it’s afternoon. I took the easy way out and used VBA’s TimeValue function, which returns a time value from a string. The next series of statements determines the user’s first name, as recorded in the General tab in Excel’s Options dialog box. I used VBA’s InStr function to locate the first space in the user’s name. When I first wrote this procedure, I didn’t consider a username that has no space. So when I ran this procedure on a machine with a username of Nobody, the code failed — which goes to show you that I can’t think of everything, and even the simplest procedures can run aground. (By the way, if the user’s name is left blank, Excel always substitutes the name User.) The solution to this problem was to use the length of the full name for the SpaceInName variable so that the Left function extracts the full name. The MsgBox function concatenates the date and time but uses the built-in vbCrLf constant to insert a line break between them. vbOKOnly is a predefined constant that returns 0, causing the message box to appear with only an OK button. The final argument is the Greeting, constructed earlier in the procedure. CD-ROM The DateAndTime procedure is available on the companion CD-ROM in a file named date and time.xlsm. Part III: Understanding Visual Basic for Applications350 17_044018 ch11.qxp 2/28/07 6:40 PM Page 350 Getting a list of fonts If you need to get a list of all installed fonts, you’ll find that Excel does not provide a direct way to retrieve that information. In previous editions of this book, I presented a technique that read the font names from the Font control on the Formatting toolbar. Excel 2007 no longer has a Formatting toolbar, and it’s impossible to access the controls on the Ribbon using VBA. For compatibility purposes, however, Excel 2007 still supports the old CommandBar properties and methods — it’s just that they don’t work as you would expect. In any case, I was able to modify my old procedure so it works reliably in Excel 2007. The ShowInstalledFonts macro displays a list of the installed fonts in column A of the active worksheet. It creates a temporary toolbar (a CommandBar object), adds the Font control, and reads the fonts from that control. The temporary toolbar is then deleted. Sub ShowInstalledFonts() Dim FontList As CommandBarControl Dim TempBar As CommandBar Dim i As Long ‘ Create temporary CommandBar Set TempBar = Application.CommandBars.Add Set FontList = TempBar.Controls.Add(ID:=1728) ‘ Put the fonts into column A Range(“A:A”).ClearContents For i = 0 To FontList.ListCount - 1 Cells(i + 1, 1) = FontList.List(i + 1) Next i ‘ Delete temporary CommandBar TempBar.Delete End Sub TIP As an option, you can display each font name in the actual font (as shown in Figure 11-15). To do so, add this statement inside of the For-Next loop: Cells(i+1,1).Font.Name = FontList.List(i+1) Be aware, however, that using many fonts in a workbook can eat up lots of system resources, and it could even crash your system. CD-ROM This procedure is available on the companion CD-ROM. The file is named list fonts.xlsm. Chapter 11: VBA Programming Examples and Techniques 351 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 351 Figure 11-15: Listing font names in the actual fonts. Sorting an array Although Excel has a built-in command to sort worksheet ranges, VBA doesn’t offer a method to sort arrays. One viable (but cumbersome) workaround is to transfer your array to a worksheet range, sort it by using Excel’s commands, and then return the result to your array. But if speed is essential, it’s better to write a sorting routine in VBA. In this section, I cover four different sorting techniques: • Worksheet sort transfers an array to a worksheet range, sorts it, and transfers it back to the array. This procedure accepts an array as its only argument. • Bubble sort is a simple sorting technique (also used in the Chapter 9 sheet-sorting exam- ple). Although easy to program, the bubble-sorting algorithm tends to be rather slow, especially when the number of elements is large. • Quick sort is a much faster sorting routine than bubble sort, but it is also more difficult to understand. This technique works only with Integer and Long data types. • Counting sort is lightning fast, but also difficult to understand. Like the quick sort, this technique works only with Integer and Long data types. Part III: Understanding Visual Basic for Applications352 17_044018 ch11.qxp 2/28/07 6:40 PM Page 352 CD-ROM The companion CD-ROM includes a workbook application (named sorting demo.xlsm) that demonstrates these sorting methods. This workbook is useful for comparing the techniques with arrays of varying sizes. Figure 11-16 shows the dialog box for this project. I tested the sorting procedures with seven different array sizes, ranging from 100 to 100,000 elements. The arrays contained random numbers (of type Long). Figure 11-16: Comparing the time required to perform sorts of various array sizes. Table 11-1 shows the results of my tests. A 0.00 entry means that the sort was virtually instantaneous (less than .01 second). TABLE 11-1 SORTING TIMES (IN SECONDS) FOR FOUR SORT ALGORITHMS USING RANDOMLY FILLED ARRAYS Array Excel VBA VBA VBA Elements Worksheet Sort Bubble Sort Quick Sort Counting Sort 100 0.05 0.00 0.00 0.02 500 0.05 0.02 0.00 0.02 1,000 0.06 0.08 0.01 0.02 5,000 0.14 1.92 0.02 0.03 10,000 0.22 8.23 0.03 0.03 50,000 0.97 186.56 0.27 0.08 100,000 1.97 713.03 0.50 0.14 Chapter 11: VBA Programming Examples and Techniques 353 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 353 The worksheet sort algorithm is amazingly fast, especially when you consider that the array is transferred to the sheet, sorted, and then transferred back to the array. The bubble sort algorithm is reasonably fast with small arrays, but for larger arrays (more than 5,000 elements), forget it. The quick sort and counting sort algorithm are both very fast, but they are limited to Integer and Long data types. Processing a series of files One common use for macros, of course, is to repeat an operation a number of times. The example in this section demonstrates how to execute a macro on several different files stored on disk. This example — which may help you set up your own routine for this type of task — prompts the user for a file specification and then processes all matching files. In this case, processing consists of importing the file and entering a series of summary formu- las that describe the data in the file. NOTE In previous editions of this book, I used the FileSearch object. Excel 2007 no longer supports the FileSearch object, so I resorted to the more cumbersome Dir function. Sub BatchProcess() Dim FileSpec As String Dim i As Integer Dim FileName As String Dim FileList() As String Dim FoundFiles As Integer ‘ Specify path and file spec FileSpec = ThisWorkbook.Path & “\” & “text??.txt” FileName = Dir(FileSpec) ‘ Was a file found? If FileName <> “” Then FoundFiles = 1 ReDim Preserve FileList(1 To FoundFiles) FileList(FoundFiles) = FileName Else MsgBox “No files were found that match “ & FileSpec Exit Sub End If ‘ Get other filenames Do FileName = Dir Part III: Understanding Visual Basic for Applications354 17_044018 ch11.qxp 2/28/07 6:40 PM Page 354 If FileName = “” Then Exit Do FoundFiles = FoundFiles + 1 ReDim Preserve FileList(1 To FoundFiles) FileList(FoundFiles) = FileName & “*” Loop ‘ Loop through the files and process them For i = 1 To FoundFiles Call ProcessFiles(FileList(i)) Next i End Sub CD-ROM This example, named batch processing.xlsm, is on the companion CD-ROM. It uses three additional files (also on the CD): text01.txt, text02.txt, and text03.txt. You’ll need to modify the routine to import other text files. The matching filenames are stored in an array named FoundFiles, and the procedure uses a For-Next loop to process the files. Within the loop, the processing is done by call- ing the ProcessFiles procedure, which follows. This simple procedure uses the OpenText method to import the file and then inserts five formulas. You may, of course, substitute your own routine in place of this one: Sub ProcessFiles(FileName As String) ‘ Import the file Workbooks.OpenText FileName:=FileName, _ Origin:=xlWindows, _ StartRow:=1, _ DataType:=xlFixedWidth, _ FieldInfo:= _ Array(Array(0, 1), Array(3, 1), Array(12, 1)) ‘ Enter summary formulas Range(“D1”).Value = “A” Range(“D2”).Value = “B” Range(“D3”).Value = “C” Range(“E1:E3”).Formula = “=COUNTIF(B:B,D1)” Range(“F1:F3”).Formula = “=SUMIF(B:B,D1,C:C)” End Sub CROSS-REFERENCE For more information about working with files using VBA, refer to Chapter 27. Chapter 11: VBA Programming Examples and Techniques 355 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 355 Some Useful Functions for Use in Your Code In this section, I present some custom utility functions that you may find useful in your own applications and that may provide inspiration for creating similar functions. These functions are most useful when called from another VBA procedure. Therefore, they are declared by using the Private keyword and thus will not appear in Excel’s Insert Function dialog box. CD-ROM The examples in this section are available on the companion CD-ROM. The file is named VBA utility functions.xlsm. The FileExists function This function takes one argument (a path with filename) and returns True if the file exists: Private Function FileExists(fname) As Boolean ‘ Returns TRUE if the file exists FileExists = (Dir(fname) <> “”) End Function The FileNameOnly function This function accepts one argument (a path with filename) and returns only the filename. In other words, it strips out the path. Private Function FileNameOnly(pname) As String ‘ Returns the filename from a path/filename string Dim i As Integer, length As Integer, temp As String Dim Cnt As Integer ‘ Count the path separator characters Cnt = 0 For i = 1 To Len(pname) If Mid(pname, i, 1) = Application.PathSeparator Then Cnt = Cnt + 1 End If Next i FileNameOnly = Split(pname, Application.PathSeparator, Cnt) End Function If the argument is c:\excel files\2007\backup\budget.xls, the function returns the string budget.xls. Part III: Understanding Visual Basic for Applications356 17_044018 ch11.qxp 2/28/07 6:40 PM Page 356 The FileNameOnly function works with any path and filename (even if the file does not exist). If the file exists, the following function is a simpler way to strip off the path and return only the filename. Private Function FileNameOnly2(pname) As String FileNameOnly2 = Dir(pname) End Function The PathExists function This function accepts one argument (a path) and returns True if the path exists: Private Function PathExists(pname) As Boolean ‘ Returns TRUE if the path exists If Dir(pname, vbDirectory) = “” Then PathExists = False Else PathExists = (GetAttr(pname) And vbDirectory) = vbDirectory End If End Function The RangeNameExists function This function accepts a single argument (a range name) and returns True if the range name exists in the active workbook: Private Function RangeNameExists(nname) As Boolean ‘ Returns TRUE if the range name exists Dim n As Name RangeNameExists = False For Each n In ActiveWorkbook.Names If UCase(n.Name) = UCase(nname) Then RangeNameExists = True Exit Function End If Next n End Function Another way to write this function follows. This version attempts to create an object vari- able using the name. If doing so generates an error, then the name does not exist. Private Function RangeNameExists2(nname) As Boolean ‘ Returns TRUE if the range name exists Dim n As Range On Error Resume Next Set n = Range(nname) Chapter 11: VBA Programming Examples and Techniques 357 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 357 If Err.Number = 0 Then RangeNameExists2 = True _ Else RangeNameExists2 = False End Function The SheetExists function This function accepts one argument (a worksheet name) and returns True if the worksheet exists in the active workbook: Private Function SheetExists(sname) As Boolean ‘ Returns TRUE if sheet exists in the active workbook Dim x As Object On Error Resume Next Set x = ActiveWorkbook.Sheets(sname) If Err.Number = 0 Then SheetExists = True _ Else SheetExists = False End Function The WorkbookIsOpen function This function accepts one argument (a workbook name) and returns True if the workbook is open: Private Function WorkbookIsOpen(wbname) As Boolean ‘ Returns TRUE if the workbook is open Dim x As Workbook On Error Resume Next Set x = Workbooks(wbname) If Err.Number = 0 Then WorkbookIsOpen = True _ Else WorkbookIsOpen = False End Function Retrieving a value from a closed workbook VBA does not include a method to retrieve a value from a closed workbook file. You can, however, take advantage of Excel’s ability to work with linked files. This section contains a custom VBA function (GetValue, which follows) that retrieves a value from a closed work- book. It does so by calling an XLM macro, which is an old-style macro used in versions prior to Excel 5. Fortunately, Excel still supports this old macro system. Private Function GetValue(path, file, sheet, ref) ‘ Retrieves a value from a closed workbook Dim arg As String ‘ Make sure the file exists Part III: Understanding Visual Basic for Applications358 17_044018 ch11.qxp 2/28/07 6:40 PM Page 358 If Right(path, 1) <> “\” Then path = path & “\” If Dir(path & file) = “” Then GetValue = “File Not Found” Exit Function End If ‘ Create the argument arg = “‘“ & path & “[“ & file & “]” & sheet & “‘!” & _ Range(ref).Range(“A1”).Address(, , xlR1C1) ‘ Execute an XLM macro GetValue = ExecuteExcel4Macro(arg) End Function Chapter 11: VBA Programming Examples and Techniques 359 Part IIIThe following function procedure is a generic function that you can use to determine whether an object is a member of a collection: Private Function IsInCollection(Coln As Object, _ Item As String) As Boolean Dim Obj As Object On Error Resume Next Set Obj = Coln(Item) IsInCollection = Not Obj Is Nothing End Function This function accepts two arguments: the collection (an object) and the item (a string) that might or might not be a member of the collection. The function attempts to create an object variable that represents the item in the collection. If the attempt is successful, the function returns True; otherwise, it returns False. You can use the IsInCollection function in place of three other functions listed in this chapter: RangeNameExists, SheetExists, and WorkbookIsOpen. To determine whether a range named Data exists in the active workbook, call the IsInCollection function with this statement: MsgBox IsInCollection(ActiveWorkbook.Names, “Data”) To determine whether a workbook named Budget is open, use this statement: MsgBox IsInCollection(Workbooks, “budget.xlsx”) To determine whether the active workbook contains a sheet named Sheet1, use this statement. MsgBox IsInCollection(ActiveWorkbook.Worksheets, “Sheet1”) Testing for Membership in a Collection 17_044018 ch11.qxp 2/28/07 6:40 PM Page 359 The GetValue function takes four arguments: • path: The drive and path to the closed file (for example, “d:\files”) • file: The workbook name (for example, “budget.xlsx”) • sheet: The worksheet name (for example, “Sheet1”) • ref: The cell reference (for example, “C4”) The following Sub procedure demonstrates how to use the GetValue function. It simply displays the value in cell A1 in Sheet1 of a file named 2007budget.xlsx, located in the XLFiles\Budget directory on drive C. Sub TestGetValue() Dim p As String, f As String Dim s As String, a As String p = “c:\XLFiles\Budget” f = “2007budget.xlsx” s = “Sheet1” a = “A1” MsgBox GetValue(p, f, s, a) End Sub Another example follows. This procedure reads 1,200 values (100 rows and 12 columns) from a closed file and then places the values into the active worksheet. Sub TestGetValue2() Dim p As String, f As String Dim s As String, a As String Dim r As Long, c As Long p = “c:\XLFiles\Budget” f = “2007Budget.xlsx” s = “Sheet1” Application.ScreenUpdating = False For r = 1 To 100 For c = 1 To 12 a = Cells(r, c).Address Cells(r, c) = GetValue(p, f, s, a) Next c Next r End Sub Part III: Understanding Visual Basic for Applications360 17_044018 ch11.qxp 2/28/07 6:40 PM Page 360 NOTE The GetValue function does not work if used in a worksheet formula. Actually, there is no need to use this function in a formula. You can simply create a link formula to retrieve a value from a closed file. CD-ROM This example is available on the companion CD-ROM. The file is named value from a closed workbook.xlsm. The example uses a file named myworkbook.xlsx for the closed file. Some Useful Worksheet Functions The examples in this section are custom functions that can be used in worksheet formulas. Remember, these Function procedures must be defined in a VBA module (not a code mod- ule associated with ThisWorkbook, a Sheet, or a UserForm). CD-ROM The examples in this section are available on the companion CD-ROM in a file named worksheet functions.xlsm. Returning cell formatting information This section contains a number of custom functions that return information about a cell’s formatting. These functions are useful if you need to sort data based on formatting (for example, sort such that all bold cells are together). Keep in mind that these functions work only with explicitly-applied formatting; they do not work for formatting applied using con- ditional formatting. CAUTION You’ll find that these functions aren’t always updated automatically. This is because changing formatting, for example, doesn’t trigger Excel’s recalculation engine. To force a global recalculation (and update all the custom functions), press Ctrl+Alt+F9. Alternatively, you can add the following statement to your function: Application.Volatile When this statement is present, then pressing F9 will recalculate the function. Chapter 11: VBA Programming Examples and Techniques 361 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 361 The following function returns TRUE if its single-cell argument has bold formatting. If a range is passed as the argument, the function uses the upper-left cell of the range. Function IsBold(cell) As Boolean ‘ Returns TRUE if cell is bold IsBold = cell.Range(“A1”).Font.Bold End Function The following function returns TRUE if its single-cell argument has italic formatting: Function IsItalic(cell) As Boolean ‘ Returns TRUE if cell is italic IsItalic = cell.Range(“A1”).Font.Italic End Function Both of the preceding functions will return an error if the cell has mixed formatting — for example, if only some characters are bold. The following function returns TRUE only if all characters in the cell are bold: Function AllBold(cell) As Boolean ‘ Returns TRUE if all characters in cell are bold If IsNull(cell.Font.Bold) Then AllBold = False Else AllBold = cell.Font.Bold End If End Function The ALLBOLD function can be simplified as follows: Function AllBold (cell) As Boolean ‘ Returns TRUE if all characters in cell are bold AllBold = Not IsNull(cell.Font.Bold) End Function The FillColor function returns an integer that corresponds to the color index of the cell’s interior. The actual color depends on the workbook theme that’s applied. If the cell’s interior is not filled, the function returns –4142. Function FillColor(cell) As Integer ‘ Returns an integer corresponding to ‘ cell’s interior color FillColor = cell.Range(“A1”).Interior.ColorIndex End Function Part III: Understanding Visual Basic for Applications362 17_044018 ch11.qxp 2/28/07 6:40 PM Page 362 A talking worksheet The SayIt function uses Excel’s text-to-speech generator to “speak” it’s argument (which can be literal text or a cell reference). Function SayIt(txt) Application.Speech.Speak (txt) SayIt = txt End Function This function has some amusement possibilities, but it can also be useful. For example, use the function in a formula like this: =IF(SUM(A:A)>25000,SayIt(“Goal Reached”)) If the sum of the values in column A exceeds 25,000, you’ll hear the synthesized voice tell you that the goal has been reached. You can also use the Speak method at the end of a lengthy procedure. That way, you can do something else and you’ll get an audible notice when the procedure ends. Displaying the date when a file was saved or printed An Excel workbook contains several built-in document properties, accessible from the BuiltinDocumentProperties property of the Workbook object. The following function returns the date and time that the workbook was last saved: Function LastSaved() Application.Volatile LastSaved = ThisWorkbook. _ BuiltinDocumentProperties(“Last Save Time”) End Function The following function is similar, but it returns the date and time when the workbook was last printed or previewed. If the workbook has never been printed or previewed, the func- tion returns a #VALUE error. Function LastPrinted() Application.Volatile LastPrinted = ThisWorkbook. _ BuiltinDocumentProperties(“Last Print Date”) End Function If you use these functions in a formula, you might need to force a recalculation (by press- ing F9) to get the current values of these properties. Chapter 11: VBA Programming Examples and Techniques 363 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 363 NOTE Quite a few additional built-in properties are available, but Excel does not use all of them. For example, attempting to access the Number of Bytes property will generate an error. For a list of all built-in properties, consult the Help. The preceding LastSaved and LastPrinted functions are designed to be stored in the workbook in which they are used. In some cases, you might want to store the function in a different workbook (for example, personal.xlsb) or in an add-in. Because these functions reference ThisWorkbook, they will not work correctly. Following are more general-purpose versions of these functions. These functions use Application.Caller, which returns a Range object that represents the cell that calls the function. The use of Parent.Parent returns the workbook (that is, the parent of the parent of the Range object — a Workbook object). This topic is explained further in the next section. Function LastSaved2() Application.Volatile LastSaved2 = Application.Caller.Parent.Parent. _ BuiltinDocumentProperties(“Last Save Time”) End Function Understanding object parents As you know, Excel’s object model is a hierarchy: Objects are contained in other objects. At the top of the hierarchy is the Application object. Excel contains other objects, and these objects contain other objects, and so on. The following hierarchy depicts how a Range object fits into this scheme: Application object Workbook object Worksheet object Range object In the lingo of object-oriented programming, a Range object’s parent is the Worksheet object that contains it. A Worksheet object’s parent is the Workbook object that contains the worksheet, and a Workbook object’s parent is the Application object. How can this information be put to use? Examine the SheetName VBA function that fol- lows. This function accepts a single argument (a range) and returns the name of the work- sheet that contains the range. It uses the Parent property of the Range object. The Parent property returns an object: the object that contains the Range object. Function SheetName(ref) As String SheetName = ref.Parent.Name End Function Part III: Understanding Visual Basic for Applications364 17_044018 ch11.qxp 2/28/07 6:40 PM Page 364 The next function, WorkbookName, returns the name of the workbook for a particular cell. Notice that it uses the Parent property twice. The first Parent property returns a Worksheet object, and the second Parent property returns a Workbook object. Function WorkbookName(ref) As String WorkbookName = ref.Parent.Parent.Name End Function The AppName function that follows carries this exercise to the next logical level, accessing the Parent property three times. This function returns the name of the Application object for a particular cell. It will, of course, always return Microsoft Excel. Function AppName(ref) As String AppName = ref.Parent.Parent.Parent.Name End Function Counting cells between two values The following function, named CountBetween, returns the number of values in a range (first argument) that fall between values represented by the second and third arguments: Function CountBetween(InRange, num1, num2) As Long ‘ Counts number of values between num1 and num2 With Application.WorksheetFunction If num1 <= num2 Then CountBetween = .CountIf(InRange, “>=” & num1) - _ .CountIf(InRange, “>” & num2) Else CountBetween = .CountIf(InRange, “>=” & num2) - _ .CountIf(InRange, “>” & num1) End If End With End Function Note that this function uses Excel’s COUNTIF function. In fact, the CountBetween func- tion is essentially a wrapper that can simplify your formulas. Following is an example formula that uses the CountBetween function. The formula returns the number of cells in A1:A100 that are greater than or equal to 10 and less than or equal to 20. =CountBetween(A1:A100,10,20) Using this VBA function is simpler than entering the following lengthy (and somewhat con- fusing) formula: =COUNTIF(A1:A100,”>=10”)-COUNTIF(A1:A100,”>20”) Chapter 11: VBA Programming Examples and Techniques 365 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 365 Counting visible cells in a range The CountVisible function that follows accepts a range argument and returns the num- ber of non-empty visible cells in the range. A cell is not visible if it’s in a hidden row or a hidden column. Function CountVisible(rng) ‘ Counts visible cells Dim CellCount As Long Dim cell As Range Application.Volatile CellCount = 0 Set rng = Intersect(rng.Parent.UsedRange, rng) For Each cell In rng If Not IsEmpty(cell) Then If Not cell.EntireRow.Hidden And _ Not cell.EntireColumn.Hidden Then _ CellCount = CellCount + 1 End If Next cell CountVisible = CellCount End Function This function loops through each cell in the range, first checking whether the cell is empty. If it’s not empty, it checks the hidden properties of the cell’s row and column. If neither the row nor column is hidden, the CellCount variable is incremented. The CountVisible function is useful when you’re working with AutoFilters or outlines. Both of these features make use of hidden rows. TIP If you’re using Excel 2003 or later, the CountVisible function is no longer required. You can use Excel’s SUBTOTAL function instead. For example, to count the visible numeric cells in a range named data, use this formula: =SUBTOTAL(103,data) This formula works even if rows or columns are hidden manually. In previous versions, the SUBTOTAL function worked correctly only if the cells were hidden by using AutoFilters or outlines. Determining the last non-empty cell in a column or row In this section, I present two useful functions: LastInColumn returns the contents of the last non-empty cell in a column; LastInRow returns the contents of the last non-empty cell in a row. Each function accepts a range as its single argument. The range argument Part III: Understanding Visual Basic for Applications366 17_044018 ch11.qxp 2/28/07 6:40 PM Page 366 can be a complete column (for LastInColumn) or a complete row (for LastInRow). If the supplied argument is not a complete column or row, the function uses the column or row of the upper-left cell in the range. For example, the following formula returns the last value in column B: =LastInColumn(B5) The following formula returns the last value in row 7: =LastInRow(C7:D9) The LastInColumn function follows: Function LastInColumn(rng As Range) ‘ Returns the contents of the last non-empty cell in a column Dim LastCell As Range Application.Volatile With rng.Parent With .Cells(.Rows.Count, rng.Column) If Not IsEmpty(.Value) Then LastInColumn = .Value ElseIf IsEmpty(.End(xlUp)) Then LastInColumn = “” Else LastInColumn = .End(xlUp).Value End If End With End With End Function This function is rather complicated, so here are a few points that may help you understand it: • Application.Volatile causes the function to be executed whenever the sheet is calculated. • Rows.Count returns the number of rows in the worksheet. I used this, rather than hard-coding the value, because not all worksheets have the same number of rows. • rng.Column returns the column number of the upper-left cell in the rng argument. • Using rng.Parent causes the function to work properly even if the rng argument refers to a different sheet or workbook. • The End method (with the xlUp argument) is equivalent to activating the last cell in a column, pressing End, and then pressing the up-arrow key. • The IsEmpty function checks whether the cell is empty. If so, it returns an empty string. Without this statement, an empty cell would be returned as 0. Chapter 11: VBA Programming Examples and Techniques 367 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 367 The LastInRow function follows. This is very similar to the LastInColumn function. Function LastInRow(rng As Range) ‘ Returns the contents of the last non-empty cell in a row Application.Volatile With rng.Parent With .Cells(rng.Row, .Columns.Count) If Not IsEmpty(.Value) Then LastInRow = .Value ElseIf IsEmpty(.End(xlToLeft)) Then LastInRow = “” Else LastInRow = .End(xlToLeft).Value End If End With End With End Function Does a string match a pattern? The IsLike function is very simple (but also very useful). This function returns TRUE if a text string matches a specified pattern. This function, which follows, is remarkably simple. As you can see, the function is essentially a wrapper that lets you take advantage of VBA’s powerful Like operator in your formulas. Function IsLike(text As String, pattern As String) As Boolean ‘ Returns true if the first argument is like the second IsLike = text Like pattern End Function This IsLike function takes two arguments: • text: A text string or a reference to a cell that contains a text string • pattern: A string that contains wildcard characters according to the following list: Character(s) in pattern Matches in text ? Any single character * Zero or more characters # Any single digit (0–9) [charlist] Any single character in charlist [!charlist] Any single character not in charlist Part III: Understanding Visual Basic for Applications368 17_044018 ch11.qxp 2/28/07 6:40 PM Page 368 The following formula returns TRUE because * matches any number of characters. It returns TRUE if the first argument is any text that begins with g. =IsLike(“guitar”,”g*”) The following formula returns TRUE because ? matches any single character. If the first argument were “Unit12”, the function would return FALSE. =IsLike(“Unit1”,”Unit?”) The next formula returns TRUE because the first argument is a single character in the sec- ond argument. =ISLIKE(“a”,”[aeiou]”) The following formula returns TRUE if cell A1 contains a, e, i, o, u, A, E, I, O, or U. Using the UPPER function for the arguments makes the formula not case-sensitive. =IsLike(UPPER(A1), UPPER(“[aeiou]”)) The following formula returns TRUE if cell A1 contains a value that begins with 1 and has exactly three digits (that is, any integer between 100 and 199). =IsLike(A1,”1##”) Extracting the nth element from a string ExtractElement is a custom worksheet function (which can also be called from a VBA procedure) that extracts an element from a text string. For example, if a cell contains the following text, you can use the ExtractElement function to extract any of the substrings between the hyphens. 123-456-789-0133-8844 The following formula, for example, returns 0133, which is the fourth element in the string. The string uses a hyphen (-) as the separator. =ExtractElement(“123-456-789-0133-8844”,4,”-”) The ExtractElement function uses three arguments: • Txt: The text string from which you’re extracting. This can be a literal string or a cell reference. • n: An integer that represents the element to extract. • Separator: A single character used as the separator. Chapter 11: VBA Programming Examples and Techniques 369 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 369 NOTE If you specify a space as the Separator character, multiple spaces are treated as a sin- gle space, which is almost always what you want. If n exceeds the number of elements in the string, the function returns an empty string. The VBA code for the ExtractElement function follows: Function ExtractElement(Txt, n, Separator) As String ‘ Returns the nth element of a text string, where the ‘ elements are separated by a specified separator character Dim AllElements As Variant AllElements = Split(Txt, Separator) ExtractElement = AllElements(n - 1) End Function This function uses VBA’s Split function, which returns a variant array that contains each element of the text string. This array begins with 0 (not 1), so using n-1references the desired element. The Split function was introduced in Excel 2000. For compatibility with older versions of Excel, you need to use the following function: Function ExtractElement2(Txt, n, Separator) As String ‘ Returns the nth element of a text string, where the ‘ elements are separated by a specified separator character Dim Txt1 As String, TempElement As String Dim ElementCount As Integer, i As Integer Txt1 = Txt ‘ If space separator, remove excess spaces If Separator = Chr(32) Then Txt1 = Application.Trim(Txt1) ‘ Add a separator to the end of the string If Right(Txt1, Len(Txt1)) <> Separator Then _ Txt1 = Txt1 & Separator ‘ Initialize ElementCount = 0 TempElement = “” ‘ Extract each element For i = 1 To Len(Txt1) If Mid(Txt1, i, 1) = Separator Then ElementCount = ElementCount + 1 If ElementCount = n Then ‘ Found it, so exit Part III: Understanding Visual Basic for Applications370 17_044018 ch11.qxp 2/28/07 6:40 PM Page 370 ExtractElement2 = TempElement Exit Function Else TempElement = “” End If Else TempElement = TempElement & Mid(Txt1, i, 1) End If Next i ExtractElement2 = “” End Function A multifunctional function This example describes a technique that may be helpful in some situations: making a sin- gle worksheet function act like multiple functions. For example, the following VBA listing is for a custom function called StatFunction. It takes two arguments: the range (rng) and the operation (op). Depending on the value of op, the function returns a value com- puted using any of the following worksheet functions: AVERAGE, COUNT, MAX, MEDIAN, MIN, MODE, STDEV, SUM, or VAR. For example, you can use this function in your worksheet as follows: =StatFunction(B1:B24,A24) The result of the formula depends on the contents of cell A24, which should be a string such as Average, Count, Max, and so on. You can adapt this technique for other types of functions. Function StatFunction(rng, op) Select Case UCase(op) Case “SUM” StatFunction = WorksheetFunction.Sum(rng) Case “AVERAGE” StatFunction = WorksheetFunction.Average(rng) Case “MEDIAN” StatFunction = WorksheetFunction.Median(rng) Case “MODE” StatFunction = WorksheetFunction.Mode(rng) Case “COUNT” StatFunction = WorksheetFunction.Count(rng) Case “MAX” StatFunction = WorksheetFunction.Max(rng) Case “MIN” StatFunction = WorksheetFunction.Min(rng) Case “VAR” Chapter 11: VBA Programming Examples and Techniques 371 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 371 StatFunction = WorksheetFunction.Var(rng) Case “STDEV” StatFunction = WorksheetFunction.StDev(rng) Case Else StatFunction = CVErr(xlErrNA) End Select End Function The SheetOffset function You probably know that Excel’s support for 3-D workbooks is limited. For example, if you need to refer to a different worksheet in a workbook, you must include the worksheet’s name in your formula. This is not a big problem . . . until you attempt to copy the formula across other worksheets. The copied formulas continue to refer to the original worksheet name, and the sheet references are not adjusted as they would be in a true 3-D workbook. The example discussed in this section is a VBA function (named SheetOffset) that enables you to address worksheets in a relative manner. For example, you can refer to cell A1 on the previous worksheet by using this formula: =SheetOffset(-1,A1) The first argument represents the relative sheet, and it can be positive, negative, or zero. The second argument must be a reference to a single cell. You can copy this formula to other sheets, and the relative referencing will be in effect in all the copied formulas. The VBA code for the SheetOffset function follows: Function SheetOffset(Offset As Long, Optional Cell As Variant) ‘ Returns cell contents at Ref, in sheet offset Dim WksIndex As Long, WksNum As Long Dim wks As Worksheet Application.Volatile If IsMissing(Cell) Then Set Cell = Application.Caller WksNum = 1 For Each wks In Application.Caller.Parent.Parent.Worksheets If Application.Caller.Parent.Name = wks.Name Then SheetOffset = Worksheets(WksNum + Offset).Range(Cell(1).Address) Exit Function Else WksNum = WksNum + 1 End If Next wks End Function Part III: Understanding Visual Basic for Applications372 17_044018 ch11.qxp 2/28/07 6:40 PM Page 372 Returning the maximum value across all worksheets If you need to determine the maximum value in cell B1 across a number of worksheets, you would use a formula such as this: =MAX(Sheet1:Sheet4!B1) This formula returns the maximum value in cell B1 for Sheet1, Sheet4, and all the sheets in between. But what if you add a new sheet (Sheet5) after Sheet4? Your formula won’t adjust auto- matically, so you need to edit it to include the new sheet reference: =MAX(Sheet1:Sheet5!B1) The MaxAllSheets function, which follows, accepts a single-cell argument and returns the maximum value in that cell across all worksheets in the workbook. The formula that follows, for example, returns the maximum value in cell B1 for all sheets in the workbook: =MaxAllSheets(B1) If you add a new sheet, there’s no need to edit the formula: Function MaxAllSheets(cell) Dim MaxVal As Double Dim Addr As String Dim Wksht As Object Application.Volatile Addr = cell.Range(“A1”).Address MaxVal = -9.9E+307 For Each Wksht In cell.Parent.Parent.Worksheets If Wksht.Name = cell.Parent.Name And _ Addr = Application.Caller.Address Then ‘ avoid circular reference Else If IsNumeric(Wksht.Range(Addr)) Then If Wksht.Range(Addr) > MaxVal Then _ MaxVal = Wksht.Range(Addr).Value End If End If Next Wksht If MaxVal = -9.9E+307 Then MaxVal = 0 MaxAllSheets = MaxVal End Function The For Each statement uses the following expression to access the workbook: cell.Parent.Parent.Worksheets Chapter 11: VBA Programming Examples and Techniques 373 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 373 The parent of the cell is a worksheet, and the parent of the worksheet is the workbook. Therefore, the For Each-Next loop cycles among all worksheets in the workbook. The first If statement inside the loop performs a check to see whether the cell being checked is the cell that contains the function. If so, that cell is ignored to avoid a circular reference error. NOTE This function can be modified easily to perform other cross-worksheet calculations, such as minimum, average, sum, and so on. Returning an array of nonduplicated random integers The function in this section, RandomIntegers, returns an array of nonduplicated integers. The function is intended to be used in a multicell array formula. {=RandomIntegers()} Select a range and then enter the formula by pressing Ctrl+Shift+Enter. The formula returns an array of nonduplicated integers, arranged randomly. For example, if you enter the formula into a 50-cell range, the formulas will return nonduplicated integers from 1 to 50. The code for RandomIntegers follows: Function RandomIntegers() Dim FuncRange As Range Dim V() As Variant, ValArray() As Variant Dim CellCount As Double Dim i As Integer, j As Integer Dim r As Integer, c As Integer Dim Temp1 As Variant, Temp2 As Variant Dim RCount As Integer, CCount As Integer ‘ Create Range object Set FuncRange = Application.Caller ‘ Return an error if FuncRange is too large CellCount = FuncRange.Count If CellCount > 1000 Then RandomIntegers = CVErr(xlErrNA) Exit Function End If ‘ Assign variables RCount = FuncRange.Rows.Count CCount = FuncRange.Columns.Count Part III: Understanding Visual Basic for Applications374 17_044018 ch11.qxp 2/28/07 6:40 PM Page 374 ReDim V(1 To RCount, 1 To CCount) ReDim ValArray(1 To 2, 1 To CellCount) ‘ Fill array with random numbers ‘ and consecutive integers For i = 1 To CellCount ValArray(1, i) = Rnd ValArray(2, i) = i Next i ‘ Sort ValArray by the random number dimension For i = 1 To CellCount For j = i + 1 To CellCount If ValArray(1, i) > ValArray(1, j) Then Temp1 = ValArray(1, j) Temp2 = ValArray(2, j) ValArray(1, j) = ValArray(1, i) ValArray(2, j) = ValArray(2, i) ValArray(1, i) = Temp1 ValArray(2, i) = Temp2 End If Next j Next i ‘ Put the randomized values into the V array i = 0 For r = 1 To RCount For c = 1 To CCount i = i + 1 V(r, c) = ValArray(2, i) Next c Next r RandomIntegers = V End Function Randomizing a range The RangeRandomize function, which follows, accepts a range argument and returns an array that consists of the input range — in random order: Function RangeRandomize(rng) Dim V() As Variant, ValArray() As Variant Dim CellCount As Double Dim i As Integer, j As Integer Dim r As Integer, c As Integer Dim Temp1 As Variant, Temp2 As Variant Chapter 11: VBA Programming Examples and Techniques 375 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 375 Dim RCount As Integer, CCount As Integer ‘ Return an error if rng is too large CellCount = rng.Count If CellCount > 1000 Then RangeRandomize = CVErr(xlErrNA) Exit Function End If ‘ Assign variables RCount = rng.Rows.Count CCount = rng.Columns.Count ReDim V(1 To RCount, 1 To CCount) ReDim ValArray(1 To 2, 1 To CellCount) ‘ Fill ValArray with random numbers ‘ and values from rng For i = 1 To CellCount ValArray(1, i) = Rnd ValArray(2, i) = rng(i) Next i ‘ Sort ValArray by the random number dimension For i = 1 To CellCount For j = i + 1 To CellCount If ValArray(1, i) > ValArray(1, j) Then Temp1 = ValArray(1, j) Temp2 = ValArray(2, j) ValArray(1, j) = ValArray(1, i) ValArray(2, j) = ValArray(2, i) ValArray(1, i) = Temp1 ValArray(2, i) = Temp2 End If Next j Next i ‘ Put the randomized values into the V array i = 0 For r = 1 To RCount For c = 1 To CCount i = i + 1 V(r, c) = ValArray(2, i) Next c Next r RangeRandomize = V End Function Part III: Understanding Visual Basic for Applications376 17_044018 ch11.qxp 2/28/07 6:40 PM Page 376 The code is very similar to that for the RandomIntegers function. Figure 11-17 shows the function in use. The array formula in B2:B11 is: {= RangeRandomize(A2:A11)} This formula returns the contents of A2:A11, but in random order. Figure 11-17: The RangeRandomize function returns the contents of a range, in random order. Windows API Calls VBA has the capability to use functions that are stored in Dynamic Link Libraries (DLLs). The examples in this section use common Windows API calls to DLLs. Determining file associations In Windows, many file types are associated with a particular application. This association makes it possible to double-click the file to load it into its associated application. The following function, named GetExecutable, uses a Windows API call to get the full path to the application associated with a particular file. For example, your system has many files with a .txt extension — one named Readme.txt is probably in your Windows directory right now. You can use the GetExecutable function to determine the full path of the application that opens when the file is double-clicked. NOTE Windows API declarations must appear at the top of your VBA module. Chapter 11: VBA Programming Examples and Techniques 377 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 377 Private Declare Function FindExecutableA Lib “shell32.dll” _ (ByVal lpFile As String, ByVal lpDirectory As String, _ ByVal lpResult As String) As Long Function GetExecutable(strFile As String) As String Dim strPath As String Dim intLen As Integer strPath = Space(255) intLen = FindExecutableA(strFile, “\”, strPath) GetExecutable = Trim(strPath) End Function Figure 11-18 shows the result of calling the GetExecutable function, with an argument of the filename for an MP3 audio file. The function returns the full path of the application that’s associated with the file. Figure 11-18: Determining the path and name of the application associated with a particular file. CD-ROM This example is available on the companion CD-ROM. The filename is file association.xlsm. Determining disk drive information VBA doesn’t have a way to directly get information about disk drives. But with the assis- tance of three API functions, you can get just about all of the information you need. Figure 11-19 shows the output from a VBA procedure that identifies all connected drives, determines the drive type, and calculates total space, used space, and free space. In the example shown, the system has six drives connected. The code is rather lengthy, so I don’t list it here, but the interested reader should be able to figure it out by examining the code on the CD-ROM. CD-ROM This example is available on the companion CD-ROM in a file named drive information.xlsm. Part III: Understanding Visual Basic for Applications378 17_044018 ch11.qxp 2/28/07 6:40 PM Page 378 Figure 11-19: Using Windows API functions to get disk drive information. Determining default printer information The example in this section uses a Windows API function to return information about the active printer. The information is contained in a single text string. The example parses the string and displays the information in a more readable format. Private Declare Function GetProfileStringA Lib “kernel32” _ (ByVal lpAppName As String, ByVal lpKeyName As String, _ ByVal lpDefault As String, ByVal lpReturnedString As _ String, ByVal nSize As Long) As Long Sub DefaultPrinterInfo() Dim strLPT As String * 255 Dim Result As String Call GetProfileStringA _ (“Windows”, “Device”, “”, strLPT, 254) Result = Application.Trim(strLPT) ResultLength = Len(Result) Comma1 = InStr(1, Result, “,”, 1) Comma2 = InStr(Comma1 + 1, Result, “,”, 1) ‘ Gets printer’s name Printer = Left(Result, Comma1 - 1) ‘ Gets driver Driver = Mid(Result, Comma1 + 1, Comma2 - Comma1 - 1) ‘ Gets last part of device line Port = Right(Result, ResultLength - Comma2) ‘ Build message Msg = “Printer:” & Chr(9) & Printer & Chr(13) Msg = Msg & “Driver:” & Chr(9) & Driver & Chr(13) Chapter 11: VBA Programming Examples and Techniques 379 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 379 Msg = Msg & “Port:” & Chr(9) & Port ‘ Display message MsgBox Msg, vbInformation, “Default Printer Information” End Sub NOTE The ActivePrinter property of the Application object returns the name of the active printer (and lets you change it), but there’s no direct way to determine what printer driver or port is being used. That’s why this function may be useful. Figure 11-20 shows a sample message box returned by this procedure. Figure 11-20: Getting information about the active printer by using a Windows API call. CD-ROM This example is available on the companion CD-ROM. The filename is printer info.xlsm. Determining video display information The example in this section uses Windows API calls to determine a system’s current video mode for the primary display monitor. If your application needs to display a certain amount of information on one screen, knowing the display size helps you scale the text accordingly. In addition, the code determines the number of monitors. If more than one monitor is installed, the procedure reports the virtual screen size. ‘32-bit API declaration Declare Function GetSystemMetrics Lib “user32” _ (ByVal nIndex As Long) As Long Public Const SM_CMONITORS = 80 Public Const SM_CXSCREEN = 0 Public Const SM_CYSCREEN = 1 Part III: Understanding Visual Basic for Applications380 17_044018 ch11.qxp 2/28/07 6:40 PM Page 380 Public Const SM_CXVIRTUALSCREEN = 78 Public Const SM_CYVIRTUALSCREEN = 79 Sub DisplayVideoInfo() Dim numMonitors As Long Dim vidWidth As Long, vidHeight As Long Dim virtWidth As Long, virtHeight As Long Dim Msg As String numMonitors = GetSystemMetrics(SM_CMONITORS) vidWidth = GetSystemMetrics(SM_CXSCREEN) vidHeight = GetSystemMetrics(SM_CYSCREEN) virtWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN) virtHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN) If numMonitors > 1 Then Msg = numMonitors & “ display monitors” & vbCrLf Msg = Msg & “Virtual screen: “ & virtWidth & “ X “ Msg = Msg & virtHeight & vbCrLf & vbCrLf Msg = Msg & “The video mode on the primary display is: “ Msg = Msg & vidWidth & “ X “ & vidHeight Else Msg = Msg & “The video display mode: “ Msg = Msg & vidWidth & “ X “ & vidHeight End If MsgBox Msg End Sub Figure 11-21 shows the message box returned by this procedure when running on a dual- monitor system. Figure 11-21: Using a Windows API call to determine the video display mode. CD-ROM This example is available on the companion CD-ROM. The filename is video mode.xlsm. Chapter 11: VBA Programming Examples and Techniques 381 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 381 Adding sound to your applications The example in this section adds some sound capability to Excel. Specifically, it enables your application to play WAV or MIDI files. For example, you might like to play a short sound clip when a dialog box is displayed. Or maybe not. In any case, if you want Excel to play WAV or MIDI files, this section has what you need. CD-ROM The examples in this section are available on the companion CD-ROM in a file named sound.xlsm. PLAYING A WAV FILE The following example contains the API function declaration plus a simple procedure to play a sound file called sound.wav, which is presumed to be in the same directory as the workbook: 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 Sub PlayWAV() WAVFile = “sound.wav” WAVFile = ThisWorkbook.Path & “\” & WAVFile Call PlaySound(WAVFile, 0&, SND_ASYNC Or SND_FILENAME) End Sub In the preceding example, the WAV file is played asynchronously. This means that execu- tion continues while the sound is playing. To stop code execution while the sound is play- ing, use this statement instead: Call PlaySound(WAVFile, 0&, SND_SYNC Or SND_FILENAME) PLAYING A MIDI FILE If the sound file is a MIDI file, you’ll need to use a different API call. The PlayMIDI pro- cedure starts playing a MIDI file. Executing the StopMIDI procedure stops playing the MIDI file. This example uses a file named xfiles.mid. Part III: Understanding Visual Basic for Applications382 17_044018 ch11.qxp 2/28/07 6:40 PM Page 382 Private Declare Function mciExecute Lib “winmm.dll” _ (ByVal lpstrCommand As String) As Long Sub PlayMIDI() MIDIFile = “xfiles.mid” MIDIFile = ThisWorkbook.Path & “\” & MIDIFile mciExecute (“play “ & MIDIFile) End Sub Sub StopMIDI() MIDIFile = “xfiles.mid” MIDIFile = ThisWorkbook.Path & “\” & MIDIFile mciExecute (“stop “ & MIDIFile) End Sub PLAYING SOUND FROM A WORKSHEET FUNCTION The Alarm function, which follows, is designed to be used in a worksheet formula. It uses a Windows API function to play a sound file when a cell meets a certain condition. Declare Function PlaySound Lib “winmm.dll” _ Alias “PlaySoundA” (ByVal lpszName As String, _ ByVal hModule As Long, ByVal dwFlags As Long) As Long Function Alarm(Cell, Condition) Dim WAVFile As String Const SND_ASYNC = &H1 Const SND_FILENAME = &H20000 If Evaluate(Cell.Value & Condition) Then WAVFile = ThisWorkbook.Path & “\sound.wav” Call PlaySound(WAVFile, 0&, SND_ASYNC Or SND_FILENAME) Alarm = True Else Alarm = False End If End Function The Alarm function accepts two arguments: a cell reference and a condition (expressed as a string). The following formula, for example, uses the Alarm function to play a WAV file when the value in cell B13 is greater than or equal to 1000. =ALARM(B13,”>=1000”) The function uses VBA’s Evaluate function to determine whether the cell’s value matches the specified criterion. When the criterion is met (and the alarm has sounded), the function returns True; otherwise, it returns False. Chapter 11: VBA Programming Examples and Techniques 383 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 383 CROSS-REFERENCE The SayIt function, presented earlier in this chapter, is a much simpler way to use sound in a function. Reading from and writing to the Registry Most Windows applications use the Windows Registry database to store settings. (See Chapter 4 for some additional information about the Registry.) Your VBA procedures can read values from the Registry and write new values to the Registry. Doing so requires the following Windows API declarations: Private Declare Function RegOpenKeyA Lib “ADVAPI32.DLL” _ (ByVal hKey As Long, ByVal sSubKey As String, _ ByRef hkeyResult As Long) As Long Private Declare Function RegCloseKey Lib “ADVAPI32.DLL” _ (ByVal hKey As Long) As Long Private Declare Function RegSetValueExA Lib “ADVAPI32.DLL” _ (ByVal hKey As Long, ByVal sValueName As String, _ ByVal dwReserved As Long, ByVal dwType As Long, _ ByVal sValue As String, ByVal dwSize As Long) As Long Private Declare Function RegCreateKeyA Lib “ADVAPI32.DLL” _ (ByVal hKey As Long, ByVal sSubKey As String, _ ByRef hkeyResult As Long) As Long Private Declare Function RegQueryValueExA Lib “ADVAPI32.DLL” _ (ByVal hKey As Long, ByVal sValueName As String, _ ByVal dwReserved As Long, ByRef lValueType As Long, _ ByVal sValue As String, ByRef lResultLen As Long) As Long CD-ROM I developed two wrapper functions that simplify the task of working with the Registry: GetRegistry and WriteRegistry. These functions are available on the companion CD-ROM in a file named windows registry.xlsm. This workbook includes a proce- dure that demonstrates reading from the Registry and writing to the Registry. READING FROM THE REGISTRY The GetRegistry function returns a setting from the specified location in the Registry. It takes three arguments: Part III: Understanding Visual Basic for Applications384 17_044018 ch11.qxp 2/28/07 6:40 PM Page 384 • RootKey: A string that represents the branch of the Registry to address. This string can be one of the following: • HKEY_CLASSES_ROOT • HKEY_CURRENT_USER • HKEY_LOCAL_MACHINE • HKEY_USERS • HKEY_CURRENT_CONFIG • HKEY_DYN_DATA • Path: The full path of the Registry category being addressed. • RegEntry: The name of the setting to retrieve. Here’s an example. If you’d like to find which graphic file, if any, is being used for the Desktop wallpaper, you can call GetRegistry as follows. (Notice that the arguments are not case-sensitive.) RootKey = “hkey_current_user” Path = “Control Panel\Desktop” RegEntry = “Wallpaper” MsgBox GetRegistry(RootKey, Path, RegEntry), _ vbInformation, Path & “\RegEntry” The message box will display the path and filename of the graphic file (or an empty string if wallpaper is not used). WRITING TO THE REGISTRY The WriteRegistry function writes a value to the Registry at a specified location. If the operation is successful, the function returns True; otherwise, it returns False. WriteRegistry takes the following arguments (all of which are strings): • RootKey: A string that represents the branch of the Registry to address. This string may be one of the following: • HKEY_CLASSES_ROOT • HKEY_CURRENT_USER • HKEY_LOCAL_MACHINE • HKEY_USERS • HKEY_CURRENT_CONFIG • HKEY_DYN_DATA • Path: The full path in the Registry. If the path doesn’t exist, it is created. Chapter 11: VBA Programming Examples and Techniques 385 Part III 17_044018 ch11.qxp 2/28/07 6:40 PM Page 385 • RegEntry: The name of the Registry category to which the value will be written. If it doesn’t exist, it is added. • RegVal: The value that you are writing. Here’s an example that writes a value representing the time and date Excel was started to the Registry. The information is written in the area that stores Excel’s settings. Sub Auto_Open() RootKey = “hkey_current_user” Path = “software\microsoft\office\12.0\excel\LastStarted” RegEntry = “DateTime” RegVal = Now() If WriteRegistry(RootKey, Path, RegEntry, RegVal) Then msg = RegVal & “ has been stored in the registry.” Else msg = “An error occurred” End If MsgBox msg End Sub If you store this routine in your personal macro workbook, the setting is automatically updated whenever you start Excel. Part III: Understanding Visual Basic for Applications386 If you want to use the Windows Registry to store and retrieve settings for your Excel applications, you don’t have to bother with the Windows API calls. Rather, you can use VBA’s GetSetting and SaveSetting functions. These two functions are described in the online Help, so I won’t cover the details here. However, it’s important to understand that these functions work only with the following key name: HKEY_CURRENT_USER\Software\VB and VBA Program Settings In other words, you can’t use these functions to access any key in the Registry. Rather, these functions are most useful for storing information about your Excel application that you need to maintain between sessions. An Easier Way to Access the Registry 17_044018 ch11.qxp 2/28/07 6:40 PM Page 386 Working with UserForms Chapter 12 Custom Dialog Box Alternatives Chapter 13 Introducing UserForms Chapter 14 UserForm Examples Chapter 15 Advanced UserForm Techniques IVPart 18_044018 pt04.qxp 2/28/07 6:40 PM Page 387 18_044018 pt04.qxp 2/28/07 6:40 PM Page 388 Chapter Custom Dialog Box Alternatives In This Chapter Dialog boxes are, perhaps, the most important user interface element in Windows programs. Virtually every Windows program uses them, and most users have a good understanding of how they work. Excel developers implement custom dialog boxes by creating UserForms. However, VBA provides the means to display some built-in dialog boxes. This chapter covers the following topics: ◆ Using an input box to get user input ◆ Using a message box to display messages or get a simple response ◆ Selecting a file from a dialog box ◆ Selecting a directory ◆ Displaying Excel’s built-in dialog boxes Before I get into the nitty-gritty of creating UserForms, you might find it help- ful to understand some of Excel’s built-in tools that display dialog boxes. That’s the focus of this chapter. 12 389 19_044018 ch12.qxp 2/28/07 6:40 PM Page 389 Before You Create That UserForm . . . In some cases, you can save yourself the trouble of creating a custom dialog box by using one of several prebuilt dialog boxes. The sections that follow describe various dialog boxes that you can display without creating a UserForm. Using an Input Box An input box is a simple dialog box that allows the user to make a single entry. For exam- ple, you can use an input box to let the user enter text, a number, or even select a range. There are actually two ways to generate an InputBox: one by using a VBA function, and the other by using a method of the Application object. The VBA InputBox function The syntax for VBA’s InputBox function is: InputBox(prompt[,title][,default][,xpos][,ypos][,helpfile, context]) • prompt: Required. The text displayed in the InputBox. • title: Optional. The caption of the InputBox window. • default: Optional. The default value to be displayed in the dialog box. • xpos, ypos: Optional. The screen coordinates of the upper-left corner of the window. • helpfile, context: Optional. The help file and help topic. The InputBox function prompts the user for a single piece of information. The function always returns a string, so it may be necessary to convert the results to a value. The prompt may consist of about 1,024 characters (more or less, depending on the width of the characters used). In addition, you can provide a title for the dialog box and a default value and specify its position on the screen. And you can specify a custom help topic; if you do, the input box includes a Help button. The following example, whose output is shown in Figure 12-1, uses the VBA InputBox function to ask the user for his or her full name. The code then extracts the first name and displays a greeting in a message box. Sub GetName() Dim UserName As String Dim FirstSpace As Integer Do Until UserName <> “” UserName = InputBox(“Enter your full name: “, _ Part IV: Working with UserForms390 19_044018 ch12.qxp 2/28/07 6:40 PM Page 390 “Identify Yourself”) Loop FirstSpace = InStr(UserName, “ “) If FirstSpace <> 0 Then UserName = Left(UserName, FirstSpace - 1) End If MsgBox “Hello “ & UserName End Sub Figure 12-1: VBA’s InputBox function at work. Notice that this InputBox function is written in a Do Until loop to ensure that something is entered when the input box appears. If the user clicks Cancel or doesn’t enter any text, UserName contains an empty string, and the input box reappears. The procedure then attempts to extract the first name by searching for the first space character (by using the InStr function) and then using the Left function to extract all characters before the first space. If a space character is not found, the entire name is used as entered. As I mentioned, the InputBox function always returns a string. If the string returned by the InputBox function looks like a number, you can convert it to a value by using VBA’s Val function. Or you can use Excel’s InputBox method, which I describe in the next section. Figure 12-2 shows another example of the VBA InputBox function. The user is asked to fill in the missing word. This example also illustrates the use of named arguments. The prompt text is retrieved from a worksheet cell. Sub GetWord() Dim TheWord As String Dim p As String Dim t As String p = Range(“A1”) t = “What’s the missing word?” TheWord = InputBox(prompt:=p, Title:=t) If UCase(TheWord) = “BATTLEFIELD” Then MsgBox “Correct.” Else MsgBox “That is incorrect.” End If End Sub Chapter 12: Custom Dialog Box Alternatives 391 Part IV 19_044018 ch12.qxp 2/28/07 6:40 PM Page 391 Figure 12-2: Using VBA’s InputBox function with a long prompt. CD-ROM The two examples in this section are available on the companion CD-ROM. The file is named VBA inputbox.xlsm. The Excel InputBox method Using Excel’s InputBox method offers three advantages over VBA’s InputBox function: • You can specify the data type returned. • The user can specify a worksheet range by dragging in the worksheet. • Input validation is performed automatically. The syntax for the Excel InputBox method is: InputBox(Prompt [,Title][,Default][,Left][,Top][,HelpFile, HelpContextID] [,Type]) • Prompt: Required. The text displayed in the input box. • Title: Optional. The caption in the input box window. • Default: Optional. The default value to be returned by the function if the user enters nothing. • Left, Top: Optional. The screen coordinates of the upper-left corner of the window. Part IV: Working with UserForms392 19_044018 ch12.qxp 2/28/07 6:40 PM Page 392 • HelpFile, HelpContextID: Optional. The help file and help topic. • Type: Optional. A code for the data type returned, as listed in Table 12-1. TABLE 12-1 CODES TO DETERMINE THE DATA TYPE RETURNED BY EXCEL’S INPUTBOX METHOD Code Meaning 0 A formula 1 A number 2 A string (text) 4 A logical value (True or False) 8 A cell reference, as a range object 16 An error value, such as #N/A 64 An array of values Excel’s InputBox method is quite versatile. To allow more than one data type to be returned, use the sum of the pertinent codes. For example, to display an input box that can accept text or numbers, set type equal to 3 (that is, 1 + 2, or number plus text). If you use 8 for the type argument, the user can enter a cell or range address manually or point to a range in the worksheet. The EraseRange procedure, which follows, uses the InputBox method to allow the user to select a range to erase (see Figure 12-3). The user can either type the range address manu- ally or use the mouse to select the range in the sheet. Figure 12-3: Using the InputBox method to specify a range. Chapter 12: Custom Dialog Box Alternatives 393 Part IV 19_044018 ch12.qxp 2/28/07 6:40 PM Page 393 The InputBox method with a type argument of 8 returns a Range object (note the Set keyword). This range is then erased (by using the Clear method). The default value dis- played in the input box is the current selection’s address. The On Error statement ends the procedure if the input box is canceled. Sub EraseRange() Dim UserRange As Range On Error GoTo Canceled Set UserRange = Application.InputBox _ (Prompt:=”Range to erase:”, _ Title:=”Range Erase”, _ Default:=Selection.Address, _ Type:=8) UserRange.Clear UserRange.Select Canceled: End Sub CD-ROM This example is available on the companion CD-ROM in a file named inputbox method.xlsm. Yet another advantage of using Excel’s InputBox method is that Excel performs input valida- tion automatically. In the GetRange example, if you enter something other than a range address, Excel displays an informative message and lets the user try again (see Figure 12-4). Figure 12-4: Excel’s InputBox method performs validation automatically. The VBA MsgBox Function VBA’s MsgBox function is an easy way to display a message to the user or to get a simple response (such as OK or Cancel). I use the MsgBox function in many of this book’s exam- ples as a way to display a variable’s value. Part IV: Working with UserForms394 19_044018 ch12.qxp 2/28/07 6:40 PM Page 394 The official syntax for MsgBox is as follows: MsgBox(prompt[,buttons][,title][,helpfile, context]) • prompt: Required. The text displayed in the message box. • buttons: Optional. A numeric expression that determines which buttons and icon are displayed in the message box. See Table 12-2. • title: Optional. The caption in the message box window. • helpfile, context: Optional. The helpfile and help topic. You can easily customize your message boxes because of the flexibility of the buttons argu- ment. (Table 12-2 lists the many constants that you can use for this argument.) You can spec- ify which buttons to display, whether an icon appears, and which button is the default. TABLE 12-2 CONSTANTS USED FOR BUTTONS IN THE MSGBOX FUNCTION Constant Value Description vbOKOnly 0 Display OK button only. vbOKCancel 1 Display OK and Cancel buttons. vbAbortRetryIgnore 2 Display Abort, Retry, and Ignore buttons. vbYesNoCancel 3 Display Yes, No, and Cancel buttons. vbYesNo 4 Display Yes and No buttons. vbRetryCancel 5 Display Retry and Cancel buttons. vbCritical 16 Display Critical Message icon. vbQuestion 32 Display Warning Query icon. vbExclamation 48 Display Warning Message icon. vbInformation 64 Display Information Message icon. vbDefaultButton1 0 First button is default. vbDefaultButton2 256 Second button is default. vbDefaultButton3 512 Third button is default. vbDefaultButton4 768 Fourth button is default. continued Chapter 12: Custom Dialog Box Alternatives 395 Part IV 19_044018 ch12.qxp 2/28/07 6:40 PM Page 395 TABLE 12-2 CONSTANTS USED FOR BUTTONS IN THE MSGBOX FUNCTION (continued) Constant Value Description vbSystemModal 4096 All applications are suspended until the user responds to the message box (might not work under all conditions). vbMsgBoxHelpButton 16384 Display a Help button. However, there is no way to display any help if the button is clicked. You can use the MsgBox function by itself (to simply display a message) or assign its result to a variable. When MsgBox does return a result, it represents the button clicked by the user. The following example displays a message and an OK button and does not return a result: Sub MsgBoxDemo() MsgBox “Macro finished with no errors.” End Sub To get a response from a message box, you can assign the results of the MsgBox function to a variable. In the following code, I use some built-in constants (described in Table 12-3) to make it easier to work with the values returned by MsgBox: Sub GetAnswer() Dim Ans As Integer Ans = MsgBox(“Continue?”, vbYesNo) Select Case Ans Case vbYes ‘ ...[code if Ans is Yes]... Case vbNo ‘ ...[code if Ans is No]... End Select End Sub TABLE 12-3 CONSTANTS USED FOR MSGBOX RETURN VALUE Constant Value Button Clicked vbOK 1 OK vbCancel 2 Cancel Part IV: Working with UserForms396 19_044018 ch12.qxp 2/28/07 6:40 PM Page 396 Constant Value Button Clicked vbAbort 3 Abort vbRetry 4 Retry vbIgnore 5 Ignore vbYes 6 Yes vbNo 7 No The variable returned by the MsgBox function is an Integer data type. Actually, it’s not even necessary to use a variable to utilize the result of a message box. The following pro- cedure is another way of coding the GetAnswer procedure. Sub GetAnswer2() If MsgBox(“Continue?”, vbYesNo) = vbYes Then ‘ ...[code if Ans is Yes]... Else ‘ ...[code if Ans is No]... End If End Sub The following function example uses a combination of constants to display a message box with a Yes button, a No button, and a question mark icon; the second button is designated as the default button (see Figure 12-5). For simplicity, I assigned these constants to the Config variable. Private Function ContinueProcedure() As Boolean Dim Config As Integer Dim Ans As Integer Config = vbYesNo + vbQuestion + vbDefaultButton2 Ans = MsgBox(“An error occurred. Continue?”, Config) If Ans = vbYes Then ContinueProcedure = True _ Else ContinueProcedure = False End Function Figure 12-5: The buttons argument of the MsgBox function determines which buttons appear. Chapter 12: Custom Dialog Box Alternatives 397 Part IV 19_044018 ch12.qxp 2/28/07 6:40 PM Page 397 The ContinueProcedure function can be called from another procedure. For example, the following statement calls the ContinueProcedure function (which displays the mes- sage box). If the function returns False (that is, the user selects No), the procedure ends. Otherwise, the next statement would be executed. If Not ContinueProcedure() Then Exit Sub The width of the message box depends on your video resolution. If you would like to force a line break in the message, use the vbCrLf (or vbNewLine) constant in the text. The fol- lowing example displays the message in three lines. Figure 12-6 shows how it looks. Sub MultiLine() Dim Msg As String Msg = “This is the first line.” & vbCrLf & vbCrLf Msg = Msg & “This is the second line.” & vbCrLf Msg = Msg & “And this is the last line.” MsgBox Msg End Sub Figure 12-6: Splitting a message into multiple lines. You can also insert a tab character by using the vbTab constant. The following procedure uses a message box to display the values in a 20 x 8 range of cells in A1:H20 (see Figure 12-7). It separates the columns by using a vbTab constant and inserts a new line by using the vbCrLf constant. The MsgBox function accepts a maximum string length of 1,023 characters, which will limit the number of cells that you can display. Sub ShowRange() Dim Msg As String Dim r As Integer, c As Integer Msg = “” For r = 1 To 20 For c = 1 To 8 Msg = Msg & Cells(r, c) & vbTab Next c Msg = Msg & vbCrLf Next r MsgBox Msg End Sub Part IV: Working with UserForms398 19_044018 ch12.qxp 2/28/07 6:40 PM Page 398 Figure 12-7: This message box displays text with tabs and line breaks. CROSS-REFERENCE Chapter 15 includes a UserForm example that emulates the MsgBox function. The Excel GetOpenFilename Method If your application needs to ask the user for a filename, you can use the InputBox function. But this approach is tedious and error-prone. A better approach is to use the GetOpenFilename method of the Application object, which ensures that your application gets a valid filename (as well as its complete path). This method displays the normal Open dialog box, but it does not actually open the file specified. Rather, the method returns a string that contains the path and filename selected by the user. Then you can write code to do whatever you want with the filename. The syntax for the GetOpenFilename method is as follows (all arguments are optional): ApplicationGetOpenFilename(FileFilter, FilterIndex, Title, ButtonText, MultiSelect) • FileFilter: Optional. A string specifying file-filtering criteria. • FilterIndex: Optional. The index number of the default file-filtering criteria. • Title: Optional. The title of the dialog box. If omitted, the title is Open. Chapter 12: Custom Dialog Box Alternatives 399 Part IV 19_044018 ch12.qxp 2/28/07 6:40 PM Page 399 • ButtonText: For Macintosh only. • MultiSelect: Optional. If True, multiple filenames can be selected. The default value is False. The FileFilter argument determines what appears in the dialog box’s Files of Type drop- down list. The argument consists of pairs of file filter strings followed by the wildcard file filter specification, with each part and each pair separated by commas. If omitted, this argument defaults to the following: “All Files (*.*),*.*” Notice that the first part of this string (All Files (*.*)) is the text displayed in the Files of Type drop-down list. The second part (*.*) actually determines which files are displayed. The following instruction assigns a string to a variable named Filt. This string can then be used as a FileFilter argument for the GetOpenFilename method. In this case, the dialog box will allow the user to select from four different file types (plus an All Files option). Notice that I used VBA’s line continuation sequence to set up the Filt variable; doing so makes it much easier to work with this rather complicated argument. Filt = “Text Files (*.txt),*.txt,” & _ “Lotus Files (*.prn),*.prn,” & _ “Comma Separated Files (*.csv),*.csv,” & _ “ASCII Files (*.asc),*.asc,” & _ “All Files (*.*),*.*” The FilterIndex argument specifies which FileFilter is the default, and the title argument is text that is displayed in the title bar. If the MultiSelect argument is True, the user can select multiple files, all of which are returned in an array. The following example prompts the user for a filename. It defines five file filters. Sub GetImportFileName() Dim Filt As String Dim FilterIndex As Integer Dim Title As String Dim FileName As Variant ‘ Set up list of file filters Filt = “Text Files (*.txt),*.txt,” & _ “Lotus Files (*.prn),*.prn,” & _ “Comma Separated Files (*.csv),*.csv,” & _ “ASCII Files (*.asc),*.asc,” & _ “All Files (*.*),*.*” ‘ Display *.* by default Part IV: Working with UserForms400 19_044018 ch12.qxp 2/28/07 6:40 PM Page 400 FilterIndex = 5 ‘ Set the dialog box caption Title = “Select a File to Import” ‘ Get the file name FileName = Application.GetOpenFilename _ (FileFilter:=Filt, _ FilterIndex:=FilterIndex, _ Title:=Title) ‘ Exit if dialog box canceled If FileName = False Then MsgBox “No file was selected.” Exit Sub End If ‘ Display full path and name of the file MsgBox “You selected “ & FileName End Sub Figure 12-8 shows the dialog box that appears when this procedure is executed and the user selects the Comma Separated Files filter. Figure 12-8: The GetOpenFilename method displays a dialog box used to specify a file. The following example is similar to the previous example. The difference is that the user can press Ctrl or Shift and select multiple files when the dialog box is displayed. Notice that I check for the Cancel button click by determining whether FileName is an array. If Chapter 12: Custom Dialog Box Alternatives 401 Part IV 19_044018 ch12.qxp 2/28/07 6:40 PM Page 401 the user doesn’t click Cancel, the result is an array that consists of at least one element. In this example, a list of the selected files is displayed in a message box. Sub GetImportFileName2() Dim Filt As String Dim FilterIndex As Integer Dim FileName As Variant Dim Title As String Dim i As Integer Dim Msg As String ‘ Set up list of file filters Filt = “Text Files (*.txt),*.txt,” & _ “Lotus Files (*.prn),*.prn,” & _ “Comma Separated Files (*.csv),*.csv,” & _ “ASCII Files (*.asc),*.asc,” & _ “All Files (*.*),*.*” ‘ Display *.* by default FilterIndex = 5 ‘ Set the dialog box caption Title = “Select a File to Import” ‘ Get the file name FileName = Application.GetOpenFilename _ (FileFilter:=Filt, _ FilterIndex:=FilterIndex, _ Title:=Title, _ MultiSelect:=True) ‘ Exit if dialog box canceled If Not IsArray(FileName) Then MsgBox “No file was selected.” Exit Sub End If ‘ Display full path and name of the files For i = LBound(FileName) To UBound(FileName) Msg = Msg & FileName(i) & vbCrLf Next i MsgBox “You selected:” & vbCrLf & Msg End Sub The FileName variable is defined as a variant (not a string, as in the previous examples). This is done because FileName can potentially hold an array rather than a single filename. Part IV: Working with UserForms402 19_044018 ch12.qxp 2/28/07 6:40 PM Page 402 The Excel GetSaveAsFilename Method The GetSaveAsFilename method is very similar to the GetOpenFilename method. It dis- plays a Save As dialog box and lets the user select (or specify) a file. It returns a file- name and path but doesn’t take any action. Like the GetOpenFilename method, all of the GetSaveAsFilename method’s arguments are optional. The syntax for this method is: Application.GetSaveAsFilename(InitialFilename, FileFilter, FilterIndex, Title, ButtonText) The arguments are: • InitialFilename: Optional. Specifies the suggested filename. • FileFilter: Optional. A string specifying file-filtering criteria. • FilterIndex: Optional. The index number of the default file-filtering criteria. • Title: Optional. The title of the dialog box. • ButtonText: For Macintosh only. Prompting for a Directory If you need to get a filename, the simplest solution is to use the GetOpenFileName method, as I describe earlier. But if you need to get a directory name only (no file), you have two choices. • A Windows API function. This method is a bit more complicated but displays a commonly used (and familiar) dialog box. A limitation is that you can’t specify a default or starting directory. • Excel’s FileDialog object. This method is much easier to implement, and the dialog box resembles the standard Open dialog box. The FileDialog object was introduced in Excel 2002, so it won’t work with earlier versions of Excel. Both of these techniques are described in the sections that follow. CD-ROM The companion CD-ROM contains a workbook that demonstrates both of these meth- ods. The file is named get directory.xlsm. Chapter 12: Custom Dialog Box Alternatives 403 Part IV 19_044018 ch12.qxp 2/28/07 6:40 PM Page 403 Using a Windows API function to select a directory In this section, I present a function named GetDirectory that displays the dialog box shown in Figure 12-9 and returns a string that represents the selected directory. If the user clicks Cancel, the function returns an empty string. Figure 12-9: Use an API function to display this dialog box. The GetDirectory function takes one argument, which is optional. This argument is a string that will be displayed in the dialog box. If the argument is omitted, the dialog box displays Select a folder as the message. Following are the API declarations required at the beginning of the workbook module. This function also uses a custom data type, called BROWSEINFO. ‘32-bit API declarations Declare Function SHGetPathFromIDList Lib “shell32.dll” _ Alias “SHGetPathFromIDListA” (ByVal pidl As Long, ByVal _ pszPath As String) As Long Declare Function SHBrowseForFolder Lib “shell32.dll” _ Alias “SHBrowseForFolderA” (lpBrowseInfo As BROWSEINFO) _ As Long Public Type BROWSEINFO hOwner As Long pidlRoot As Long pszDisplayName As String lpszTitle As String ulFlags As Long Part IV: Working with UserForms404 19_044018 ch12.qxp 2/28/07 6:40 PM Page 404 lpfn As Long lParam As Long iImage As Long End Type The GetDirectory function follows: Function GetDirectory(Optional Msg) As String Dim bInfo As BROWSEINFO Dim path As String Dim r As Long, x As Long, pos As Integer ‘ Root folder = Desktop bInfo.pidlRoot = 0& ‘ Title in the dialog If IsMissing(Msg) Then bInfo.lpszTitle = “Select a folder.” Else bInfo.lpszTitle = Msg End If ‘ Type of directory to return bInfo.ulFlags = &H1 ‘ Display the dialog x = SHBrowseForFolder(bInfo) ‘ Parse the result path = Space$(512) r = SHGetPathFromIDList(ByVal x, ByVal path) If r Then pos = InStr(path, Chr$(0)) GetDirectory = Left(path, pos - 1) Else GetDirectory = “” End If End Function The simple procedure that follows demonstrates how to use the GetDirectory function in your code. Executing this procedure displays the dialog box. When the user clicks OK, the MsgBox function displays the full path of the selected directory. If the user clicks Cancel, the message box displays Canceled. Sub GetAFolder1() Dim Msg As String Dim UserFile As String Chapter 12: Custom Dialog Box Alternatives 405 Part IV 19_044018 ch12.qxp 2/28/07 6:40 PM Page 405 Msg = “Please select a location for the backup.” UserFile = GetDirectory(Msg) If UserFile = “” Then MsgBox “Canceled” Else MsgBox UserFile End If End Sub NOTE Unfortunately, there is no easy way to specify a default or starting directory. Using the FileDialog object to select a directory If users of your application all use Excel 2002 or later, you might prefer to use a much sim- pler technique that uses the FileDialog object. The following procedure displays a dialog box (see Figure 12-10) that allows the user to select a directory. The selected directory name (or Canceled) is then displayed by using the MsgBox function. Sub GetAFolder2() ‘ For Excel 2002 and later With Application.FileDialog(msoFileDialogFolderPicker) .InitialFileName = Application.DefaultFilePath & “\” .Title = “Please select a location for the backup” .Show If .SelectedItems.Count = 0 Then MsgBox “Canceled” Else MsgBox .SelectedItems(1) End If End With End Sub The FileDialog object lets you specify the starting directory by specifying a value for the InitialFileName property. In this case, the code uses Excel’s default file path as the starting directory. Part IV: Working with UserForms406 19_044018 ch12.qxp 2/28/07 6:40 PM Page 406 Figure 12-10: Using the FileDialog object to select a directory. Displaying Excel’s Built-In Dialog Boxes Code that you write in VBA can execute many of Excel’s Ribbon commands. And, if the command leads to a dialog box, your code can “make choices” in the dialog box (although the dialog box itself isn’t displayed). For example, the following VBA statement is equiva- lent to choosing the Home ➪ Editing ➪ Find & Select ➪ Go To command, specifying range A1:C3, and clicking OK. But the Go To dialog box never appears (which is what you want). Application.Goto Reference:=Range(“A1:C3”) In some cases, however, you may want to display one of Excel’s built-in dialog boxes so that the end user can make the choices. You can do this by accessing the Dialogs collec- tion of the Application object. About the Dialogs collection The Dialogs collection of the Application object consists of more than 200 members that represent most of Excel’s built-in dialog boxes. Each has a predefined constant to make it easy to specify the dialog box that you need. For example, Excel’s Go To dialog box is represented by the constant xlDialogFormulaGoto. Chapter 12: Custom Dialog Box Alternatives 407 Part IV 19_044018 ch12.qxp 2/28/07 6:40 PM Page 407 Use the Show method to actually display the dialog box. Here’s an example that displays the Go To dialog box (see Figure 12-11): Application.Dialogs(xlDialogFormulaGoto).Show Figure 12-11: This dialog box was displayed with a VBA statement. When the Go To dialog box is shown, the user can specify a named range or enter a cell address to go to. This dialog box is the one that appears when you choose Home ➪ Editing ➪ Find & Select ➪ Go To (or press F5). You can also write code to determine how the user dismissed the dialog box. Do this by using a variable. In the following statement, the Result variable will be True if the user clicks OK and False if the user clicks Cancel or presses Esc. Result = Application.Dialogs(xlDialogFormulaGoto).Show NOTE Contrary to what you might expect, the Result variable does not hold the range that was specified in the Go To dialog box. The statement below displays the Open dialog box (equivalent to selecting Office ➪ Open): Application.Dialogs(xlDialogOpen).Show Unfortunately, the Dialogs collection is poorly documented, and the newer dialog boxes aren’t even available. The next section describes a better way to display built-in dialog boxes — for Excel 2007 only. Executing Ribbon commands In previous versions of Excel, programmers created custom menus and toolbars by using the CommandBar object. In Excel 2007, the CommandBar object is still available, but it doesn’t work like it has in the past. Part IV: Working with UserForms408 19_044018 ch12.qxp 2/28/07 6:40 PM Page 408 CROSS-REFERENCE Refer to Chapter 22 for more information about the CommandBar object. The CommandBar object has also been enhanced in Excel 2007. You can use the CommandBar object to execute Ribbon commands using VBA. Many of the Ribbon commands display a dialog box. For example, the statement below displays the Go To dialog box: Application.CommandBars.ExecuteMso(“GoTo”) The ExecuteMso method accepts one argument, an idMso parameter that represents a Ribbon control. Unfortunately, these parameters are not listed in the Help system. CD-ROM The companion CD-ROM contains a file, ribbon control names.xlsx, that lists all of the Excel Ribbon command parameter names. Following is another example. This statement, when executed, displays the Font tab of the Format Cells dialog box (see Figure 12-12): Application.CommandBars.ExecuteMso(“FormatCellsFontDialog”) Figure 12-12: Using the ExecuteMso method to display a dialog box. Chapter 12: Custom Dialog Box Alternatives 409 Part IV 19_044018 ch12.qxp 2/28/07 6:40 PM Page 409 Displaying a Data Form Many people use Excel to manage lists in which the information is arranged in rows. Excel offers a simple way to work with this type of data through the use of a built-in data entry form that Excel can create automatically. This data form works with either a normal range of data or a range that has been designated as a table (by using the Insert ➪ Tables ➪ Table command). Figure 12-13 shows an example of a data form in use. Making the data form accessible For some reason, the command to access the data form is not in the Excel 2007 Ribbon. In order to access the data form from Excel’s user interface, you must add it to your Quick Access Toolbar (QAT): 1. Right-click the QAT and select Customize Quick Access Toolbar. This displays the Customization panel of the Excel Options dialog box. 2. In the Choose Commands From drop-down, select Commands Not in the Ribbon. 3. In the list box on the left, select Form. 4. Click the Add button to add the selected command to your QAT. 5. Click OK to close the Excel Options dialog box. After performing these steps, a new icon will appear on your QAT. Part IV: Working with UserForms410 Another technique to display a built-in dialog box requires knowledge of the pre-Excel 2007 toolbars (officially known as CommandBar objects). Although Excel 2007 no longer uses CommandBar objects, they are still supported for compatibility. The following statement, for example, is equivalent to selecting the Go To menu item on the Edit menu: Application.CommandBars(“Worksheet Menu Bar”). _ Controls(“Edit”).Controls(“Go To...”).Execute This statement, when executed, displays the Go To dialog box. Notice that the menu item captions must match exactly (including the ellipsis after Go To). Here’s another example. This statement displays the Format Cells dialog box: Application.CommandBars(“Worksheet Menu Bar”). _ Controls(“Format”).Controls(“Cells...”).Execute It’s probably not a good idea to rely on CommandBar objects because they may be removed from a future version of Excel. Executing an Old Menu Item Directly 19_044018 ch12.qxp 2/28/07 6:40 PM Page 410 Figure 12-13: Some users prefer to use Excel’s built-in data form for data-entry tasks. To use a data entry form, you must arrange your data so that Excel can recognize it as a table. Start by entering headings for the columns in the first row of your data entry range. Select any cell in the table and click the Form button on your QAT. Excel then displays a dialog box customized to your data. You can use the Tab key to move between the text boxes and supply information. If a cell contains a formula, the formula result appears as text (not as an edit box). In other words, you cannot modify formulas from the data entry form. When you complete the data form, click the New button. Excel enters the data into a row in the worksheet and clears the dialog box for the next row of data. Displaying a data form by using VBA Use the ShowDataForm method to display Excel’s data form. The only requirement is that the active cell must be within a range. The following code activates cell A1 (which is in a table) and then displays the data form: Sub DisplayDataForm() Range(“A1”).Select ActiveSheet.ShowDataForm End Sub CD-ROM A workbook with this example is available on the companion CD-ROM. The file is named data form example.xlsm. Chapter 12: Custom Dialog Box Alternatives 411 Part IV 19_044018 ch12.qxp 2/28/07 6:40 PM Page 411 19_044018 ch12.qxp 2/28/07 6:40 PM Page 412 Chapter Introducing UserForms In This Chapter Excel makes it relatively easy to create custom dialog boxes for your applica- tions. In fact, you can duplicate the look and feel of many of Excel’s dialog boxes. This chapter provides an introduction to and overview of UserForms. ◆ Creating, showing, and unloading UserForms ◆ A discussion of the UserForm controls available to you ◆ Setting the properties of UserForm controls ◆ Controlling UserForms with VBA procedures ◆ A hands-on example of creating a UserForm ◆ An introduction to the types of events relevant to UserForms and controls ◆ Customizing your control Toolbox ◆ A handy checklist for creating UserForms Excel developers have always had the ability to create custom dialog boxes for their applications. Beginning with Excel 97, things changed substantially — UserForms replaced the clunky old dialog sheets. You’ll find that UserForms are much easier to work with, and they offer many additional capabilities. Even though UserForms have not been upgraded over the years, you’ll find that this feature works well and is very flexible. 13 413 20_044018 ch13.qxp 2/28/07 6:41 PM Page 413 How Excel Handles Custom Dialog Boxes A custom dialog box is created on a UserForm, and you access UserForms in the Visual Basic Editor (VBE). Following is the typical sequence that you will follow when you create a UserForm: 1. Insert a new UserForm into your workbook’s VB Project. 2. Write a procedure that will display the UserForm. This procedure will be located in a VBA module (not in the code module for the UserForm). 3. Add controls to the UserForm. 4. Adjust some of the properties of the controls that you added. 5. Write event handler procedures for the controls. These procedures, which are located in the code window for the UserForm, are executed when various events (such as a button click) occur. Inserting a New UserForm To insert a new UserForm, activate the VBE (press Alt+F11), select your workbook’s pro- ject from the Project window, and then choose Insert ➪ UserForm. UserForms have default names like UserForm1, UserForm2, and so on. TIP You can change the name of a UserForm to make it easier to identify. Select the form and use the Properties window to change the Name property. (Press F4 if the Properties window is not displayed.) Figure 13-1 shows the Properties window when an empty UserForm is selected. A workbook can have any number of UserForms, and each UserForm holds a single custom dialog box. Part IV: Working with UserForms414 20_044018 ch13.qxp 2/28/07 6:41 PM Page 414 Figure 13-1: The Properties window for an empty UserForm. Adding Controls to a UserForm To add controls to a UserForm, use the Toolbox. (The VBE does not have menu commands that add controls.) If the Toolbox is not displayed, choose View ➪ Toolbox. Figure 13-2 shows the Toolbox. Figure 13-2: Use the Toolbox to add controls to a UserForm. Chapter 13: Introducing UserForms 415 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 415 Click the Toolbox button that corresponds to the control that you want to add and then click inside the dialog box to create the control (using its default size). Or you can click the control and then drag in the dialog box to specify the dimensions for the control. When you add a new control, it is assigned a name that combines the control type with the numeric sequence for that type of control. For example, if you add a CommandButton control to an empty UserForm, it is named CommandButton1. If you then add a second CommandButton control, it is named CommandButton2. TIP It’s a good idea to rename all the controls that you will be manipulating with your VBA code. Doing so lets you refer to meaningful names (such as ProductListBox) rather than generic names (such as ListBox1). To change the name of a control, use the Properties window in the VBE. Just select the object and change the Name property. Toolbox Controls In the sections that follow, I briefly describe the controls available to you in the Toolbox. CD-ROM Figure 13-3 shows a UserForm with one of each control. This workbook, named all userform controls.xlsm, is available on the companion CD-ROM. TIP Your UserForms can also use other ActiveX controls. See “Customizing the Toolbox,” later in this chapter. CheckBox A CheckBox control is useful for getting a binary choice: yes or no, true or false, on or off, and so on. When a CheckBox is checked, it has a value of True; when it’s not checked, the CheckBox value is False. ComboBox A ComboBox control presents a list of items in a drop-down box and displays only one item at a time. Unlike a ListBox control, a ComboBox can be set up to allow the user to enter a value that doesn’t appear in the list of items. Part IV: Working with UserForms416 20_044018 ch13.qxp 2/28/07 6:41 PM Page 416 Figure 13-3: This UserForm has one of each of the 15 controls. CommandButton Every dialog box that you create will probably have at least one CommandButton control. Usually, you’ll want to have a CommandButton labeled OK and another labeled Cancel. Frame A Frame control is used to enclose other controls. You do this either for aesthetic purposes or to logically group a set of controls. A frame is particularly useful when the dialog box contains more than one set of OptionButton controls. Image An Image control is used to display a graphic image, which can come from a file or can be pasted from the Clipboard. You might want to use an Image control to display your company’s logo in a dialog box. The graphics image is stored in the workbook. That way, if you distribute your workbook to someone else, you don’t have to include a copy of the graphics file. Chapter 13: Introducing UserForms 417 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 417 CAUTION Some graphics files are very large, and using such images can make your workbook increase dramatically in size. For best results, use graphics sparingly or use small graph- ics files. Label A Label control simply displays text in your dialog box. ListBox The ListBox control presents a list of items, and the user can select an item (or multiple items). ListBox controls are very flexible. For example, you can specify a worksheet range that holds the ListBox items, and this range can consist of multiple columns. Or you can fill the ListBox with items by using VBA. MultiPage A MultiPage control lets you create tabbed dialog boxes, like the Format Cells dialog box. By default, a MultiPage control has two pages, but you can add any number of addi- tional pages. OptionButton OptionButton controls are useful when the user needs to select one item from a small number of choices. OptionButtons are always used in groups of at least two. When one OptionButton is selected, the other OptionButtons in its group are deselected. If your UserForm contains more than one set of OptionButtons, the OptionButtons in each set must share a unique GroupName property value. Otherwise, all OptionButtons become part of the same set. Alternatively, you can enclose the OptionButtons in a Frame control, which automatically groups the OptionButtons contained in the frame. RefEdit The RefEdit control is used when you need to let the user select a range in a worksheet. ScrollBar The ScrollBar control is similar to a SpinButton control. The difference is that the user can drag the ScrollBar button to change the control’s value in larger increments. The ScrollBar control is most useful for selecting a value that extends across a wide range of possible values. Part IV: Working with UserForms418 20_044018 ch13.qxp 2/28/07 6:41 PM Page 418 SpinButton The SpinButton control lets the user select a value by clicking either of two arrows: one to increase the value and the other to decrease the value. A SpinButton is often used in conjunction with a TextBox control or Label control, which displays the current value of the SpinButton. TabStrip A TabStrip control is similar to a MultiPage control, but it’s not as easy to use. A TabStrip control, unlike a MultiPage control, does not serve as a container for other objects. Generally, you’ll find that the MultiPage control is much more versatile. TextBox A TextBox control lets the user input text. ToggleButton A ToggleButton control has two states: on and off. Clicking the button toggles between these two states, and the button changes its appearance. Its value is either True (pressed) or False (not pressed). I never use this control because I think a CheckBox is much clearer. Chapter 13: Introducing UserForms 419 Part IV Many of the UserForm controls can be embedded directly into a worksheet. These controls are accessible by using Excel’s Developer ➪ Controls ➪ Insert command. Adding such controls to a worksheet requires much less effort than creating a UserForm. In addition, you may not have to create any macros because you can link a control to a worksheet cell. For example, if you insert a CheckBox control on a worksheet, you can link it to a particular cell by setting its LinkedCell property. When the CheckBox is checked, the linked cell displays TRUE. When the CheckBox is unchecked, the linked cell displays FALSE. The accompanying figure shows a worksheet that contains some ActiveX controls. This workbook, named activex worksheet controls.xlsx, is available on the companion CD-ROM. The workbook uses linked cells and contains no macros. continued Using Controls on a Worksheet 20_044018 ch13.qxp 2/28/07 6:41 PM Page 419 Part IV: Working with UserForms420 continued Adding controls to a worksheet can be a bit confusing because controls can come from two sources: • Form controls: These controls are insertable objects. • ActiveX controls: These controls are a subset of those that are available for use on UserForms. You can use the controls from either of these sources, but it’s important that you understand the distinctions between them. The Form controls work much differently than the ActiveX controls. When you add an ActiveX control to a worksheet, Excel goes into design mode. In this mode, you can adjust the properties of any controls on your worksheet, add or edit event handler procedures for the control, or change its size or position. To display the Properties window for an ActiveX control, use the Developer ➪ Controls ➪ Properties command. For simple buttons, I often use the Button control from the Form controls because it lets me attach any macro to it. If I use a CommandButton control from the ActiveX controls, clicking it will execute its event handler procedure (for example, CommandButton1_Click) in the code module for the Sheet object — you can’t attach just any macro to it. When Excel is in design mode, you can’t try out the controls. To test the controls, you must exit design mode by clicking the Developer ➪ Controls ➪ Design mode button (which is a toggle). 20_044018 ch13.qxp 2/28/07 6:41 PM Page 420 Adjusting UserForm Controls After a control is placed in a UserForm, you can move and resize the control by using stan- dard mouse techniques. TIP You can select multiple controls by Shift-clicking or by clicking and dragging to lasso a group of controls. A UserForm can contain vertical and horizontal grid lines (displayed as dots) that help you align the controls that you add. When you add or move a control, it snaps to the grid to help you line up the controls. If you don’t like to see these grid lines, you can turn them off by choosing Tools ➪ Options in the VBE. In the Options dialog box, select the General tab and set your desired options in the Form Grid Settings section. The Format menu in the VBE window provides several commands to help you precisely align and space the controls in a dialog box. Before you use these commands, select the controls that you want to work with. These commands work just as you would expect, so I don’t explain them here. Figure 13-4 shows a dialog box with several OptionButton con- trols about to be aligned. Figure 13-5 shows the controls after being aligned and assigned equal vertical spacing. Figure 13-4: Use the Format ➪ Align command to change the alignment of controls. Chapter 13: Introducing UserForms 421 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 421 Figure 13-5: Six controls, aligned and evenly spaced. TIP When you select multiple controls, the last control that you select appears with white handles rather than the normal black handles. The control with the white handles is used as the basis for sizing or positioning. Adjusting a Control’s Properties Every control has a number of properties that determine how the control looks and behaves. You can change a control’s properties, as follows: • At design time when you’re developing the UserForm. You use the Properties window for this. • During runtime when the UserForm is being displayed for the user. You use VBA instruc- tions to change a control’s properties at runtime. Using the Properties window In the VBE, the Properties window adjusts to display the properties of the selected item (which can be a control or the UserForm itself). In addition, you can select a control from the drop-down list at the top of the Properties window (see Figure 13-6). Part IV: Working with UserForms422 20_044018 ch13.qxp 2/28/07 6:41 PM Page 422 Figure 13-6: Selecting a control (CheckBox3) from the drop-down list at the top of the Properties window. NOTE The Properties window has two tabs. The Alphabetic tab displays the properties for the selected object in alphabetical order. The Categorized tab displays them grouped into logical categories. Both tabs contain the same properties but in a different order. To change a property, just click it and specify the new property. Some properties can take on a finite number of values, selectable from a list. If so, the Properties window will dis- play a button with a downward-pointing arrow. Click the button, and you’ll be able to select the property’s value from the list. For example, the TextAlign property can have any of the following values: 1 - fmTextAlignLeft, 2 - fmTextAlignCenter, or 3 - fmTextAlignRight. A few properties (for example, Font and Picture) display a small button with an ellipsis when selected. Click the button to display a dialog box associated with the property. The Image control Picture property is worth mentioning because you can either select a graphic file that contains the image or paste an image from the Clipboard. When pasting an image, first copy it to the Clipboard; then select the Picture property for the Image con- trol and press Ctrl+V to paste the Clipboard contents. NOTE If you select two or more controls at once, the Properties window displays only the prop- erties that are common to the selected controls. Chapter 13: Introducing UserForms 423 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 423 TIP The UserForm itself has many properties that you can adjust. These properties are then used as defaults for controls that you add to the UserForm. For example, if you change the UserForm Font property, all controls added to the UserForm will use that font. Common properties Although each control has its own unique set of properties, many controls have some com- mon properties. For example, every control has a Name property and properties that deter- mine its size and position (Height, Width, Left, and Right). If you’re going to manipulate a control by using VBA, it’s an excellent idea to provide a meaningful name for the control. For example, the first OptionButton that you add to a UserForm has a default name of OptionButton1. You refer to this object in your code with a statement such as the following: OptionButton1.Value = True But if you give the OptionButton a more meaningful name (such as obLandscape), you can use a statement such as this one: obLandscape.Value = True TIP Many people find it helpful to use a name that also identifies the type of object. In the preceding example, I use ob as the prefix to identify the fact that this control is an OptionButton. You can adjust the properties of several controls at once. For example, you might have sev- eral OptionButtons that you want left-aligned. You can simply select all the OptionButtons and then change the Left property in the Properties box. All the selected controls will then take on that new Left property value. Learning more about properties The best way to learn about the various properties for a control is to use the Help system. Simply click on a property in the Property window and press F1. Figure 13-7 shows an example of the type of help provided for a property. Accommodating keyboard users Many users prefer to navigate through a dialog box by using the keyboard: The Tab and Shift+Tab keystrokes cycle through the controls, and pressing a hot key (an underlined letter) operates the control. To make sure that your dialog box works properly for keyboard users, you must be mindful of two issues: tab order and accelerator keys. Part IV: Working with UserForms424 20_044018 ch13.qxp 2/28/07 6:41 PM Page 424 Figure 13-7: The Help system provides information about each property for every control. CHANGING THE TAB ORDER OF CONTROLS The tab order determines the sequence in which the controls are activated when the user presses Tab or Shift+Tab. It also determines which control has the initial focus. If a user is entering text into a TextBox control, for example, the TextBox has the focus. If the user clicks an OptionButton, the OptionButton has the focus. The control that’s first in the tab order has the focus when a dialog box is first displayed. To set the tab order of your controls, choose View ➪ Tab Order. You can also right-click the UserForm and choose Tab Order from the shortcut menu. In either case, Excel displays the Tab Order dialog box, as shown in Figure 13-8. The Tab Order dialog box lists all the con- trols, the sequence of which corresponds to the order in which controls pass the focus between each other in the UserForm. To move a control, select it and click the arrow keys up or down. You can choose more than one control (click while pressing Shift or Ctrl) and move them all at once. Alternatively, you can set an individual control’s position in the tab order via the Properties window. The first control in the tab order has a TabIndex property of 0. Changing the TabIndex property for a control may also affect the TabIndex property of other controls. These adjustments are made automatically to ensure that no control has a TabIndex greater than the number of controls. If you want to remove a control from the tab order, set its TabStop property to False. Chapter 13: Introducing UserForms 425 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 425 Figure 13-8: Use the Tab Order dialog box to specify the tab order of the controls. NOTE Some controls, such as Frame and MultiPage, act as containers for other controls. The controls inside a container have their own tab order. To set the tab order for a group of OptionButtons inside a Frame control, select the Frame control before you choose the View ➪ Tab Order command. SETTING HOT KEYS You can assign an accelerator key, or hot key, to most dialog box controls. This allows the user to access the control by pressing Alt+ the hot key. Use the Accelerator property in the Properties window for this purpose. TIP Some controls, such as a TextBox, don’t have an Accelerator property because they don’t display a caption. You still can allow direct keyboard access to these controls by using a Label control. Assign an accelerator key to the Label and put it ahead of the TextBox in the tab order. Part IV: Working with UserForms426 20_044018 ch13.qxp 2/28/07 6:41 PM Page 426 Displaying and Closing UserForms In this section, I provide an overview of using VBA to work with UserForms. Displaying a UserForm To display a UserForm from VBA, you create a procedure that uses the Show method of the UserForm object. You cannot display a UserForm from Excel without using at least one line of VBA code. If your UserForm is named UserForm1, the following procedure displays the dialog box on that form: Sub ShowForm() UserForm1.Show End Sub This procedure must be located in a standard VBA module and not in the code module for the UserForm. When the UserForm is displayed, it remains visible onscreen until it is dismissed. Usually, you’ll add a CommandButton to the UserForm that executes a procedure that dismisses the UserForm. The procedure can either unload the UserForm (with the Unload command) or hide the UserForm (with the Hide method of the UserForm object). This concept will become clearer as you work through various examples in this and subsequent chapters. DISPLAYING A MODELESS USERFORM By default, UserForms are displayed modally. This means that the UserForm must be dis- missed before the user can do anything in the worksheet. You can also display a modeless UserForm. When a modeless UserForm is displayed, the user can continue working in Excel, and the UserForm remains visible. To display a modeless UserForm, use the follow- ing syntax: UserForm1.Show vbModeless Chapter 13: Introducing UserForms 427 Part IV You’ll usually want to test your UserForm while you’re developing it. There are three ways that you can test a UserForm without actually calling it from a VBA procedure: • Choose the Run ➪ Run Sub/UserForm command. • Press F5. • Click the Run Sub/UserForm button on the Standard toolbar. These three techniques all trigger the UserForm’s Initialize event. When a dialog box is displayed in this test mode, you can try out the tab order and the accelerator keys. Testing a UserForm 20_044018 ch13.qxp 2/28/07 6:41 PM Page 427 DISPLAYING A USERFORM BASED ON A VARIABLE In some cases, you may have several UserForms, and your code makes a decision regarding which of them to display. If the name of the UserForm is stored as a string variable, you can use the Add method to add the UserForm to the UserForms collection and then use the Show method of the UserForms collection. Here’s an example that assigns the name of a UserForm to the MyForm variable and then displays the UserForm. MyForm = “UserForm1” UserForms.Add(MyForm).Show LOADING A USERFORM VBA also has a Load statement. Loading a UserForm loads it into memory, but it is not vis- ible until you use the Show method. To load a UserForm, use a statement like this: Load UserForm1 If you have a complex UserForm, you might want to load it into memory before it is needed so that it will appear more quickly when you use the Show method. In the majority of situa- tions, however, it’s not necessary to use the Load statement. Closing a UserForm To close a UserForm, use the Unload command, as shown in this example: Unload UserForm1 Or, if the code is located in the code module for the UserForm, you can use the following: Unload Me In this case, the keyword Me refers to the UserForm. Using Me rather than the UserForm’s name eliminates the need to modify your code if you change the name of the UserForm. Normally, your VBA code should include the Unload command after the UserForm has per- formed its actions. For example, your UserForm may have a CommandButton that serves as an OK button. Clicking this button executes a macro. One of the statements in the macro will unload the UserForm. The UserForm remains visible on the screen until the macro that contains the Unload statement finishes. When a UserForm is unloaded, its controls are reset to their original values. In other words, your code will not be able to access the user’s choices after the UserForm is unloaded. If the user’s choice must be used later on (after the UserForm is unloaded), you need to store the value in a Public variable, declared in a standard VBA module. Or you could store the value in a worksheet cell. Part IV: Working with UserForms428 20_044018 ch13.qxp 2/28/07 6:41 PM Page 428 NOTE A UserForm is automatically unloaded when the user clicks the Close button (the X in the UserForm title bar). This action also triggers a UserForm QueryClose event, followed by a UserForm Terminate event. UserForms also have a Hide method. When you invoke this method, the UserForm disap- pears, but it remains loaded in memory, so your code can still access the various properties of the controls. Here’s an example of a statement that hides a UserForm: UserForm1.Hide Or, if the code is in the code module for the UserForm, you can use the following: Me.Hide If for some reason you would like your UserForm to disappear immediately while its macro is executing, use the Hide method at the top of the procedure. For example, in the follow- ing procedure, the UserForm disappears immediately when CommandButton1 is clicked. The last statement in the procedure unloads the UserForm. Private Sub CommandButton1_Click() Me.Hide For r = 1 To 10000 Cells(r, 1) = r Next r Unload Me End Sub CROSS-REFERENCE In Chapter 15, I describe how to display a progress indicator, which takes advantage of the fact that a UserForm remains visible while the macro executes. About event handler procedures After the UserForm is displayed, the user interacts with it — selecting an item from a ListBox, clicking a CommandButton, and so on. In official terminology, the user causes an event to occur. For example, clicking a CommandButton causes the Click event for the CommandButton. You need to write procedures that execute when these events occur. These procedures are sometimes known as event handler procedures. NOTE Event handler procedures must be located in the Code window for the UserForm. However, your event handler procedure can call another procedure that’s located in a standard VBA module. Chapter 13: Introducing UserForms 429 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 429 Your VBA code can change the properties of the controls while the UserForm is displayed (that is, at runtime). For example, you could assign to a ListBox control a procedure that changes the text in a Label when an item is selected. This type of manipulation will become clearer later in this chapter. Creating a UserForm: An Example If you’ve never created a UserForm, you might want to walk through the example in this section. The example includes step-by-step instructions for creating a simple dialog box and developing a VBA procedure to support the dialog box. This example uses a UserForm to obtain two pieces of information: a person’s name and sex. The dialog box uses a TextBox control to get the name and three OptionButtons to get the sex (Male, Female, or Unknown). The information collected in the dialog box is then sent to the next blank row in a worksheet. Creating the UserForm Figure 13-9 shows the finished UserForm for this example. For best results, start with a new workbook with only one worksheet in it. Then follow these steps: 1. Press Alt+F11 to activate the VBE. 2. In the Project window, select the workbook’s project and choose Insert ➪ UserForm to add an empty UserForm. Figure 13-9: This dialog box asks the user to enter a name and a sex. Part IV: Working with UserForms430 20_044018 ch13.qxp 2/28/07 6:41 PM Page 430 3. The UserForm’s Caption property will have its default value: UserForm1. Use the Properties window to change the UserForm’s Caption property to Get Name and Sex. (If the Properties window isn’t visible, press F4.) 4. Add a Label control and adjust the properties as follows: Property Value Accelerator N Caption Name: TabIndex 0 5. Add a TextBox control and adjust the properties as follows: Property Value Name TextName TabIndex 1 6. Add a Frame control and adjust the properties as follows: Property Value Caption Sex TabIndex 2 7. Add an OptionButton control inside the Frame and adjust the properties as follows: Property Value Accelerator M Caption Male Name OptionMale TabIndex 0 Chapter 13: Introducing UserForms 431 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 431 8. Add another OptionButton control inside the Frame and adjust the properties as follows: Property Value Accelerator F Caption Female Name OptionFemale TabIndex 1 9. Add yet another OptionButton control inside the Frame and adjust the properties as follows: Property Value Accelerator U Caption Unknown Name OptionUnknown TabIndex 2 Value True 10. Add a CommandButton control outside the Frame and adjust the properties as follows: Property Value Caption OK Default True Name OKButton TabIndex 3 Part IV: Working with UserForms432 20_044018 ch13.qxp 2/28/07 6:41 PM Page 432 11. Add another CommandButton control and adjust the properties as follow s: Property Value Caption Close Cancel True Name CloseButton TabIndex 4 TIP When you are creating several controls that are similar, you may find it easier to copy an existing control rather than create a new one. To copy a control, press Ctrl while you drag the control to make a new copy of it. Then adjust the properties on the copied control. Writing code to display the dialog box Next, you add an ActiveX CommandButton to the worksheet. This button will execute a procedure that displays the UserForm. Here’s how: 1. Activate Excel. (Alt+F11 is the shortcut key combination.) 2. Choose Developer ➪ Controls ➪ Insert and click CommandButton from the ActiveX Controls section. 3. Drag in the worksheet to create the button. If you like, you can change the caption for the worksheet CommandButton. To do so, right-click the button and choose CommandButton Object ➪ Edit from the shortcut menu. You can then edit the text that appears on the CommandButton. To change other properties of the object, right-click and choose Properties. Then make the changes in the Properties box. 4. Double-click the CommandButton. This activates the VBE. More specifically, the code module for the worksheet will be dis- played, with an empty event handler procedure for the worksheet’s CommandButton. 5. Enter a single statement in the CommandButton1_Click procedure (see Figure 13-10). This short procedure uses the Show method of an object (UserForm1) to display the UserForm. Chapter 13: Introducing UserForms 433 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 433 Figure 13-10: The CommandButton1_Click procedure is executed when the button on the worksheet is clicked. Testing the dialog box The next step is to try out the procedure that displays the dialog box. NOTE When you click the CommandButton on the worksheet, you’ll find that nothing happens. Rather, the button is selected. That’s because Excel is still in design mode — which hap- pens automatically when you insert an ActiveX control. To exit design mode, click the Developer ➪ Controls ➪ Design Mode button. To make any changes to your CommandButton, you’ll need to put Excel back into design mode. When you exit design mode, clicking the button will display the UserForm (see Figure 13-11). When the dialog box is displayed, enter some text into the text box and click OK. Nothing happens — which is understandable considering you haven’t yet created any event handler procedures for the UserForm. NOTE Click the Close button in the UserForm title bar to get rid of the dialog box. Part IV: Working with UserForms434 20_044018 ch13.qxp 2/28/07 6:41 PM Page 434 Figure 13-11: The CommandButton’s Click event procedure displays the UserForm. Adding event handler procedures In this section, I explain how to write the procedures that will handle the events that occur when the UserForm is displayed. To continue the example, do the following: 1. Press Alt+F11 to activate the VBE. 2. Make sure the UserForm is displayed and double-click its Close button. This will activate the Code window for the UserForm and insert an empty procedure named CloseButton_Click. Notice that this procedure consists of the object’s name, an underscore character, and the event that it handles. 3. Modify the procedure as follows. (This is the event handler for the CloseButton’s Click event.) Private Sub CloseButton_Click() Unload UserForm1 End Sub This procedure, which is executed when the user clicks the Close button, simply unloads the UserForm. 4. Press Shift+F7 to redisplay UserForm1 (or click the View Object icon at the top of the Project Explorer window). 5. Double-click the OK button and enter the following procedure. (This is the event handler for the OKButton’s Click event.) Private Sub OKButton_Click() Dim NextRow As Long ‘ Make sure Sheet1 is active Chapter 13: Introducing UserForms 435 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 435 Sheets(“Sheet1”).Activate ‘ Determine the next empty row NextRow = _ Application.WorksheetFunction.CountA(Range(“A:A”)) + 1 ‘ Transfer the name Cells(NextRow, 1) = TextName.Text ‘ Transfer the sex If OptionMale Then Cells(NextRow, 2) = “Male” If OptionFemale Then Cells(NextRow, 2) = “Female” If OptionUnknown Then Cells(NextRow, 2) = “Unknown” ‘ Clear the controls for the next entry TextName.Text = “” OptionUnknown = True TextName.SetFocus End Sub 6. Activate Excel and click the CommandButton again to display the UserForm. Run the procedure again. You’ll find that the UserForm controls now function correctly. Here’s how the OKButton_Click procedure works: First, the procedure makes sure that the proper worksheet (Sheet1) is active. It then uses Excel’s COUNTA function to deter- mine the next blank cell in column A. Next, it transfers the text from the TextBox control to column A. It then uses a series of If statements to determine which OptionButton was selected and writes the appropriate text (Male, Female, or Unknown) to column B. Finally, the dialog box is reset to make it ready for the next entry. Notice that clicking OK doesn’t close the dialog box. To end data entry (and unload the UserForm), click the Close button. Validating the data Play around with this example some more, and you’ll find that it has a small problem: It doesn’t ensure that the user actually enters a name into the text box. To make sure the user enters a name, insert the following code in the OKButton_Click procedure, before the text is transferred to the worksheet. It ensures that the user enters a name (well, at least some text) in the TextBox. If the TextBox is empty, a message appears, and the focus is set to the TextBox so that the user can try again. The Exit Sub statement ends the pro- cedure with no further action. ‘ Make sure a name is entered If TextName.Text = “” Then MsgBox “You must enter a name.” TextName.SetFocus Exit Sub End If Part IV: Working with UserForms436 20_044018 ch13.qxp 2/28/07 6:41 PM Page 436 The finished dialog box After making all these modifications, you’ll find that the dialog box works flawlessly. (Don’t forget to test the hot keys.) In real life, you’d probably need to collect more informa- tion than just name and sex. However, the same basic principles apply. You just need to deal with more UserForm controls. CD-ROM A workbook with this example is available on the companion CD-ROM in a file named get name and sex.xlsm. Understanding UserForm Events Each UserForm control (as well as the UserForm itself) is designed to respond to certain types of events, and these events can be triggered by a user or by Excel. For example, clicking a CommandButton generates a Click event for the CommandButton. You can write code that is executed when a particular event occurs. Some actions generate multiple events. For example, clicking the upward arrow of a SpinButton control generates a SpinUp event and also a Change event. When a UserForm is displayed by using the Show method, Excel generates an Initialize event and an Activate event for the UserForm. (Actually, the Initialize event occurs when the UserForm is loaded into memory and before it is actually displayed.) CROSS-REFERENCE Excel also supports events associated with a Sheet object, Chart objects, and the ThisWorkbook object. I discuss these types of events in Chapter 18. Learning about events To find out which events are supported by a particular control, do the following: 1. Add a control to a UserForm. 2. Double-click the control to activate the code module for the UserForm. The VBE will insert an empty event handler procedure for the default event for the control. 3. Click the drop-down list in the upper-right corner of the module window, and you’ll see a complete list of events for the control (see Figure 13-12). 4. Select an event from the list, and the VBE will create an empty event handler procedure for you. Chapter 13: Introducing UserForms 437 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 437 Figure 13-12: The event list for a CheckBox control. NOTE To find out specific details about an event, consult the Help system. The Help system also lists the events available for each control. When you locate an event for an object, make sure the Help system table of contents is displayed. Then you can see a list of all other events for the object. CAUTION Event handler procedures incorporate the name of the object in the procedure’s name. Therefore, if you change the name of a control, you’ll also need to make the appropriate changes to the control’s event handler procedure(s). The name changes are not per- formed automatically! To make things easy on yourself, it’s a good idea to provide names for your controls before you begin creating event handler procedures. UserForm events Several events are associated with showing and unloading a UserForm: • Initialize: Occurs before a UserForm is loaded or shown but does not occur if the UserForm was previously hidden. • Activate: Occurs when a UserForm is shown. • Deactivate: Occurs when a UserForm is deactivated but does not occur if the form is hidden. Part IV: Working with UserForms438 20_044018 ch13.qxp 2/28/07 6:41 PM Page 438 • QueryClose: Occurs before a UserForm is unloaded. • Terminate: Occurs after the UserForm is unloaded. NOTE Often, it’s critical that you choose the appropriate event for your event handler proce- dure and that you understand the order in which the events occur. Using the Show method invokes the Initialize and Activate events (in that order). Using the Load command invokes only the Initialize event. Using the Unload command triggers the QueryClose and Terminate events (in that order). Using the Hide method doesn’t trigger either of these events. CD-ROM The companion CD-ROM contains a workbook (named userform events.xlsm) that monitors all these events and displays a message box when an event occurs. If you’re con- fused about UserForm events, studying the code in this example should clear things up. SpinButton events To help clarify the concept of events, this section takes a close look at the events associ- ated with a SpinButton control. CD-ROM The companion CD-ROM contains a workbook that demonstrates the sequence of events that occur for a SpinButton and the UserForm that contains it. The workbook, named spinbutton events.xlsm, contains a series of event-handler routines — one for each SpinButton and UserForm event. Each of these routines simply displays a mes- sage box that tells you the event that just fired. Table 13-1 lists all the events for the SpinButton control. TABLE 13-1 SPINBUTTON EVENTS Event Description AfterUpdate Occurs after the control is changed through the user interface. BeforeDragOver Occurs when a drag-and-drop operation is in progress. BeforeDropOrPaste Occurs when the user is about to drop or paste data onto the control. continued Chapter 13: Introducing UserForms 439 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 439 TABLE 13-1 SPINBUTTON EVENTS (continued) Event Description BeforeUpdate Occurs before the control is changed. Change Occurs when the Value property changes. Enter Occurs before the control actually receives the focus from a control on the same UserForm. Error Occurs when the control detects an error and cannot return the error information to a calling program. Exit Occurs immediately before a control loses the focus to another control on the same form. KeyDown Occurs when the user presses a key and the object has the focus. KeyPress Occurs when the user presses any key that produces a typeable character. KeyUp Occurs when the user releases a key and the object has the focus. SpinDown Occurs when the user clicks the lower (or left) SpinButton arrow. SpinUp Occurs when the user clicks the upper (or right) SpinButton arrow. A user can operate a SpinButton control by clicking it with the mouse or (if the control has the focus) by using the up-arrow and down-arrow keys. MOUSE-INITIATED EVENTS When the user clicks the upper SpinButton arrow, the following events occur in this pre- cise order: 1. Enter (triggered only if the SpinButton did not already have the focus) 2. Change 3. SpinUp KEYBOARD-INITIATED EVENTS The user can also press Tab to set the focus to the SpinButton and then use the arrow keys to increment or decrement the control. If so, the following events occur (in this order): 1. Enter 2. KeyDown Part IV: Working with UserForms440 20_044018 ch13.qxp 2/28/07 6:41 PM Page 440 3. Change 4. SpinUp (or SpinDown) 5. KeyUp WHAT ABOUT CHANGES VIA CODE? The SpinButton control can also be changed by VBA code — which also triggers the appropriate event(s). For example, the following statement sets the SpinButton1 Value property to 0 and also triggers the Change event for the SpinButton control — but only if the SpinButton value was not already 0: SpinButton1.Value = 0 You might think that you could disable events by setting the EnableEvents property of the Application object to False. Unfortunately, this property applies only to events that involve true Excel objects: Workbooks, Worksheets, and Charts. Pairing a SpinButton with a TextBox A SpinButton has a Value property, but this control doesn’t have a caption in which to dis- play its value. In many cases, however, you will want the user to see the SpinButton value. And sometimes you’ll want the user to be able to change the SpinButton value directly instead of clicking the SpinButton repeatedly. The solution is to pair a SpinButton with a TextBox, which enables the user to specify a value either by typing it into the TextBox directly or by clicking the SpinButton to incre- ment or decrement the value in the TextBox. Figure 13-13 shows a simple example. The SpinButton’s Min property is 1, and its Max property is 100. Therefore, clicking the SpinButton’s arrows will change its value to an integer between 1 and 100. Figure 13-13: This SpinButton is paired with a TextBox. CD-ROM This workbook is available on the companion CD-ROM. The file is named spinbutton and textbox.xlsm. Chapter 13: Introducing UserForms 441 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 441 The code required to link a SpinButton with a TextBox is relatively simple. It’s basically a matter of writing event handler procedures to ensure that the SpinButton’s Value property is always in sync with the TextBox’s Text property. The following procedure is executed whenever the SpinButton’s Change event is triggered. That is, the procedure is executed when the user clicks the SpinButton or changes its value by pressing the up arrow or down arrow. Private Sub SpinButton1_Change() TextBox1.Text = SpinButton1.Value End Sub The procedure simply assigns the SpinButton’s Value to the Text property of the TextBox control. Here, the controls have their default names (SpinButton1 and TextBox1). If the user enters a value directly into the TextBox, its Change event is triggered, and the fol- lowing procedure is executed: Private Sub TextBox1_Change() NewVal = Val(TextBox1.Text) If NewVal >= SpinButton1.Min And _ NewVal <= SpinButton1.Max Then _ SpinButton1.Value = NewVal End Sub This procedure starts by using VBA’s Val function to convert the text in the TextBox to a value. (If the TextBox contains non-numeric text, the Val function returns 0.) The next statement determines whether the value is within the proper range for the SpinButton. If so, the SpinButton’s Value property is set to the value entered in the TextBox. The example is set up so that clicking the OK button (which is named OKButton) transfers the SpinButton’s value to the active cell. The event handler for this CommandButton’s Click event is as follows: Private Sub OKButton_Click() ‘ Enter the value into the active cell If CStr(SpinButton1.Value) = TextBox1.Text Then ActiveCell = SpinButton1.Value Unload Me Else MsgBox “Invalid entry.”, vbCritical TextBox1.SetFocus TextBox1.SelStart = 0 TextBox1.SelLength = Len(TextBox1.Text) End If End Sub Part IV: Working with UserForms442 20_044018 ch13.qxp 2/28/07 6:41 PM Page 442 This procedure does one final check: It makes sure that the text entered in the TextBox matches the SpinButton’s value. This is necessary in the case of an invalid entry. For example, if the user enters 3r into the TextBox, the SpinButton’s value would not be changed, and the result placed in the active cell would not be what the user intended. Notice that the SpinButton’s Value property is converted to a string by using the CStr function. This ensures that the comparison will not generate an error if a value is com- pared with text. If the SpinButton’s value does not match the TextBox’s contents, a mes- sage box is displayed. Notice that the focus is set to the TextBox object, and the contents are selected (by using the SelStart and SelLength properties). This makes it very easy for the user to correct the entry. Chapter 13: Introducing UserForms 443 Part IV Every UserForm and control has a Tag property. This property doesn’t represent anything specific, and, by default, is empty. You can use the Tag property to store information for your own use. For example, you might have a series of TextBox controls in a UserForm. The user may be required to enter text into some but not all of them. You can use the Tag property to identify (for your own use) which fields are required. In this case, you can set the Tag property to a string such as Required. Then when you write code to validate the user’s entries, you can refer to the Tag property. The following example is a function that examines all TextBox controls on UserForm1 and returns the number of required TextBox controls that are empty: Function EmptyCount() Dim ctl As Control EmptyCount= 0 For Each ctl In UserForm1.Controls If TypeName(ctl) = “TextBox” Then If ctl.Tag = “Required” Then If ctl.Text = “” Then EmptyCount = EmptyCount + 1 End If End If End If Next ctl End Function As you work with UserForms, you will probably think of other uses for the Tag property. About the Tag Property 20_044018 ch13.qxp 2/28/07 6:41 PM Page 443 Referencing UserForm Controls When working with controls on a UserForm, the VBA code is usually contained in the code window for the UserForm. You can also refer to UserForm controls from a general VBA module. To do so, you need to qualify the reference to the control by specifying the UserForm name. For example, consider the following procedure, which is located in a VBA module. It simply displays the UserForm named UserForm1. Sub GetData() UserForm1.Show End Sub Assume that UserForm1 contains a text box (named TextBox1), and you want to provide a default value for the text box. You could modify the procedure as follows: Sub GetData() UserForm1.TextBox1.Value = “John Doe” UserForm1.Show End Sub Part IV: Working with UserForms444 The controls on a UserForm make up a collection. For example, the following statement displays the number of controls on UserForm1: MsgBox UserForm1.Controls.Count VBA does not maintain a collection of each control type. For example, there is no collection of CommandButton controls. However, you can determine the type of control by using the TypeName function. The following procedure uses a For Each structure to loop through the Controls collection and then displays the number of CommandButton controls on UserForm1: Sub CountButtons() Dim cbCount As Integer Dim ctl as Control cbCount = 0 For Each ctl In UserForm1.Controls If TypeName(ctl) = “CommandButton” Then _ cbCount = cbCount + 1 Next ctl MsgBox cbCount End Sub Understanding the Controls Collection 20_044018 ch13.qxp 2/28/07 6:41 PM Page 444 Another way to set the default value is to take advantage of the UserForm’s Initialize event. You can write code in the UserForm_Initialize procedure, which is located in the code module for the UserForm. Here’s an example: Private Sub UserForm_Initialize() TextBox1.Value = “John Doe” End Sub Notice that when the control is referenced in the code module for the UserForm, you don’t need to qualify the references with the UserForm name. However, qualifying references to controls does have an advantage: You will then be able to take advantage of the Auto List Members feature, which lets you choose the control names from a drop-down list. TIP Rather than use the actual name of the UserForm, it is preferable to use Me. Then, if you change the name of the UserForm, you won’t need to replace the references in your code. Customizing the Toolbox When a UserForm is active in the VBE, the Toolbox displays the controls that you can add to the UserForm. This section describes ways to customize the Toolbox. Changing icons or tip text If you would prefer a different icon or different tip text for a particular tool, right-click the toolbox control and select Customize xxx from the shortcut menu (where xxx is the con- trol’s name). This brings up a new dialog box that lets you change the ToolTip text, edit the icon, or load a new icon image from a file. Adding new pages The Toolbox initially contains a single tab. Right-click this tab and select New Page to add a new tab to the Toolbox. You can also change the text displayed on the tab by selecting Rename from the shortcut menu. Customizing or combining controls A very handy feature lets you customize a control and then save it for future use. You can, for instance, create a CommandButton control that’s set up to serve as an OK button. For example, you can set the following properties: Width, Height, Caption, Default, and Name. Then drag the customized CommandButton to the Toolbox. This will create a new control. Right-click the new control to rename it or change its icon. Chapter 13: Introducing UserForms 445 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 445 You can also create a new Toolbox entry that consists of multiple controls. For example, you can create two CommandButtons that represent a UserForm’s OK and Cancel buttons. Customize them as you want and then select them both and drag them to the Toolbox. In this case, you can use this new Toolbox control to add two customized buttons in one fell swoop. This also works with controls that act as containers. For example, create a Frame control and add four customized OptionButtons, neatly spaced and aligned. Then drag the Frame to the Toolbox to create a customized Frame control. TIP You might want to place your customized controls on a separate page in the Toolbox. This lets you export the entire page so you can share it with other Excel users. To export a Toolbox page, right-click the tab and select Export Page. CD-ROM The companion CD-ROM contains a PAG file (named newcontrols.pag) that contains some customized controls. You can import this file as a new page in your Toolbox. Right- click a tab and select Import Page. Then locate the PAG file. Your Toolbox will resemble Figure 13-14. The new controls are: an exclamation point image, a “critical” red x image, a question mark image, an information image, an OK and Cancel button, a Frame with four OptionButton controls, a TextBox and a Spinner, and six CheckBox controls. Figure 13-14: The Toolbox, with a new page of controls. Part IV: Working with UserForms446 20_044018 ch13.qxp 2/28/07 6:41 PM Page 446 Adding new ActiveX controls UserForms can use other ActiveX controls developed by Microsoft or other vendors. To add an additional ActiveX control to the Toolbox, right-click the Toolbox and select Additional Controls. This will display the dialog box shown in Figure 13-15. Figure 13-15: The Additional Controls dialog box lets you add other ActiveX controls. The Additional Controls dialog box lists all ActiveX controls that are installed on your sys- tem. Select the control(s) that you want to add and then click OK to add an icon for each selected control. CAUTION Not all ActiveX controls that are installed on your system will work in Excel UserForms. In fact, most of them probably won’t work. Also, some controls require a license in order to use them in an application. If you (or the users of your application) aren’t licensed to use a particular control, an error will occur. Creating UserForm Templates You might find that when you design a new UserForm, you tend to add the same controls each time. For example, every UserForm might have two CommandButtons that serve as OK and Cancel buttons. In the previous section, I describe how to create a new control that combines these two (customized) buttons into a single control. Another option is to create your UserForm template and then export it so it can be imported into other projects. An advantage is that the event handler code for the controls is stored with the template. Start by creating a UserForm that contains all the controls and customizations that you would need to reuse in other projects. Then make sure the UserForm is selected and choose File ➪ Export File (or press Ctrl+E). You’ll be prompted for a filename. Then, when you start your next project, choose File ➪ Import File to load the saved UserForm. Chapter 13: Introducing UserForms 447 Part IV 20_044018 ch13.qxp 2/28/07 6:41 PM Page 447 A UserForm Checklist Before you unleash a UserForm on end users, be sure that everything is working correctly. The following checklist should help you identify potential problems. • Are similar controls the same size? • Are the controls evenly spaced? • Is the dialog box too overwhelming? If so, you may want to group the controls by using a MultiPage control. • Can every control be accessed with a hot key? • Are any of the hot keys duplicated? • Is the tab order set correctly? • Will your VBA code take appropriate action if the user presses Esc or clicks the Close button on the UserForm? • Are there any misspellings in the text? • Does the dialog box have an appropriate caption? • Will the dialog box display properly at all video resolutions? • Are the controls grouped logically (by function)? • Do ScrollBar and SpinButton controls allow valid values only? • Are ListBoxes set properly (Single, Multi, or Extended)? See Chapter 14 for details on ListBox controls. Part IV: Working with UserForms448 The look and feel of Windows dialog boxes differs from program to program. When developing applications for Excel, it’s best to try to mimic Excel’s dialog box style whenever possible. In fact, a good way to learn how to create effective dialog boxes is to try to copy one of Excel’s dialog boxes down to the smallest detail. For example, make sure that you get all the hot keys defined and be sure that the tab order is the same. To re-create one of Excel’s dialog boxes, you need to test it under various circumstances and see how it behaves. I guarantee that your analysis of Excel’s dialog boxes will improve your own dialog boxes. You will find that it’s impossible to duplicate some of Excel’s dialog boxes. Emulating Excel’s Dialog Boxes 20_044018 ch13.qxp 2/28/07 6:41 PM Page 448 Chapter UserForm Examples In This Chapter This chapter presents many useful and informative examples that introduce you to some additional techniques that involve UserForms. ◆ Using a UserForm for a simple menu ◆ Selecting ranges from a UserForm ◆ Using a UserForm as a splash screen ◆ Changing the size of a UserForm while it’s displayed ◆ Zooming and scrolling a sheet from a UserForm ◆ Understanding various techniques that involve a ListBox control ◆ Using an external control ◆ Using the MultiPage control ◆ Animating a Label control You might be able to adapt these techniques to your own work. All the exam- ples are available on the CD-ROM that accompanies this book. CROSS-REFERENCE Chapter 15 contains additional examples of more advanced UserForm tech- niques. 14 449 21_044018 ch14.qxp 2/28/07 6:41 PM Page 449 Creating a UserForm “Menu” Sometimes, you might want to use a UserForm as a type of menu. In other words, the UserForm presents some options, and the user makes a choice. This section presents two ways to do this: using CommandButtons or using a ListBox. Using CommandButtons in a UserForm Figure 14-1 shows an example of a UserForm that uses CommandButton controls as a sim- ple menu. Setting up this sort of thing is very easy, and the code behind the UserForm is very straightforward. Each CommandButton has its own event handler procedure. For example, the following procedure is executed when CommandButton1 is clicked: Private Sub CommandButton1_Click() Call Macro1 Unload Me End Sub Figure 14-1: This dialog box uses CommandButtons as a menu. This procedure simply calls Macro1 and closes the UserForm. The other buttons have simi- lar event handler procedures. Using a ListBox in a UserForm Figure 14-2 shows another example that uses a ListBox as a menu. Before the UserForm is displayed, its Initialize event handler procedure is called. This procedure, which fol- lows, uses the AddItem method to add six items to the ListBox: Private Sub UserForm_Initialize() With ListBox1 .AddItem “Macro1” .AddItem “Macro2” .AddItem “Macro3” .AddItem “Macro4” .AddItem “Macro5” Part IV: Working with UserForms450 21_044018 ch14.qxp 2/28/07 6:41 PM Page 450 .AddItem “Macro6” End With End Sub Figure 14-2: This dialog box uses a ListBox as a menu. The Execute button also has a procedure to handle its Click event: Private Sub ExecuteButton_Click() Select Case ListBox1.ListIndex Case -1 MsgBox “Select a macro from the list.” Exit Sub Case 0: Call Macro1 Case 1: Call Macro2 Case 2: Call Macro3 Case 3: Call Macro4 Case 4: Call Macro5 Case 5: Call Macro6 End Select Unload Me End Sub This procedure accesses the ListIndex property of the ListBox to determine which item is selected. The procedure uses a Select Case structure to execute the appropriate macro. If the ListIndex is –1, nothing is selected in the ListBox, and the user sees a message. CD-ROM The two examples in this section are available on the companion CD-ROM. The filename is userform menus.xlsm. CROSS-REFERENCE Chapter 15 shows a similar example in which you can use a UserForm to simulate a toolbar. Chapter 14: UserForm Examples 451 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 451 Selecting Ranges from a UserForm Many of Excel’s built-in dialog boxes allow the user to specify a range. For example, the Goal Seek dialog box asks the user to select two single-cell ranges. The user can either type the range names directly or use the mouse to point and click in a sheet to make a range selection. Your UserForms can also provide this type of functionality, thanks to the RefEdit control. The RefEdit control doesn’t look exactly like the range selection control used in Excel’s built-in dialog boxes, but it works in a similar manner. If the user clicks the small button on the right side of the control, the dialog box disappears temporarily, and a small range selector is displayed — which is exactly what happens with Excel’s built-in dialog boxes. NOTE Unfortunately, the RefEdit control has a few quirks that still haven’t been fixed. You’ll find that this control does not allow the user to use shortcut range selection keys (for example, pressing End, followed by Shift+↓ will not select cells to the end of the col- umn). In addition, after clicking the small button on the right side of the control (to tem- porarily hide the dialog box), you’re limited to mouse selections only. The keyboard can’t be used at all to make a selection. Figure 14-3 shows a UserForm that contains a RefEdit control. This dialog box enables the user to perform a simple mathematical operation on all nonformula (and non-empty) cells in the selected range. The operation that’s performed corresponds to the selected OptionButton. CD-ROM This example is available on the companion CD-ROM in a file named range selection demo.xlsm. Figure 14-3: The RefEdit control shown here allows the user to select a range. Part IV: Working with UserForms452 21_044018 ch14.qxp 2/28/07 6:41 PM Page 452 Following are a few things to keep in mind when using a RefEdit control: • The RefEdit control returns a text string that represents a range address. You can convert this string to a Range object by using a statement such as Set UserRange = Range(RefEdit1.Text) • It’s a good practice to initialize the RefEdit control to display the current range selec- tion. You can do so in the UserForm_Initialize procedure by using a statement such as RefEdit1.Text = ActiveWindow.RangeSelection.Address • For best results, do not put a RefEdit control inside of a Frame or a MultiPage con- trol. Doing so may cause Excel to crash. • Don’t assume that RefEdit will always return a valid range address. Pointing to a range isn’t the only way to get text into this control. The user can type any text and can also edit or delete the displayed text. Therefore, you need to make sure that the range is valid. The following code is an example of a way to check for a valid range. If an invalid range is detected, the user is given a message, and focus is set to the RefEdit control so the user can try again. On Error Resume Next Set UserRange = Range(RefEdit1.Text) If Err.Number <> 0 Then MsgBox “Invalid range selected” RefEdit1.SetFocus Exit Sub End If On Error GoTo 0 • The user can also click the worksheet tabs while selecting a range with the RefEdit control. Therefore, you can’t assume that the selection is on the active sheet. However, if a different sheet is selected, the range address is preceded by a sheet name. For example: Sheet2!$A$1:$C$4 • If you need to get a single cell selection from the user, you can pick out the upper-left cell of a selected range by using a statement such as Set OneCell = Range(RefEdit1.Text).Range(“A1”) CROSS-REFERENCE As I discuss in Chapter 12, you can also use Excel’s InputBox method to allow the user to select a range. Chapter 14: UserForm Examples 453 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 453 Creating a Splash Screen Some developers like to display some introductory information when the application is opened. This is commonly known as a splash screen. You are undoubtedly familiar with Excel’s splash screen, which appears for a few seconds when Excel is loading. You can create a splash screen for your Excel application with a UserForm. This example is essentially a UserForm that displays automatically and then dismisses itself after five seconds. CD-ROM The companion CD-ROM contains a workbook that demonstrates this procedure. The file is named splash screen.xlsm. Follow these instructions to create a splash screen for your project: 1. Create your workbook. 2. Activate the Visual Basic Editor (VBE) and insert a new UserForm into the project. The code in this example assumes that this form is named UserForm1. 3. Place any controls that you like on UserForm1. For example, you may want to insert an Image control that has your company’s logo. Figure 14-4 shows an example. Figure 14-4: This splash screen is displayed briefly when the workbook is opened. 4. Insert the following procedure into the code module for the ThisWorkbook object: Part IV: Working with UserForms454 21_044018 ch14.qxp 2/28/07 6:41 PM Page 454 Private Sub Workbook_Open() UserForm1.Show End Sub 5. Insert the following procedure into the code module for UserForm1. For something other than a five-second delay, change the argument for the TimeValue function. Private Sub UserForm_Activate() Application.OnTime Now + _ TimeValue(“00:00:05”), “KillTheForm” End Sub 6. Insert the following procedure into a general VBA module: Private Sub KillTheForm() Unload UserForm1 End Sub When the workbook is opened, the Workbook_Open procedure is executed. The proce- dure in Step 4 displays the UserForm. At that time, the UserForm’s Activate event occurs, which triggers the UserForm_Activate procedure (see Step 5). This proce- dure uses the OnTime method of the Application object to execute a procedure named KillTheForm at a particular time. In this case, the time is five seconds after the activation event. The KillTheForm procedure simply unloads the UserForm. 7. As an option, you can add a small CommandButton named CancelButton, set its Cancel property to True, and insert the following event handler procedure in the UserForm’s code module: Private Sub CancelButton_Click() KillTheForm End Sub Doing so lets the user cancel the splash screen before the time has expired by pressing Esc. You can even place this small button behind another object so it won’t be visible. CAUTION Keep in mind that the splash screen is not displayed until the workbook is entirely loaded. In other words, if you would like to display the splash screen to give the user something to look at while the workbook is loading, this technique won’t fill the bill. TIP If your application needs to run some VBA procedures at startup, you can display the UserForm modeless so that the code will continue running while the UserForm is dis- played. To do so, change the Workbook_Open procedure as follows: Private Sub Workbook_Open() UserForm1.Show vbModeless ‘ other code goes here End Sub Chapter 14: UserForm Examples 455 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 455 Disabling a UserForm’s Close Button When a UserForm is displayed, clicking the Close button (the X in the upper-right corner) will unload the form. You might have a situation in which you don’t want this to happen. For example, you might require that the UserForm be closed only by clicking a particular CommandButton. Although you can’t actually disable the Close button, you can prevent the user from closing a UserForm by clicking it. You can do so by monitoring the UserForm’s QueryClose event. The following procedure, which is located in the code module for the UserForm, is executed before the form is closed (that is, when the QueryClose event occurs): Private Sub UserForm_QueryClose _ (Cancel As Integer, CloseMode As Integer) If CloseMode = vbFormControlMenu Then MsgBox “Click the OK button to close the form.” Cancel = True End If End Sub The UserForm_QueryClose procedure uses two arguments. The CloseMode argument contains a value that indicates the cause of the QueryClose event. If CloseMode is equal to vbFormControlMenu (a built-in constant), that means that the user clicked the Close button. If a message is displayed, the Cancel argument is set to True, and the form is not actually closed. CD-ROM The example in this section is available on the companion CD-ROM in a file named queryclose demo.xlsm. NOTE Keep in mind that a user can press Ctrl+Break to break out of the macro. In this exam- ple, pressing Ctrl+Break while the UserForm is displayed causes the UserForm to be dis- missed. To prevent this from happening, execute the following statement prior to displaying the UserForm: Application.EnableCancelKey = xlDisabled Make sure that your application is debugged before you add this statement. Otherwise, you’ll find that it’s impossible to break out of an accidental endless loop. Part IV: Working with UserForms456 21_044018 ch14.qxp 2/28/07 6:41 PM Page 456 Changing a UserForm’s Size Many applications use dialog boxes that change their own size. For example, Excel’s Find and Replace dialog box (displayed when you choose Home ➪ Editing ➪ Find & Select ➪ Replace) increases its height when the user clicks the Options button. The example in this section demonstrates how to get a UserForm to change its size dynam- ically. Changing a dialog box’s size is done by altering the Width or Height property of the UserForm object. CROSS-REFERENCE Refer to Chapter 15 for an example that allows the user to change the UserForm’s size by dragging the lower-right corner. Figure 14-5 shows the dialog box as it is first displayed, and Figure 14-6 shows it after the user clicks the Options button. Notice that the button’s caption changes, depending on the size of the UserForm. Figure 14-5: A sample dialog box in its standard mode. Figure 14-6: The same dialog box enlarged to show some options. While you’re creating the UserForm, set it to its largest size to enable you to work with the controls. Then use the UserForm_Initialize procedure to set it to its default (smaller) size. Chapter 14: UserForm Examples 457 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 457 This example displays a list of worksheets in the active workbook and lets the user select which sheets to print. Following is the event handler that’s executed when the CommandButton named OptionsButton is clicked: Private Sub OptionsButton_Click() If OptionsButton.Caption = “Options >>” Then Me.Height = 164 OptionsButton.Caption = “<< Options” Else Me.Height = 128 OptionsButton.Caption = “Options >>” End If End Sub This procedure examines the Caption of the CommandButton and sets the UserForm’s Height property accordingly. NOTE When controls are not displayed because they are outside the visible portion of the UserForm, the accelerator keys for such controls continue to function. In this example, the user can press the Alt+L hot key (to select the Landscape mode option) even if that option is not visible. To block access to nondisplayed controls, you can write code to dis- able the controls when they are not displayed. CD-ROM The example in this section is available on the companion CD-ROM. The file is named change userform size.xlsm. Zooming and Scrolling a Sheet from a UserForm The example in this section demonstrates how to use ScrollBar controls to allow sheet scrolling and zooming while a dialog box is displayed. Figure 14-7 shows how the example dialog box is set up. When the UserForm is displayed, the user can adjust the worksheet’s zoom factor (from 10% to 400%) by using the ScrollBar at the top. The two ScrollBars in the bottom section of the dialog box allow the user to scroll the worksheet horizontally and vertically. CD-ROM This example, named zoom and scroll sheet.xlsm, is available on the companion CD-ROM. Part IV: Working with UserForms458 21_044018 ch14.qxp 2/28/07 6:41 PM Page 458 Figure 14-7: Here, ScrollBar controls allow zooming and scrolling of the worksheet. If you look at the code for this example, you’ll see that it’s remarkably simple. The con- trols are initialized in the UserForm_Initialize procedure, which follows: Private Sub UserForm_Initialize() LabelZoom.Caption = ActiveWindow.Zoom & “%” ‘ Zoom With ScrollBarZoom .Min = 10 .Max = 400 .SmallChange = 1 .LargeChange = 10 .Value = ActiveWindow.Zoom End With ‘ Horizontally scrolling With ScrollBarColumns .Min = 1 .Max = ActiveSheet.UsedRange.Columns.Count .Value = ActiveWindow.ScrollColumn .LargeChange = 25 .SmallChange = 1 End With ‘ Vertically scrolling With ScrollBarRows .Min = 1 .Max = ActiveSheet.UsedRange.Rows.Count .Value = ActiveWindow.ScrollRow .LargeChange = 25 Chapter 14: UserForm Examples 459 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 459 .SmallChange = 1 End With End Sub This procedure sets various properties of the ScrollBar controls by using values based on the active window. When the ScrollBarZoom control is used, the ScrollBarZoom_Change procedure (which follows) is executed. This procedure sets the ScrollBar control’s Value to the ActiveWindow’s Zoom property value. It also changes a label to display the current zoom factor. Private Sub ScrollBarZoom_Change() With ActiveWindow .Zoom = ScrollBarZoom.Value LabelZoom = .Zoom & “%” End With End Sub Worksheet scrolling is accomplished by the two procedures that follow. These procedures set the ScrollRow or ScrollColumns property of the ActiveWindow object equal to the appropriate ScrollBar control value. Private Sub ScrollBarColumns_Change() ActiveWindow.ScrollColumn = ScrollBarColumns.Value End Sub Private Sub ScrollBarRows_Change() ActiveWindow.ScrollRow = ScrollBarRows.Value End Sub TIP Rather than use the Change event in the preceding procedures, you can use the Scroll event. The difference is that the event is triggered when the ScrollBars are dragged — resulting in smooth zooming and scrolling. To use the Scroll event, just make the Change part of the procedure name Scroll. ListBox Techniques The ListBox control is extremely versatile, but it can be a bit tricky to work with. This section contains of a number of simple examples that demonstrate common techniques that involve the ListBox control. Part IV: Working with UserForms460 21_044018 ch14.qxp 2/28/07 6:41 PM Page 460 NOTE In most cases, the techniques described in this section also work with a ComboBox control. About the ListBox control Following are a few points to keep in mind when working with ListBox controls. Examples in the sections that follow demonstrate many of these points: • The items in a ListBox can be retrieved from a range of cells (specified by the RowSource property), or they can be added by using VBA code (using the AddItem method). • A ListBox can be set up to allow a single selection or a multiple selection. This is deter- mined by the MultiSelect property. • If a ListBox is not set up for a multiple selection, the value of the ListBox can be linked to a worksheet cell by using the ControlSource property. • It’s possible to display a ListBox with no items selected (the ListIndex property will be –1). However, after an item is selected, the user cannot deselect all items. The exception to this is if the MultiSelect property is True. • A ListBox can contain multiple columns (controlled by the ColumnCount property) and even a descriptive header (controlled by the ColumnHeads property). • The vertical height of a ListBox displayed in a UserForm window isn’t always the same as the vertical height when the UserForm is actually displayed. • The items in a ListBox can be displayed either as check boxes (if multiple selection is allowed) or as option buttons (if a single selection is allowed). This is controlled by the ListStyle property. For complete details on the properties and methods for a ListBox control, consult the Help system. Adding items to a ListBox control Before displaying a UserForm that uses a ListBox control, you’ll probably need to fill the ListBox with items. You can fill a ListBox at design time using items stored in a worksheet range or at runtime using VBA to add the items to the ListBox. The two examples in this section presume that • You have a UserForm named UserForm1. • This UserForm contains a ListBox control named ListBox1. • The workbook contains a sheet named Sheet1, and range A1:A12 contains the items to be displayed in the ListBox. Chapter 14: UserForm Examples 461 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 461 ADDING ITEMS TO A LISTBOX AT DESIGN TIME To add items to a ListBox at design time, the ListBox items must be stored in a worksheet range. Use the RowSource property to specify the range that contains the ListBox items. Figure 14-8 shows the Properties window for a ListBox control. The RowSource property is set to Sheet1!A1:A12. When the UserForm is displayed, the ListBox will contain the 12 items in this range. The items appear in the ListBox at design time as soon as you spec- ify the range for the RowSource property. Figure 14-8: Setting the RowSource property at design time. CAUTION In most cases, you’ll want to include the worksheet name when you specify the RowSource property; otherwise, the ListBox will use the specified range on the active worksheet. In some cases, you might need to fully qualify the range by including the workbook name. For example: [budget.xlsx]Sheet1!A1:A12 A better practice is to define a name for the range and use that name in your code. This will ensure that the proper range is used even if rows above the range are added or deleted. ADDING ITEMS TO A LISTBOX AT RUNTIME To add ListBox items at runtime, you have two choices: • Set the RowSource property to a range address by using code. • Write code that uses the AddItem method to add the ListBox items. Part IV: Working with UserForms462 21_044018 ch14.qxp 2/28/07 6:41 PM Page 462 As you might expect, you can set the RowSource property via code rather than with the Properties window. For example, the following procedure sets the RowSource property for a ListBox before displaying the UserForm. In this case, the items consist of the cell entries in a range named Categories on the Budget worksheet. UserForm1.ListBox1.RowSource = “Budget!Categories” UserForm1.Show If the ListBox items are not contained in a worksheet range, you can write VBA code to fill the ListBox before the dialog box appears. The following procedure fills the ListBox with the names of the months by using the AddItem method. Sub ShowUserForm2() ‘ Fill the list box With UserForm2.ListBox1 .RowSource=”” .AddItem “January” .AddItem “February” .AddItem “March” .AddItem “April” .AddItem “May” .AddItem “June” .AddItem “July” .AddItem “August” .AddItem “September” .AddItem “October” .AddItem “November” .AddItem “December” End With UserForm2.Show End Sub CAUTION In the preceding code, notice that I set the RowSource property to an empty string. This is to avoid a potential error that occurs if the Properties window has a nonempty RowSource setting. If you try to add items to a ListBox that has a non-null RowSource setting, you’ll get a “permission denied” error. You can also use the AddItem method to retrieve ListBox items from a range. Here’s an example that fills a ListBox with the contents of A1:A12 on Sheet1. For Row = 1 To 12 UserForm1.ListBox1.AddItem Sheets(“Sheet1”).Cells(Row, 1) Next Row Chapter 14: UserForm Examples 463 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 463 Using the List property is even simpler. The statement that follows has the same effect as the preceding For Next loop. UserForm1.ListBox1.List = Application.Transpose(Sheets(“Sheet1”).Range(“A1:A12”)) Note that I used the Transpose function because the List property expects a horizontal array and the range is in a column rather than a row. You can also use the List property if your data is stored in a one-dimensional array. For example, assume that you have an array named MyList that contains 50 elements. The fol- lowing statement will create a 50-item list in ListBox1: UserForm1.ListBox1.List = MyList CD-ROM The examples in this section are available on the companion CD-ROM. The file is named fill listbox.xlsm. ADDING ONLY UNIQUE ITEMS TO A LISTBOX In some cases, you might need to fill a ListBox with unique (nonduplicated) items from a list. For example, assume you have a worksheet that contains customer data. One of the columns might contain the state (see Figure 14-9). You would like to fill a ListBox with the state name of your customers, but you don’t want to include duplicate state names. Figure 14-9: A Collection object is used to fill a ListBox with the unique items from column B. Part IV: Working with UserForms464 21_044018 ch14.qxp 2/28/07 6:41 PM Page 464 One technique involves using a Collection object. You can add items to a Collection object with the following syntax: object.Add item, key, before, after The key argument, if used, must be a unique text string that specifies a separate key that can be used to access a member of the collection. The important word here is unique. If you attempt to add a non-unique key to a collection, an error occurs, and the item is not added. You can take advantage of this situation and use it to create a collection that consists only of unique items. The following procedure starts by declaring a new Collection object named NoDupes. It assumes that a range named Data contains a list of items, some of which may be duplicated. The code loops through the cells in the range and attempts to add the cell’s value to the NoDupes collection. It also uses the cell’s value (converted to a string) for the key argu- ment. Using the On Error Resume Next statement causes VBA to ignore the error that occurs if the key is not unique. When an error occurs, the item is not added to the collection — which is just what you want. The procedure then transfers the items in the NoDupes collection to the ListBox. The UserForm also contains a label that displays the number of unique items. Sub RemoveDuplicates1() Dim AllCells As Range, Cell As Range Dim NoDupes As New Collection On Error Resume Next For Each Cell In Range(“State”) NoDupes.Add Cell.Value, CStr(Cell.Value) Next Cell On Error GoTo 0 ‘ Add the non-duplicated items to a ListBox For Each Item In NoDupes UserForm1.ListBox1.AddItem Item Next Item ‘ Display the count UserForm1.Label1.Caption = _ “Unique items: “ & NoDupes.Count ‘ Show the UserForm UserForm1.Show End Sub Chapter 14: UserForm Examples 465 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 465 CD-ROM This example, named listbox unique items1.xlsm, is available on the companion CD-ROM. A workbook named listbox unique items2.xlsm has a slightly more sophisticated version of this technique and displays the items sorted. Determining the selected item The examples in preceding sections merely display a UserForm with a ListBox filled with various items. These procedures omit a key point: how to determine which item or items were selected by the user. NOTE This discussion assumes a single-selection ListBox object — one whose MultiSelect property is set to 0. To determine which item was selected, access the ListBox’s Value property. The state- ment that follows, for example, displays the text of the selected item in ListBox1. MsgBox ListBox1.Value If no item is selected, this statement will generate an error. If you need to know the position of the selected item in the list (rather than the content of that item), you can access the ListBox’s ListIndex property. The following example uses a message box to display the item number of the selected ListBox item: MsgBox “You selected item #” & ListBox1.ListIndex If no item is selected, the ListIndex property will return –1. NOTE The numbering of items in a ListBox begins with 0 — not 1. Therefore, the ListIndex of the first item is 0, and the ListIndex of the last item is equivalent to the value of the ListCount property less 1. Determining multiple selections in a ListBox A ListBox’s MultiSelect property can be any of three values: • 0 (fmMultiSelectSingle): Only one item can be selected. This is the default setting. • 1 (fmMultiSelectMulti): Pressing the Space key or clicking selects or deselects an item in the list. Part IV: Working with UserForms466 21_044018 ch14.qxp 2/28/07 6:41 PM Page 466 • 2 (fmMultiSelectExtended): Shift-clicking extends the selection from the previ- ously selected item to the current item. You can also use Shift and one of the arrow keys to extend the selected items. If the ListBox allows multiple selections (that is, if its MultiSelect property is either 1 or 2), trying to access the ListIndex or Value properties will result in an error. Instead, you need to use the Selected property, which returns an array whose first item has an index of 0. For example, the following statement displays True if the first item in the ListBox list is selected: MsgBox ListBox1.Selected(0) CD-ROM The companion CD-ROM contains a workbook that demonstrates how to identify the selected item(s) in a ListBox. It works for single-selection and multiple-selection ListBoxes. The file is named listbox selected items.xlsm. The following code, from the example workbook on the CD-ROM, loops through each item in the ListBox. If the item was selected, it appends the item’s text to a variable called Msg. Finally, the names of all the selected items are displayed in a message box. Private Sub OKButton_Click() Msg = “” For i = 0 To ListBox1.ListCount - 1 If ListBox1.Selected(i) Then _ Msg = Msg & ListBox1.List(i) & vbCrLf Next i MsgBox “You selected: “ & vbCrLf & Msg Unload Me End Sub Figure 14-10 shows the result when multiple ListBox items are selected. Multiple lists in a single ListBox This example demonstrates how to create a ListBox in which the contents change depend- ing on the user’s selection from a group of OptionButtons. Figure 14-11 shows the sample UserForm. The ListBox gets its items from a worksheet range. The procedures that handle the Click event for the OptionButton controls simply set the ListBox’s RowSource property to a different range. One of these procedures follows: Private Sub obMonths_Click() ListBox1.RowSource = “Sheet1!Months” End Sub Chapter 14: UserForm Examples 467 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 467 Figure 14-10: This message box displays a list of items selected in a ListBox. Figure 14-11: The contents of this ListBox depend on the OptionButton selected. Clicking the OptionButton named obMonths changes the RowSource property of the ListBox to use a range named Months on Sheet1. CD-ROM This example, named listbox multiple lists.xlsm, is available on the companion CD-ROM. ListBox item transfer Some applications require a user to select several items from a list. It’s often useful to cre- ate a new list of the selected items and display the new list in another ListBox. For an example of this situation, check out the Customization tab of the Excel Options dialog box. Figure 14-12 shows a dialog box with two ListBoxes. The Add button adds the item selected in the left ListBox to the right ListBox. The Delete button removes the selected Part IV: Working with UserForms468 21_044018 ch14.qxp 2/28/07 6:41 PM Page 468 item from the list on the right. A check box determines the behavior when a duplicate item is added to the list: Namely, if the Allow Duplicates check box is not marked, a message box appears if the user attempts to add an item that’s already on the list. Figure 14-12: Building a list from another list. The code for this example is relatively simple. Here’s the procedure that is executed when the user clicks the Add button: Private Sub AddButton_Click() If ListBox1.ListIndex = -1 Then Exit Sub If Not cbDuplicates Then ‘ See if item already exists For i = 0 To ListBox2.ListCount - 1 If ListBox1.Value = ListBox2.List(i) Then Beep Exit Sub End If Next i End If ListBox2.AddItem ListBox1.Value End Sub The code for the Remove button is even simpler: Private Sub RemoveButton_Click() If ListBox2.ListIndex = -1 Then Exit Sub ListBox2.RemoveItem ListBox2.ListIndex End Sub Notice that both of these routines check to make sure that an item is actually selected. If the ListBox’s ListIndex property is –1, no items are selected, and the procedure ends. Chapter 14: UserForm Examples 469 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 469 This example has two additional procedures that control whether the Remove button is enabled or disabled. These events are triggered when the ListBox is entered (either via a keystroke or a mouse click). The net effect is that the Remove button is enabled only when the user is working in ListBox2. Private Sub ListBox1_Enter() RemoveButton.Enabled = False End Sub Private Sub ListBox2_Enter() RemoveButton.Enabled = True End Sub CD-ROM This example, named listbox item transfer.xlsm, is available on the companion CD-ROM. Moving items in a ListBox Often, the order of items in a list is important. The example in this section demonstrates how to allow the user to move items up or down in a ListBox. The VBE uses this type of technique to let you control the tab order of the items in a UserForm (right-click a UserForm and choose Tab Order from the shortcut menu). Figure 14-13 shows a dialog box that contains a ListBox and two CommandButtons. Clicking the Move Up button moves the selected item up in the ListBox; clicking the Move Down button moves the selected item down. Figure 14-13: The buttons allow the user to move items up or down in the ListBox. CD-ROM This example, named listbox move items.xlsm, is available on the companion CD-ROM. Part IV: Working with UserForms470 21_044018 ch14.qxp 2/28/07 6:41 PM Page 470 The event handler procedures for the two CommandButtons follow: Private Sub MoveUpButton_Click() If ListBox1.ListIndex <= 0 Then Exit Sub NumItems = ListBox1.ListCount Dim TempList() ReDim TempList(0 To NumItems - 1) ‘ Fill array with list box items For i = 0 To NumItems - 1 TempList(i) = ListBox1.List(i) Next i ‘ Selected item ItemNum = ListBox1.ListIndex ‘ Exchange items TempItem = TempList(ItemNum) TempList(ItemNum) = TempList(ItemNum - 1) TempList(ItemNum - 1) = TempItem ListBox1.List = TempList ‘ Change the list index ListBox1.ListIndex = ItemNum - 1 End Sub Private Sub MoveDownButton_Click() If ListBox1.ListIndex = ListBox1.ListCount - 1 Then Exit Sub NumItems = ListBox1.ListCount Dim TempList() ReDim TempList(0 To NumItems - 1) ‘ Fill array with list box items For i = 0 To NumItems - 1 TempList(i) = ListBox1.List(i) Next i ‘ Selected item ItemNum = ListBox1.ListIndex ‘ Exchange items TempItem = TempList(ItemNum) TempList(ItemNum) = TempList(ItemNum + 1) TempList(ItemNum + 1) = TempItem ListBox1.List = TempList ‘ Change the list index ListBox1.ListIndex = ItemNum + 1 End Sub These procedures work fairly well, but you’ll find that, for some reason, relatively rapid clicking doesn’t always register. For example, you may click the Move Down button three times in quick succession, but the item only moves one or two positions. The solution is to Chapter 14: UserForm Examples 471 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 471 add a new DblClick event handler for each CommandButton. These procedures, which simply call the Click procedures, are as follows: Private Sub MoveUpButton_DblClick _ (ByVal Cancel As MSForms.ReturnBoolean) Call MoveUpButton_Click End Sub Private Sub MoveDownButton_DblClick _ (ByVal Cancel As MSForms.ReturnBoolean) Call MoveDownButton_Click End Sub Working with multicolumn ListBox controls A normal ListBox has a single column for its contained items. You can, however, create a ListBox that displays multiple columns and (optionally) column headers. Figure 14-14 shows an example of a multicolumn ListBox that gets its data from a worksheet range. Figure 14-14: This ListBox displays a three-column list with column headers. CD-ROM This example, named listbox multicolumn1.xlsm, is available on the companion CD-ROM. To set up a multicolumn ListBox that uses data stored in a worksheet range, follow these steps: 1. Make sure that the ListBox’s ColumnCount property is set to the correct number of columns. Part IV: Working with UserForms472 21_044018 ch14.qxp 2/28/07 6:41 PM Page 472 2. Specify the correct multicolumn range in the Excel worksheet as the ListBox’s RowSource property. 3. If you want to display column headers, set the ColumnHeads property to True. Do not include the column headings on the worksheet in the range setting for the RowSource property. VBA will instead automatically use the row directly above the first row of the RowSource range. 4. Adjust the column widths by assigning a series of values, specified in points (1⁄72 of one inch) and separated by semicolons, to the ColumnWidths property. For example, for a three-column list box, the ColumnWidths property might be set to the following text string: 100;40;30 5. Specify the appropriate column as the BoundColumn property. The bound column speci- fies which column is referenced when an instruction polls the ListBox’s Value property. To fill a ListBox with multicolumn data without using a range, you first create a two- dimensional array and then assign the array to the ListBox’s List property. The following statements demonstrate this using a 12-row x 2-column array named Data. The two- column ListBox shows the month names in column 1 and the number of the days in the month in column 2 (see Figure 14-15). Notice that the procedure sets the ColumnCount property to 2. Private Sub UserForm_Initialize() ‘ Fill the list box Dim Data(1 To 12, 1 To 2) For i = 1 To 12 Data(i, 1) = Format(DateSerial(2007, i, 1), “mmmm”) Next i For i = 1 To 12 Data(i, 2) = Day(DateSerial(2007, i + 1, 1) - 1) Next i ListBox1.ColumnCount = 2 ListBox1.List = Data End Sub Figure 14-15: A two-column ListBox filled with data stored in an array. Chapter 14: UserForm Examples 473 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 473 CD-ROM This example is available on the companion CD-ROM. The file is named listbox multicolumn2.xlsm. NOTE There appears to be no way to specify column headers for the ColumnHeads property when the list source is a VBA array. Using a ListBox to select worksheet rows The example in this section displays a ListBox that consists of the entire used range of the active worksheet (see Figure 14-16). The user can select multiple items in the ListBox. Clicking the All button selects all items, and clicking the None button deselects all items. Clicking OK selects those corresponding rows in the worksheet. You can, of course, select multiple noncontiguous rows directly in the worksheet by pressing Ctrl while you click the row borders. However, you might find that selecting rows is easier when using this method. CD-ROM This example, named listbox select rows.xlsm, is available on the companion CD-ROM. Figure 14-16: This ListBox makes it easy to select rows in a worksheet. Part IV: Working with UserForms474 21_044018 ch14.qxp 2/28/07 6:41 PM Page 474 Selecting multiple items is possible because the ListBox’s MultiSelect property is set to 1 - fmMultiSelectMulti. The check boxes on each item are displayed because the ListBox’s ListStyle property is set to 1 - fmListStyleOption. The UserForm’s Initialize procedure follows. This procedure creates a Range object named rng that consists of the active sheet’s used range. Additional code sets the ListBox’s ColumnCount and RowSource properties and adjusts the ColumnWidths prop- erty so that the ListBox columns are proportional to the column widths in the worksheet. Private Sub UserForm_Initialize() Dim ColCnt As Integer Dim rng As Range Dim cw As String Dim c As Integer ColCnt = ActiveSheet.UsedRange.Columns.Count Set rng = ActiveSheet.UsedRange With ListBox1 .ColumnCount = ColCnt .RowSource = rng.Address cw = “” For c = 1 To .ColumnCount cw = cw & rng.Columns(c).Width & “;” Next c .ColumnWidths = cw .ListIndex = 0 End With End Sub The All and None buttons (named SelectAllButton and SelectNoneButton, respec- tively) have simple event handler procedures as follows: Private Sub SelectAllButton_Click() Dim r As Integer For r = 0 To ListBox1.ListCount - 1 ListBox1.Selected(r) = True Next r End Sub Private Sub SelectNoneButton_Click() Dim r As Integer For r = 0 To ListBox1.ListCount - 1 ListBox1.Selected(r) = False Next r End Sub The OKButton_Click procedure follows. This procedure creates a Range object named RowRange that consists of the rows that correspond to the selected items in the ListBox. To determine whether a row was selected, the code examines the Selected property of Chapter 14: UserForm Examples 475 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 475 the ListBox control. Notice that it uses the Union function to add ranges to the RowRange object. Private Sub OKButton_Click() Dim RowRange As Range RowCnt = 0 For r = 0 To ListBox1.ListCount - 1 If ListBox1.Selected(r) Then RowCnt = RowCnt + 1 If RowCnt = 1 Then Set RowRange = ActiveSheet.UsedRange.Rows(r + 1) Else Set RowRange = _ Union(RowRange, ActiveSheet.UsedRange.Rows(r + 1)) End If End If Next r If Not RowRange Is Nothing Then RowRange.Select Unload Me End Sub CD-ROM This example is available on the companion CD-ROM. The file is named listbox select rows.xlsm. Using a ListBox to activate a sheet The example in this section is just as useful as it is instructive. This example uses a multicolumn ListBox to display a list of sheets within the active workbook. The columns represent • The sheet’s name • The type of sheet (worksheet, chart, or Excel 5/95 dialog sheet) • The number of nonempty cells in the sheet • Whether the sheet is visible Figure 14-17 shows an example of the dialog box. Part IV: Working with UserForms476 21_044018 ch14.qxp 2/28/07 6:41 PM Page 476 Figure 14-17: This dialog box lets the user activate a sheet. The code in the UserForm_Initialize procedure (which follows) creates a two- dimensional array and collects the information by looping through the sheets in the active workbook. It then transfers this array to the ListBox. Public OriginalSheet As Object Private Sub UserForm_Initialize() Dim SheetData() As String Set OriginalSheet = ActiveSheet ShtCnt = ActiveWorkbook.Sheets.Count ReDim SheetData(1 To ShtCnt, 1 To 4) ShtNum = 1 For Each Sht In ActiveWorkbook.Sheets If Sht.Name = ActiveSheet.Name Then _ ListPos = ShtNum - 1 SheetData(ShtNum, 1) = Sht.Name Select Case TypeName(Sht) Case “Worksheet” SheetData(ShtNum, 2) = “Sheet” SheetData(ShtNum, 3) = _ Application.CountA(Sht.Cells) Case “Chart” SheetData(ShtNum, 2) = “Chart” SheetData(ShtNum, 3) = “N/A” Case “DialogSheet” SheetData(ShtNum, 2) = “Dialog” SheetData(ShtNum, 3) = “N/A” End Select If Sht.Visible Then SheetData(ShtNum, 4) = “True” Else Chapter 14: UserForm Examples 477 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 477 SheetData(ShtNum, 4) = “False” End If ShtNum = ShtNum + 1 Next Sht With ListBox1 .ColumnWidths = “100 pt;30 pt;40 pt;50 pt” .List = SheetData .ListIndex = ListPos End With End Sub The ListBox1_Click procedure follows: Private Sub ListBox1_Click() If cbPreview Then _ Sheets(ListBox1.Value).Activate End Sub The value of the CheckBox control (named cbPreview) determines whether the selected sheet is previewed when the user clicks an item in the ListBox. Clicking the OK button (named OKButton) executes the OKButton_Click procedure, which follows: Private Sub OKButton_Click() Dim UserSheet As Object Set UserSheet = Sheets(ListBox1.Value) If UserSheet.Visible Then UserSheet.Activate Else If MsgBox(“Unhide sheet?”, _ vbQuestion + vbYesNoCancel) = vbYes Then UserSheet.Visible = True UserSheet.Activate Else OriginalSheet.Activate End If End If Unload Me End Sub The OKButton_Click procedure creates an object variable that represents the selected sheet. If the sheet is visible, it is activated. If it’s not visible, the user is presented with a message box asking whether it should be unhidden. If the user responds in the affirmative, the sheet is unhidden and activated. Otherwise, the original sheet (stored in a public object variable named OriginalSheet) is activated. Part IV: Working with UserForms478 21_044018 ch14.qxp 2/28/07 6:41 PM Page 478 Double-clicking an item in the ListBox has the same result as clicking the OK button. The ListBox1_DblClick procedure, which follows, simply calls the OKButton_Click procedure. Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean) Call OKButton_Click End Sub CD-ROM This example is available on the companion CD-ROM. The file is named listbox activate sheet.xlsm. Using the MultiPage Control in a UserForm The MultiPage control is very useful for UserForms that must display many controls. The MultiPage control lets you group the choices and place each group on a separate tab. Figure 14-18 shows an example of a UserForm that contains a MultiPage control. In this case, the control has three pages, each with its own tab. Figure 14-18: MultiPage groups your controls on pages, making them accessible from a tab. Chapter 14: UserForm Examples 479 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 479 CD-ROM This example is available on the companion CD-ROM. The file is named multipage control demo.xlsm. NOTE The Toolbox also contains a control named TabStrip, which resembles a MultiPage control. However, unlike the MultiPage control, the TabStrip control is not a con- tainer for other objects. The MultiPage control is much more versatile, and I’ve never had a need to actually use the TabStrip control. Using a MultiPage control can be a bit tricky. The following are some things to keep in mind when using this control: • The tab (or page) that’s displayed up front is determined by the control’s Value func- tion. A value of 0 displays the first tab, a value of 1 displays the second tab, and so on. • By default, a MultiPage control has two pages. To add a new page in the VBE, right- click a tab and select New Page from the shortcut menu. • When you’re working with a MultiPage control, just click a tab to set the properties for that particular page. The Properties window will display the properties that you can adjust. • You might find it difficult to select the actual MultiPage control because clicking the control selects a page within the control. To select the control itself, click its border. Or, you can use the Tab key to cycle among all the controls. Yet another option is to select the MultiPage control from the drop-down list in the Properties window. • If your MultiPage control has lots of tabs, you can set its MultiRow property to True to display the tabs in more than one row. • If you prefer, you can display buttons instead of tabs. Just change the Style property to 1. If the Style property value is 2, the MultiPage control won’t display tabs or buttons. • The TabOrientation property determines the location of the tabs on the MultiPage control. • For each page, you can set a transition effect by changing the TransitionEffect prop- erty. For example, clicking a tab can cause the new page to push the former page out of the way. Use the TransitionPeriod property to set the speed of the transition effect. Using an External Control The example in this section uses the Microsoft Date and Time Picker Control. Although this is not an Excel control (it’s installed with Windows), it works fine in a UserForm. Part IV: Working with UserForms480 21_044018 ch14.qxp 2/28/07 6:41 PM Page 480 To make this control available, add a UserForm to a workbook and follow these steps: 1. Activate the VBE. 2. Right-click the Toolbox and choose Additional Controls. Select View ➪ Toolbox if the Toolbox is not visible. 3. In the Additional Controls dialog box, scroll down and place a check mark next to Microsoft Date and Time Picker Control 6.0. 4. Click OK. Your Toolbox will display a new control. Figure 14-19 shows the Date and Time Picker Control in a UserForm, along with the Property window. The Format property determines whether it works with dates or times. Figure 14-19: The Date and Time Picker Control in a UserForm. Figure 14-20 shows this control being used. Clicking the drop-down button displays a cal- endar. When the user clicks a calendar date, that date is displayed in the control and is assigned to the Value property for the control. This dialog box is displayed modeless, so the user can select a new cell without closing the dialog box. Chapter 14: UserForm Examples 481 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 481 When the UserForm is displayed, the Date and Time Picker displays the current date by setting its Value property in the UserForm_Initialize procedure: Private Sub UserForm_Initialize() DTPicker1.Value = Date End Sub The code to handle the Insert button click is as follows: Private Sub InsertButton_Click() ActiveCell = DTPicker1.Value ActiveCell.Columns.EntireColumn.AutoFit End Sub Figure 14-20: Inserting a date using the Date and Time Picker Control. CD-ROM This example, named date and time picker.xlsm, is available on the companion CD-ROM. Animating a Label The final example in this chapter demonstrates how to animate a Label control. The UserForm shown in Figure 14-21 is an interactive random number generator. Two TextBox controls hold the lower and upper values for the random number. A Label control initially displays four question marks, but the text is animated to show random numbers when the user clicks the Start button. The Start button changes to a Stop button, and clicking it again stops the animation and displays the random number. Figure 14-22 shows the dialog box displaying a random number between 1 and 10,000. Part IV: Working with UserForms482 21_044018 ch14.qxp 2/28/07 6:41 PM Page 482 Figure 14-21: Generating a random number. Figure 14-22: A random number has been chosen. The code that’s attached to the button is as follows: Dim Stopped As Boolean Private Sub StartStopButton_Click() Dim Low As Double, Hi As Double If StartStopButton.Caption = “Start” Then ‘ validate low and hi values If Not IsNumeric(TextBox1.Text) Then MsgBox “Non-numeric starting value.”, vbInformation With TextBox1 .SelStart = 0 .SelLength = Len(.Text) .SetFocus End With Exit Sub End If If Not IsNumeric(TextBox2.Text) Then Chapter 14: UserForm Examples 483 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 483 MsgBox “Non-numeric ending value.”, vbInformation With TextBox2 .SelStart = 0 .SelLength = Len(.Text) .SetFocus End With Exit Sub End If ‘ Make sure they aren’t in the wrong order Low = Application.Min(Val(TextBox1.Text), Val(TextBox2.Text)) Hi = Application.Max(Val(TextBox1.Text), Val(TextBox2.Text)) ‘ Adjust font size, if necessary Select Case Application.Max(Len(TextBox1.Text), Len(TextBox2.Text)) Case Is < 5: Label1.Font.Size = 72 Case 5: Label1.Font.Size = 60 Case 6: Label1.Font.Size = 48 Case Else: Label1.Font.Size = 36 End Select StartStopButton.Caption = “Stop” Stopped = False Randomize Do Until Stopped Label1.Caption = Int((Hi - Low + 1) * Rnd + Low) DoEvents ‘ Causes the animation Loop Else Stopped = True StartStopButton.Caption = “Start” End If End Sub Because the button serves two purposes (starting and stopping), the procedure uses a pub- lic variable, Stopped, to keep track of the state. The first part of the procedure consists of two If-Then structures to validate the contents of the TextBox controls. Two more statements ensure that the low value is in fact less than the high value. The next section adjusts the Label control’s font size, based on the maximum value. The Do Until loop is responsible for generating and displaying the random numbers. Notice the DoEvents state- ment. This statement causes Excel to “yield” to the operating system. Without the state- ment, the Label control would not display each random number as it is generated. In other words, the DoEvents statement is what makes the animation possible. The UserForm also contains a CommandButton that serves as a Cancel button. This con- trol is positioned off the UserForm so it’s not visible. This CommandButton has its Cancel Part IV: Working with UserForms484 21_044018 ch14.qxp 2/28/07 6:41 PM Page 484 property set to True, so pressing Esc is equivalent to clicking the button. It’s click event handler procedure simply sets the Stopped variable to True and unloads the UserForm: Private Sub CancelButton_Click() Stopped = True Unload Me End Sub CD-ROM This example, named random number generator.xlsm, is available on the companion CD-ROM. Chapter 14: UserForm Examples 485 Part IV 21_044018 ch14.qxp 2/28/07 6:41 PM Page 485 21_044018 ch14.qxp 2/28/07 6:41 PM Page 486 Chapter Advanced UserForm Techniques In This Chapter This chapter picks up where Chapter 14 left off. Here, you’ll find additional examples of UserForms. ◆ Using modeless UserForms ◆ Displaying a progress indicator ◆ Creating a wizard — an interactive series of dialog boxes ◆ Creating a function that emulates VBA’s MsgBox function ◆ Allowing users to move UserForm controls ◆ Displaying a UserForm with no title bar ◆ Simulating a toolbar with a Userform ◆ Allowing users to resize a UserForm ◆ Handling multiple controls with a single event handler ◆ Using a dialog box to select a color ◆ Displaying a chart in a UserForm ◆ Using an enhanced data form ◆ Creating a moving tile puzzle 15 487 22_044018 ch15.qxp 2/28/07 6:42 PM Page 487 Most of these examples are advanced, and the majority focus on practical applications. But even the less-than-practical examples demonstrate some useful techniques. CROSS-REFERENCE Chapter 28 contains still more UserForm examples. Specifically, you’ll find information on how to create a UserForm on the fly. A Modeless Dialog Box Most dialog boxes that you encounter are modal dialog boxes, which must be dismissed from the screen before the user can do anything with the underlying application. Some dialogs, however, are modeless, which means the user can continue to work in the applica- tion while the dialog box is displayed. To display a modeless UserForm, use a statement such as UserForm1.Show vbModeless The word vbModeless is a built-in constant that has a value of 0. Therefore, the following statement works identically: UserForm1.Show 0 Figure 15-1 shows a modeless dialog box that displays information about the active cell. When the dialog box is displayed, the user is free to move the cell cursor, activate other sheets, and perform other Excel actions. Figure 15-1: This modeless dialog box remains visible while the user continues working. Part IV: Working with UserForms488 22_044018 ch15.qxp 2/28/07 6:42 PM Page 488 CD-ROM This example, named modeless userform1.xlsm, is available on the companion CD-ROM. The key is determining when to update the information in the dialog box. To do so, the example monitors two workbook events: SheetSelectionChange and SheetActivate. These event handler procedures are located in the code module for the ThisWorkbook object. CROSS-REFERENCE Refer to Chapter 19 for additional information about events. The event handler procedures follow: Private Sub Workbook_SheetSelectionChange _ (ByVal Sh As Object, ByVal Target As Range) Call UpdateBox End Sub Private Sub Workbook_SheetActivate(ByVal Sh As Object) Call UpdateBox End Sub The two previous procedures call the UpdateBox procedure, which follows: Sub UpdateBox() With UserForm1 ‘ Make sure a worksheet is active If TypeName(ActiveSheet) <> “Worksheet” Then .lblFormula.Caption = “N/A” .lblNumFormat.Caption = “N/A” .lblLocked.Caption = “N/A” Exit Sub End If .Caption = “Cell: “ & ActiveCell.Address(False, False) ‘ Formula If ActiveCell.HasFormula Then .lblFormula.Caption = ActiveCell.Formula Else .lblFormula.Caption = “(none)” End If ‘ Number format .lblNumFormat.Caption = ActiveCell.NumberFormat ‘ Locked Chapter 15: Advanced UserForm Techniques 489 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 489 .lblLocked.Caption = ActiveCell.Locked End With End Sub The UpdateBox procedure changes the UserForm’s caption to show the active cell’s address; then it updates the three Label controls (lblFormula, lblNumFormat, and lblLocked). Following are a few points to help you understand how this example works: • The UserForm is displayed modeless so that you can still access the worksheet while it’s displayed. • Code at the top of the procedure checks to make sure that the active sheet is a work- sheet. If the sheet is not a worksheet, the Label controls are assigned the text N/A. • The workbook monitors the active cell by using a Selection_Change event (which is located in the ThisWorkbook code module). • The information is displayed in Label controls on the UserForm. Figure 15-2 shows a more sophisticated version of this example. This version displays quite a bit of additional information about the selected cell. Long-time Excel users might notice the similarity to the Info window — a feature that was removed from Excel several years ago. The code is too lengthy to display here, but you can view the well-commented code in the example workbook. Figure 15-2: This modeless UserForm displays various information about the active cell. Part IV: Working with UserForms490 22_044018 ch15.qxp 2/28/07 6:42 PM Page 490 CD-ROM This example, named modeless userform2.xlsm, is available on the companion CD-ROM. Following are some key points about this more sophisticated version: • The UserForm has a check box (Auto Update). When this check box is selected, the UserForm is updated automatically. When Auto Update is not turned on, the user can use the Update button to refresh the information. • The workbook uses a class module to monitor two events for all open workbooks: the SheetSelectionChange event and the SheetActivate event. As a result, the code to display the information about the current cell is executed automatically whenever these events occur in any workbook (assuming that the Auto Update option is in effect). Some actions (such as changing a cell’s number format) do not trigger either of these events. Therefore, the UserForm also contains an Update button. CROSS-REFERENCE Refer to Chapter 29 for more information about class modules. • The counts displayed for the cell precedents and dependents fields include cells in the active sheet only. This is a limitation of the Precedents and Dependents properties. • Because the length of the information will vary, VBA code is used to size and vertically space the labels — and also change the height of the UserForm if necessary. Displaying a Progress Indicator One of the most common requests among Excel developers involves progress indicators. A progress indicator is a graphical thermometer-type display that shows the progress of a task, such as a lengthy macro. Displaying a progress indicator is relatively easy. In this section, I describe how to create three types of progress indicators for: • A macro that’s not initiated by a UserForm (a standalone progress indicator). • A macro that is initiated by a UserForm. In this case, the UserForm uses a MultiPage control that displays the progress indicator while the macro is running. • A macro that is initiated by a UserForm. In this case, the UserForm increases in height while the macro is running, and the progress indicator appears at the bottom of the dia- log box. Chapter 15: Advanced UserForm Techniques 491 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 491 Using a progress indicator requires that you are (somehow) able to gauge how far along your macro might be in completing its given task. How you do this will vary, depending on the macro. For example, if your macro writes data to cells and you know the number of cells that will be written to, it’s a simple matter to write code that calculates the percent completed. Even if you can’t accurately gauge the progress of a macro, it’s a good idea to give the user some indication that the macro is still running and Excel hasn’t crashed. CAUTION A progress indicator will slow down your macro a bit because of the extra overhead of having to update it. If speed is absolutely critical, you might prefer to forgo using a progress indicator. Creating a standalone progress indicator This section describes how to set up a standalone progress indicator — that is, one that is not initiated by displaying a UserForm — to display the progress of a macro. The macro simply clears the worksheet and writes 20,000 random numbers to a range of cells: Sub GenerateRandomNumbers() ‘ Inserts random numbers on the active worksheet Const RowMax As Integer = 500 Part IV: Working with UserForms492 A simple way to display the progress of a macro is to use Excel’s status bar. The advantage is that it’s very easy to program. However, the disadvantage is that most users aren’t accustomed to watching the status bar and prefer a more visual display. To write text to the status bar, use a statement such as Application.StatusBar = “Please wait...” You can, of course, update the status bar while your macro progresses. For example, if you have a variable named Pct that represents the percent completed, you can write code that periodically executes a statement such as this: Application.StatusBar = “Processing... “ & Pct & “% Completed” When your macro finishes, you must reset the status bar to its normal state with the following statement: Application.StatusBar = False If you don’t reset the status bar, the final message will continue to display. Displaying Progress in the Status Bar 22_044018 ch15.qxp 2/28/07 6:42 PM Page 492 Const ColMax As Integer = 40 Dim r As Integer, c As Integer If TypeName(ActiveSheet) <> “Worksheet” Then Exit Sub Cells.Clear For r = 1 To RowMax For c = 1 To ColMax Cells(r, c) = Int(Rnd * 1000) Next c Next r End Sub After you make a few modifications to this macro (described below), the UserForm, shown in Figure 15-3, displays the progress. Figure 15-3: A UserForm displays the progress of a macro. CD-ROM This example, named progress indicator1.xlsm, is available on the companion CD-ROM. BUILDING THE STANDALONE PROGRESS INDICATOR USERFORM Follow these steps to create the UserForm that will be used to display the progress of your task: 1. Insert a new UserForm and change its Caption property setting to Progress. 2. Add a Frame control and name it FrameProgress. Chapter 15: Advanced UserForm Techniques 493 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 493 3. Add a Label control inside the Frame and name it LabelProgress. Remove the label’s caption and make its background color (BackColor property) something that will stand out. The label’s size and placement don’t matter for now. 4. Add another label above the frame to describe what’s going on (optional). In this exam- ple, the label reads, Entering random numbers... 5. Adjust the UserForm and controls so that they look something like Figure 15-4. Figure 15-4: This UserForm will serve as a progress indicator. You can, of course, apply any other type of formatting to the controls. For example, I changed the SpecialEffect property for the Frame control to make it “sunken.” CREATING THE EVENT HANDLER PROCEDURES FOR THE STANDALONE PROGRESS INDICATOR The trick here involves running a procedure automatically when the UserForm is dis- played. One option is to use the Initialize event. However, this event occurs before the UserForm is actually displayed, so it’s not appropriate. The Activate event, on the other hand, is triggered when the UserForm is displayed, so it’s perfect for this application. Insert the following procedure in the code window for the UserForm. This procedure simply calls a procedure named GenerateRandomNumbers when the UserForm is displayed. This procedure, which is stored in a VBA module, is the actual macro that runs while the progress indicator is displayed. Private Sub UserForm_Activate() Call GenerateRandomNumbers End Sub The modified version of the GenerateRandomNumber procedure (which was presented earlier) follows. Notice that additional code keeps track of the progress and stores it in a variable named PctDone. Part IV: Working with UserForms494 22_044018 ch15.qxp 2/28/07 6:42 PM Page 494 Sub GenerateRandomNumbers() ‘ Inserts random numbers on the active worksheet Dim Counter As Integer Const RowMax As Integer = 500 Const ColMax As Integer = 40 Dim r As Integer, c As Integer Dim PctDone As Single If TypeName(ActiveSheet) <> “Worksheet” Then Exit Sub Cells.Clear Counter = 1 For r = 1 To RowMax For c = 1 To ColMax Cells(r, c) = Int(Rnd * 1000) Counter = Counter + 1 Next c PctDone = Counter / (RowMax * ColMax) Call UpdateProgress(PctDone) Next r Unload UserForm1 End Sub The GenerateRandomNumbers procedure contains two loops. Within the inner loop is a call to the UpdateProgress procedure, which takes one argument (the PctDone vari- able, which represents the progress of the macro). PctDone will contain a value between 0 and 100. Sub UpdateProgress(Pct) With UserForm1 .FrameProgress.Caption = Format(Pct, “0%”) .LabelProgress.Width = Pct * (.FrameProgress.Width - 10) .Repaint End With End Sub CREATING THE START-UP PROCEDURE FOR A STANDALONE PROGRESS INDICATOR All that’s missing is a procedure to display the UserForm. Enter the following procedure in a VBA module: Sub ShowUserForm() With UserForm1 .LabelProgress.Width = 0 .Show End With End Sub Chapter 15: Advanced UserForm Techniques 495 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 495 TIP An additional accoutrement is to make the progress bar color match the workbook’s cur- rent theme. To do so, just add this statement to the ShowUserForm procedure: .LabelProgress.BackColor = ActiveWorkbook.Theme. _ ThemeColorScheme.Colors(msoThemeAccent1) HOW THE STANDALONE PROGRESS INDICATOR WORKS When you execute the ShowUserForm procedure, the Label object’s width is set to 0. Then the Show method of the UserForm1 object displays the UserForm (which is the progress indicator). When the UserForm is displayed, its Activate event is triggered, which executes the GenerateRandomNumbers procedure. The GenerateRandomNumbers procedure contains code that calls the UpdateProgress procedure every time the r loop counter variable changes. Notice that the UpdateProgress procedure uses the Repaint method of the UserForm object. Without this statement, the changes to the label would not be updated. Before the GenerateRandomNumbers procedure ends, the last statement unloads the UserForm. To customize this technique, you need to figure out how to determine the percentage com- pleted and assign it to the PctDone variable. This will vary, depending on your application. If your code runs in a loop (as in this example), determining the percentage completed is easy. If your code is not in a loop, you might need to estimate the progress completed at various points in your code. Showing a progress indicator by using a MultiPage control In the preceding example, the macro was not initiated by a UserForm. In many cases, your lengthy macro is kicked off when the user clicks the OK button on a UserForm. The tech- nique that I describe in this section is a better solution and assumes the following: • Your project is completed and debugged. • Your project uses a UserForm (without a MultiPage control) to initiate a lengthy macro. • You have a way to gauge the progress of your macro. CD-ROM The companion CD-ROM contains an example that demonstrates this technique. The file is named progress indicator2.xlsm. Like the previous example, this one enters random numbers into a worksheet. The differ- ence here is that the application contains a UserForm that allows the user to specify the number of rows and columns for the random numbers (see Figure 15-5). Part IV: Working with UserForms496 22_044018 ch15.qxp 2/28/07 6:42 PM Page 496 Figure 15-5: The user specifies the number of rows and columns for the random numbers. MODIFYING YOUR USERFORM FOR A PROGRESS INDICATOR WITH A MULTIPAGE CONTROL This step assumes that you have a UserForm all set up. You’ll add a MultiPage con- trol. The first page of the MultiPage control will contain all your original UserForm controls. The second page will contain the controls that display the progress indicator. When the macro begins executing, VBA code will change the Value property of the MultiPage control. This will effectively hide the original controls and display the progress indicator. The first step is to add a MultiPage control to your UserForm. Then move all the existing controls on the UserForm and paste them to Page1 of the MultiPage control. Next, activate Page2 of the MultiPage control and set it up as shown in Figure 15-6. This is essentially the same combination of controls used in the example in the previous section. Figure 15-6: Page2 of the MultiPage control will display the progress indicator. Chapter 15: Advanced UserForm Techniques 497 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 497 1. Add a Frame control and name it FrameProgress. 2. Add a Label control inside the Frame and name it LabelProgress. Remove the label’s caption and make its background color red. 3. Add another label to describe what’s going on (optional). 4. Next, activate the MultiPage control itself (not a page on the control) and set its Style property to 2 – fmTabStyleNone. (This will hide the tabs.) You’ll probably need to adjust the size of the MultiPage control to account for the fact that the tabs are not displayed. TIP The easiest way to select the MultiPage control when the tabs are hidden is to use the drop-down list in the Properties window. INSERTING THE UPDATEPROGRESS PROCEDURE FOR A PROGRESS INDICATOR WITH A MULTIPAGE CONTROL Insert the following procedure in the code module for the UserForm: Sub UpdateProgress(Pct) With UserForm1 .FrameProgress.Caption = Format(Pct, “0%”) .LabelProgress.Width = Pct * (.FrameProgress.Width - 10) .Repaint End With End Sub The UpdateProgress procedure is called from the macro that’s executed when the user clicks the OK button, and it performs the updating of the progress indicator. MODIFYING YOUR PROCEDURE FOR A PROGRESS INDICATOR WITH A MULTIPAGE CONTROL You need to modify the procedure that is executed when the user clicks the OK Button — the Click event handler procedure for the button named OKButton_Click. First, insert the following statement at the top of your procedure: MultiPage1.Value = 1 This statement activates Page2 of the MultiPage control (the page that displays the progress indicator). In the next step, you’re pretty much on your own. You need to write code to calculate the percent completed and assign this value to a variable named PctDone. Most likely, this calculation will be performed inside of a loop. Then insert the following statement, which will update the progress indicator: Call UpdateProgress(PctDone) Part IV: Working with UserForms498 22_044018 ch15.qxp 2/28/07 6:42 PM Page 498 HOW A PROGRESS INDICATOR WITH A MULTIPAGE CONTROL WORKS This technique is very straightforward and, as you’ve seen, it involves only one UserForm. The code switches pages of the MultiPage control and converts your normal dialog box into a progress indicator. Because the MultiPage tabs are hidden, it doesn’t even resem- ble a MultiPage control. Showing a progress indicator without using a MultiPage control The example in this section is similar to the example in the preceding section. However, this technique is simpler because it doesn’t use a MultiPage control. Rather, the progress indi- cator is stored at the bottom of the UserForm — but the UserForm’s height is reduced so that the progress indicator controls are not visible. When it’s time to display the progress indica- tor, the UserForm’s height is increased, which makes the progress indicator visible. CD-ROM The companion CD-ROM contains an example that demonstrates this technique. The file is named progress indicator3.xlsm. Figure 15-7 shows the UserForm in the VBE. The Height property of the UserForm is 172. However, before the UserForm is displayed, VBA code changes the Height to 124 (which means the progress indicator controls are not visible to the user). When the user clicks OK, VBA code changes the Height property to 172 with the following statement: Me.Height = 172 Figure 15-8 shows the UserForm with the progress indicator section unhidden. Figure 15-7: The progress indicator will be hidden by reducing the height of the UserForm. Chapter 15: Advanced UserForm Techniques 499 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 499 Figure 15-8: The progress indicator in action. Creating Wizards Many applications incorporate wizards to guide users through an operation. Excel’s Text Import Wizard is a good example. A wizard is essentially a series of dialog boxes that solicit information from the user. Usually, the user’s choices in earlier dialog boxes influence the contents of later dialog boxes. In most wizards, the user is free to go forward or backward through the dialog box sequence or to click the Finish button to accept all defaults. You can create wizards by using VBA and a series of UserForms. However, I’ve found that the most efficient way to create a wizard is to use a single UserForm and a MultiPage control with the tabs hidden. Figure 15-9 shows an example of a simple four-step wizard, which consists of a single UserForm that contains a MultiPage control. Each step of the wizard displays a different page in the MultiPage control. CD-ROM The wizard example in this section is available on the companion CD-ROM. The file is named wizard demo.xlsm. The sections that follow describe how I created the sample wizard. Setting up the MultiPage control for the wizard Start with a new UserForm and add a MultiPage control. By default, this control contains two pages. Right-click the MultiPage tab and insert enough new pages to handle your wiz- ard (one page for each wizard step). The example on the CD-ROM is a four-step wizard, so the MultiPage control has four pages. The names of the MultiPage tabs are irrelevant because they will not be seen. The MultiPage control’s Style property will eventually be set to 2 - fmTabStyleNone. Part IV: Working with UserForms500 22_044018 ch15.qxp 2/28/07 6:42 PM Page 500 Figure 15-9: This four-step wizard uses a MultiPage control. TIP While working on the UserForm, you’ll want to keep the MultiPage tabs visible to make it easier to access various pages. Next, add the desired controls to each page of the MultiPage control. This will, of course, vary depending on your application. You might need to resize the MultiPage control while you work in order to have room for the controls. Adding the buttons to the wizard UserForm Now add the buttons that control the progress of the wizard. These buttons are placed out- side the MultiPage control because they are used while any of the pages are displayed. Most wizards have four buttons: • Cancel: Cancels the wizard and performs no action. • Back: Returns to the previous step. During Step 1 of the wizard, this button should be disabled. • Next: Advances to the next step. During the last wizard step, this button should be disabled. • Finish: Finishes the wizard. Chapter 15: Advanced UserForm Techniques 501 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 501 NOTE In some cases, the user is allowed to click the Finish button at any time and accept the defaults for items that were skipped over. In other cases, the wizard requires a user response for some items. If this is the case, the Finish button is disabled until all required input is made. The example on the CD-ROM requires an entry in the TextBox in Step 1. In the example, these CommandButtons are named CancelButton, BackButton, NextButton, and FinishButton. Programming the wizard buttons Each of the four wizard buttons requires a procedure to handle its Click event. The event handler for CancelButton follows. This procedure uses a MsgBox function (see Figure 15-10) to verify that the user really wants to exit. If the user clicks the Yes button, the UserForm is unloaded with no action taken. This type of verification, of course, is optional. Private Sub CancelButton_Click() Dim Msg As String Dim Ans As Integer Msg = “Cancel the wizard?” Ans = MsgBox(Msg, vbQuestion + vbYesNo, APPNAME) If Ans = vbYes Then Unload Me End Sub Figure 15-10: Clicking the Cancel button displays a confirmation message box. The event handler procedures for the Back and Next buttons follow: Private Sub BackButton_Click() MultiPage1.Value = MultiPage1.Value - 1 UpdateControls End Sub Private Sub NextButton_Click() MultiPage1.Value = MultiPage1.Value + 1 UpdateControls End Sub Part IV: Working with UserForms502 22_044018 ch15.qxp 2/28/07 6:42 PM Page 502 These two procedures are very simple. They change the Value property of the MultiPage control and then call another procedure named UpdateControls (which follows). The UpdateControls procedure is responsible for enabling and disabling the BackButton and NextButton controls. Sub UpdateControls() Select Case MultiPage1.Value Case 0 BackButton.Enabled = False NextButton.Enabled = True Case MultiPage1.Pages.Count - 1 BackButton.Enabled = True NextButton.Enabled = False Case Else BackButton.Enabled = True NextButton.Enabled = True End Select ‘ Update the caption Me.Caption = APPNAME & “ Step “ _ & MultiPage1.Value + 1 & “ of “ _ & MultiPage1.Pages.Count ‘ The Name field is required If tbName.Text = “” Then FinishButton.Enabled = False Else FinishButton.Enabled = True End If End Sub The procedure changes the UserForm’s caption to display the current step and the total number of steps. APPNAME is a public constant, defined in Module1. The procedure then examines the name field on the first page (a TextBox named tbName). This is a required field, so the user can’t click the Finish button if it’s empty. If the TextBox is empty, the FinishButton is disabled; otherwise, it’s enabled. Programming dependencies in a wizard In most wizards, a user’s response on a particular step can affect what’s displayed in a subsequent step. In this example, the user indicates which products he or she uses in Step 3 and then rates those products in Step 4. The OptionButtons for a product’s rating are visible only if the user has indicated a particular product. Chapter 15: Advanced UserForm Techniques 503 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 503 Programmatically, this is accomplished by monitoring the MultiPage’s Change event. Whenever the value of the MultiPage is changed (by clicking the Back or Next button), the MultiPage1_Change procedure is executed. If the MultiPage control is on the last tab (Step 4), the procedure examines the values of the CheckBox controls in Step 3 and makes the appropriate adjustments in Step 4. In this example, the code uses two arrays of controls — one for the product CheckBox con- trols (Step 3) and one for the Frame controls (Step 4). The code uses a For-Next loop to hide the Frames for the products that are not used and then adjusts their vertical positioning. If none of the check boxes in Step 3 is checked, everything in Step 4 is hidden except a TextBox that displays Click Finish to exit (if a name is entered in Step 1) or A name is required in Step 1 (if a name is not entered in Step 1). The MultiPage1_Change procedure follows: Private Sub MultiPage1_Change() ‘ Set up the Ratings page? If MultiPage1.Value = 3 Then ‘ Create an array of CheckBox controls Dim ProdCB(1 To 3) As MSForms.CheckBox Set ProdCB(1) = cbExcel Set ProdCB(2) = cbWord Set ProdCB(3) = cbAccess ‘ Create an array of Frame controls Dim ProdFrame(1 To 3) As MSForms.Frame Set ProdFrame(1) = FrameExcel Set ProdFrame(2) = FrameWord Set ProdFrame(3) = FrameAccess TopPos = 22 FSpace = 8 AtLeastOne = False ‘ Loop through all products For i = 1 To 3 If ProdCB(i) Then ProdFrame(i).Visible = True ProdFrame(i).Top = TopPos TopPos = TopPos + ProdFrame(i).Height + FSpace AtLeastOne = True Else ProdFrame(i).Visible = False End If Next i ‘ Uses no products? Part IV: Working with UserForms504 22_044018 ch15.qxp 2/28/07 6:42 PM Page 504 If AtLeastOne Then lblHeadings.Visible = True Image4.Visible = True lblFinishMsg.Visible = False Else lblHeadings.Visible = False Image4.Visible = False lblFinishMsg.Visible = True If tbName = “” Then lblFinishMsg.Caption = _ “A name is required in Step 1.” Else lblFinishMsg.Caption = _ “Click Finish to exit.” End If End If End If End Sub Performing the task with the wizard When the user clicks the Finish button, the wizard performs its task: transferring the infor- mation from the UserForm to the next empty row in the worksheet. This procedure, named FinishButton_Click , is very straightforward. It starts by determining the next empty worksheet row and assigns this value to a variable (r). The remainder of the procedure simply translates the values of the controls and enters data into the worksheet. Private Sub FinishButton_Click() r = Application.WorksheetFunction. _ CountA(Range(“A:A”)) + 1 ‘ Insert the name Cells(r, 1) = tbName.Text ‘ Insert the gender Select Case True Case obMale: Cells(r, 2) = “Male” Case obFemale: Cells(r, 2) = “Female” Case obNoAnswer: Cells(r, 2) = “Unknown” End Select ‘ Insert usage Cells(r, 3) = cbExcel Cells(r, 4) = cbWord Chapter 15: Advanced UserForm Techniques 505 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 505 Cells(r, 5) = cbAccess ‘ Insert ratings If obExcel1 Then Cells(r, 6) = “” If obExcel2 Then Cells(r, 6) = 0 If obExcel3 Then Cells(r, 6) = 1 If obExcel4 Then Cells(r, 6) = 2 If obWord1 Then Cells(r, 7) = “” If obWord2 Then Cells(r, 7) = 0 If obWord3 Then Cells(r, 7) = 1 If obWord4 Then Cells(r, 7) = 2 If obAccess1 Then Cells(r, 8) = “” If obAccess2 Then Cells(r, 8) = 0 If obAccess3 Then Cells(r, 8) = 1 If obAccess4 Then Cells(r, 8) = 2 ‘ Unload the form Unload Me End Sub After you test your wizard, and everything is working properly, you can set the MultiPage control’s Style property to 2 - fmTabStyleNone to hide the tabs. Emulating the MsgBox Function VBA’s MsgBox function is a bit unusual because, unlike most functions, it displays a dialog box. But, similar to other functions, it also returns a value: an integer that represents which button the user clicked. This section describes a custom function that I created that emulates VBA’s MsgBox func- tion. On first thought, creating such a function might seem rather easy. Think again! The MsgBox function is extraordinarily versatile because of the arguments that it accepts. Consequently, creating a function to emulate MsgBox is no small feat. NOTE The point of this exercise is not to create an alternative messaging function. Rather, it’s to demonstrate how to develop a relatively complex function that also incorporates a UserForm. However, some people might like the idea of being able to customize their messages. If so, you’ll find that this function is very easy to customize. For example, you can change the font, colors, button text, and so on. Part IV: Working with UserForms506 22_044018 ch15.qxp 2/28/07 6:42 PM Page 506 I named my pseudo-MsgBox function MyMsgBox. The emulation is close, but not perfect. The MyMsgBox function has the following limitations: • It does not support the Helpfile argument (which adds a Help button that, when clicked, opens a Help file). • It does not support the Context argument (which specifies the context ID for the Help file). • It does not support the system modal option, which puts everything in Windows on hold until you respond to the dialog box. • It does not play a sound when it is called. The syntax for MyMsgBox is MyMsgBox(prompt[, buttons] [, title]) This syntax is exactly the same as the MsgBox syntax except that it doesn’t use the last two optional arguments (Helpfile and Context). MyMsgBox also uses the same prede- fined constants as MsgBox: vbOKOnly, vbQuestion, vbDefaultButton1, and so on. NOTE If you’re not familiar with the VBA MsgBox function, consult the Help system to become familiar with its arguments. MsgBox emulation: MyMsgBox code The MyMsgBox function uses a UserForm named MyMsgBoxForm. The function itself, which follows, is very short. The bulk of the work is done in the UserForm_Initialize procedure. CD-ROM The complete code for the MyMsgBox function is too lengthy to list here, but it’s avail- able in a workbook named msgbox emulation.xlsm, available on the companion CD-ROM. The workbook is set up so you can easily try various options. Public Prompt1 As String Public Buttons1 As Integer Public Title1 As String Public UserClick As Integer Function MyMsgBox(ByVal Prompt As String, _ Optional ByVal Buttons As Integer, _ Chapter 15: Advanced UserForm Techniques 507 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 507 Optional ByVal Title As String) As Integer Prompt1 = Prompt Buttons1 = Buttons Title1 = Title MyMsgBoxForm.Show MyMsgBox = UserClick End Function Figure 15-11 shows MyMsgBox in use. It looks very similar to the VBA message box, but I used a different font for the message text (Calibri 12-point bold). Figure 15-11: The result of the MsgBox emulation function. Here’s the code that I used to execute the function: Prompt = “You are about to wipe out your entire hard drive.” Prompt = Prompt & vbCrLf & vbCrLf & “OK to continue?” Buttons = vbQuestion + vbYesNo Title = “We have a problem” Ans = MyMsgBox(Prompt, Buttons, Title) NOTE This example, of course, does not really wipe out your entire hard drive. How the MyMsgBox function works Notice the use of four Public variables. The first three (Prompt1, Buttons1, and Title1) represent the arguments that are passed to the function. The other variable (UserClick) represents the values returned by the function. The UserForm_Initialize procedure needs a way to get this information and send it back to the function, and using Public variables is the only way to accomplish that. The UserForm (shown in Figure 15-12) contains four Image controls (one for each of the four possible icons), three CommandButton controls, and a TextBox control. Part IV: Working with UserForms508 22_044018 ch15.qxp 2/28/07 6:42 PM Page 508 Figure 15-12: The UserForm for the MyMsgBox function. The code in the UserForm_Initialize procedure examines the arguments and does the following: • Determines which, if any, image to display (and hides the others) • Determines which button(s) to display (and hides the others) • Determines which button is the default button • Centers the buttons in the dialog box • Determines the captions for the CommandButtons • Determines the position of the text within the dialog box • Determines how wide to make the dialog box (by using an API function call to get the video resolution) • Determines how tall to make the dialog box • Displays the UserForm Three additional event handler procedures are included (one for each CommandButton). These routines determine which button was clicked and return a value for the function by setting a value for the UserClick variable. Interpreting the second argument (buttons) is a bit challenging. This argument can con- sist of a number of constants added together. For example, the second argument can be something like this: VbYesNoCancel + VbQuestion + VbDefaultButton3 Chapter 15: Advanced UserForm Techniques 509 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 509 This argument creates a three-button MsgBox (with Yes, No, and Cancel buttons), displays the Question icon, and makes the third button the default button. The actual argument is 547 (3 + 32 + 512). The challenge was pulling three pieces of information from a single number. The solution involves converting the argument to a binary number and then examining specific bits. For example, 547 in binary is 1000100011. Binary digits 4 through 6 determine the image dis- played; digits 8 through 10 determine which buttons to display; and digits 1 and 2 deter- mine which button is the default button. Using the MyMsgBox function in the MsgBox emulation To use this function in your own project, export the MyMsgBoxMod module and the MyMsgBoxForm UserForm. Then import these two files into your project. You can then use the MyMsgBox function in your code just as you’d use the MsgBox function. A UserForm with Movable Controls I’m not sure of the practical significance of this technique, but the example in this section will help you understand mouse-related events. The UserForm shown in Figure 15-13 con- tains three Image controls. The user can use the mouse to drag these images around in the dialog box. Figure 15-13: The three Image controls can be dragged and rearranged by using the mouse. Part IV: Working with UserForms510 22_044018 ch15.qxp 2/28/07 6:42 PM Page 510 CD-ROM This example is available on the companion CD-ROM. The file is named move controls.xlsm. Each of the Image controls has two associated event procedures: MouseDown and MouseMove. The event procedures for the Image1 control are shown here (the others are identical except for the control names). Private Sub Image1_MouseDown(ByVal Button As Integer, _ ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) ‘ Starting position when button is pressed OldX = X OldY = Y Image1.ZOrder 0 End Sub Private Sub Image1_MouseMove(ByVal Button As Integer, _ ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) ‘ Move the image If Button = 1 Then Image1.Left = Image1.Left + (X - OldX) Image1.Top = Image1.Top + (Y - OldY) End If End Sub When the mouse button is pressed, the MouseDown event occurs, and the X and Y positions of the mouse pointer are stored. Two public variables are used to keep track of the original position of the controls: OldX and OldY. This procedure also changes the ZOrder property, which puts the image “on top” of the others. When the mouse is being moved, the MouseMove event occurs repeatedly. The event proce- dure checks the mouse button. If the Button argument is 1, it means that the left mouse button is depressed. If so, then the Image control is shifted relative to its old position. Also, notice that the mouse pointer changes when it’s over an image. That’s because the MousePointer property is set to 15 - fmMousePointerSizeAll. This mouse pointer style is commonly used to indicate that something can be moved. A UserForm with No Title Bar Excel provides no direct way to display a UserForm without its title bar. But this feat is possible with the help of a few API functions. Figure 15-14 shows a UserForm with no title bar. Another example of a UserForm without a title bar is in Figure 15-15. This dialog box con- tains an Image control and a CommandButton control. Chapter 15: Advanced UserForm Techniques 511 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 511 Figure 15-14: This UserForm lacks a title bar. Figure 15-15: Another UserForm without a title bar. CD-ROM Both of these examples are in a workbook named no title bar.xlsm, which is avail- able on the companion CD-ROM. The CD also contains another version of the splash screen example presented in Chapter 14. This version, named splash screen2.xlsm, displays the UserForm without a title bar. Displaying a UserForm without a title bar requires four windows API functions: GetWindowLong, SetWindowLong, DrawMenuBar, and FindWindowA (see the example file on the CD for the function declaration) The UserForm_Initialize procedure calls these functions: Private Sub UserForm_Initialize() Dim lngWindow As Long, lFrmHdl As Long lFrmHdl = FindWindowA(vbNullString, Me.Caption) lngWindow = GetWindowLong(lFrmHdl, GWL_STYLE) Part IV: Working with UserForms512 22_044018 ch15.qxp 2/28/07 6:42 PM Page 512 lngWindow = lngWindow And (Not WS_CAPTION) Call SetWindowLong(lFrmHdl, GWL_STYLE, lngWindow) Call DrawMenuBar(lFrmHdl) End Sub One problem is that, without a title bar, the user has no way to reposition the dialog box. The solution is to use the MouseDown and MouseMove events, as described in the preced- ing section. NOTE Because the FindWindowA function uses the UserForm’s caption, this technique will not work if the Caption property is set to an empty string. Simulating a Toolbar with a UserForm Creating a custom toolbar in versions prior to Excel 2007 was relatively easy. With Excel 2007, it’s impossible. More accurately, it’s still possible to create a custom toolbar with VBA, but Excel ignores many of your VBA instructions. In Excel 2007, all custom toolbars are displayed in the Add-Ins ➪ Custom Toolbars Ribbon group. These toolbars cannot be moved, floated, resized, or docked. This section describes how to create a toolbar alternative: A modeless UserForm that simu- lates a floating toolbar. Figure 15-16 shows a UserForm that may substitute for a toolbar. Figure 15-16: A UserForm set up to function as a toolbar. CD-ROM This example, named simulated toolbar.xlm, is available on the companion CD-ROM. Chapter 15: Advanced UserForm Techniques 513 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 513 The UserForm contains eight Image controls, and each executes a macro. Figure 15-17 shows the UserForm in the VBE. Notice that: • The controls are not aligned. • The UserForm is not the final size. • The title bar is the standard size. Figure 15-17: The UserForm that simulates a toolbar. The VBA code takes care of the cosmetic details. It aligns the controls and adjusts the size of the UserForm so there’s no wasted space. In addition, the code uses Windows API func- tions to make the UserForm’s title bar smaller — just like a real toolbar. To make the UserForm look even more like a toolbar, I also set the ControlTipText property of each Image control — which displays a very toolbar-like tooltip when the mouse is hovered over the control. If you open the file on the CD-ROM, you’ll also notice that the images change slightly when the mouse is hovered over them. That’s because each Image control has an associ- ated MouseMove event handler that changes the SpecialEffect property. Here’s the MouseMove event handler procedure for Image1 (the others are identical): Private Sub Image1_MouseMove(ByVal Button As Integer, _ ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) Call NoRaise Image1.SpecialEffect = fmSpecialEffectRaised End Sub Part IV: Working with UserForms514 22_044018 ch15.qxp 2/28/07 6:42 PM Page 514 This procedure calls the NoRaise procedure, which turns off the raised special effect for each control. Private Sub NoRaise() ‘ Remove the raised effect from all controls Dim ctl As Control For Each ctl In Controls ctl.SpecialEffect = fmSpecialEffectFlat Next ctl End Sub The net effect is that the user gets some visual feedback when the mouse moves over a control — just like a real toolbar. The toolbar simulation only goes so far, however. It’s not possible to resize the UserForm (for example, make the images display vertically rather than horizontally). And, of course, it’s not possible to dock the pseudo-toolbar to one of the Excel window borders. TIP The images displayed on the controls are characters from the Wingding font. I used Excel’s Insert ➪ Text ➪ Symbol command to enter the character into a cell. Then I copied it to the Clipboard and pasted it into the Picture property in the Properties box. This is a quick and easy way to add images to UserForm controls. A Resizable UserForm Excel uses several resizable dialog boxes. For example, the Name Manager dialog box can be resized by clicking and dragging the bottom-right corner. If you would like to create a resizable UserForm, you’ll quickly discover that there’s no direct way to do it. One solution is to resort to Windows API calls. That method works, but it’s complicated to set up. In this section, I present a much simpler technique for creating a user-resizable UserForm. NOTE Credit for this technique goes to Andy Pope, an Excel expert and Microsoft MVP who lives in the UK. Andy is one of the most creative Excel developers I’ve ever met. For a real treat (and lots of interesting downloads), visit his Web site at http://andypope.info. Figure 15-18 shows the UserForm that’s described in this section. It contains a ListBox control that displays data from a worksheet. Notice the scrollbars on the ListBox. That means there’s a lot more information that doesn’t fit. Also, notice the bottom-right corner of the dialog box. It displays a (perhaps) familiar sizing control. Chapter 15: Advanced UserForm Techniques 515 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 515 Figure 15-18: This is a resizable UserForm. Figure 15-19 shows the same UserForm after being resized by the user. Notice that the size of the ListBox is also increased, and the Close button remains in the same relative posi- tion. You can stretch this UserForm to the limits of your monitor. A UserForm can have a maximum width and height of 12,287.25 units. The minimum height is 24.75 units (equal to the height of the title bar), and the minimum width is 105 units. Figure 15-19: The UserForm after being increased in size. Part IV: Working with UserForms516 22_044018 ch15.qxp 2/28/07 6:42 PM Page 516 CD-ROM This example is available on the companion CD-ROM. The filename is resizable user- form.xlsm. The trick here involves a Label control, which is added to the UserForm at runtime. The sizing control at the bottom-right corner is actually a Label control that displays a single character: The letter o (character 111) from the Marlett font, character set 2. This control (named objResizer) is added to the UserForm in the UserForm_Initialize procedure: Private Sub UserForm_Initialize() ‘ Add a resizing control to bottom right corner of UserForm Set objResizer = Me.Controls.Add(“Forms.label.1”, MResizer, True) With objResizer .Caption = Chr(111) .Font.Name = “Marlett” .Font.Charset = 2 .Font.Size = 14 .BackStyle = fmBackStyleTransparent .AutoSize = True .ForeColor = RGB(100, 100, 100) .MousePointer = fmMousePointerSizeNWSE .ZOrder .Top = Me.InsideHeight - .Height .Left = Me.InsideWidth - .Width End With End Sub NOTE Although the Label control is added at runtime, the event-handler code for the object is contained in the module. Including code for an object that doesn’t exist does not pre- sent a problem. This technique relies on these facts: • The user can move a control on a UserForm (see “A UserForm with Movable Controls,” earlier in this chapter). • Events exist that can identify mouse movements and pointer coordinates. Specifically, these events are MouseDown and MouseMove. • VBA code can change the size of a UserForm at runtime, but a user cannot. Chapter 15: Advanced UserForm Techniques 517 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 517 Do a bit of creative thinking about these facts, and you see that it’s possible to translate the user’s movement of a Label control into information that can be used to resize a UserForm. When the user clicks the objResizer Label object, the objResizer_MouseDown event- handler procedure is executed: Private Sub objResizer_MouseDown(ByVal Button As Integer, _ ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) If Button = 1 Then LeftResizePos = X TopResizePos = Y End If End Sub This procedure executes only if the left mouse button is pressed (that is, the Button argu- ment is 1) and the cursor is on the objResizer label. The X and Y mouse coordinates at the time of the button click are stored in module-level variables: LeftResizePos and TopResizePos. Subsequent mouse movements fire the MouseMove event, and the objResizer_ MouseMove event handler kicks into action. Here’s an initial take on this procedure: Private Sub objResizer_MouseMove(ByVal Button As Integer, _ ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) If Button = 1 Then With objResizer .Move .Left + X - LeftResizePos, .Top + Y - TopResizePos Me.Width = Me.Width + X - LeftResizePos Me.Height = Me.Height + Y - TopResizePos .Left = Me.InsideWidth - .Width .Top = Me.InsideHeight - .Height End With End Sub If you study the code, you’ll see that the UserForm’s Width and Height properties are adjusted, based on the movement of the objResizer Label control. Figure 15-20 shows how the UserForm looks after the user moves the Label control down and to the right. The problem, of course, is that the other controls in the UserForm don’t respond to the UserForm’s new size. The ListBox should be expanded, and the CommandButton should be relocated so it remains in the lower-left corner. Part IV: Working with UserForms518 22_044018 ch15.qxp 2/28/07 6:42 PM Page 518 Figure 15-20: VBA code converts Label control movements into new Width and Height properties for the UserForm. More VBA code is needed to adjust the controls in the UserForm when the UserForm size is changed. The location for this new code is in the objResizer_MouseMove event han- dler procedure. The statements that follow do the job: ‘ Adjust the ListBox On Error Resume Next With ListBox1 .Width = Me.Width - 22 .Height = Me.Height - 100 End With On Error GoTo 0 ‘ Adjust the Close Button With CloseButton .Left = Me.Width - 70 .Top = Me.Height - 54 End With These two controls are adjusted relative to the UserForm’s size (that is, Me). After adding this new code, the dialog box works like a charm. The user can make it as large as needed, and the controls adjust. It should be clear that the most challenging part of creating a resizable dialog box is figur- ing out how to adjust the controls. When you have more than two or three controls, things can get very complicated. Chapter 15: Advanced UserForm Techniques 519 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 519 Handling Multiple UserForm Controls with One Event Handler Every CommandButton on a UserForm must have its own procedure to handle its events. For example, if you have two CommandButtons, you’ll need two event handler procedures for the controls’ click events: Private Sub CommandButton1_Click() ‘ Code goes here End Sub Private Sub CommandButton2_Click() ‘ Code goes here End Sub In other words, you cannot assign a macro to execute when any CommandButton is clicked. Each Click event handler is hard-wired to its CommandButton. You can, however, have each event handler call another all-inclusive macro in the event handler procedures, but you’ll need to pass an argument to indicate which button was clicked. In the follow- ing examples, clicking either CommandButton1 or CommandButton2 executes the ButtonClick procedure, and the single argument tells the ButtonClick procedure which button was clicked: Private Sub CommandButton1_Click() Call ButtonClick(1) End Sub Private Sub CommandButton2_Click() Call ButtonClick(2) End Sub If your UserForm has many CommandButtons, setting up all these event handlers can get tedious. You might prefer to have a single procedure that can determine which button was clicked and take the appropriate action. This section describes a way around this limitation by using a class module to define a new class. CD-ROM This example, named multiple buttons.xlsm, is available on the companion CD-ROM. Part IV: Working with UserForms520 22_044018 ch15.qxp 2/28/07 6:42 PM Page 520 The following steps describe how to re-create the example UserForm shown in Fig- ure 15-21. Figure 15-21: Many CommandButtons with a single event-handler procedure. 1. Create your UserForm as usual and add several CommandButtons. (The example on the CD contains 16 CommandButton controls.) This example assumes that the form is named UserForm1. 2. Insert a class module into your project (choose Insert ➪ Class Module), give it the name BtnClass, and enter the following code. You will need to customize the ButtonGroup_Click procedure. Public WithEvents ButtonGroup As MsForms.CommandButton Private Sub ButtonGroup_Click() Msg = “You clicked “ & ButtonGroup.Name & vbCrLf & vbCrLf Msg = Msg & “Caption: “ & ButtonGroup.Caption & vbCrLf Msg = Msg & “Left Position: “ & ButtonGroup.Left & vbCrLf Msg = Msg & “Top Position: “ & ButtonGroup.Top MsgBox Msg, vbInformation, ButtonGroup.Name End Sub TIP You can adapt this technique to work with other types of controls. You need to change the type name in the Public WithEvents declaration. For example, if you have OptionButtons instead of CommandButtons, use a declaration statement like this: Public WithEvents ButtonGroup As MsForms.OptionButton 3. Insert a normal VBA module and enter the following code. This routine simply displays the UserForm: Sub ShowDialog() UserForm1.Show End Sub Chapter 15: Advanced UserForm Techniques 521 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 521 4. In the code module for the UserForm, enter the UserForm_Initialize code that follows. This procedure is kicked off by the UserForm’s Initialize event. Notice that the code excludes a button named OKButton from the button group. Therefore, clicking the OK button does not execute the ButtonGroup_Click procedure. Dim Buttons() As New BtnClass Private Sub UserForm_Initialize() Dim ButtonCount As Integer Dim ctl As Control ‘ Create the Button objects ButtonCount = 0 For Each ctl In UserForm1.Controls If TypeName(ctl) = “CommandButton” Then ‘Skip the OKButton If ctl.Name <> “OKButton” Then ButtonCount = ButtonCount + 1 ReDim Preserve Buttons(1 To ButtonCount) Set Buttons(ButtonCount).ButtonGroup = ctl End If End If Next ctl End Sub After performing these steps, you can execute the ShowDialog procedure to display the UserForm. Clicking any of the CommandButtons (except the OK button) executes the ButtonGroup_Click procedure. Figure 15-22 shows an example of the message displayed when a button is clicked. Figure 15-22: The ButtonGroup_Click procedure describes the button that was clicked. Part IV: Working with UserForms522 22_044018 ch15.qxp 2/28/07 6:42 PM Page 522 Selecting a Color in a UserForm The example in this section is a function that displays a dialog box (similar in concept to the MyMsgBox function, presented earlier). The function, named GetAColor, returns a color value: Public ColorValue As Variant Function GetAColor() As Variant UserForm1.Show GetAColor = ColorValue End Function You can use the GetAColor function with a statement like the following: UserColor = GetAColor() Executing this statement displays the UserForm. The user selects a color and clicks OK. The function then assigns the user’s selected color value to the UserColor variable. The UserForm, shown in Figure 15-23, contains three ScrollBar controls — one for each of the color components (red, green, and blue). The value range for each scrollbar is from 0 to 255. Figure 15-23: This dialog box lets the user select a color by specifying the red, green, and blue components. CD-ROM This example, named getacolor function.xlsm, is available on the companion CD-ROM. The GetAColor UserForm has another twist: It remembers the last color that was selected. When the function ends, the three Scrollbar values are stored in the Windows Registry, using this code (APPNAME is a string defined in Module1): SaveSetting APPNAME, “Colors”, “RedValue”, ScrollBarRed.Value SaveSetting APPNAME, “Colors”, “BlueValue”, ScrollBarBlue.Value SaveSetting APPNAME, “Colors”, “GreenValue”, ScrollBarGreen.Value Chapter 15: Advanced UserForm Techniques 523 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 523 The UserForm_Initialize procedure retrieves these values and assigns them to the scrollbars: ScrollBarRed.Value = GetSetting(APPNAME, “Colors”, “RedValue”, 128) ScrollBarGreen.Value = GetSetting(APPNAME, “Colors”, “GreenValue”, 128) ScrollBarBlue.Value = GetSetting(APPNAME, “Colors”, “BlueValue”, 128) The last argument for the GetSetting function is the default value, which is used if the Registry key is not found. In this case, each color defaults to 128, which produces middle gray. The SaveSetting and GetSetting functions always use this Registry key: HKEY_CURRENT_USER\Software\VB and VBA Program Settings\ Figure 15-24 shows the Registry data, displayed with the Windows Regedit.exe program. Figure 15-24: The user’s ScrollBar values are stored in the Windows Registry and retrieved the next time the GetAColor function is used. CROSS-REFERENCE To learn more about how Excel uses colors, refer to Chapter 30. Displaying a Chart in a UserForm Oddly, Excel provides no direct way to display a chart in a UserForm. You can, of course, copy the chart and paste it to the Picture property of an Image control, but this creates a static image of the chart, so it won’t display any changes that are made to the chart. This section describes a technique to display a chart in a UserForm. Figure 15-25 shows a UserForm with a chart displayed in an Image object. The chart actually resides on a work- sheet, and the UserForm always displays the current chart. This technique works by copy- ing the chart to a temporary graphics file and then using the LoadPicture function to specify that file for the Image control’s Picture property. Part IV: Working with UserForms524 22_044018 ch15.qxp 2/28/07 6:42 PM Page 524 Figure 15-25: With a bit of trickery, a UserForm can display “live” charts. CD-ROM This workbook is available on the companion CD-ROM. The filename is chart in userform.xlsm. General steps to display a chart in a userform To display a chart in a UserForm, follow these general steps: 1. Create your chart or charts as usual. 2. Insert a UserForm and then add an Image control. 3. Write VBA code to save the chart as a GIF file and then set the Image control’s Picture property to the GIF file. You need to use VBA’s LoadPicture function to do this. 4. Add other bells and whistles as desired. For example, the UserForm in the demo file contains controls that let you change the chart type. Alternatively, you could write code to display multiple charts. Saving a chart as a GIF file The following code demonstrates how to create a GIF file (named temp.gif) from a chart (in this case, the first chart object on the sheet named Data): Set CurrentChart = Sheets(“Data”).ChartObjects(1).Chart Fname = ThisWorkbook.Path & “\temp.gif” CurrentChart.Export FileName:=Fname, FilterName:=”GIF” Chapter 15: Advanced UserForm Techniques 525 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 525 Changing the Image control Picture property If the Image control on the UserForm is named Image1, the following statement loads the image (represented by the Fname variable) into the Image control: Image1.Picture = LoadPicture(Fname) NOTE This technique works fine, but you might notice a slight delay when the chart is saved and then retrieved. On a fast system, however, this delay is barely noticeable. An Enhanced Data Form Next is one of the more complex UserForms that you’ll encounter. I designed it as a replacement for Excel’s built-in Data Form, which is shown in Figure 15-26. Figure 15-26: Excel’s Data Form. NOTE Displaying Excel’s Data Form is not easy in Excel 2007. You need to use the Excel Options dialog box, click the Customization tab, and add the Form command from the Commands Not in the Ribbon group. Then, the Form command will appear on your Quick Access Toolbar. Like Excel’s Data Form, my Enhanced Data Form works with a list in a worksheet. But as you can see in Figure 15-27, it has a dramatically different appearance and offers several advantages. Part IV: Working with UserForms526 22_044018 ch15.qxp 2/28/07 6:42 PM Page 526 Figure 15-27: My Enhanced Data Form. About the Enhanced Data Form The Enhanced Data Form features the enhancements listed in Table 15-1. TABLE 15-1 COMPARING THE ENHANCED DATA FORM WITH THE EXCEL DATA FORM Enhanced Data Form Excel Data Form Handles any number of records and fields. Limited to 32 fields. Dialog box can be displayed in any size Dialog box adjusts its size based on the that you like. number of fields. In fact, it can take up the entire screen! Fields can consist of either InputBox or Uses only InputBoxes. ComboBox controls. Record displayed in the dialog box is Doesn’t scroll the screen for you and doesn’t always visible onscreen and is highlighted highlight the current record. so you know exactly where you are. continued Chapter 15: Advanced UserForm Techniques 527 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 527 TABLE 15-1 COMPARING THE ENHANCED DATA FORM WITH THE EXCEL DATA FORM (continued) Enhanced Data Form Excel Data Form At start-up, the dialog box always displays Always starts with the first record in the the record at the active cell. database. When you close the dialog box, the Doesn’t change your selection when you exit. current record is selected for you. Lets you insert a new record at any Adds new records only at the end of the position in the database. database. Includes an Undo button for Data Entry, Includes only a Restore button. Insert Record, Delete Record, and New Record. Search criteria are stored in a separate The search criteria are not always apparent. panel, so you always know exactly what you’re searching for. Supports approximate matches while Excel’s Data Form does not support wildcard searching (*, ?, and #). characters. The complete VBA source code is Data Form is not written in VBA and cannot available, so you can customize it to your be customized. needs. CD-ROM The Enhanced Data Form is a commercial product (sort of). The Excel 2003 version of the add-in is available on the companion CD-ROM, and it can be used and distributed freely. To download a copy of the Excel 2007 version, visit my Web site: http://j-walk. com/ss. If you would like to customize the code or UserForm, access to the complete VBA source is available for a modest fee. Installing the Enhanced Data Form add-in To try out the Enhanced Data Form, install the add-in: 1. Copy the dataform2.xla file from the CD-ROM to a directory on your hard drive. 2. In Excel, choose Office ➪ Excel Options. Part IV: Working with UserForms528 22_044018 ch15.qxp 2/28/07 6:42 PM Page 528 3. In the Excel Options dialog box, click the Add-Ins tab. 4. Select Excel Add-Ins from the Manage drop-down list and click Go to display the Add- Ins dialog box. 5. In the Add-Ins dialog box, click Browse and locate the dataform2.xla in the direc- tory from Step 1. After performing these steps, you can access the Enhanced Data Form by using the Add-Ins ➪ Menu Commands ➪ JWalk Enhanced Data Form command. You can use the Enhanced Data Form to work with any worksheet list or table. NOTE Version 3 of this product will be incorporated into the Ribbon and will also feature a resizable UserForm and a few other new features. The updated version was not ready in time to be included on the CD-ROM. A Puzzle on a UserForm The final example in this chapter is a familiar sliding puzzle, displayed on a UserForm (see Figure 15-28). This puzzle was invented by Noyes Chapman in the late 1800s. Figure 15-28: A sliding tile puzzle in a UserForm. The goal is to arrange the shuffled tiles (CommandButton controls) in numerical order. Click a button next to the empty space, and the button moves to the empty space. The ComboBox control lets the user choose from three configurations: 3x3, 4x4, and 5x5. The New button shuffles the tiles, and a Label control keeps track of the number of moves. This application uses a class module to handle all the button events (see “Handling Multiple UserForm Controls with One Event Handler,” earlier in this chapter). Chapter 15: Advanced UserForm Techniques 529 Part IV 22_044018 ch15.qxp 2/28/07 6:42 PM Page 529 The VBA code is rather lengthy, so it’s not listed here. Here are a few points to keep in mind when examining the code: • The CommandButton controls are added to the UserForm via code. The number and size of the buttons are determined by the ComboBox value. • The tiles are shuffled by simulating a few thousand random clicks on the buttons. Another option is to simply assign random numbers, but that would result in some unsolvable games. • The blank space is actually a CommandButton with its Visible property set to False. • The class module contains one event procedure (MouseUp), which is executed when- ever the user clicks a tile. • When the user clicks a CommandButton tile, its Caption is swapped with the hidden button. No buttons are actually moved. CD-ROM This workbook, named sliding tile puzzle.xlsm, is available on the companion CD-ROM. Part IV: Working with UserForms530 22_044018 ch15.qxp 2/28/07 6:42 PM Page 530 Advanced Programming Techniques Chapter 16 Developing Excel Utilities with VBA Chapter 17 Working with Pivot Tables Chapter 18 Working with Charts Chapter 19 Understanding Excel’s Events Chapter 20 Interacting with Other Applications Chapter 21 Creating and Using Add-Ins VPart 23_044018 pt05.qxp 2/28/07 6:42 PM Page 531 23_044018 pt05.qxp 2/28/07 6:42 PM Page 532 Chapter Developing Excel Utilities with VBA In This Chapter A utility, in general, is something that enhances software, adding useful features or making existing features more accessible. This chapter is about Excel utilities: ◆ Exploring Excel utilities and utilities in general ◆ Why use VBA to develop utilities ◆ What you need to know to develop good utilities ◆ Step-by-step details for developing a useful Excel utility to manipulate text in cells ◆ Where to go for more Excel utilities As you’ll see, creating utilities for Excel is an excellent way to make a great product even better. About Excel Utilities A utility isn’t an end product, such as a quarterly report. Rather, it’s a tool that helps you produce an end product. An Excel utility is (almost always) an add-in that enhances Excel with new features or capabilities. Excel is a great product, but many users soon develop a wish-list of features that they would like to see added to the software. For example, some users 16 533 24_044018 ch16.qxp 2/28/07 6:43 PM Page 533 prefer to turn off the dotted-line page break display, and they want a feature that toggles this attribute so that they don’t have to scroll through the Excel Options dialog box looking for the command. Users who work with dates might want a pop-up calendar feature to facilitate entering dates into cells. And some users desire an easier way to export a range of data to a separate file. These are all examples of features that aren’t currently available in Excel. You can, however, add these features by creating a utility. Utilities don’t need to be complicated. Some of the most useful ones are actually very sim- ple. For example, the following VBA procedure is a utility that toggles the page break dis- play in the active window: Sub TogglePageBreaks() With ActiveSheet .DisplayPageBreaks = Not .DisplayPageBreaks End With End Sub You can store this macro in your Personal Macro Workbook so that it’s always available. Or you may prefer to package your favorite utilities in an add-in. For quicker access, you can assign your utility macros to a shortcut key, a right-click shortcut menu, or even modify the Ribbon. Using VBA to Develop Utilities Excel 5, released in 1992, was the first version of Excel to include VBA. When I received the beta version of Excel 5, I was very impressed by VBA’s potential. VBA was light years ahead of Excel’s powerful (but cryptic) XLM macro language, and I decided that I wanted to explore this new language and see what it was capable of. In an effort to learn VBA, I wrote a collection of Excel utilities by using only VBA. I figured that I would learn the language more quickly if I gave myself a tangible goal. The result was a product that I call the Power Utility Pak for Excel, which is available to you at no charge as a benefit of buying this book (use the coupon in the back of the book to order your copy). I learned several things from my initial efforts on this project: • VBA can be difficult to grasp at first, but it becomes much easier with practice. • Experimentation is the key to mastering VBA. Every project that I undertake usually involves dozens of small coding experiments that eventually lead to a finished product. • VBA enables you to extend Excel in a way that is consistent with Excel’s look and feel, including custom worksheet functions and dialog boxes. And, if you’re willing to step outside of VBA, you can write XML code to customize the Ribbon. • Excel can do almost anything. When you reach a dead end, chances are that another path leads to a solution. It helps if you’re creative and know where to look for help. Part V: Advanced Programming Techniques534 24_044018 ch16.qxp 2/28/07 6:43 PM Page 534 Few other software packages include such an extensive set of tools that enable the end user to extend the software. What Makes a Good Utility? An Excel utility, of course, should ultimately make your job easier or more efficient. But if you’re developing utilities for other users, what makes an Excel utility valuable? I’ve put together a list of elements that are common to good utilities: • It adds something to Excel. This could be a new feature, a way to combine existing fea- tures, or just a way to make an existing feature easier to use. • It’s general in nature. Ideally, a utility should be useful under a wide variety of conditions. Of course, it’s more difficult to write a general-purpose utility than it is to write one that works in a highly defined environment. • It’s flexible. The best utilities provide many options to handle various situations. • It looks, works, and feels like an Excel command. Although adding your own special touch to utilities is tempting, other users will find them easier to use if they look and act like familiar Excel commands and dialog boxes. • It provides help for the user when needed. In other words, the utility requires documenta- tion that’s thorough and accessible. • It traps errors. An end user should never see a VBA error message. Any error messages that appear should be ones that you write. • Users can undo its effects. Users who don’t like the result caused by your utility should be able to reverse their path. Text Tools: The Anatomy of a Utility In this section, I describe an Excel utility that I developed (and that is part of my Power Utility Pak add-in). The Text Tools utility enables the user to manipulate text in a selected range of cells. Specifically, this utility enables the user to do the following: • Change the case of the text (uppercase, lowercase, proper case, sentence case, or toggle case). • Add characters to the text (at the beginning, to the end, or at a specific character position). • Remove characters from the text (from the beginning, from the end, or from a specific position within the string). Chapter 16: Developing Excel Utilities with VBA 535 Part V 24_044018 ch16.qxp 2/28/07 6:43 PM Page 535 • Remove spaces from the text (either all spaces or excess spaces). • Delete characters from text (non-printing characters, alphabetic characters, non- numeric characters, non-alphabetic characters, or numeric characters). Figure 16-1 shows the Text Tools Utility dialog box. Figure 16-1: Use the Text Tools utility to change the case of selected text. CD-ROM The Text Tools utility is available on the CD-ROM that accompanies this book. This is a standalone version of the tool that is included with the Power Utility Pak. The file, named text tools.xlam, is a standard Excel add-in. When installed, it adds a new command to the Ribbon: Home ➪ Utilities ➪ Text Tools. The VBA project is not protected with a password, so you can examine the code to see how it works. Background for Text Tools Excel has many worksheet functions that can manipulate text strings in useful ways. For example, you can uppercase the text in a cell (UPPER), add characters to text (CONCATE- NATE), remove spaces (TRIM), and so on. But to perform any of these operations, you need to write formulas, copy them, convert the formulas to values, and then paste the values over the original text. In other words, Excel doesn’t make it particularly easy to modify text. Wouldn’t it be nice if Excel had some text manipulation tools that didn’t require for- mulas? By the way, many good utility ideas come from statements that begin: “Wouldn’t it be nice if . . .?” Part V: Advanced Programming Techniques536 24_044018 ch16.qxp 2/28/07 6:43 PM Page 536 Project goals for Text Tools The first step in designing a utility is to envision exactly how you want the utility to work. Here’s my original plan, stated in the form of ten goals: • Its main features will be those listed at the beginning of this section. • It will enable the user to specify that the preceding types of changes work with nontext cells as well as with text cells. • It will have the same look and feel of other Excel commands. In other words, it will have a dialog box that looks like Excel’s dialog boxes. • It will be in the form of an add-in and will also be accessible from the Ribbon. • It will operate with the current selection of cells (including multiple selections), and it will enable the user to modify the range selection while the dialog box is displayed. • It will remember the last operation used and display those settings the next time the dialog box is invoked. • It will have no effect on cells that contain formulas. • It will be fast and efficient. For example, if the user selects an entire column, the utility should ignore the empty cells in the column. • It will enable the user to undo the changes. • Comprehensive help will be available. The Text Tools workbook The Text Tools utility is an XLAM add-in file. During development, I worked with the file as a macro-enabled XLSM workbook. When I was satisfied that all was working properly, I saved the workbook as an add-in. The Text Tools workbook consists of the following components: • One worksheet: Every workbook must have at least one worksheet. I take advantage of this fact and use this worksheet to handle the undo procedure (see “Implementing Undo,” later in this chapter). • One VBA module: This module contains public variable and constant declarations, the code to display the UserForm, and the code to handle the undo procedure. • One UserForm: This contains the dialog box. The code that does the actual text manipu- lation work is stored in the code module for the UserForm. Chapter 16: Developing Excel Utilities with VBA 537 Part V 24_044018 ch16.qxp 2/28/07 6:43 PM Page 537 NOTE The file also contains some manual modifications that I made in order to get the com- mand to display on the Ribbon. See “Adding the RibbonX code,” later in this chapter. Unfortunately, it’s not possible to modify Excel’s Ribbon using only VBA. How the Text Tools utility works The Text Tools add-in contains some RibbonX code that creates a new item in the Ribbon: Home ➪ Utilities ➪ Text Tools. Selecting this item executes the StartTextTools proce- dure, which calls the ShowTextToolsDialog procedure. CROSS-REFERENCE To find out why this utility requests both the StartTextTools procedure and the ShowTextToolsDialog procedure, see “Adding the RibbonX code,” later in this chapter. The user can specify various text modifications and click the Apply button to perform them. The changes are visible in the worksheet, and the dialog box remains displayed. Each operation can be undone, or the user can perform additional text modifications. Clicking the Help button displays a help window, and clicking the Close button dismisses the dialog box. Note that this is a modeless dialog box. In other words, you can keep work- ing in Excel while the dialog box is displayed. In that sense, it’s similar to a toolbar. The UserForm for the Text Tools utility When I create a utility, I usually begin by designing the user interface. In this case, it’s the dialog box that’s displayed to the user. Creating the dialog box forces me to think through the project one more time. Part V: Advanced Programming Techniques538 To install an add-in, including the text tools.xlam add-in, follow these steps: 1. Select Office ➪ Excel Options. 2. In the Excel Options dialog box, click the Add-Ins tab. 3. In the drop-down list labeled Manage, select Excel Add-Ins and then click Go to display the Add-Ins dialog box. 4. If the add-in that you want to install is listed in the Add-Ins Available list, place a check mark next to the item. If the add-in is not listed, click Browse to locate the XLAM or XLA add-in file. 5. Click OK, and the add-in will be installed. It will remain installed until you deselect it from the list. Installing an Add-In 24_044018 ch16.qxp 2/28/07 6:43 PM Page 538 Figure 16-2 shows the UserForm for the Text Tools utility. Figure 16-2: The UserForm for the Text Tools utility. Notice that the controls on this UserForm are laid out differently from how they actually appear to the user. That’s because some options use different controls, and the positioning of the controls is handled dynamically in the code. The controls are listed and described next. • The Operation ComboBox: This always appears on the left, and it is used to select the operation to be performed. • Proc1 ComboBox: Most of the text manipulation options use this ComboBox to further specify the operation. • Proc2 ComboBox: Two of the text manipulation options use this ComboBox to specify the operation even further. Specifically, this additional ComboBox is used by Add Text and Remove by Position. • Check box: The Skip Non-Text Cells check box is an option relevant to some of the operations. • Help button: Clicking this CommandButton displays help. • Close button: Clicking this CommandButton unloads the UserForm. • Apply button: Clicking this CommandButton applies the selected text manipulation option. • Progress bar: This consists of a Label control inside a Frame control. • Text box: This text box is used for the Add Text option. Figure 16-3 shows how the UserForm looks for each of the five operations. Notice that the configuration of the controls varies, depending on which option is selected. Chapter 16: Developing Excel Utilities with VBA 539 Part V 24_044018 ch16.qxp 2/28/07 6:43 PM Page 539 Figure 16-3: The UserForm layout changes for each operation. NOTE You’ll notice that this utility violates one of the design rules that I outline earlier in this chapter (see “What Makes a Good Utility?”). Unlike most of Excel’s built-in dialog boxes, the Text Tools utility dialog box does not have an OK or a Cancel button, and clicking the Apply button does not dismiss the dialog box. The original version of Text Tools had an OK button and was designed so that clicking OK performed the task and closed the dia- log box. User feedback, however, convinced me to change the design. Many people, it turns out, like to perform several different manipulations at one time. Therefore, I changed the utility to accommodate user preferences. The Module1 VBA module The Module1 VBA module contains the declarations, a simple procedure that kicks off the utility, and a procedure that handles the undo operation. DECLARATIONS IN THE MODULE1 VBA MODULE Following are the declarations at the top of the Module1 module: Public Const APPNAME As String = “Text Tools Utility” Public Const PROGRESSTHRESHOLD = 2000 Public UserChoices(1 To 8) As Variant ‘stores user’s last choices Public UndoRange As Range ‘ For undoing Public UserSelection As Range ‘For undoing I declare a Public constant containing a string that stores the name of the application. This string is used in the UserForm caption and in various message boxes. Part V: Advanced Programming Techniques540 24_044018 ch16.qxp 2/28/07 6:43 PM Page 540 The PROGRESSTHRESHOLD constant specifies the number of cells that will display the progress indicator. When this constant is 2,000, the progress indicator will be shown only if the utility is working on 2,000 or more cells. The UserChoices array holds the value of each control. This information is stored in the Windows Registry when the user closes the dialog box and is retrieved when the utility is executed again. This is a convenience feature I added because I found that many users tend to perform the same operation every time they use the utility. Two other Range object variables are used to store information used for undoing. THE SHOWTEXTTOOLSDIALOG PROCEDURE IN THE MODULE1 VBA MODULE The ShowTextToolsDialog procedure follows: Sub ShowTextToolsDialog() Dim InvalidContext As Boolean If Val(Application.Version) < 12 Then MsgBox “This utility requires Excel 2007 or later.”, vbCritical Exit Sub End If If ActiveSheet Is Nothing Then InvalidContext = True If TypeName(ActiveSheet) <> “Worksheet” Then InvalidContext = True If InvalidContext Then MsgBox “Select some cells in a range.”, vbCritical, APPNAME Else UserForm1.Show vbModeless End If End Sub As you can see, it’s rather simple. The procedure starts by checking the version of Excel. If the version is prior to Excel 2007, the user is informed that the utility requires Excel 2007 or later. NOTE It’s certainly possible to design this utility so it also works with previous versions. For simplicity, I made this an Excel 2007–only application. If the user is running the appropriate version, the ShowTextToolsDialog procedure checks to make sure that a sheet is active, and then it makes sure that the sheet is a work- sheet. If either of these is not true, the InvalidContext variable is set to True. The If- Then-Else construct checks this variable and displays either a message (see Figure 16-4) or the UserForm. Notice that the Show method uses the vbModeless argument, which makes it a modeless UserForm (that is, the user can keep working in Excel while it is displayed). Notice that the code does not ensure that a range is selected. This additional error han- dling is included in the code that’s executed when the Apply button is clicked. Chapter 16: Developing Excel Utilities with VBA 541 Part V 24_044018 ch16.qxp 2/28/07 6:43 PM Page 541 Figure 16-4: This message is displayed if no workbook is active or if the active sheet is not a worksheet. TIP While I was developing this utility, I assigned a keyboard shortcut (Ctrl+Shift+T) to the ShowTextToolsDialog procedure for testing purposes. That’s because I saved the Ribbon modification task for last, and I needed a way to test the utility. After I added the Ribbon button, I removed the keyboard shortcut. To assign a keyboard shortcut to a macro, press Alt+F8 to display the Macro dialog box. Type ShowTextToolsDialog in the Macro Name box and then click Options. Use the Macro Options dialog box to assign the shortcut key combination. THE UNDOTEXTTOOLS PROCEDURE IN THE MODULE1 VBA MODULE The UndoTextTools procedure is executed when the user clicks the Undo button (or presses Ctrl+Z). This technique is explained later in this chapter (see “Implementing Undo”). The UserForm1 code module All the real work is done by VBA code contained in the code module for UserForm1. Here, I briefly describe each of the procedures in this module. The code is too lengthy to list here, but you can view it by opening the text tools.xlam file on the companion CD-ROM. THE USERFORM_INITIALIZE PROCEDURE IN THE USERFORM1 CODE MODULE This procedure is executed before the UserForm is displayed. It sizes the UserForm and retrieves (from the Windows Registry) the previously selected values for the controls. It also adds the list items to the ComboBox (named ComboBoxOperation) that determines which operation will be performed. These items are: • Change case • Add text • Remove by position • Remove spaces • Delete characters Part V: Advanced Programming Techniques542 24_044018 ch16.qxp 2/28/07 6:43 PM Page 542 THE COMBOBOXOPERATION_CHANGE PROCEDURE IN THE USERFORM1 CODE MODULE This procedure is executed whenever the user selects an item in the ComboBoxOperation. It does the work of displaying or hiding the other controls. For example, if the user selects the Change Case option, the code unhides the second ComboBox control (named ComboProc1) and fills it with the following choices: • UPPER CASE • lower case • Proper Case • Sentence case • tOGGLE cASE THE APPLYBUTTON_CLICK PROCEDURE IN THE USERFORM1 CODE MODULE This procedure is executed when the Apply button is clicked. It does some error checking to ensure that a range is selected and then calls the CreateWorkRange function to make sure empty cells are not included in the cells to be processed. See the upcoming section, “Making the Text Tools utility efficient.” The ApplyButton_Click procedure also calls the SaveForUndo procedure, which saves the current data in case the user needs to undo the operation. See “Implementing Undo,” later in this chapter. The procedure then uses a Select Case construct to call the appropriate procedure to perform the operation. It calls one of the following Sub procedures: • ChangeCase • AddText • RemoveText • RemoveSpaces • RemoveCharacters Some of these procedures make calls to function procedures. For example, the ChangeCase procedure might call the ToggleCase or SentenceCase procedures. THE CLOSEBUTTON_CLICK PROCEDURE IN THE USERFORM1 CODE MODULE This procedure is executed when the Close button is clicked. It saves the current control settings to the Windows Registry and then unloads the UserForm. Chapter 16: Developing Excel Utilities with VBA 543 Part V 24_044018 ch16.qxp 2/28/07 6:43 PM Page 543 THE HELPBUTTON_CLICK PROCEDURE IN THE USERFORM1 CODE MODULE This procedure is executed when the Help button is clicked. It simply displays the Help file (which is a standard compiled HTML help file). Making the Text Tools utility efficient The procedures in the Text Tools utility work by looping through a range of cells. It makes no sense to loop through cells that will not be changed — for example, empty cells and cells that contain a formula. The ApplyButton_Click procedure calls a Function procedure named CreateWorkRange. This function creates and returns a Range object that consists of all non-empty and nonformula cells in the user’s selected range. For example, assume that column A contains text in the range A1:A12. If the user selects the entire column, the CreateWorkRange function would convert that complete column range into a subset that consists of only the non-empty cells (that is, the range A:A would be converted to A1:A12). This makes the code much more efficient because empty cells and formulas need not be included in the loop. The CreateWorkRange function accepts two arguments: • Rng: A Range object that represents the range selected by the user. • TextOnly: A Boolean value. If True, the function returns only text cells. Otherwise, it returns all non-empty cells. Private Function CreateWorkRange(Rng, TextOnly) ‘ Creates and returns a Range object Set CreateWorkRange = Nothing ‘ Single cell, has a formula If Rng.Count = 1 And Rng.HasFormula Then Set CreateWorkRange = Nothing Exit Function End If ‘ Single cell, or single merged cell If Rng.Count = 1 Or Rng.MergeCells = True Then If TextOnly Then If Not IsNumeric(Rng(1).Value) Then Set CreateWorkRange = Rng Exit Function Else Set CreateWorkRange = Nothing Exit Function End If Else If Not IsEmpty(Rng(1)) Then Set CreateWorkRange = Rng Part V: Advanced Programming Techniques544 24_044018 ch16.qxp 2/28/07 6:43 PM Page 544 Exit Function End If End If End If On Error Resume Next Set Rng = Intersect(Rng, Rng.Parent.UsedRange) If TextOnly = True Then Set CreateWorkRange = Rng.SpecialCells(xlConstants, xlTextValues) If Err <> 0 Then Set CreateWorkRange = Nothing On Error GoTo 0 Exit Function End If Else Set CreateWorkRange = Rng.SpecialCells _ (xlConstants, xlTextValues + xlNumbers) If Err <> 0 Then Set CreateWorkRange = Nothing On Error GoTo 0 Exit Function End If End If End Function NOTE The CreateWorkRange function makes heavy use of the SpecialCells property. To learn more about the SpecialCells property, try recording a macro while making vari- ous selections in Excel’s Go To Special dialog box. You can display this dialog box by pressing F5 and then clicking the Special button in the Go To dialog box. It’s important to understand how the Go To Special dialog box works. Normally, it operates on the current range selection. For example, if an entire column is selected, the result is a subset of that column. But if a single cell is selected, it operates on the entire worksheet. Because of this, the CreateWorkRange function checks the number of cells in the range passed to it. Saving the Text Tools utility settings The Text Tools utility has a very useful feature: It remembers the last settings that you used. This is handy because many people tend to use the same option each time they invoke it. The most recently used settings are stored in the Windows Registry. When the user clicks the Close button, the code uses VBA’s SaveSetting function to save the value of each control. When the Text Tools utility is started, it uses the GetSetting function to retrieve those values and set the controls accordingly. Chapter 16: Developing Excel Utilities with VBA 545 Part V 24_044018 ch16.qxp 2/28/07 6:43 PM Page 545 In the Windows Registry, the settings are stored at the following location: HKEY_CURRENT_USER\Software\VB and VBA Program Settings\ Text Tools Utility\Settings Figure 16-5 shows these settings in the Windows Registry Editor program (regedit.exe). Figure 16-5: Use the Windows Registry Editor program to view the settings stored in the Registry. If you examine the code for the Text Tools utility, you’ll find that I used an eight-element array (named UserChoices) to store the settings. I could have used separate variables for each setting, but using an array made the coding a bit easier. The following VBA reads the settings from the Registry and stores them in the UserChoices array: ‘ Get previous settings UserChoices(1) = GetSetting(APPNAME, “Settings”, “OperationIndex”, 0) UserChoices(2) = GetSetting(APPNAME, “Settings”, “ChangeCaseIndex”, 0) UserChoices(3) = GetSetting(APPNAME, “Settings”, “TextToAdd”, “”) UserChoices(4) = GetSetting(APPNAME, “Settings”, “AddTextIndex”, 0) UserChoices(5) = GetSetting(APPNAME, “Settings”, “CharsToRemoveIndex”, 0) UserChoices(6) = GetSetting(APPNAME, “Settings”, “RemovePositionIndex”, 0) UserChoices(7) = GetSetting(APPNAME, “Settings”, “RemoveSpacesIndex”, 0) UserChoices(8) = GetSetting(APPNAME, “Settings”, “RemoveCharactersIndex”, 0) cbSkipNonText.Value = GetSetting(APPNAME, “cbSkipNonText”, 0) The code that follows is executed when the dialog box is closed. These statements retrieve the values from the UserChoices array and write them to the Registry. ‘ Store settings SaveSetting APPNAME, “Settings”, “OperationIndex”, UserChoices(1) SaveSetting APPNAME, “Settings”, “ChangeCaseIndex”, UserChoices(2) SaveSetting APPNAME, “Settings”, “TextToAdd”, UserChoices(3) Part V: Advanced Programming Techniques546 24_044018 ch16.qxp 2/28/07 6:43 PM Page 546 SaveSetting APPNAME, “Settings”, “AddTextIndex”, UserChoices(4) SaveSetting APPNAME, “Settings”, “CharsToRemoveIndex”, UserChoices(5) SaveSetting APPNAME, “Settings”, “RemovePositionIndex”, UserChoices(6) SaveSetting APPNAME, “Settings”, “RemoveSpacesIndex”, UserChoices(7) SaveSetting APPNAME, “Settings”, “RemoveCharactersIndex”, UserChoices(8) SaveSetting APPNAME, “Settings”, “cbSkipNonText”, cbSkipNonText.Value * -1 Implementing Undo Unfortunately, Excel does not provide a direct way to undo an operation performed using VBA. Undoing a VBA macro is possible, but it takes quite a bit of work. And, unlike Excel’s Undo feature, the undo technique used in the Text Tools utility is a single level. In other words, the user can undo only the most recent operation. Refer to the sidebar, “Undoing a VBA Procedure,” for additional information about using Undo with your applications. The Text Tools utility implements undo by saving the original data in a worksheet. If the user undoes the operation, that data is then copied back to the user’s workbook. In the Text Tools utility, recall that the Module1 VBA module declared two public vari- ables for handling undo: Public UndoRange As Range Public UserSelection As Range Before modifying any data, the ApplyButton_Click procedure calls the SaveForUndo procedure. The procedure starts with three statements: Set UserSelection = Selection Set UndoRange = WorkRange ThisWorkbook.Sheets(1).UsedRange.Clear The UserSelection object variable saves the user’s current selection so it can be re-selected after the undo operation. WorkRange is a Range object that’s returned by the CreateWorkRange function. The range consists of the non-empty and nonformula cells in the user’s selection. The preceding third statement erases any existing saved data from the worksheet. Next, the following loop is executed: For Each RngArea In WorkRange.Areas ThisWorkbook.Sheets(1).Range _ (RngArea.Address).Formula = RngArea.Formula Next RngArea This code loops through each area of the WorkRange and stores the data in the worksheet. (If the WorkRange consists of a contiguous range of cells, it will contain only one area.) Chapter 16: Developing Excel Utilities with VBA 547 Part V 24_044018 ch16.qxp 2/28/07 6:43 PM Page 547 After the specified operation is performed, the code then uses the OnUndo method to spec- ify the procedure to execute if the user chooses Undo. For example, after performing a case change operation, this statement is executed: Application.OnUndo “Undo Change Case”, “UndoTextTools” Excel’s Undo drop-down will then contain a menu item: Undo Change Case (see Figure 16-6). If the user selects the command, the UndoTextTools procedure, shown next, will be executed. Private Sub UndoTextTools() ‘ Undoes the last operation Dim a As Range On Error GoTo ErrHandler Application.ScreenUpdating = False With UserSelection .Parent.Parent.Activate .Parent.Activate .Select End With For Each a In UndoRange.Areas a.Formula = ThisWorkbook.Sheets(1).Range(a.Address).Formula Next a Application.ScreenUpdating = True On Error GoTo 0 Exit Sub ErrHandler: Application.ScreenUpdating = True MsgBox “Can’t undo”, vbInformation, APPNAME On Error GoTo 0 End Sub Figure 16-6: The Text Tools utility includes a single level of undo. Part V: Advanced Programming Techniques548 24_044018 ch16.qxp 2/28/07 6:43 PM Page 548 The UndoTextTools procedure first ensures that the correct workbook and worksheet are activated and then selects the original range selected by the user. Then it loops through each area of the stored data (which is available because of the UndoRange public variable) and puts the data back to its original location (overwriting the changes, of course). CD-ROM The companion CD-ROM contains a simpler example that demonstrates how to enable the Undo command after a VBA procedure is executed. This example, named simple undo demo.xlsm, stores the data in an array rather than a worksheet. The array is made up of a custom data type that includes the value and address of each cell. Displaying the Help file I created a simple compiled HTML Help file named texttools.chm for this utility. Clicking the HelpButton on the UserForm executes this procedure: Private Sub HelpButton_Click() Application.Help (ThisWorkbook.Path & “\” & “texttools.chm”) End Sub Chapter 16: Developing Excel Utilities with VBA 549 Part V Computer users have become accustomed to being able to undo an operation. Almost every operation that you perform in Excel can be undone. Even better, Excel 2007 increased the number of undo levels from 16 to 100. If you program in VBA, you may have wondered whether it’s possible to undo the effects of a procedure. Although the answer is yes, the qualified answer is it’s not always easy. Making the effects of your VBA procedures undoable isn’t automatic. Your procedure needs to store the previous state so that it can be restored if the user chooses the Undo command (which is located in the Quick Access Toolbar). How you do this can vary depending on what the procedure does. You can save the old information in a worksheet or in an array. In extreme cases, you might need to save an entire work- sheet. If your procedure modifies a range, for example, you need to save only the contents of that range. Also, keep in mind that executing a VBA Sub procedure wipes out Excel’s undo stack. In other words, after you run a macro, it’s impossible to undo previous operations. The Application object contains an OnUndo method, which lets the programmer specify text to appear on the Undo drop-down and a procedure to execute if the user chooses the Undo command. For example, the following statement causes the Undo drop-down to display Undo my cool macro. If the user chooses Undo ➪ Undo My Cool Macro, the UndoMyMacro procedure is executed: Application.OnUndo “Undo my cool macro”, “UndoMyMacro” Undoing a VBA Procedure 24_044018 ch16.qxp 2/28/07 6:43 PM Page 549 Figure 16-7 shows one of the help screens. Figure 16-7: A help screen for the Text Tools utility. CD-ROM The companion CD-ROM includes all of the source files that were used to create the Help file. These files are in a directory named \helpsource. If you’re not familiar with HTML Help files, refer to Chapter 24 for additional information. Adding the RibbonX code The final task in creating this utility is to provide a way to execute it. Before Excel 2007, it was relatively easy to insert a new menu command or toolbar button. But, with the new Ribbon user interface, this once-simple job is significantly more challenging. To add a custom Ribbon interface to an Excel file (which is actually a package of files), you must modify the file in two ways: • Create a new folder within the package and add a new part — an XML file that describes the Ribbon modification. • Define a relationship to the new part. Part V: Advanced Programming Techniques550 24_044018 ch16.qxp 2/28/07 6:43 PM Page 550 CROSS-REFERENCE Chapter 22 contains additional information about working with the Ribbon. Figure 16-8 shows the Ribbon with a new group (called Utilities) added to the end of the Home tab. This group contains a single control that, when clicked, executes this procedure: Sub StartTextTools(control As IRibbonControl) Call ShowTextToolsDialog End Sub Figure 16-8: The Ribbon contains a new group in the Home tab. Following is a step-by-step list of instructions that will add that single control to the Ribbon. These instructions assume that the workbook is an XLAM add-in file. 1. Close the text tools.xlam add-in file. 2. Make a copy of the add-in file and name it copy of text tools.xlam.zip. Excel 2007 files are actually ZIP compressed files, but without the .zip extension. If you add a .zip extension, you can work with the individual files within the ZIP file. 3. Use a zip utility to extract the files into a new directory named copy of text tools. You can use the zip tools built into Windows. Right-click the ZIP file and choose Extract All to launch the Windows Extraction Wizard. Figure 16-9 shows the files in the directory. Figure 16-9: The files in text tools.xlam. 4. Add a new folder named customUI to the copy of text tools directory. Chapter 16: Developing Excel Utilities with VBA 551 Part V 24_044018 ch16.qxp 2/28/07 6:43 PM Page 551 5. In the customUI folder, create a text file named customUI.xml with the following contents (the spacing isn’t important). This is the XML part.